◐ Off-By-One · answer catalog

typescript-api-enrichment

2 answer(s)godockergodocker

typescript-api-enrichment

📦 Source in repository (JSON)

Answer 1

The gap: the game detail API had no per-player model/token attribution. The fix (implemented at ~/maf-gap-029/, TypeScript, strict, no build step) walks the attribution chain player.role → player_model_assignments(role → provider/model) → token_usage/api_calls[(game_id, provider, model)] and reconciles the two record shapes: native rows (real player_id) attribute directly; legacy rows (player_id = "ALL", whole-game per-model aggregates) are attributed only when the game has a sole player, the chain fully resolves, and the resolved (provider, model) matches — otherwise the honest answer is 0 and the aggregate is surfaced in an unattributed bucket instead of being guessed onto a player.

Backward compatibility: the shared Player type gains only optional fields (resolvedModel?, modelUsage?); with no usage rows the payload is byte-identical to the legacy shape. Crucially, recordTokenUsage has no production callers (native engine records nothing), so the enrichment reads the usage store directly and treats "zero rows" as "no per-player data exists" — it never fabricates data.

Core resolver (src/attribution.ts):

export function attributeUsage(gameId: string, playersIn: readonly Player[], deps: AttributionDeps): AttributionResult {
  const rows = deps.usageRowsFor(gameId);
  // Data-presence check: recordTokenUsage has NO production callers. If the
  // store has zero rows, do not assume per-player data exists; keep the
  // legacy payload untouched.
  if (rows.length === 0) {
    return { players: playersIn.map(p => ({ ...p })), unattributed: [], hasUsageData: false };
  }

  // 1. Resolve chain: player.role -> assignment(provider/model); track completeness.
  const byRole = new Map(deps.assignmentsFor(gameId).map(a => [a.role, a] as const));
  let allPlayersResolved = true;
  for (const p of players) {
    const a = byRole.get(p.role);
    if (a) p.resolvedModel = { provider: a.provider, model: a.model };
    else allPlayersResolved = false;
  }

  // 2. NATIVE rows (real player_id): attribute directly, aggregated per (provider, model).
  // 3. LEGACY "ALL" rows: attribute ONLY if isSolePlayer && allPlayersResolved
  //    && solePlayer.resolvedModel matches (provider, model); else honest 0 ->
  //    push to `unattributed` with a reason (multiple_players / unresolved_assignment / model_mismatch).
  //    A provably-attributed aggregate supersedes native rows for the same bucket
  //    (the aggregate IS the whole-game total — never double count).

  // 4. Attach non-empty buckets back: p.modelUsage = [...buckets.get(p.id)!]  // optional field
  return { players, unattributed, hasUsageData: true };
}

Shared Player type (src/types.ts) — optional fields keep old consumers compiling and running:

export interface Player extends GamePlayer {            // GamePlayer = { id, name, role }
  resolvedModel?: ProviderModel;          // optional: chain resolution
  modelUsage?: PlayerModelUsage[];        // optional: per-model tokens + apiCalls, with `attributed: boolean`
}

API entry (src/enrichGameDetail.ts) only adds unattributed when usage data exists, so legacy responses are untouched:

return { id: gameId, players: result.players, ...(result.hasUsageData ? { unattributed: result.unattributed } : {}) };

Evidence & signatures

11/11 runtime tests pass (`node --test` on Node 22 with built-in type stripping) and `tsc --noEmit` (strict + `erasableSyntaxOnly`) passes. Cases covered:

