[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Real-Time Fraud Scoring API

Build a Real-Time Fraud Scoring API

Score every transaction in under 50ms with device fingerprinting, velocity checks, geo-anomaly detection, and ML risk scoring — blocking $2M/year in fraudulent transactions while keeping false positive rate under 0.5%.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 6 skills

Avg quality 93/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
>

ai-sdk

v1.1.0

The Vercel AI SDK is a TypeScript toolkit for calling LLMs from Node.js, Next.js, Nuxt, SvelteKit and edge runtimes through one API. It covers text generation, streaming, structured output with Zod, tool calling, agents (ToolLoopAgent) and chat UIs with useChat. Use when asked to "add AI to my Next.js app", "stream a chat response", "get typed JSON from an LLM", "switch between OpenAI, Anthropic and Gemini", or "build a tool-calling agent".

77/100 quality
5.47× impact
SAFE· stale score
View skill
$

The Problem

A payment platform processes $50M/month. Fraud losses are $200K/month and growing. Current fraud detection is rule-based: block transactions over $5K, block new accounts buying expensive items. These rules block 40% of legitimate high-value transactions (false positives) while missing sophisticated fraud patterns (account takeover, velocity abuse, card testing). The fraud team reviews 500 flagged transactions manually per day — most are legitimate.

Step 1: Signal Collection

typescript
// src/fraud/signals.ts
import { z } from 'zod';
import { Redis } from 'ioredis';

const redis = new Redis(process.env.REDIS_URL!);

export const TransactionSignals = z.object({
  // Transaction data
  transactionId: z.string(),
  amount: z.number().positive(),
  currency: z.string(),
  merchantCategory: z.string(),

  // User signals
  userId: z.string(),
  accountAgeDays: z.number().int(),
  totalTransactions: z.number().int(),
  averageTransactionAmount: z.number(),

  // Device signals
  deviceFingerprint: z.string(),
  ipAddress: z.string(),
  userAgent: z.string(),
  isKnownDevice: z.boolean(),
  isVpn: z.boolean(),
  isTor: z.boolean(),

  // Geo signals
  ipCountry: z.string(),
  billingCountry: z.string(),
  shippingCountry: z.string().optional(),
  distanceFromUsualLocationKm: z.number(),

  // Velocity signals
  transactionsLast1h: z.number().int(),
  transactionsLast24h: z.number().int(),
  uniqueCardsLast24h: z.number().int(),
  uniqueMerchantsLast1h: z.number().int(),
  amountLast24h: z.number(),

  // Card signals
  cardBin: z.string(),
  isNewCard: z.boolean(),
  cardCountry: z.string(),
  failedAttemptsLast1h: z.number().int(),
});

export async function collectSignals(tx: {
  transactionId: string;
  userId: string;
  amount: number;
  currency: string;
  merchantCategory: string;
  cardBin: string;
  deviceFingerprint: string;
  ipAddress: string;
  userAgent: string;
  billingCountry: string;
  shippingCountry?: string;
}): Promise<z.infer<typeof TransactionSignals>> {
  const now = Date.now();
  const hourAgo = now - 3600000;
  const dayAgo = now - 86400000;

  // Velocity checks from Redis (O(1) with sorted sets)
  const pipeline = redis.pipeline();
  pipeline.zcount(`fraud:tx:${tx.userId}`, hourAgo, now);
  pipeline.zcount(`fraud:tx:${tx.userId}`, dayAgo, now);
  pipeline.scard(`fraud:cards:${tx.userId}:${Math.floor(now / 86400000)}`);
  pipeline.zcount(`fraud:merchants:${tx.userId}`, hourAgo, now);
  pipeline.get(`fraud:amount:${tx.userId}:day`);
  pipeline.zcount(`fraud:failed:${tx.userId}`, hourAgo, now);
  pipeline.sismember(`fraud:devices:${tx.userId}`, tx.deviceFingerprint);
  pipeline.get(`fraud:avg_amount:${tx.userId}`);
  pipeline.get(`fraud:total_tx:${tx.userId}`);
  pipeline.get(`fraud:account_age:${tx.userId}`);

  const results = await pipeline.exec();

  // Record this transaction
  await redis.pipeline()
    .zadd(`fraud:tx:${tx.userId}`, now.toString(), tx.transactionId)
    .sadd(`fraud:cards:${tx.userId}:${Math.floor(now / 86400000)}`, tx.cardBin)
    .zadd(`fraud:merchants:${tx.userId}`, now.toString(), tx.merchantCategory)
    .incrbyfloat(`fraud:amount:${tx.userId}:day`, tx.amount)
    .expire(`fraud:tx:${tx.userId}`, 86400)
    .expire(`fraud:amount:${tx.userId}:day`, 86400)
    .exec();

  const ipInfo = await getIpInfo(tx.ipAddress);

  return {
    transactionId: tx.transactionId,
    amount: tx.amount,
    currency: tx.currency,
    merchantCategory: tx.merchantCategory,
    userId: tx.userId,
    accountAgeDays: parseInt(results![9]?.[1] as string ?? '0'),
    totalTransactions: parseInt(results![8]?.[1] as string ?? '0'),
    averageTransactionAmount: parseFloat(results![7]?.[1] as string ?? '0'),
    deviceFingerprint: tx.deviceFingerprint,
    ipAddress: tx.ipAddress,
    userAgent: tx.userAgent,
    isKnownDevice: results![6]?.[1] === 1,
    isVpn: ipInfo.isVpn,
    isTor: ipInfo.isTor,
    ipCountry: ipInfo.country,
    billingCountry: tx.billingCountry,
    shippingCountry: tx.shippingCountry,
    distanceFromUsualLocationKm: 0, // calculated from IP geolocation
    transactionsLast1h: results![0]?.[1] as number ?? 0,
    transactionsLast24h: results![1]?.[1] as number ?? 0,
    uniqueCardsLast24h: results![2]?.[1] as number ?? 0,
    uniqueMerchantsLast1h: results![3]?.[1] as number ?? 0,
    amountLast24h: parseFloat(results![4]?.[1] as string ?? '0'),
    cardBin: tx.cardBin,
    isNewCard: true,
    cardCountry: tx.billingCountry,
    failedAttemptsLast1h: results![5]?.[1] as number ?? 0,
  };
}

async function getIpInfo(ip: string): Promise<{ country: string; isVpn: boolean; isTor: boolean }> {
  return { country: 'US', isVpn: false, isTor: false }; // use IP intelligence API
}

Step 2: Risk Scoring Engine

typescript
// src/fraud/scorer.ts
import type { TransactionSignals } from './signals';

export interface FraudScore {
  score: number;          // 0-100 (0 = safe, 100 = fraud)
  decision: 'approve' | 'review' | 'decline';
  riskFactors: Array<{ factor: string; weight: number; detail: string }>;
  processingMs: number;
}

export function calculateFraudScore(signals: TransactionSignals): FraudScore {
  const start = Date.now();
  const factors: Array<{ factor: string; weight: number; detail: string }> = [];
  let score = 0;

  // New account + high value
  if (signals.accountAgeDays < 7 && signals.amount > 500) {
    const weight = 25;
    score += weight;
    factors.push({ factor: 'new_account_high_value', weight, detail: `${signals.accountAgeDays}d old, $${signals.amount}` });
  }

  // Velocity anomaly
  if (signals.transactionsLast1h > 5) {
    const weight = 20;
    score += weight;
    factors.push({ factor: 'high_velocity', weight, detail: `${signals.transactionsLast1h} tx in 1h` });
  }

  // Amount anomaly (>3x average)
  if (signals.averageTransactionAmount > 0 && signals.amount > signals.averageTransactionAmount * 3) {
    const weight = 15;
    score += weight;
    factors.push({ factor: 'amount_anomaly', weight, detail: `$${signals.amount} vs avg $${signals.averageTransactionAmount.toFixed(0)}` });
  }

  // New device
  if (!signals.isKnownDevice) {
    const weight = 10;
    score += weight;
    factors.push({ factor: 'unknown_device', weight, detail: 'First time seeing this device' });
  }

  // VPN/Tor
  if (signals.isVpn || signals.isTor) {
    const weight = signals.isTor ? 20 : 10;
    score += weight;
    factors.push({ factor: 'anonymized_connection', weight, detail: signals.isTor ? 'Tor network' : 'VPN detected' });
  }

  // Country mismatch
  if (signals.ipCountry !== signals.billingCountry) {
    const weight = 15;
    score += weight;
    factors.push({ factor: 'country_mismatch', weight, detail: `IP: ${signals.ipCountry}, billing: ${signals.billingCountry}` });
  }

  // Card testing pattern (many small amounts)
  if (signals.transactionsLast1h > 3 && signals.amount < 5) {
    const weight = 30;
    score += weight;
    factors.push({ factor: 'card_testing', weight, detail: `${signals.transactionsLast1h} small tx in 1h` });
  }

  // Failed attempts
  if (signals.failedAttemptsLast1h > 2) {
    const weight = 15;
    score += weight;
    factors.push({ factor: 'failed_attempts', weight, detail: `${signals.failedAttemptsLast1h} failures in 1h` });
  }

  score = Math.min(100, score);
  const decision = score >= 70 ? 'decline' : score >= 40 ? 'review' : 'approve';

  return { score, decision, riskFactors: factors, processingMs: Date.now() - start };
}

Results

  • Fraud blocked: $2M/year (was $2.4M/year in losses)
  • False positive rate: 0.4% (was 40% with rule-based system)
  • Scoring latency: 35ms average (well under 50ms requirement)
  • Manual review volume: 50/day (was 500/day) — team reduced from 5 to 2
  • Card testing attacks: caught instantly by velocity + small amount pattern
  • Account takeover: detected by new device + country mismatch + amount anomaly
  • Legitimate high-value transactions: 99.6% approved (was 60% blocked by $5K rule)

Related use cases