cctally 1.69.0 → 1.69.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1375 @@
1
+ """Pure source-aware accounting contracts for the #294 S3 adapter."""
2
+ from __future__ import annotations
3
+
4
+ import datetime as dt
5
+ import hashlib
6
+ import math
7
+ from collections import defaultdict
8
+ from dataclasses import dataclass, replace
9
+ from typing import Generic, Iterable, Literal, TypeVar
10
+
11
+
12
+ Availability = Literal["ok", "empty", "partial", "unavailable"]
13
+ _T = TypeVar("_T")
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class SourceWarning:
18
+ """A privacy-safe, source-scoped degradation diagnostic."""
19
+
20
+ code: str
21
+ message: str
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class SourceResult(Generic[_T]):
26
+ """One provider result without collapsing provider-native state."""
27
+
28
+ source: str
29
+ status: Availability
30
+ data: _T
31
+ warnings: tuple[SourceWarning, ...] = ()
32
+
33
+
34
+ QUALIFIED_METADATA_WARNING = SourceWarning(
35
+ "qualified_metadata_unavailable",
36
+ "Codex qualified project metadata is unavailable.",
37
+ )
38
+ QUOTA_STATE_WARNING = SourceWarning(
39
+ "quota_state_unavailable",
40
+ "Codex quota state is unavailable.",
41
+ )
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class QualifiedCodexEntry:
46
+ """One accounting row joined to its S1-qualified project identity."""
47
+
48
+ timestamp: dt.datetime
49
+ source_root_key: str
50
+ conversation_key: str
51
+ project_key: str
52
+ project_label: str
53
+ model: str
54
+ input_tokens: int
55
+ cached_input_tokens: int
56
+ output_tokens: int
57
+ reasoning_output_tokens: int
58
+ total_tokens: int
59
+ cost_usd: float
60
+ # The raw label remains available for ambiguity diagnostics. The emitted
61
+ # label is assigned once over the complete qualified population before any
62
+ # project/model filtering, so a rendered ``label (N)`` is selectable.
63
+ display_label: str | None = None
64
+ # Dashboard source read models reuse this same qualified accounting stream
65
+ # for the shipped Codex period/session kernels. These are internal-only;
66
+ # source-analytics renderers never expose either raw identity.
67
+ session_id: str = ""
68
+ source_path: str = ""
69
+
70
+
71
+ def emitted_project_label(entry: QualifiedCodexEntry) -> str:
72
+ """Return the deterministic privacy-safe label exposed to users."""
73
+ return entry.display_label or entry.project_label
74
+
75
+
76
+ def assign_collision_safe_project_labels(
77
+ entries: Iterable[QualifiedCodexEntry],
78
+ ) -> tuple[QualifiedCodexEntry, ...]:
79
+ """Annotate a complete qualified population with stable display labels.
80
+
81
+ Labels are deterministic by opaque project key. Keeping the annotation on
82
+ the entry (rather than adding it while rendering rows) gives project
83
+ selection the exact string that terminal, JSON, and share emit.
84
+ """
85
+ values = tuple(entries)
86
+ keys_by_label: dict[str, set[str]] = defaultdict(set)
87
+ for entry in values:
88
+ keys_by_label[entry.project_label].add(entry.project_key)
89
+ # Never let an allocator-owned ``label (N)`` shadow a literal project
90
+ # label. Reserving every raw label also makes a later presentation pass
91
+ # unnecessary, which avoids producing ``label (N) (N)``.
92
+ reserved_labels = set(keys_by_label)
93
+ used_labels: set[str] = set()
94
+ assigned: dict[str, str] = {}
95
+ for label in sorted(keys_by_label):
96
+ keys = sorted(keys_by_label[label])
97
+ if len(keys) == 1:
98
+ assigned[keys[0]] = label
99
+ used_labels.add(label)
100
+ continue
101
+ ordinal = 1
102
+ for key in keys:
103
+ candidate = f"{label} ({ordinal})"
104
+ while candidate in reserved_labels or candidate in used_labels:
105
+ ordinal += 1
106
+ candidate = f"{label} ({ordinal})"
107
+ assigned[key] = candidate
108
+ used_labels.add(candidate)
109
+ ordinal += 1
110
+ return tuple(
111
+ replace(entry, display_label=assigned.get(entry.project_key, entry.project_label))
112
+ for entry in values
113
+ )
114
+
115
+
116
+ def opaque_project_key(source: str, root_key: str, resolved_key: str) -> str:
117
+ """Return the stable opaque identity for one provider-qualified project."""
118
+ if not all(isinstance(value, str) and value for value in (source, root_key, resolved_key)):
119
+ raise ValueError("source, root_key, and resolved_key must be non-empty strings")
120
+ payload = f"{source}\0{root_key}\0{resolved_key}".encode("utf-8")
121
+ return "project:" + hashlib.sha256(payload).hexdigest()[:24]
122
+
123
+
124
+ UTC = dt.timezone.utc
125
+
126
+
127
+ def _require_aware(value: dt.datetime, name: str) -> dt.datetime:
128
+ if value.tzinfo is None or value.utcoffset() is None:
129
+ raise ValueError(f"{name} must be timezone-aware")
130
+ return value.astimezone(UTC)
131
+
132
+
133
+ def _opaque_key(kind: str, *parts: str) -> str:
134
+ if not kind or any(not isinstance(part, str) or not part for part in parts):
135
+ raise ValueError("opaque key parts must be non-empty strings")
136
+ digest = hashlib.sha256("\0".join((kind, *parts)).encode("utf-8")).hexdigest()[:24]
137
+ return f"{kind}:{digest}"
138
+
139
+
140
+ def opaque_quota_key(
141
+ source: str, source_root_key: str, logical_limit_key: str,
142
+ observed_slot: str, window_minutes: int,
143
+ ) -> str:
144
+ """Return an opaque full-native-identity key for a quota series."""
145
+ if not isinstance(window_minutes, int) or isinstance(window_minutes, bool) or window_minutes <= 0:
146
+ raise ValueError("window_minutes must be a positive integer")
147
+ return _opaque_key(
148
+ "quota", source, source_root_key, logical_limit_key, observed_slot,
149
+ str(window_minutes),
150
+ )
151
+
152
+
153
+ @dataclass(frozen=True)
154
+ class TokenTotals:
155
+ """Explicit Codex-native accounting totals.
156
+
157
+ Codex input is inclusive of cache and output is inclusive of reasoning.
158
+ The separate fields make that vocabulary visible instead of inventing a
159
+ Claude cache-hit analogue.
160
+ """
161
+
162
+ input_tokens: int = 0
163
+ cached_input_tokens: int = 0
164
+ output_tokens: int = 0
165
+ reasoning_output_tokens: int = 0
166
+ total_tokens: int = 0
167
+ cost_usd: float = 0.0
168
+
169
+ @property
170
+ def non_cached_input_tokens(self) -> int:
171
+ return max(0, self.input_tokens - self.cached_input_tokens)
172
+
173
+ @property
174
+ def cached_input_percent(self) -> float | None:
175
+ if self.input_tokens <= 0:
176
+ return None
177
+ return self.cached_input_tokens * 100.0 / self.input_tokens
178
+
179
+
180
+ def _totals(entries: Iterable[QualifiedCodexEntry]) -> TokenTotals:
181
+ values = assign_collision_safe_project_labels(entries)
182
+ return TokenTotals(
183
+ input_tokens=sum(entry.input_tokens for entry in values),
184
+ cached_input_tokens=sum(entry.cached_input_tokens for entry in values),
185
+ output_tokens=sum(entry.output_tokens for entry in values),
186
+ reasoning_output_tokens=sum(entry.reasoning_output_tokens for entry in values),
187
+ total_tokens=sum(entry.total_tokens for entry in values),
188
+ cost_usd=math.fsum(entry.cost_usd for entry in values),
189
+ )
190
+
191
+
192
+ def _codex_tokens_wire(totals: TokenTotals, *, cached_percent: bool = False) -> dict[str, object]:
193
+ result: dict[str, object] = {
194
+ "inputTokens": totals.input_tokens,
195
+ "cachedInputTokens": totals.cached_input_tokens,
196
+ "nonCachedInputTokens": totals.non_cached_input_tokens,
197
+ "outputTokens": totals.output_tokens,
198
+ "reasoningOutputTokens": totals.reasoning_output_tokens,
199
+ "totalTokens": totals.total_tokens,
200
+ }
201
+ if cached_percent:
202
+ result["cachedInputPercent"] = totals.cached_input_percent
203
+ return result
204
+
205
+
206
+ def _metric_wire(totals: TokenTotals) -> dict[str, object]:
207
+ return {
208
+ "cost_usd": totals.cost_usd,
209
+ "total_tokens": totals.total_tokens,
210
+ "input_tokens": totals.input_tokens,
211
+ "cached_input_tokens": totals.cached_input_tokens,
212
+ "output_tokens": totals.output_tokens,
213
+ "reasoning_output_tokens": totals.reasoning_output_tokens,
214
+ }
215
+
216
+
217
+ @dataclass(frozen=True)
218
+ class AnalyticsWindow:
219
+ """One resolved absolute analytics interval; its end is exclusive."""
220
+
221
+ label: str
222
+ kind: str
223
+ start_at: dt.datetime
224
+ end_at: dt.datetime
225
+ used_pct_mode: str | None = None
226
+
227
+ def __post_init__(self) -> None:
228
+ start_at = _require_aware(self.start_at, "start_at")
229
+ end_at = _require_aware(self.end_at, "end_at")
230
+ if end_at <= start_at:
231
+ raise ValueError("window end_at must be after start_at")
232
+ object.__setattr__(self, "start_at", start_at)
233
+ object.__setattr__(self, "end_at", end_at)
234
+
235
+
236
+ @dataclass(frozen=True)
237
+ class QuotaAttribution:
238
+ quota_key: str | None
239
+ slot: str | None
240
+ window_minutes: int | None
241
+ reset_at: dt.datetime | None
242
+ used_percent: float | None
243
+ attributed_used_percent: float | None
244
+ cost_per_percent: float | None
245
+ status: str = "ok"
246
+
247
+ @classmethod
248
+ def unavailable(cls) -> "QuotaAttribution":
249
+ """Represent unavailable S2 attribution without inventing identity."""
250
+ return cls(None, None, None, None, None, None, None, "unavailable")
251
+
252
+
253
+ @dataclass(frozen=True)
254
+ class CodexProjectRow:
255
+ project_key: str
256
+ display_label: str
257
+ session_count: int
258
+ first_seen: dt.datetime
259
+ last_seen: dt.datetime
260
+ totals: TokenTotals
261
+ models: tuple[tuple[str, TokenTotals], ...]
262
+ quota_attributions: tuple[QuotaAttribution, ...]
263
+
264
+
265
+ @dataclass(frozen=True)
266
+ class CodexProjectData:
267
+ range_start: dt.datetime
268
+ range_end: dt.datetime
269
+ window_kind: str
270
+ totals: TokenTotals
271
+ projects: tuple[CodexProjectRow, ...]
272
+ include_breakdown: bool = False
273
+
274
+
275
+ def _entry_in_half_open(entry: QualifiedCodexEntry, start: dt.datetime, end: dt.datetime) -> bool:
276
+ timestamp = _require_aware(entry.timestamp, "entry timestamp")
277
+ return start <= timestamp < end
278
+
279
+
280
+ def build_codex_project_result(
281
+ entries: Iterable[QualifiedCodexEntry], *, range_start: dt.datetime,
282
+ range_end: dt.datetime, blocks: Iterable[object] = (), as_of: dt.datetime | None = None,
283
+ window_kind: str = "calendar", sort: str = "cost", order: str = "desc",
284
+ include_breakdown: bool = False, allocation_entries: Iterable[QualifiedCodexEntry] | None = None,
285
+ quota_available: bool = True,
286
+ ) -> SourceResult[CodexProjectData]:
287
+ """Group qualified Codex accounting without merging provider-root identity.
288
+
289
+ Quota usage is allocated per physical root-qualified block by source-native
290
+ cost only. Each block remains a child attribution, rather than becoming a
291
+ fabricated singular Codex subscription percentage.
292
+ """
293
+ start = _require_aware(range_start, "range_start")
294
+ end = _require_aware(range_end, "range_end")
295
+ if end <= start:
296
+ raise ValueError("range_end must be after range_start")
297
+ as_of_utc = _require_aware(as_of or end, "as_of")
298
+ all_entries = tuple(entries)
299
+ # Command adapters attach labels before filtering. Preserve that complete
300
+ # population metadata, while keeping the pure builder collision-safe for
301
+ # direct callers that supply only raw labels.
302
+ if all(entry.display_label is None for entry in all_entries):
303
+ all_entries = assign_collision_safe_project_labels(all_entries)
304
+ physical_population = (
305
+ all_entries if allocation_entries is None else tuple(allocation_entries)
306
+ )
307
+ selected = tuple(entry for entry in all_entries if _entry_in_half_open(entry, start, end))
308
+ grouped: dict[tuple[str, str], list[QualifiedCodexEntry]] = defaultdict(list)
309
+ for entry in selected:
310
+ grouped[(entry.source_root_key, entry.project_key)].append(entry)
311
+
312
+ attributions: dict[tuple[str, str], list[QuotaAttribution]] = defaultdict(list)
313
+ for block in blocks if quota_available else ():
314
+ identity = block.identity
315
+ if getattr(identity, "source", None) != "codex":
316
+ continue
317
+ nominal_start = _require_aware(block.nominal_start_at, "block nominal_start_at")
318
+ reset_at = _require_aware(block.resets_at, "block resets_at")
319
+ block_end = min(as_of_utc, reset_at)
320
+ if block_end <= nominal_start:
321
+ continue
322
+ block_entries = tuple(
323
+ entry for entry in physical_population
324
+ if entry.source_root_key == identity.source_root_key
325
+ and _entry_in_half_open(entry, nominal_start, block_end)
326
+ )
327
+ block_totals = _totals(block_entries)
328
+ current_percent = float(block.current_percent)
329
+ can_allocate = block_totals.cost_usd > 0 and current_percent > 0
330
+ quota_key = opaque_quota_key(
331
+ identity.source, identity.source_root_key, identity.logical_limit_key,
332
+ identity.observed_slot, identity.window_minutes,
333
+ )
334
+ for (root_key, project_key), project_entries in grouped.items():
335
+ if root_key != identity.source_root_key:
336
+ continue
337
+ project_cost = math.fsum(
338
+ entry.cost_usd for entry in project_entries
339
+ if _entry_in_half_open(entry, nominal_start, block_end)
340
+ )
341
+ if project_cost <= 0:
342
+ continue
343
+ attributions[(root_key, project_key)].append(QuotaAttribution(
344
+ quota_key=quota_key,
345
+ slot=identity.observed_slot,
346
+ window_minutes=identity.window_minutes,
347
+ reset_at=reset_at,
348
+ used_percent=current_percent,
349
+ attributed_used_percent=(
350
+ current_percent * project_cost / block_totals.cost_usd
351
+ if can_allocate else None
352
+ ),
353
+ cost_per_percent=(
354
+ block_totals.cost_usd / current_percent if can_allocate else None
355
+ ),
356
+ ))
357
+
358
+ rows: list[CodexProjectRow] = []
359
+ for (root_key, project_key), project_entries in grouped.items():
360
+ ordered = tuple(sorted(project_entries, key=lambda entry: entry.timestamp))
361
+ by_model: dict[str, list[QualifiedCodexEntry]] = defaultdict(list)
362
+ for entry in ordered:
363
+ by_model[entry.model].append(entry)
364
+ rows.append(CodexProjectRow(
365
+ project_key=project_key,
366
+ display_label=emitted_project_label(ordered[0]),
367
+ session_count=len({entry.conversation_key for entry in ordered}),
368
+ first_seen=_require_aware(ordered[0].timestamp, "entry timestamp"),
369
+ last_seen=_require_aware(ordered[-1].timestamp, "entry timestamp"),
370
+ totals=_totals(ordered),
371
+ models=tuple((model, _totals(by_model[model])) for model in sorted(by_model)),
372
+ quota_attributions=(
373
+ tuple(attributions[(root_key, project_key)])
374
+ if quota_available else (QuotaAttribution.unavailable(),)
375
+ ),
376
+ ))
377
+ if sort not in {"cost", "name", "last-seen"}:
378
+ raise ValueError("Codex project sort must be cost, name, or last-seen")
379
+ if order not in {"asc", "desc"}:
380
+ raise ValueError("Codex project order must be asc or desc")
381
+ if sort == "name":
382
+ rows.sort(
383
+ key=lambda row: (row.display_label.lower(), row.project_key),
384
+ reverse=(order == "desc"),
385
+ )
386
+ elif sort == "last-seen":
387
+ rows.sort(
388
+ key=lambda row: (row.last_seen, row.display_label, row.project_key),
389
+ reverse=(order == "desc"),
390
+ )
391
+ else:
392
+ rows.sort(
393
+ key=lambda row: (row.totals.cost_usd, row.display_label, row.project_key),
394
+ reverse=(order == "desc"),
395
+ )
396
+ data = CodexProjectData(
397
+ start, end, window_kind, _totals(selected), tuple(rows), include_breakdown,
398
+ )
399
+ status: Availability = "empty" if not selected else ("ok" if quota_available else "partial")
400
+ warnings = () if quota_available else (QUOTA_STATE_WARNING,)
401
+ return SourceResult("codex", status, data, warnings)
402
+
403
+
404
+ @dataclass(frozen=True)
405
+ class CodexRangeData:
406
+ start: dt.datetime
407
+ end: dt.datetime
408
+ totals: TokenTotals
409
+ models: tuple[tuple[str, TokenTotals], ...]
410
+ include_breakdown: bool = False
411
+
412
+
413
+ def build_codex_range_result(
414
+ entries: Iterable[QualifiedCodexEntry], start: dt.datetime, end: dt.datetime,
415
+ *, include_breakdown: bool = False,
416
+ ) -> SourceResult[CodexRangeData]:
417
+ """Build the inclusive-end range-cost result for Codex accounting."""
418
+ start_utc = _require_aware(start, "start")
419
+ end_utc = _require_aware(end, "end")
420
+ if end_utc < start_utc:
421
+ raise ValueError("end must not be before start")
422
+ selected = tuple(
423
+ entry for entry in entries
424
+ if start_utc <= _require_aware(entry.timestamp, "entry timestamp") <= end_utc
425
+ )
426
+ by_model: dict[str, list[QualifiedCodexEntry]] = defaultdict(list)
427
+ for entry in selected:
428
+ by_model[entry.model].append(entry)
429
+ models = tuple((model, _totals(by_model[model])) for model in sorted(by_model))
430
+ data = CodexRangeData(
431
+ start_utc, end_utc, _totals(selected), models, include_breakdown,
432
+ )
433
+ return SourceResult("codex", "empty" if not selected else "ok", data)
434
+
435
+
436
+ @dataclass(frozen=True)
437
+ class ReuseRow:
438
+ key: str
439
+ label: str
440
+ project_key: str | None
441
+ project_label: str | None
442
+ last_activity: dt.datetime
443
+ totals: TokenTotals
444
+ models: tuple[tuple[str, TokenTotals], ...]
445
+
446
+
447
+ @dataclass(frozen=True)
448
+ class CodexReuseData:
449
+ start: dt.datetime | None
450
+ end: dt.datetime | None
451
+ group_by: str
452
+ totals: TokenTotals
453
+ rows: tuple[ReuseRow, ...]
454
+ project_metadata_available: bool = True
455
+ sort: str = "date"
456
+
457
+
458
+ def build_codex_reuse_result(
459
+ entries: Iterable[QualifiedCodexEntry], *, group_by: Literal["date", "session", "model"] = "date",
460
+ project_metadata_available: bool = True, sort: str | None = None,
461
+ range_start: dt.datetime | None = None, range_end: dt.datetime | None = None,
462
+ ) -> SourceResult[CodexReuseData]:
463
+ """Build token-reuse rows with Codex-inclusive token semantics."""
464
+ if (range_start is None) != (range_end is None):
465
+ raise ValueError("Codex reuse range_start and range_end must be supplied together")
466
+ if range_start is not None and range_end is not None:
467
+ requested_start = _require_aware(range_start, "range_start")
468
+ requested_end = _require_aware(range_end, "range_end")
469
+ if requested_end <= requested_start:
470
+ raise ValueError("Codex reuse range_end must be after range_start")
471
+ values = tuple(
472
+ entry for entry in entries
473
+ if _entry_in_half_open(entry, requested_start, requested_end)
474
+ )
475
+ else:
476
+ requested_start = requested_end = None
477
+ values = tuple(entries)
478
+ if group_by not in {"date", "session", "model"}:
479
+ raise ValueError("group_by must be date, session, or model")
480
+ grouped: dict[tuple[str, ...], list[QualifiedCodexEntry]] = defaultdict(list)
481
+ for entry in values:
482
+ timestamp = _require_aware(entry.timestamp, "entry timestamp")
483
+ if group_by == "date":
484
+ key = (timestamp.date().isoformat(),)
485
+ elif group_by == "session":
486
+ key = (entry.source_root_key, entry.conversation_key)
487
+ else:
488
+ key = (entry.model,)
489
+ grouped[key].append(entry)
490
+
491
+ rows: list[ReuseRow] = []
492
+ for key, group in grouped.items():
493
+ ordered = tuple(sorted(group, key=lambda entry: entry.timestamp))
494
+ by_model: dict[str, list[QualifiedCodexEntry]] = defaultdict(list)
495
+ for entry in ordered:
496
+ by_model[entry.model].append(entry)
497
+ if group_by == "date":
498
+ row_key, label = _opaque_key("reuse-date", key[0]), key[0]
499
+ project_key = project_label = None
500
+ elif group_by == "session":
501
+ row_key = _opaque_key("reuse-session", key[0], key[1])
502
+ label = "Codex session"
503
+ project_key = ordered[0].project_key if project_metadata_available else None
504
+ project_label = emitted_project_label(ordered[0]) if project_metadata_available else None
505
+ else:
506
+ row_key, label = _opaque_key("reuse-model", key[0]), key[0]
507
+ project_key = project_label = None
508
+ rows.append(ReuseRow(
509
+ key=row_key,
510
+ label=label,
511
+ project_key=project_key,
512
+ project_label=project_label,
513
+ last_activity=_require_aware(ordered[-1].timestamp, "entry timestamp"),
514
+ totals=_totals(ordered),
515
+ models=tuple((model, _totals(by_model[model])) for model in sorted(by_model)),
516
+ ))
517
+ resolved_sort = sort or ("recent" if group_by == "session" else "date")
518
+ if resolved_sort not in {"date", "recent", "cost", "reuse"}:
519
+ raise ValueError("Codex reuse sort must be date, recent, cost, or reuse")
520
+ if resolved_sort == "recent":
521
+ rows.sort(key=lambda row: (row.last_activity, row.label, row.key), reverse=True)
522
+ elif resolved_sort == "cost":
523
+ rows.sort(key=lambda row: (row.totals.cost_usd, row.label, row.key), reverse=True)
524
+ elif resolved_sort == "reuse":
525
+ rows.sort(
526
+ key=lambda row: (
527
+ row.totals.cached_input_percent if row.totals.cached_input_percent is not None else -1.0,
528
+ row.label, row.key,
529
+ ),
530
+ reverse=True,
531
+ )
532
+ else:
533
+ rows.sort(key=lambda row: (row.label, row.key))
534
+ timestamps = sorted(_require_aware(entry.timestamp, "entry timestamp") for entry in values)
535
+ data = CodexReuseData(
536
+ requested_start if requested_start is not None else (timestamps[0] if timestamps else None),
537
+ requested_end if requested_end is not None else (timestamps[-1] if timestamps else None),
538
+ group_by,
539
+ _totals(values),
540
+ tuple(rows),
541
+ project_metadata_available,
542
+ resolved_sort,
543
+ )
544
+ status: Availability = "empty" if not values else "ok"
545
+ warnings: tuple[SourceWarning, ...] = ()
546
+ if not project_metadata_available:
547
+ status = "partial"
548
+ warnings = (QUALIFIED_METADATA_WARNING,)
549
+ return SourceResult("codex", status, data, warnings)
550
+
551
+
552
+ @dataclass(frozen=True)
553
+ class DiffWindowTotals:
554
+ window: AnalyticsWindow
555
+ totals: TokenTotals
556
+
557
+
558
+ @dataclass(frozen=True)
559
+ class DiffRow:
560
+ key: str
561
+ label: str
562
+ status: Literal["changed", "new", "dropped"]
563
+ a: TokenTotals
564
+ b: TokenTotals
565
+
566
+
567
+ @dataclass(frozen=True)
568
+ class DiffSection:
569
+ key: str
570
+ label: str
571
+ status: Literal["ok", "empty", "unavailable"]
572
+ rows: tuple[DiffRow, ...]
573
+
574
+
575
+ @dataclass(frozen=True)
576
+ class CodexDiffData:
577
+ windows: tuple[DiffWindowTotals, DiffWindowTotals]
578
+ combined_a: TokenTotals
579
+ combined_b: TokenTotals
580
+ sections: tuple[DiffSection, ...]
581
+ only: str | None = None
582
+ smart_filter: bool = False
583
+ sort: str = "cost"
584
+ limit: int | None = None
585
+ normalization: Literal["none", "per-day"] | None = None
586
+ min_delta_usd: float | None = None
587
+ min_delta_pct: float | None = None
588
+
589
+
590
+ @dataclass(frozen=True)
591
+ class CodexReportRow:
592
+ block_start: dt.datetime
593
+ reset_at: dt.datetime
594
+ used_percent: float | None
595
+ cost_usd: float
596
+ cost_per_percent: float | None
597
+ status: Literal["ok", "unavailable"]
598
+ detail: tuple["CodexReportDetailRow", ...] = ()
599
+
600
+
601
+ @dataclass(frozen=True)
602
+ class CodexReportDetailRow:
603
+ """One native quota-percent crossing within a selected reset block."""
604
+
605
+ percent_threshold: int
606
+ captured_at: dt.datetime
607
+ cumulative_cost_usd: float
608
+ marginal_cost_usd: float
609
+
610
+
611
+ @dataclass(frozen=True)
612
+ class CodexReportSeries:
613
+ quota_key: str
614
+ slot: str
615
+ window_minutes: int
616
+ rows: tuple[CodexReportRow, ...]
617
+
618
+
619
+ @dataclass(frozen=True)
620
+ class CodexReportData:
621
+ as_of: dt.datetime
622
+ series: tuple[CodexReportSeries, ...]
623
+ quota_status: Literal["ok", "empty", "unavailable"]
624
+ quota_warnings: tuple[SourceWarning, ...] = ()
625
+
626
+
627
+ def build_codex_report_result(
628
+ entries: Iterable[QualifiedCodexEntry], blocks: Iterable[object], *,
629
+ as_of: dt.datetime, quota_available: bool = True, include_detail: bool = False,
630
+ ) -> SourceResult[CodexReportData | None]:
631
+ """Build per-logical-limit quota accounting without cross-limit arithmetic.
632
+
633
+ A physical Codex accounting entry can truthfully participate in more than
634
+ one logical-limit series. This function therefore never sums series, has
635
+ no combined field, and deliberately does not receive project metadata.
636
+ """
637
+ as_of_utc = _require_aware(as_of, "as_of")
638
+ if not quota_available:
639
+ data = CodexReportData(
640
+ as_of_utc, (), "unavailable", (QUOTA_STATE_WARNING,),
641
+ )
642
+ return SourceResult("codex", "partial", data, (QUOTA_STATE_WARNING,))
643
+ values = tuple(entries)
644
+ grouped: dict[tuple[str, str, str, str, int], list[CodexReportRow]] = defaultdict(list)
645
+ for block in blocks:
646
+ identity = block.identity
647
+ if getattr(identity, "source", None) != "codex":
648
+ continue
649
+ nominal_start = _require_aware(block.nominal_start_at, "block nominal_start_at")
650
+ reset_at = _require_aware(block.resets_at, "block resets_at")
651
+ block_end = min(as_of_utc, reset_at)
652
+ if block_end <= nominal_start:
653
+ continue
654
+ block_entries = tuple(
655
+ entry for entry in values
656
+ if entry.source_root_key == identity.source_root_key
657
+ and _entry_in_half_open(entry, nominal_start, block_end)
658
+ )
659
+ totals = _totals(block_entries)
660
+ used_percent = float(block.current_percent)
661
+ key = (
662
+ identity.source, identity.source_root_key, identity.logical_limit_key,
663
+ identity.observed_slot, identity.window_minutes,
664
+ )
665
+ detail: tuple[CodexReportDetailRow, ...] = ()
666
+ if include_detail:
667
+ observations = tuple(sorted(
668
+ getattr(block, "observations", ()),
669
+ key=lambda observation: observation.captured_at,
670
+ ))
671
+ if observations:
672
+ prior_percent = math.floor(float(observations[0].used_percent) + 1e-9)
673
+ prior_boundary = observations[0].captured_at
674
+ cumulative_cost = 0.0
675
+ detail_rows: list[CodexReportDetailRow] = []
676
+ for observation in observations[1:]:
677
+ current_percent = math.floor(float(observation.used_percent) + 1e-9)
678
+ if current_percent <= prior_percent:
679
+ continue
680
+ marginal_cost = math.fsum(
681
+ entry.cost_usd for entry in block_entries
682
+ if prior_boundary < entry.timestamp <= observation.captured_at
683
+ )
684
+ cumulative_cost += marginal_cost
685
+ for threshold in range(prior_percent + 1, current_percent + 1):
686
+ detail_rows.append(CodexReportDetailRow(
687
+ percent_threshold=threshold,
688
+ captured_at=observation.captured_at,
689
+ cumulative_cost_usd=cumulative_cost,
690
+ marginal_cost_usd=(
691
+ marginal_cost if threshold == prior_percent + 1 else 0.0
692
+ ),
693
+ ))
694
+ prior_percent = current_percent
695
+ prior_boundary = observation.captured_at
696
+ detail = tuple(detail_rows)
697
+ grouped[key].append(CodexReportRow(
698
+ block_start=nominal_start,
699
+ reset_at=reset_at,
700
+ used_percent=used_percent,
701
+ cost_usd=totals.cost_usd,
702
+ cost_per_percent=(totals.cost_usd / used_percent if used_percent > 0 else None),
703
+ status="ok",
704
+ detail=detail,
705
+ ))
706
+ series: list[CodexReportSeries] = []
707
+ for (source, root_key, limit_key, slot, window_minutes), rows in sorted(grouped.items()):
708
+ series.append(CodexReportSeries(
709
+ quota_key=opaque_quota_key(source, root_key, limit_key, slot, window_minutes),
710
+ slot=slot,
711
+ window_minutes=window_minutes,
712
+ rows=tuple(sorted(rows, key=lambda row: (row.reset_at, row.block_start))),
713
+ ))
714
+ status: Literal["ok", "empty", "unavailable"] = "empty" if not series else "ok"
715
+ data = CodexReportData(as_of_utc, tuple(series), status)
716
+ return SourceResult("codex", status, data)
717
+
718
+
719
+ def _diff_rows(
720
+ a_entries: Iterable[QualifiedCodexEntry], b_entries: Iterable[QualifiedCodexEntry],
721
+ *, key_fn, label_fn, classify_presence: bool,
722
+ ) -> tuple[DiffRow, ...]:
723
+ a_groups: dict[str, list[QualifiedCodexEntry]] = defaultdict(list)
724
+ b_groups: dict[str, list[QualifiedCodexEntry]] = defaultdict(list)
725
+ labels: dict[str, str] = {}
726
+ for entry in a_entries:
727
+ key = key_fn(entry)
728
+ a_groups[key].append(entry)
729
+ labels.setdefault(key, label_fn(entry))
730
+ for entry in b_entries:
731
+ key = key_fn(entry)
732
+ b_groups[key].append(entry)
733
+ labels.setdefault(key, label_fn(entry))
734
+ rows: list[DiffRow] = []
735
+ for key in sorted(set(a_groups) | set(b_groups)):
736
+ has_a, has_b = key in a_groups, key in b_groups
737
+ a = _totals(a_groups[key])
738
+ b = _totals(b_groups[key])
739
+ status: Literal["changed", "new", "dropped"] = "changed"
740
+ if classify_presence:
741
+ if not has_a:
742
+ status = "new"
743
+ elif not has_b:
744
+ status = "dropped"
745
+ rows.append(DiffRow(key, labels[key], status, a, b))
746
+ return tuple(rows)
747
+
748
+
749
+ def _diff_window_length_days(window: AnalyticsWindow) -> float:
750
+ seconds = (window.end_at - window.start_at).total_seconds()
751
+ if seconds <= 0:
752
+ raise ValueError("Codex diff windows must have a positive length")
753
+ return seconds / 86_400.0
754
+
755
+
756
+ def resolve_codex_diff_normalization(
757
+ window_a: AnalyticsWindow, window_b: AnalyticsWindow, *, allow_mismatch: bool,
758
+ ) -> Literal["none", "per-day"]:
759
+ """Validate two source windows before provider reads and select their units."""
760
+ a_days = _diff_window_length_days(window_a)
761
+ b_days = _diff_window_length_days(window_b)
762
+ mismatched = abs(a_days - b_days) > 0.01
763
+ if not mismatched:
764
+ return "none"
765
+ same_eligible_kind = (
766
+ window_a.kind == window_b.kind and window_a.kind in {"week", "month"}
767
+ )
768
+ if not same_eligible_kind and not allow_mismatch:
769
+ raise ValueError(
770
+ f"window A is {a_days:.1f} days, window B is {b_days:.1f} days; "
771
+ "pass --allow-mismatch to compare anyway with per-day normalization"
772
+ )
773
+ return "per-day"
774
+
775
+
776
+ def _normalized_diff_totals(totals: TokenTotals, *, days: float, per_day: bool) -> TokenTotals:
777
+ if not per_day:
778
+ return totals
779
+ return TokenTotals(
780
+ input_tokens=totals.input_tokens,
781
+ cached_input_tokens=totals.cached_input_tokens,
782
+ output_tokens=totals.output_tokens,
783
+ reasoning_output_tokens=totals.reasoning_output_tokens,
784
+ total_tokens=totals.total_tokens,
785
+ cost_usd=totals.cost_usd / days,
786
+ )
787
+
788
+
789
+ def _normalized_diff_rows(
790
+ rows: Iterable[DiffRow], *, a_days: float, b_days: float, per_day: bool,
791
+ ) -> tuple[DiffRow, ...]:
792
+ return tuple(
793
+ DiffRow(
794
+ row.key, row.label, row.status,
795
+ _normalized_diff_totals(row.a, days=a_days, per_day=per_day),
796
+ _normalized_diff_totals(row.b, days=b_days, per_day=per_day),
797
+ )
798
+ for row in rows
799
+ )
800
+
801
+
802
+ def _diff_row_cost_pct(row: DiffRow) -> float:
803
+ delta = row.b.cost_usd - row.a.cost_usd
804
+ if row.a.cost_usd == 0:
805
+ return 0.0 if delta == 0 else math.inf
806
+ return delta / row.a.cost_usd * 100.0
807
+
808
+
809
+ def _sort_diff_rows(rows: Iterable[DiffRow], sort: str) -> tuple[DiffRow, ...]:
810
+ if sort == "delta":
811
+ key = lambda row: (-abs(row.b.cost_usd - row.a.cost_usd), row.label, row.key)
812
+ elif sort in {"cost", "cost-a"}:
813
+ key = lambda row: (-row.a.cost_usd, row.label, row.key)
814
+ elif sort == "cost-b":
815
+ key = lambda row: (-row.b.cost_usd, row.label, row.key)
816
+ elif sort == "name":
817
+ key = lambda row: (row.label, row.key)
818
+ elif sort == "status":
819
+ order = {"dropped": 0, "changed": 1, "new": 2}
820
+ key = lambda row: (
821
+ order[row.status], -abs(row.b.cost_usd - row.a.cost_usd), row.label, row.key,
822
+ )
823
+ else:
824
+ raise ValueError("Codex diff sort must be delta, cost-a, cost-b, name, or status")
825
+ return tuple(sorted(rows, key=key))
826
+
827
+
828
+ def _visible_diff_rows(
829
+ rows: Iterable[DiffRow], *, smart_filter: bool, min_delta_usd: float,
830
+ min_delta_pct: float, sort: str, top: int | None,
831
+ ) -> tuple[DiffRow, ...]:
832
+ """Mirror the legacy diff filter/sort/top semantics for one provider leg."""
833
+ visible: list[DiffRow] = []
834
+ for row in rows:
835
+ if (
836
+ smart_filter
837
+ and row.status == "changed"
838
+ and abs(row.b.cost_usd - row.a.cost_usd) < min_delta_usd
839
+ and abs(_diff_row_cost_pct(row)) < min_delta_pct
840
+ ):
841
+ continue
842
+ visible.append(row)
843
+ visible = list(_sort_diff_rows(visible, sort))
844
+ if top is not None and top >= 0:
845
+ changed = [row for row in visible if row.status == "changed"]
846
+ terminal = [row for row in visible if row.status != "changed"]
847
+ visible = list(_sort_diff_rows((*terminal, *changed[:top]), sort))
848
+ return tuple(visible)
849
+
850
+
851
+ def build_codex_diff_result(
852
+ entries: Iterable[QualifiedCodexEntry], window_a: AnalyticsWindow,
853
+ window_b: AnalyticsWindow, *, project_metadata_available: bool = True,
854
+ only: str | None = None, allow_mismatch: bool = False, show_all: bool = True,
855
+ min_delta_usd: float = 0.10, min_delta_pct: float = 1.0,
856
+ sort: str = "cost", top: int | None = None,
857
+ normalization: Literal["none", "per-day"] | None = None,
858
+ classify_presence: bool = False,
859
+ ) -> SourceResult[CodexDiffData]:
860
+ """Compare two half-open windows without a Claude cache section."""
861
+ # The command validates exposed windows before provider I/O. Keep this
862
+ # pure builder permissive for direct in-process callers that use it to
863
+ # inspect arbitrary half-open windows; explicit normalisation is the
864
+ # command-to-kernel contract.
865
+ if normalization is None:
866
+ normalization = (
867
+ resolve_codex_diff_normalization(
868
+ window_a, window_b, allow_mismatch=True,
869
+ ) if allow_mismatch else "none"
870
+ )
871
+ per_day = normalization == "per-day"
872
+ a_days, b_days = _diff_window_length_days(window_a), _diff_window_length_days(window_b)
873
+ values = tuple(entries)
874
+ a_entries = tuple(entry for entry in values if _entry_in_half_open(entry, window_a.start_at, window_a.end_at))
875
+ b_entries = tuple(entry for entry in values if _entry_in_half_open(entry, window_b.start_at, window_b.end_at))
876
+ overall_rows = _diff_rows(
877
+ a_entries, b_entries, key_fn=lambda _entry: "overall", label_fn=lambda _entry: "Overall",
878
+ classify_presence=classify_presence,
879
+ )
880
+ model_rows = _diff_rows(
881
+ a_entries, b_entries, key_fn=lambda entry: entry.model, label_fn=lambda entry: entry.model,
882
+ classify_presence=classify_presence,
883
+ )
884
+ project_rows = _diff_rows(
885
+ a_entries, b_entries, key_fn=lambda entry: entry.project_key,
886
+ label_fn=emitted_project_label,
887
+ classify_presence=classify_presence,
888
+ )
889
+ reuse_rows = _diff_rows(
890
+ a_entries, b_entries, key_fn=lambda entry: entry.model, label_fn=lambda entry: entry.model,
891
+ classify_presence=classify_presence,
892
+ )
893
+ overall_rows = _normalized_diff_rows(
894
+ overall_rows, a_days=a_days, b_days=b_days, per_day=per_day,
895
+ )
896
+ model_rows = _visible_diff_rows(
897
+ _normalized_diff_rows(model_rows, a_days=a_days, b_days=b_days, per_day=per_day),
898
+ smart_filter=not show_all, min_delta_usd=min_delta_usd,
899
+ min_delta_pct=min_delta_pct, sort=sort, top=top,
900
+ )
901
+ project_rows = _visible_diff_rows(
902
+ _normalized_diff_rows(project_rows, a_days=a_days, b_days=b_days, per_day=per_day),
903
+ smart_filter=not show_all, min_delta_usd=min_delta_usd,
904
+ min_delta_pct=min_delta_pct, sort=sort, top=top,
905
+ )
906
+ reuse_rows = _visible_diff_rows(
907
+ _normalized_diff_rows(reuse_rows, a_days=a_days, b_days=b_days, per_day=per_day),
908
+ smart_filter=not show_all, min_delta_usd=min_delta_usd,
909
+ min_delta_pct=min_delta_pct, sort=sort, top=top,
910
+ )
911
+ sections = (
912
+ DiffSection("overall", "Overall", "empty" if not overall_rows else "ok", overall_rows),
913
+ DiffSection("models", "Models", "empty" if not model_rows else "ok", model_rows),
914
+ DiffSection(
915
+ "projects", "Projects",
916
+ "unavailable" if not project_metadata_available else ("empty" if not project_rows else "ok"),
917
+ () if not project_metadata_available else project_rows,
918
+ ),
919
+ DiffSection("token-reuse", "Token reuse", "empty" if not reuse_rows else "ok", reuse_rows),
920
+ )
921
+ selected = None if only is None else {
922
+ item.strip() for item in only.split(",") if item.strip()
923
+ }
924
+ if selected is not None:
925
+ supported = {section.key for section in sections}
926
+ unknown = selected - supported
927
+ if unknown:
928
+ raise ValueError(
929
+ "Codex diff --only contains unknown section(s): "
930
+ + ", ".join(sorted(unknown))
931
+ )
932
+ sections = tuple(section for section in sections if section.key in selected)
933
+ data = CodexDiffData(
934
+ (
935
+ DiffWindowTotals(
936
+ window_a, _normalized_diff_totals(_totals(a_entries), days=a_days, per_day=per_day),
937
+ ),
938
+ DiffWindowTotals(
939
+ window_b, _normalized_diff_totals(_totals(b_entries), days=b_days, per_day=per_day),
940
+ ),
941
+ ),
942
+ _normalized_diff_totals(_totals(a_entries), days=a_days, per_day=per_day),
943
+ _normalized_diff_totals(_totals(b_entries), days=b_days, per_day=per_day), sections,
944
+ only=only,
945
+ smart_filter=not show_all,
946
+ sort=sort,
947
+ limit=top,
948
+ normalization=(normalization if normalization != "none" else None),
949
+ min_delta_usd=(min_delta_usd if classify_presence else None),
950
+ min_delta_pct=(min_delta_pct if classify_presence else None),
951
+ )
952
+ status: Availability = "empty" if not a_entries and not b_entries else "ok"
953
+ warnings: tuple[SourceWarning, ...] = ()
954
+ if any(section.status == "unavailable" for section in sections):
955
+ status = "partial"
956
+ warnings = (QUALIFIED_METADATA_WARNING,)
957
+ return SourceResult("codex", status, data, warnings)
958
+
959
+
960
+ def _iso_z(value: dt.datetime) -> str:
961
+ return _require_aware(value, "timestamp").isoformat().replace("+00:00", "Z")
962
+
963
+
964
+ def _project_wire(row: CodexProjectRow, *, include_breakdown: bool) -> dict[str, object]:
965
+ result: dict[str, object] = {
966
+ "projectKey": row.project_key,
967
+ "displayLabel": row.display_label,
968
+ "sessionCount": row.session_count,
969
+ "firstSeen": _iso_z(row.first_seen),
970
+ "lastSeen": _iso_z(row.last_seen),
971
+ "tokens": _codex_tokens_wire(row.totals),
972
+ "costUsd": row.totals.cost_usd,
973
+ "quotaAttributions": [
974
+ {
975
+ "quotaKey": attribution.quota_key,
976
+ "slot": attribution.slot,
977
+ "windowMinutes": attribution.window_minutes,
978
+ "resetAt": _iso_z(attribution.reset_at) if attribution.reset_at else None,
979
+ "usedPercent": attribution.used_percent,
980
+ "attributedUsedPercent": attribution.attributed_used_percent,
981
+ "costPerPercent": attribution.cost_per_percent,
982
+ "status": attribution.status,
983
+ }
984
+ for attribution in row.quota_attributions
985
+ ],
986
+ }
987
+ if include_breakdown:
988
+ result["models"] = [
989
+ {"model": model, "costUsd": totals.cost_usd,
990
+ **_codex_tokens_wire(totals)}
991
+ for model, totals in row.models
992
+ ]
993
+ return result
994
+
995
+
996
+ def _range_wire(data: CodexRangeData) -> dict[str, object]:
997
+ result: dict[str, object] = {
998
+ "start": _iso_z(data.start),
999
+ "end": _iso_z(data.end),
1000
+ "totals": {"costUsd": data.totals.cost_usd, **_codex_tokens_wire(data.totals)},
1001
+ }
1002
+ result["models"] = (
1003
+ [
1004
+ {"model": model, "costUsd": totals.cost_usd, **_codex_tokens_wire(totals)}
1005
+ for model, totals in data.models
1006
+ ]
1007
+ if data.include_breakdown else []
1008
+ )
1009
+ return result
1010
+
1011
+
1012
+ def _reuse_row_wire(row: ReuseRow) -> dict[str, object]:
1013
+ return {
1014
+ "key": row.key,
1015
+ "label": row.label,
1016
+ "projectKey": row.project_key,
1017
+ "projectLabel": row.project_label,
1018
+ "lastActivity": _iso_z(row.last_activity),
1019
+ "costUsd": row.totals.cost_usd,
1020
+ **_codex_tokens_wire(row.totals, cached_percent=True),
1021
+ "models": [
1022
+ {"model": model, "costUsd": totals.cost_usd,
1023
+ **_codex_tokens_wire(totals, cached_percent=True)}
1024
+ for model, totals in row.models
1025
+ ],
1026
+ }
1027
+
1028
+
1029
+ def _reuse_wire(data: CodexReuseData) -> dict[str, object]:
1030
+ return {
1031
+ "start": _iso_z(data.start) if data.start else None,
1032
+ "end": _iso_z(data.end) if data.end else None,
1033
+ "groupBy": data.group_by,
1034
+ "totals": {
1035
+ "costUsd": data.totals.cost_usd,
1036
+ **_codex_tokens_wire(data.totals, cached_percent=True),
1037
+ },
1038
+ "sections": [
1039
+ {
1040
+ "key": "token-reuse",
1041
+ "status": "empty" if not data.rows else "ok",
1042
+ "data": {"rows": [_reuse_row_wire(row) for row in data.rows]},
1043
+ "warnings": [],
1044
+ },
1045
+ unavailable_section("project-metadata", QUALIFIED_METADATA_WARNING)
1046
+ if not data.project_metadata_available else {
1047
+ "key": "project-metadata",
1048
+ "status": "empty" if not data.rows else "ok",
1049
+ "data": {"projects": []},
1050
+ "warnings": [],
1051
+ },
1052
+ ],
1053
+ }
1054
+
1055
+
1056
+ def _diff_delta(a: TokenTotals, b: TokenTotals) -> dict[str, object]:
1057
+ return {
1058
+ "cost_usd": b.cost_usd - a.cost_usd,
1059
+ "total_tokens": b.total_tokens - a.total_tokens,
1060
+ "input_tokens": b.input_tokens - a.input_tokens,
1061
+ "cached_input_tokens": b.cached_input_tokens - a.cached_input_tokens,
1062
+ "output_tokens": b.output_tokens - a.output_tokens,
1063
+ "reasoning_output_tokens": b.reasoning_output_tokens - a.reasoning_output_tokens,
1064
+ }
1065
+
1066
+
1067
+ def _diff_wire(data: CodexDiffData) -> dict[str, object]:
1068
+ window_a, window_b = data.windows
1069
+ options: dict[str, object] = {
1070
+ "only": data.only,
1071
+ "smart_filter": data.smart_filter,
1072
+ "sort": data.sort,
1073
+ "limit": data.limit,
1074
+ }
1075
+ if data.normalization is not None:
1076
+ options["normalization"] = data.normalization
1077
+ if data.min_delta_usd is not None:
1078
+ options["min_delta_usd"] = data.min_delta_usd
1079
+ if data.min_delta_pct is not None:
1080
+ options["min_delta_pct"] = data.min_delta_pct
1081
+ return {
1082
+ "windows": {
1083
+ "a": {
1084
+ "label": window_a.window.label,
1085
+ "kind": window_a.window.kind,
1086
+ "start_at": _iso_z(window_a.window.start_at),
1087
+ "end_at": _iso_z(window_a.window.end_at),
1088
+ "used_pct_mode": window_a.window.used_pct_mode,
1089
+ },
1090
+ "b": {
1091
+ "label": window_b.window.label,
1092
+ "kind": window_b.window.kind,
1093
+ "start_at": _iso_z(window_b.window.start_at),
1094
+ "end_at": _iso_z(window_b.window.end_at),
1095
+ "used_pct_mode": window_b.window.used_pct_mode,
1096
+ },
1097
+ },
1098
+ "combined": {
1099
+ "cost_usd": {
1100
+ "a": data.combined_a.cost_usd,
1101
+ "b": data.combined_b.cost_usd,
1102
+ "delta": data.combined_b.cost_usd - data.combined_a.cost_usd,
1103
+ },
1104
+ "total_tokens": {
1105
+ "a": data.combined_a.total_tokens,
1106
+ "b": data.combined_b.total_tokens,
1107
+ "delta": data.combined_b.total_tokens - data.combined_a.total_tokens,
1108
+ },
1109
+ },
1110
+ "sections": [
1111
+ unavailable_section(section.key, QUALIFIED_METADATA_WARNING)
1112
+ if section.status == "unavailable" else {
1113
+ "key": section.key,
1114
+ "label": section.label,
1115
+ "status": section.status,
1116
+ "data": {"rows": [
1117
+ {
1118
+ "key": row.key,
1119
+ "label": row.label,
1120
+ "status": row.status,
1121
+ "a": _metric_wire(row.a),
1122
+ "b": _metric_wire(row.b),
1123
+ "delta": _diff_delta(row.a, row.b),
1124
+ }
1125
+ for row in section.rows
1126
+ ]},
1127
+ "warnings": [],
1128
+ }
1129
+ for section in data.sections
1130
+ ],
1131
+ "options": options,
1132
+ }
1133
+
1134
+
1135
+ def unavailable_section(key: str, warning: SourceWarning) -> dict[str, object]:
1136
+ """Return the frozen unavailable sibling-section shape."""
1137
+ return {
1138
+ "key": key,
1139
+ "status": "unavailable",
1140
+ "data": None,
1141
+ "warnings": [{"code": warning.code, "message": warning.message}],
1142
+ }
1143
+
1144
+
1145
+ def _report_wire(data: CodexReportData) -> dict[str, object]:
1146
+ section_data: dict[str, object] | None = {
1147
+ "series": [
1148
+ {
1149
+ "quotaKey": series.quota_key,
1150
+ "sourceLabel": "Codex",
1151
+ "slot": series.slot,
1152
+ "windowMinutes": series.window_minutes,
1153
+ "rows": [
1154
+ {
1155
+ "blockStart": _iso_z(row.block_start),
1156
+ "resetAt": _iso_z(row.reset_at),
1157
+ "usedPercent": row.used_percent,
1158
+ "costUsd": row.cost_usd,
1159
+ "costPerPercent": row.cost_per_percent,
1160
+ "status": row.status,
1161
+ "detail": [
1162
+ {
1163
+ "percentThreshold": detail.percent_threshold,
1164
+ "cumulativeCostUSD": detail.cumulative_cost_usd,
1165
+ "marginalCostUSD": detail.marginal_cost_usd,
1166
+ "capturedAt": _iso_z(detail.captured_at),
1167
+ }
1168
+ for detail in row.detail
1169
+ ],
1170
+ }
1171
+ for row in series.rows
1172
+ ],
1173
+ }
1174
+ for series in data.series
1175
+ ],
1176
+ } if data.quota_status != "unavailable" else None
1177
+ return {
1178
+ "asOf": _iso_z(data.as_of),
1179
+ "sections": [{
1180
+ "key": "quota-series",
1181
+ "status": data.quota_status,
1182
+ "data": section_data,
1183
+ "warnings": [
1184
+ {"code": warning.code, "message": warning.message}
1185
+ for warning in data.quota_warnings
1186
+ ],
1187
+ }],
1188
+ }
1189
+
1190
+
1191
+ def _physical_totals(value: object) -> TokenTotals:
1192
+ if isinstance(value, SourceResult):
1193
+ if value.status == "unavailable" or value.data is None:
1194
+ return TokenTotals()
1195
+ value = value.data
1196
+ if isinstance(value, TokenTotals):
1197
+ return value
1198
+ if isinstance(value, (CodexProjectData, CodexRangeData, CodexReuseData)):
1199
+ return value.totals
1200
+ if isinstance(value, CodexDiffData):
1201
+ raise ValueError("diff has two windows and is not one physical accounting total")
1202
+ if isinstance(value, CodexReportData):
1203
+ raise ValueError("report quota series overlap and cannot be combined")
1204
+ if isinstance(value, dict):
1205
+ try:
1206
+ return TokenTotals(
1207
+ total_tokens=int(value.get("totalTokens", value.get("total_tokens", 0))),
1208
+ cost_usd=float(value.get("costUsd", value.get("cost_usd", 0.0))),
1209
+ )
1210
+ except (TypeError, ValueError) as exc:
1211
+ raise ValueError("invalid physical accounting totals") from exc
1212
+ raise TypeError("unsupported physical accounting value")
1213
+
1214
+
1215
+ def combine_physical_accounting(claude: object, codex: object) -> dict[str, object]:
1216
+ """Combine only one already-deduplicated physical total per provider."""
1217
+ claude_totals = _physical_totals(claude)
1218
+ codex_totals = _physical_totals(codex)
1219
+ return {
1220
+ "costUsd": claude_totals.cost_usd + codex_totals.cost_usd,
1221
+ "totalTokens": claude_totals.total_tokens + codex_totals.total_tokens,
1222
+ }
1223
+
1224
+
1225
+ def _diff_combined_values(result: SourceResult[object]) -> tuple[TokenTotals, TokenTotals] | None:
1226
+ """Return one provider's truthful A/B totals without flattening its wire."""
1227
+ if result.status == "unavailable" or result.data is None:
1228
+ return None
1229
+ if isinstance(result.data, CodexDiffData):
1230
+ return result.data.combined_a, result.data.combined_b
1231
+ if not isinstance(result.data, dict):
1232
+ raise TypeError("diff source data must retain a combined object")
1233
+ combined = result.data.get("combined")
1234
+ if not isinstance(combined, dict):
1235
+ raise ValueError("diff source data is missing combined accounting")
1236
+
1237
+ def totals_for(metric_name: str) -> tuple[float, float]:
1238
+ metric = combined.get(metric_name)
1239
+ if not isinstance(metric, dict):
1240
+ raise ValueError(f"diff source data is missing {metric_name}")
1241
+ try:
1242
+ return float(metric["a"]), float(metric["b"])
1243
+ except (KeyError, TypeError, ValueError) as exc:
1244
+ raise ValueError(f"diff source data has invalid {metric_name}") from exc
1245
+
1246
+ cost_a, cost_b = totals_for("cost_usd")
1247
+ tokens_a, tokens_b = totals_for("total_tokens")
1248
+ return (
1249
+ TokenTotals(cost_usd=cost_a, total_tokens=int(tokens_a)),
1250
+ TokenTotals(cost_usd=cost_b, total_tokens=int(tokens_b)),
1251
+ )
1252
+
1253
+
1254
+ def combine_diff_accounting(
1255
+ claude: SourceResult[object], codex: SourceResult[object],
1256
+ ) -> dict[str, object]:
1257
+ """Combine truthful per-window diff accounting without scalar coercion.
1258
+
1259
+ An unavailable source yields no inferred values; only the provider blocks
1260
+ with complete, truthful A/B accounting contribute to the all-source pair.
1261
+ """
1262
+ totals = [
1263
+ values for values in (_diff_combined_values(claude), _diff_combined_values(codex))
1264
+ if values is not None
1265
+ ]
1266
+ a_cost = math.fsum(values[0].cost_usd for values in totals)
1267
+ b_cost = math.fsum(values[1].cost_usd for values in totals)
1268
+ a_tokens = sum(values[0].total_tokens for values in totals)
1269
+ b_tokens = sum(values[1].total_tokens for values in totals)
1270
+ return {
1271
+ "cost_usd": {"a": a_cost, "b": b_cost, "delta": b_cost - a_cost},
1272
+ "total_tokens": {"a": a_tokens, "b": b_tokens, "delta": b_tokens - a_tokens},
1273
+ }
1274
+
1275
+
1276
+ def source_result_wire(result: SourceResult[object], *, diff: bool = False) -> dict[str, object]:
1277
+ """Render the exact direct-provider envelope for implemented pure kernels."""
1278
+ if result.data is None:
1279
+ data: object = None
1280
+ elif isinstance(result.data, CodexProjectData):
1281
+ data: object = {
1282
+ "rangeStart": _iso_z(result.data.range_start),
1283
+ "rangeEnd": _iso_z(result.data.range_end),
1284
+ "windowKind": result.data.window_kind,
1285
+ "totals": {"costUsd": result.data.totals.cost_usd, "totalTokens": result.data.totals.total_tokens},
1286
+ "projects": [
1287
+ _project_wire(row, include_breakdown=result.data.include_breakdown)
1288
+ for row in result.data.projects
1289
+ ],
1290
+ }
1291
+ elif isinstance(result.data, CodexRangeData):
1292
+ data = _range_wire(result.data)
1293
+ elif isinstance(result.data, CodexReuseData):
1294
+ data = _reuse_wire(result.data)
1295
+ elif isinstance(result.data, CodexDiffData):
1296
+ data = _diff_wire(result.data)
1297
+ diff = True
1298
+ elif isinstance(result.data, CodexReportData):
1299
+ data = _report_wire(result.data)
1300
+ else:
1301
+ raise TypeError("unsupported source result data")
1302
+ warnings = [{"code": warning.code, "message": warning.message} for warning in result.warnings]
1303
+ if diff:
1304
+ return {
1305
+ "schema_version": 1,
1306
+ "source": result.source,
1307
+ "status": result.status,
1308
+ "data": data,
1309
+ "warnings": warnings,
1310
+ }
1311
+ return {
1312
+ "schemaVersion": 1,
1313
+ "source": result.source,
1314
+ "status": result.status,
1315
+ "data": data,
1316
+ "warnings": warnings,
1317
+ }
1318
+
1319
+
1320
+ def _source_block_wire(result: SourceResult[object], *, diff: bool) -> dict[str, object]:
1321
+ """Return one source block without changing its provider-native data."""
1322
+ if result.source == "codex":
1323
+ direct = source_result_wire(result, diff=diff)
1324
+ return {
1325
+ "source": direct["source"],
1326
+ "status": direct["status"],
1327
+ "data": direct["data"],
1328
+ "warnings": direct["warnings"],
1329
+ }
1330
+ if result.source != "claude":
1331
+ raise ValueError("all-source results require claude and codex blocks")
1332
+ warnings = [{"code": warning.code, "message": warning.message} for warning in result.warnings]
1333
+ return {
1334
+ "source": "claude",
1335
+ "status": result.status,
1336
+ "data": result.data,
1337
+ "warnings": warnings,
1338
+ }
1339
+
1340
+
1341
+ def all_source_result_wire(
1342
+ claude: SourceResult[object], codex: SourceResult[object], *,
1343
+ diff: bool = False, report: bool = False,
1344
+ ) -> dict[str, object]:
1345
+ """Compose separate provider blocks without flattening unlike semantics.
1346
+
1347
+ Only the dedicated physical accounting combiner may supply the all-source
1348
+ aggregate. Quota reports explicitly omit it because their logical-limit
1349
+ series can overlap the same physical accounting rows.
1350
+ """
1351
+ if claude.source != "claude" or codex.source != "codex":
1352
+ raise ValueError("all-source results require claude then codex")
1353
+ sources = [
1354
+ _source_block_wire(claude, diff=diff),
1355
+ _source_block_wire(codex, diff=diff),
1356
+ ]
1357
+ if diff:
1358
+ result: dict[str, object] = {
1359
+ "schema_version": 1,
1360
+ "source": "all",
1361
+ }
1362
+ if not report:
1363
+ result["combined"] = combine_diff_accounting(claude, codex)
1364
+ result["sources"] = sources
1365
+ result["warnings"] = []
1366
+ return result
1367
+ result = {
1368
+ "schemaVersion": 1,
1369
+ "source": "all",
1370
+ }
1371
+ if not report:
1372
+ result["combined"] = combine_physical_accounting(claude, codex)
1373
+ result["sources"] = sources
1374
+ result["warnings"] = []
1375
+ return result