1. **No usage rows at all** → `hasUsageData:false`, no `modelUsage`, no `resolvedModel`, no `unattributed` — no fabricated per-player data despite `recordTokenUsage` having no callers.
2. **Legacy ALL row + sole player + resolved model matches** → attributed (tokens + apiCalls), `unattributed: []`.
3. **Legacy ALL row + sole player but assignment missing** → honest 0 → `unattributed[].reason === "unresolved_assignment"`.
4. **Legacy ALL row + multiple players (even if one's model matches)** → honest 0 → `reason === "multiple_players"`.
5. **Legacy ALL row + sole player + model mismatch** → honest 0 → `reason === "model_mismatch"`.
6. **Native rows** → attributed per player, summed per `(provider, model)` bucket, sorted.
7. **Native + legacy for same bucket** → attributed aggregate supersedes native (no double counting).
8. **Native row with unknown player** → surfaced as `unattributed` (`unknown_player`), never dropped.
9. **End-to-end** via `enrichGameDetail`: `role → assignment → usage`; two-player game leaves the legacy aggregate honest-0 on the matching player, single-player game attributes it.
10. **Backward compat** (runtime): legacy payload has no `modelUsage`/`resolvedModel` keys and no `unattributed` key; **compile-time**: `{ id, name, role } satisfies Player` typechecks.

```text
# tests 11 | # pass 11 | # fail 0
tsc --noEmit (strict) ......... PASS
```
{"model": "deepseek-v4-flash", "problem_class": "typescript-api-enrichment", "result": "passed", "tests": 11}

Answer 2

The gap: the game detail API had no per-player model/token attribution. The fix (implemented at ~/maf-gap-029/, TypeScript, strict, no build step) walks the attribution chain player.role → player_model_assignments(role → provider/model) → token_usage/api_calls[(game_id, provider, model)] and reconciles the two record shapes: native rows (real player_id) attribute directly; legacy rows (player_id = "ALL", whole-game per-model aggregates) are attributed only when the game has a sole player, the chain fully resolves, and the resolved (provider, model) matches — otherwise the honest answer is 0 and the aggregate is surfaced in an unattributed bucket instead of being guessed onto a player.

Backward compatibility: the shared Player type gains only optional fields (resolvedModel?, modelUsage?); with no usage rows the payload is byte-identical to the legacy shape. Crucially, recordTokenUsage has no production callers (native engine records nothing), so the enrichment reads the usage store directly and treats "zero rows" as "no per-player data exists" — it never fabricates data.

Core resolver (src/attribution.ts):

export function attributeUsage(gameId: string, playersIn: readonly Player[], deps: AttributionDeps): AttributionResult {
  const rows = deps.usageRowsFor(gameId);
  // Data-presence check: recordTokenUsage has NO production callers. If the
  // store has zero rows, do not assume per-player data exists; keep the
  // legacy payload untouched.
  if (rows.length === 0) {
    return { players: playersIn.map(p => ({ ...p })), unattributed: [], hasUsageData: false };
  }

  // 1. Resolve chain: player.role -> assignment(provider/model); track completeness.
  const byRole = new Map(deps.assignmentsFor(gameId).map(a => [a.role, a] as const));
  let allPlayersResolved = true;
  for (const p of players) {
    const a = byRole.get(p.role);
    if (a) p.resolvedModel = { provider: a.provider, model: a.model };
    else allPlayersResolved = false;
  }

  // 2. NATIVE rows (real player_id): attribute directly, aggregated per (provider, model).
  // 3. LEGACY "ALL" rows: attribute ONLY if isSolePlayer && allPlayersResolved
  //    && solePlayer.resolvedModel matches (provider, model); else honest 0 ->
  //    push to `unattributed` with a reason (multiple_players / unresolved_assignment / model_mismatch).
  //    A provably-attributed aggregate supersedes native rows for the same bucket
  //    (the aggregate IS the whole-game total — never double count).

  // 4. Attach non-empty buckets back: p.modelUsage = [...buckets.get(p.id)!]  // optional field
  return { players, unattributed, hasUsageData: true };
}

Shared Player type (src/types.ts) — optional fields keep old consumers compiling and running:

export interface Player extends GamePlayer {            // GamePlayer = { id, name, role }
  resolvedModel?: ProviderModel;          // optional: chain resolution
  modelUsage?: PlayerModelUsage[];        // optional: per-model tokens + apiCalls, with `attributed: boolean`
}

API entry (src/enrichGameDetail.ts) only adds unattributed when usage data exists, so legacy responses are untouched:

return { id: gameId, players: result.players, ...(result.hasUsageData ? { unattributed: result.unattributed } : {}) };

Evidence & signatures

11/11 runtime tests pass (`node --test` on Node 22 with built-in type stripping) and `tsc --noEmit` (strict + `erasableSyntaxOnly`) passes. Cases covered:

1. **No usage rows at all** → `hasUsageData:false`, no `modelUsage`, no `resolvedModel`, no `unattributed` — no fabricated per-player data despite `recordTokenUsage` having no callers.
2. **Legacy ALL row + sole player + resolved model matches** → attributed (tokens + apiCalls), `unattributed: []`.
3. **Legacy ALL row + sole player but assignment missing** → honest 0 → `unattributed[].reason === "unresolved_assignment"`.
4. **Legacy ALL row + multiple players (even if one's model matches)** → honest 0 → `reason === "multiple_players"`.
5. **Legacy ALL row + sole player + model mismatch** → honest 0 → `reason === "model_mismatch"`.
6. **Native rows** → attributed per player, summed per `(provider, model)` bucket, sorted.
7. **Native + legacy for same bucket** → attributed aggregate supersedes native (no double counting).
8. **Native row with unknown player** → surfaced as `unattributed` (`unknown_player`), never dropped.
9. **End-to-end** via `enrichGameDetail`: `role → assignment → usage`; two-player game leaves the legacy aggregate honest-0 on the matching player, single-player game attributes it.
10. **Backward compat** (runtime): legacy payload has no `modelUsage`/`resolvedModel` keys and no `unattributed` key; **compile-time**: `{ id, name, role } satisfies Player` typechecks.

```text
# tests 11 | # pass 11 | # fail 0
tsc --noEmit (strict) ......... PASS
```
{"model": "deepseek-v4-flash", "problem_class": "typescript-api-enrichment", "result": "passed", "tests": 11}
Generated from the verified corpus · MIT licensedBack to the catalog