@techierathore/techieflow 1.0.3 → 1.0.4
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.
- package/.claude/commands/TechieFlow/agents/analyst.md +5 -8
- package/.claude/commands/TechieFlow/agents/architect.md +1 -10
- package/.claude/commands/TechieFlow/agents/flow-master.md +1 -0
- package/.claude/commands/TechieFlow/agents/verifier.md +1 -0
- package/.claude/commands/TechieFlow/tasks/_owner-language.md +26 -0
- package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +1 -1
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +6 -5
- package/.claude/commands/TechieFlow/tasks/build-phase.md +1 -1
- package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +4 -0
- package/.claude/commands/TechieFlow/tasks/create-doc.md +4 -0
- package/.claude/commands/TechieFlow/tasks/create-project-brief.md +22 -0
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +2 -2
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +2 -2
- package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +4 -0
- package/.claude/commands/TechieFlow/tasks/generate-html.md +5 -1
- package/.claude/commands/TechieFlow/tasks/handoff-phase.md +18 -16
- package/.claude/commands/TechieFlow/tasks/metrics-report.md +2 -0
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +1 -1
- package/.opencode/plugin/techieflow.js +2 -2
- package/.tfcore/agents/analyst.md +5 -8
- package/.tfcore/agents/architect.md +1 -10
- package/.tfcore/agents/flow-master.md +1 -0
- package/.tfcore/agents/verifier.md +1 -0
- package/.tfcore/hooks/guard-metrics.sh +13 -1
- package/.tfcore/hooks/guard-verify-deps.sh +124 -0
- package/.tfcore/routing.yaml +61 -0
- package/.tfcore/tasks/_owner-language.md +26 -0
- package/.tfcore/tasks/_smoke-test-policy.md +1 -1
- package/.tfcore/tasks/amend-docs.md +6 -5
- package/.tfcore/tasks/build-phase.md +1 -1
- package/.tfcore/tasks/create-deep-research-prompt.md +4 -0
- package/.tfcore/tasks/create-doc.md +4 -0
- package/.tfcore/tasks/create-project-brief.md +22 -0
- package/.tfcore/tasks/day1-brownfield.md +2 -2
- package/.tfcore/tasks/day1-greenfield.md +2 -2
- package/.tfcore/tasks/facilitate-brainstorming-session.md +4 -0
- package/.tfcore/tasks/generate-html.md +5 -1
- package/.tfcore/tasks/handoff-phase.md +18 -16
- package/.tfcore/tasks/metrics-report.md +2 -0
- package/.tfcore/tasks/verify-phase.md +1 -1
- package/.tfcore/telemetry/SCHEMA.md +57 -1
- package/.tfcore/telemetry/tf-metrics.sh +416 -19
- package/.tfcore/templates/v4custom/app-brief-tmpl.md +65 -0
- package/.tfcore/templates/v4custom/app-decision-request-tmpl.md +85 -0
- package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +86 -31
- package/.tfcore/templates/v4custom/html-render-shell.md +1 -1
- package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
- package/.tfcore/utils/tf-build-list.py +23 -3
- package/.tfcore/utils/tf-build-list.sh +3 -2
- package/.tfcore/utils/tf-doc-check.py +106 -4
- package/.tfcore/utils/tf-emit.sh +353 -16
- package/.tfcore/utils/tf-fix-close.sh +34 -2
- package/.tfcore/utils/tf-gitignore-audit.sh +50 -3
- package/.tfcore/utils/tf-goal.sh +93 -6
- package/.tfcore/utils/tf-misses-md.py +14 -17
- package/.tfcore/utils/tf-misses-md.sh +5 -4
- package/.tfcore/utils/tf-model-pick.sh +475 -0
- package/.tfcore/utils/tf-phase.sh +18 -0
- package/.tfcore/utils/tf-render-html.py +20 -0
- package/.tfcore/utils/tf-routing-bind.sh +87 -1
- package/.tfcore/utils/tf-routing.sh +7 -0
- package/.tfcore/utils/tf-selfcheck.sh +323 -0
- package/.tfcore/utils/tf-split-brd.py +6 -1
- package/.tfcore/utils/tf-status-facts.py +61 -12
- package/.tfcore/utils/tf-verify-screens.mjs +85 -13
- package/.tfcore/utils/tf-verify-screens.sh +12 -3
- package/.tfcore/utils/tf-verify-tests.sh +60 -17
- package/.tfcore/utils/tf-verify-verdict.py +11 -2
- package/README.md +15 -5
- package/opencode.jsonc +24 -0
- package/package.json +1 -1
- package/scripts/install.mjs +4 -0
- package/.tfcore/templates/architecture-tmpl.yaml +0 -650
- package/.tfcore/templates/brownfield-architecture-tmpl.yaml +0 -476
- package/.tfcore/templates/competitor-analysis-tmpl.yaml +0 -306
- package/.tfcore/templates/front-end-architecture-tmpl.yaml +0 -218
- package/.tfcore/templates/fullstack-architecture-tmpl.yaml +0 -823
- package/.tfcore/templates/market-research-tmpl.yaml +0 -252
- package/.tfcore/templates/project-brief-tmpl.yaml +0 -221
- package/.tfcore/utils/techieflow-doc-template.md +0 -325
|
@@ -26,7 +26,7 @@ set -uo pipefail
|
|
|
26
26
|
|
|
27
27
|
command -v python3 >/dev/null 2>&1 || { echo "tf-metrics: python3 is required." >&2; exit 1; }
|
|
28
28
|
|
|
29
|
-
exec python3 - "$@" <<'PYEOF'
|
|
29
|
+
TF_METRICS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" exec python3 - "$@" <<'PYEOF'
|
|
30
30
|
import glob
|
|
31
31
|
import json
|
|
32
32
|
import os
|
|
@@ -53,12 +53,77 @@ LATE_GATES = {"perf": "2026-08-10", "assets": "2026-08-31", "mockup-parity": "20
|
|
|
53
53
|
# fill, so counting it as "not assessed" understates the practice being measured.
|
|
54
54
|
# It leaves that field's denominator and is reported separately. SCHEMA.md §5.5.6 /
|
|
55
55
|
# §3.5. Keep this table in sync when an optional field is added to any stream.
|
|
56
|
-
FIELD_SINCE = {"why_missed": "2026-08-28", "sort": "2026-09-07"}
|
|
56
|
+
FIELD_SINCE = {"why_missed": "2026-08-28", "sort": "2026-09-07", "what": "2026-09-07"}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _span(r):
|
|
60
|
+
"""Seconds between a run's own `started` and `ended`, or None if they cannot be read."""
|
|
61
|
+
a, b = r.get("started"), r.get("ended")
|
|
62
|
+
if not isinstance(a, str) or not isinstance(b, str):
|
|
63
|
+
return None
|
|
64
|
+
try:
|
|
65
|
+
fmt = "%Y-%m-%dT%H:%M:%SZ"
|
|
66
|
+
return int((datetime.strptime(b, fmt) - datetime.strptime(a, fmt)).total_seconds())
|
|
67
|
+
except Exception:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _dur(r):
|
|
72
|
+
"""A run's duration in seconds, or None when the record does not carry a real one.
|
|
73
|
+
|
|
74
|
+
THE TIMESTAMPS WIN. A record stores a start, an end and a duration, and on a stream
|
|
75
|
+
written before the emitter checked them the third can contradict the first two:
|
|
76
|
+
TechieBlog holds 14 such records, one storing -166 and thirteen storing a plausible
|
|
77
|
+
round number (3600, 2700, 1800, 900, 600) that bears no relation to their own clocks
|
|
78
|
+
-- `refresh-status` started 20:05:00, ended 17:59:39, stored 600. Only the one that
|
|
79
|
+
stored the negative was ever detectable by reading the number alone.
|
|
80
|
+
|
|
81
|
+
So the duration is taken from the timestamps whenever they can be read, and a record
|
|
82
|
+
whose timestamps are themselves impossible carries NO duration rather than a
|
|
83
|
+
plausible one. The count of both is published (`duration_excluded_n`,
|
|
84
|
+
`duration_recomputed_n`) and printed, because "32 records, correct" is only honest
|
|
85
|
+
when the reader is also told that 14 were dropped. Every consumer goes through this
|
|
86
|
+
one function, so the two readers of a stream cannot disagree about the same record.
|
|
87
|
+
tests/regression/run.sh tf_015."""
|
|
88
|
+
span = _span(r)
|
|
89
|
+
if span is not None:
|
|
90
|
+
return span if span > 0 else None
|
|
91
|
+
d = r.get("duration_s")
|
|
92
|
+
return d if isinstance(d, (int, float)) and d > 0 else None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _dur_quality(records):
|
|
96
|
+
"""(impossible, absent, recomputed) -- what _dur changed, split so the report can be
|
|
97
|
+
accurate rather than merely alarming.
|
|
98
|
+
|
|
99
|
+
"Its start is after its end" and "it never carried a duration" are different facts and
|
|
100
|
+
were briefly reported as one, which made this repo look like it held 30 corrupt records
|
|
101
|
+
when it holds 30 records that simply record no elapsed time (`started` == `ended`, no
|
|
102
|
+
`duration_s`). Neither reached a figure before or after -- but a reader told the wrong
|
|
103
|
+
reason chases the wrong thing."""
|
|
104
|
+
impossible = absent = recomputed = 0
|
|
105
|
+
for r in records:
|
|
106
|
+
span, stored = _span(r), r.get("duration_s")
|
|
107
|
+
has_stored = isinstance(stored, (int, float)) and stored > 0
|
|
108
|
+
if span is not None and span < 0:
|
|
109
|
+
impossible += 1
|
|
110
|
+
elif span is not None and span == 0:
|
|
111
|
+
absent += 1
|
|
112
|
+
elif span is None and not has_stored:
|
|
113
|
+
absent += 1
|
|
114
|
+
elif span is not None and has_stored and abs(span - stored) > 1:
|
|
115
|
+
recomputed += 1
|
|
116
|
+
return impossible, absent, recomputed
|
|
57
117
|
|
|
58
118
|
# Fields a `miss-amend` record may complete, and their closed vocabularies.
|
|
59
119
|
# Kept identical to tf-emit.sh's `_AMENDABLE`; the emitter enforces it on write and
|
|
60
120
|
# this enforces it again on read, because a merged stream can carry records this
|
|
61
121
|
# machine's emitter never saw. SCHEMA.md §5.5.7 states the rule for extending it.
|
|
122
|
+
# `what` is the miss's own sentence: free text, not a vocabulary. Kept amendable
|
|
123
|
+
# because the never-overwrite rule -- not the vocabulary -- is what stops an amend
|
|
124
|
+
# altering a fact. MISS-TechieFlow-20260909-02; tests/regression/run.sh tf_018.
|
|
125
|
+
AMENDABLE_TEXT_FIELDS = ("what",)
|
|
126
|
+
|
|
62
127
|
AMENDABLE_FIELDS = {
|
|
63
128
|
"why_missed": ("missing-checklist-item", "insufficient-verify-method",
|
|
64
129
|
"code-audit-limitation", "ambiguous-acceptance",
|
|
@@ -71,6 +136,72 @@ SORT_WORDS = {"spec": "the app's spec did not say it", "unsaid": "the framework
|
|
|
71
136
|
MIN_N = 3 # fewer supporting records than this -> "insufficient data", never a number
|
|
72
137
|
|
|
73
138
|
|
|
139
|
+
# --------------------------------------------------- list price (report time)
|
|
140
|
+
# WHERE PRICING BELONGS (owner, 2026-09-10). The framework's job is to record what it MEASURED:
|
|
141
|
+
# tokens, per model, and whatever cost the provider itself reported. Turning tokens into money is
|
|
142
|
+
# a reporting job — this file's, and TfLens's — so it happens HERE, at report time, from the
|
|
143
|
+
# fields already on the stream. Nothing priced is ever written to a record. That is SCHEMA.md §8's
|
|
144
|
+
# existing rule ("derived metrics are computed, never stored") applied to money, and it is why a
|
|
145
|
+
# rate card can change, or a reader disagree with this one, without a single record being wrong.
|
|
146
|
+
#
|
|
147
|
+
# The rate itself comes from `tf-model-pick.sh rate`, so the rate card is defined once and the
|
|
148
|
+
# project's own `.tfcore/rate-card.json` wins where it exists.
|
|
149
|
+
_RATE_CACHE = {}
|
|
150
|
+
REPO_OF = {} # id(record) -> the repo it was read from; never written into a record
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def rate_for(repo, model):
|
|
154
|
+
key = (repo, model)
|
|
155
|
+
if key not in _RATE_CACHE:
|
|
156
|
+
got = None
|
|
157
|
+
for base in (os.path.join(repo, ".tfcore", "utils"),
|
|
158
|
+
os.path.join(os.environ.get("TF_METRICS_DIR", ""), "..", "utils")):
|
|
159
|
+
script = os.path.join(base, "tf-model-pick.sh")
|
|
160
|
+
if not os.path.isfile(script):
|
|
161
|
+
continue
|
|
162
|
+
try:
|
|
163
|
+
out = subprocess.run(["bash", script, "rate", model],
|
|
164
|
+
capture_output=True, text=True, timeout=20,
|
|
165
|
+
env=dict(os.environ, TF_PROJECT_DIR=repo))
|
|
166
|
+
if out.returncode == 0:
|
|
167
|
+
parts = (out.stdout or "").strip().split("\t")
|
|
168
|
+
if len(parts) >= 4:
|
|
169
|
+
got = tuple(float(x) for x in parts[:4])
|
|
170
|
+
break
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
_RATE_CACHE[key] = got
|
|
174
|
+
return _RATE_CACHE[key]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def list_price(repo, rec):
|
|
178
|
+
"""What one run's measured tokens cost at list price, or None when nothing prices its model.
|
|
179
|
+
|
|
180
|
+
Output tokens are exact per model (`model_tokens_out`). Input and cache tokens are counted for
|
|
181
|
+
the window as a whole, so where a window ran several models they are shared out by each
|
|
182
|
+
model's output share — arithmetic, and the caller is told."""
|
|
183
|
+
split = rec.get("model_tokens_out")
|
|
184
|
+
if not isinstance(split, dict) or not split:
|
|
185
|
+
m = rec.get("model")
|
|
186
|
+
if not m or rec.get("tokens_out") is None:
|
|
187
|
+
return None
|
|
188
|
+
split = {m: rec.get("tokens_out") or 0}
|
|
189
|
+
total_out = sum(v or 0 for v in split.values())
|
|
190
|
+
ti = rec.get("tokens_in") or 0
|
|
191
|
+
tcr = rec.get("tokens_cache_read") or 0
|
|
192
|
+
tcw = rec.get("tokens_cache_write") or 0
|
|
193
|
+
usd, priced = 0.0, 0
|
|
194
|
+
for m, out in split.items():
|
|
195
|
+
r = rate_for(repo, m)
|
|
196
|
+
if not r:
|
|
197
|
+
continue
|
|
198
|
+
share = (float(out or 0) / total_out) if total_out else 0.0
|
|
199
|
+
usd += (ti * share * r[0] + (out or 0) * r[1]
|
|
200
|
+
+ tcr * share * r[2] + tcw * share * r[3]) / 1_000_000.0
|
|
201
|
+
priced += 1
|
|
202
|
+
return round(usd, 6) if priced else None
|
|
203
|
+
|
|
204
|
+
|
|
74
205
|
# ---------------------------------------------------------------- utilities
|
|
75
206
|
def die(msg):
|
|
76
207
|
sys.stderr.write("tf-metrics: %s\n" % msg)
|
|
@@ -99,6 +230,42 @@ def read_stream(repo, stream):
|
|
|
99
230
|
return out
|
|
100
231
|
|
|
101
232
|
|
|
233
|
+
def apply_voids(runs, app=None):
|
|
234
|
+
"""Drop the run records a `run-void` names, and hand back what was dropped.
|
|
235
|
+
|
|
236
|
+
A stream is append-only, so a run record written with a wrong figure can be
|
|
237
|
+
neither edited nor deleted — and nothing inside the record says it is wrong, so
|
|
238
|
+
every figure over it is quietly polluted. The correction is therefore another
|
|
239
|
+
record: `run-void` names a run by the pair every run carries (`cmd` + `started`)
|
|
240
|
+
and says why (SCHEMA.md §2.7, written by `tf-emit.sh --void-run`).
|
|
241
|
+
|
|
242
|
+
Two rules, both the same one this file applies everywhere else. The voided record
|
|
243
|
+
leaves every figure — it is not clamped, guessed at or half-counted. And the count
|
|
244
|
+
of what left travels with the figures, because a total offered without its
|
|
245
|
+
exclusions is just a different wrong number. A void naming no run on this stream
|
|
246
|
+
is an orphan: it is counted and reported, never silently dropped, exactly as an
|
|
247
|
+
orphaned miss-amend is. tests/regression/run.sh tf_void."""
|
|
248
|
+
key = lambda r: (r.get("cmd"), r.get("started"))
|
|
249
|
+
voids = {}
|
|
250
|
+
for v in runs:
|
|
251
|
+
if v.get("kind") == "run-void" and v.get("started"):
|
|
252
|
+
voids[key(v)] = v
|
|
253
|
+
kept, dropped, reasons = [], 0, []
|
|
254
|
+
matched = set()
|
|
255
|
+
for r in runs:
|
|
256
|
+
if r.get("kind") == "run-void":
|
|
257
|
+
continue
|
|
258
|
+
if key(r) in voids:
|
|
259
|
+
matched.add(key(r))
|
|
260
|
+
dropped += 1
|
|
261
|
+
v = voids[key(r)]
|
|
262
|
+
reasons.append("%s %s %s — %s" % (app or r.get("app") or "?", r.get("cmd"),
|
|
263
|
+
r.get("started"), v.get("reason") or "no reason recorded"))
|
|
264
|
+
continue
|
|
265
|
+
kept.append(r)
|
|
266
|
+
return kept, dropped, reasons, len(set(voids) - matched)
|
|
267
|
+
|
|
268
|
+
|
|
102
269
|
def emit(repo, stream, records, dry_run, quiet=False):
|
|
103
270
|
"""Append via tf-emit.sh — the single append primitive. Never write JSONL directly.
|
|
104
271
|
|
|
@@ -227,11 +394,14 @@ def analyse_misses(misses):
|
|
|
227
394
|
for a in sorted(amends, key=lambda r: r.get("ts") or ""):
|
|
228
395
|
parent = opened_by_id.get(a.get("miss_id"))
|
|
229
396
|
fld = a.get("field")
|
|
230
|
-
if parent is None or fld not in AMENDABLE_FIELDS:
|
|
397
|
+
if parent is None or (fld not in AMENDABLE_FIELDS and fld not in AMENDABLE_TEXT_FIELDS):
|
|
231
398
|
orphan_amends += 1
|
|
232
399
|
continue
|
|
233
|
-
|
|
234
|
-
|
|
400
|
+
val = a.get("value")
|
|
401
|
+
ok = (isinstance(val, str) and val.strip()) if fld in AMENDABLE_TEXT_FIELDS \
|
|
402
|
+
else (val in AMENDABLE_FIELDS.get(fld, ()))
|
|
403
|
+
if parent.get(fld) is None and ok:
|
|
404
|
+
parent[fld] = val
|
|
235
405
|
amended += 1
|
|
236
406
|
|
|
237
407
|
# ---- eligibility floor for optional fields added mid-stream (SCHEMA.md §3.5,
|
|
@@ -360,7 +530,21 @@ def analyse_misses(misses):
|
|
|
360
530
|
# Dollars exist ONLY where a harness measured them. Claude Code carries
|
|
361
531
|
# cost_usd:null permanently (SCHEMA.md §4) and are never priced from a rate card
|
|
362
532
|
# here — a pooled sum over mixed harnesses would silently under-report.
|
|
363
|
-
|
|
533
|
+
#
|
|
534
|
+
# A null was never the only way to have no money figure, though. An OpenCode run on a
|
|
535
|
+
# subscription records cost_usd 0.0 — a real, measured zero, because the plan was already
|
|
536
|
+
# paid for — and averaging that in as a FREE repair is the TF-005 defect again, in the
|
|
537
|
+
# column right beside the one TF-005 was found in. Only a `metered` record is money
|
|
538
|
+
# (SCHEMA.md §2.5b). A record written before billing_mode existed is admitted on its old
|
|
539
|
+
# terms, and counted, so the change does not silently rewrite the past.
|
|
540
|
+
paid = [f for f in sole if f.get("cost_usd") is not None
|
|
541
|
+
and (f.get("billing_mode") in (None, "metered"))]
|
|
542
|
+
unpriced_subscription_n = len([f for f in sole if f.get("cost_usd") is not None
|
|
543
|
+
and f.get("billing_mode") not in (None, "metered")])
|
|
544
|
+
# And the figure that works on every harness: what the repair's tokens cost at list price,
|
|
545
|
+
# worked out here from the tokens the record carries. It is the only money-shaped number a
|
|
546
|
+
# Claude Code repair can have, because a Max subscription bills nothing per token.
|
|
547
|
+
listed = [p for p in (list_price(REPO_OF.get(id(f), ""), f) for f in sole) if p is not None]
|
|
364
548
|
escaped = [m for m in opened if m.get("found_by") in ("owner", "production")]
|
|
365
549
|
design = [m for m in opened if m.get("miss_class") == "unspecified-gap"]
|
|
366
550
|
|
|
@@ -462,6 +646,12 @@ def analyse_misses(misses):
|
|
|
462
646
|
"cost_usd_per_miss_measured": round(
|
|
463
647
|
sum(f["cost_usd"] for f in paid) / len(paid), 4) if len(paid) >= MIN_N else None,
|
|
464
648
|
"cost_usd_records": len(paid),
|
|
649
|
+
# Repairs whose dollars were measured and are NOT money: a subscription's zero, a local
|
|
650
|
+
# model's absent bill. Printed beside the figure, never inside it.
|
|
651
|
+
"cost_usd_excluded_not_money_n": unpriced_subscription_n,
|
|
652
|
+
"cost_list_usd_per_miss": round(
|
|
653
|
+
sum(listed) / len(listed), 4) if len(listed) >= MIN_N else None,
|
|
654
|
+
"cost_list_usd_records": len(listed),
|
|
465
655
|
}
|
|
466
656
|
|
|
467
657
|
|
|
@@ -512,9 +702,16 @@ def analyse_phases(runs):
|
|
|
512
702
|
except Exception:
|
|
513
703
|
return None
|
|
514
704
|
|
|
705
|
+
# A stored duration of zero or less is not a measurement: it is an impossible record
|
|
706
|
+
# (`ended` before `started`) that reached the stream before the emitter refused those.
|
|
707
|
+
# Treat it as absent so it is re-derived here, and -- when the timestamps are just as
|
|
708
|
+
# impossible -- excluded from every figure below rather than admitted by a truthiness
|
|
709
|
+
# test. tests/regression/run.sh tf_015.
|
|
515
710
|
derived_n = 0
|
|
711
|
+
negative_durations = sum(1 for r in live
|
|
712
|
+
if isinstance(r.get("duration_s"), (int, float)) and r["duration_s"] < 0)
|
|
516
713
|
for r in live:
|
|
517
|
-
if r
|
|
714
|
+
if _dur(r) is not None or not r.get("started"):
|
|
518
715
|
continue
|
|
519
716
|
end = r.get("ended") or r.get("ts")
|
|
520
717
|
if not end:
|
|
@@ -542,16 +739,28 @@ def analyse_phases(runs):
|
|
|
542
739
|
return scope_of(r) == "tree" and r.get("subagent_runs") is not None
|
|
543
740
|
|
|
544
741
|
grand_out = sum(r.get("tokens_out") or 0 for r in live if has_tokens(r))
|
|
545
|
-
grand_dur = sum(r
|
|
742
|
+
grand_dur = sum(_dur(r) or 0 for r in live if _dur(r) is not None)
|
|
546
743
|
|
|
547
744
|
phases = OrderedDict()
|
|
548
745
|
for cmd in sorted({r.get("cmd") or "?" for r in live}):
|
|
549
746
|
rs = [r for r in live if (r.get("cmd") or "?") == cmd]
|
|
550
747
|
priced = [r for r in rs if has_tokens(r)]
|
|
551
|
-
durs = [r
|
|
748
|
+
durs = [_dur(r) for r in rs if _dur(r) is not None]
|
|
552
749
|
fan = [r for r in rs if sees_subagents(r)]
|
|
553
750
|
|
|
554
751
|
models = defaultdict(lambda: {"runs": 0, "tokens_out": 0})
|
|
752
|
+
# List price, COMPUTED HERE from the tokens the record carries (see list_price above —
|
|
753
|
+
# pricing is a reporting job, and nothing priced is ever stored). Per model, attributed
|
|
754
|
+
# only where the window ran ONE model, the same rule the measured dollars follow below.
|
|
755
|
+
list_of = {}
|
|
756
|
+
for r in priced:
|
|
757
|
+
lp = list_price(REPO_OF.get(id(r), ""), r)
|
|
758
|
+
if lp is None:
|
|
759
|
+
continue
|
|
760
|
+
list_of[id(r)] = lp
|
|
761
|
+
ms = r.get("models") or ([r["model"]] if r.get("model") else [])
|
|
762
|
+
if len(ms) == 1:
|
|
763
|
+
models[ms[0]]["list_usd"] = round(models[ms[0]].get("list_usd", 0.0) + lp, 6)
|
|
555
764
|
for r in priced:
|
|
556
765
|
split = r.get("model_tokens_out")
|
|
557
766
|
if isinstance(split, dict) and split:
|
|
@@ -578,12 +787,42 @@ def analyse_phases(runs):
|
|
|
578
787
|
sub_out = sum(r.get("tokens_out_subagents") or 0 for r in fan)
|
|
579
788
|
fan_out_total = sum(r.get("tokens_out") or 0 for r in fan if has_tokens(r))
|
|
580
789
|
|
|
790
|
+
# DOLLARS, and what each one means. A harness split was never enough: one OpenCode
|
|
791
|
+
# machine mixes a flat-fee subscription (whose marginal cost is a true zero), a metered
|
|
792
|
+
# API key (whose dollars are real money) and a local model (which has no bill at all),
|
|
793
|
+
# and pooling those three produces a money figure that is neither the money spent nor
|
|
794
|
+
# the effort used. `billing_mode` is the separation; only `metered` is money.
|
|
581
795
|
cost_by_harness = defaultdict(float)
|
|
582
796
|
cost_n = Counter()
|
|
797
|
+
cost_by_mode = defaultdict(float)
|
|
798
|
+
mode_n = Counter()
|
|
799
|
+
model_usd = defaultdict(float)
|
|
800
|
+
model_usd_n = Counter()
|
|
801
|
+
plan_by_model = defaultdict(float)
|
|
802
|
+
usd_unattributed, usd_unattributed_n = 0.0, 0
|
|
583
803
|
for r in priced:
|
|
804
|
+
mode = r.get("billing_mode") or "unrecorded"
|
|
805
|
+
mode_n[mode] += 1
|
|
584
806
|
if r.get("cost_usd") is not None:
|
|
585
807
|
cost_by_harness[r.get("harness") or "?"] += r["cost_usd"]
|
|
586
808
|
cost_n[r.get("harness") or "?"] += 1
|
|
809
|
+
cost_by_mode[mode] += r["cost_usd"]
|
|
810
|
+
if mode == "plan":
|
|
811
|
+
ms = r.get("models") or ([r["model"]] if r.get("model") else [])
|
|
812
|
+
if len(ms) == 1:
|
|
813
|
+
plan_by_model[ms[0]] += r["cost_usd"]
|
|
814
|
+
if mode == "metered":
|
|
815
|
+
# Per-model spend is attributed ONLY where the window ran one model.
|
|
816
|
+
# Splitting one window's dollars across several models by their token
|
|
817
|
+
# share is arithmetic, not measurement — the same line §5.5.3 draws
|
|
818
|
+
# between `sole` and `shared`, one stream over.
|
|
819
|
+
ms = r.get("models") or ([r["model"]] if r.get("model") else [])
|
|
820
|
+
if len(ms) == 1:
|
|
821
|
+
model_usd[ms[0]] += r["cost_usd"]
|
|
822
|
+
model_usd_n[ms[0]] += 1
|
|
823
|
+
else:
|
|
824
|
+
usd_unattributed += r["cost_usd"]
|
|
825
|
+
usd_unattributed_n += 1
|
|
587
826
|
|
|
588
827
|
tok = lambda k: sum(r.get(k) or 0 for r in priced)
|
|
589
828
|
phases[cmd] = {
|
|
@@ -621,7 +860,7 @@ def analyse_phases(runs):
|
|
|
621
860
|
"by_mode": OrderedDict(
|
|
622
861
|
(mode, {
|
|
623
862
|
"runs": len([r for r in rs if (r.get("mode") or "—") == mode]),
|
|
624
|
-
"duration_s": sum(r
|
|
863
|
+
"duration_s": sum(_dur(r) or 0
|
|
625
864
|
for r in rs if (r.get("mode") or "—") == mode),
|
|
626
865
|
"tokens_out": sum(r.get("tokens_out") or 0
|
|
627
866
|
for r in rs if (r.get("mode") or "—") == mode and has_tokens(r)),
|
|
@@ -666,6 +905,36 @@ def analyse_phases(runs):
|
|
|
666
905
|
"cost_usd_by_harness": OrderedDict(
|
|
667
906
|
(h, {"usd": round(v, 6), "records": cost_n[h]})
|
|
668
907
|
for h, v in sorted(cost_by_harness.items())),
|
|
908
|
+
# …and per billing mode, which is the separation that decides whether a number
|
|
909
|
+
# is money at all (SCHEMA.md §2.5b).
|
|
910
|
+
"billing_modes": OrderedDict(sorted(mode_n.items())),
|
|
911
|
+
"cost_usd_by_billing_mode": OrderedDict(
|
|
912
|
+
(mode, round(v, 6)) for mode, v in sorted(cost_by_mode.items())),
|
|
913
|
+
"money_usd": round(cost_by_mode.get("metered", 0.0), 6),
|
|
914
|
+
"money_records": mode_n.get("metered", 0),
|
|
915
|
+
# A window that mixed a metered model with a subscription one really was billed, but
|
|
916
|
+
# the bill cannot be separated from the part that was already paid for. Real money,
|
|
917
|
+
# reported on its own line rather than folded into the headline or dropped.
|
|
918
|
+
"money_mixed_usd": round(cost_by_mode.get("mixed", 0.0), 6),
|
|
919
|
+
"money_mixed_records": mode_n.get("mixed", 0),
|
|
920
|
+
"spend_by_model": OrderedDict(
|
|
921
|
+
(m, {"usd": round(v, 6), "records": model_usd_n[m]})
|
|
922
|
+
for m, v in sorted(model_usd.items(), key=lambda kv: (-kv[1], kv[0]))),
|
|
923
|
+
"spend_unattributed": {"usd": round(usd_unattributed, 6), "records": usd_unattributed_n},
|
|
924
|
+
# LIST PRICE — what these tokens would cost bought at the published rate. It is the
|
|
925
|
+
# only figure that compares a subscription phase with a metered one, and it is a
|
|
926
|
+
# price list applied to a measurement, never a bill. Reported apart from `money_usd`
|
|
927
|
+
# for exactly that reason, with the unpriced count beside it.
|
|
928
|
+
"list_usd": round(sum(list_of.values()), 6),
|
|
929
|
+
"list_usd_records": len(list_of),
|
|
930
|
+
"list_usd_unpriced_n": len(priced) - len(list_of),
|
|
931
|
+
# A monthly plan reports dollars too, but they are ALLOWANCE CONSUMED against a
|
|
932
|
+
# per-model limit, not an invoice — and they are the numbers that produce the limit
|
|
933
|
+
# the fallback chain exists for, so they are worth their own line.
|
|
934
|
+
"plan_allowance_usd": round(cost_by_mode.get("plan", 0.0), 6),
|
|
935
|
+
"plan_allowance_records": mode_n.get("plan", 0),
|
|
936
|
+
"plan_allowance_by_model": OrderedDict(
|
|
937
|
+
(m, round(v, 6)) for m, v in sorted(plan_by_model.items(), key=lambda kv: -kv[1])),
|
|
669
938
|
}
|
|
670
939
|
|
|
671
940
|
return {
|
|
@@ -673,6 +942,12 @@ def analyse_phases(runs):
|
|
|
673
942
|
"scope_coverage": OrderedDict(sorted(scope_cov.items())),
|
|
674
943
|
"tokens_out_total": grand_out,
|
|
675
944
|
"duration_s_total": grand_dur,
|
|
945
|
+
# What the duration figures are actually built on. A total is only honest when the
|
|
946
|
+
# reader is told what it left out: TechieBlog's 46 run records yield 32 usable ones.
|
|
947
|
+
"duration_measured_n": len([r for r in live if _dur(r) is not None]),
|
|
948
|
+
"duration_impossible_n": _dur_quality(live)[0],
|
|
949
|
+
"duration_absent_n": _dur_quality(live)[1],
|
|
950
|
+
"duration_recomputed_n": _dur_quality(live)[2],
|
|
676
951
|
"phases": phases,
|
|
677
952
|
"note": ("Per-phase effort over LIVE run records, aggregated by `cmd`. Token "
|
|
678
953
|
"figures exclude runs whose window could not be computed; fan-out "
|
|
@@ -965,10 +1240,15 @@ def analyse(repos):
|
|
|
965
1240
|
per_repo = []
|
|
966
1241
|
commit_dupes = 0
|
|
967
1242
|
session_dupes = 0
|
|
1243
|
+
voided_runs = 0
|
|
1244
|
+
void_reasons = []
|
|
1245
|
+
void_orphans = 0
|
|
968
1246
|
for repo in repos:
|
|
969
1247
|
g = read_stream(repo, "gates")
|
|
970
|
-
r = read_stream(repo, "runs")
|
|
971
|
-
|
|
1248
|
+
r, vn, vr, vo = apply_voids(read_stream(repo, "runs"), app_name(repo))
|
|
1249
|
+
voided_runs += vn; void_reasons += vr; void_orphans += vo
|
|
1250
|
+
repo_misses = read_stream(repo, "misses")
|
|
1251
|
+
misses += repo_misses
|
|
972
1252
|
# Per repo, not across them (SCHEMA.md §4): the OpenCode plugin
|
|
973
1253
|
# appends a cumulative snapshot at every root-session idle.
|
|
974
1254
|
s, sd = dedupe_sessions(read_stream(repo, "sessions"))
|
|
@@ -976,6 +1256,11 @@ def analyse(repos):
|
|
|
976
1256
|
# Per repo, not across them: two repos may legitimately share a short sha.
|
|
977
1257
|
c, d = dedupe_commits(read_stream(repo, "commits"))
|
|
978
1258
|
commit_dupes += d
|
|
1259
|
+
# Which repo each record came from, so the report can price it with THAT project's rate
|
|
1260
|
+
# card and routing. Kept beside the records rather than written into them: nothing here
|
|
1261
|
+
# may add a field a record did not have.
|
|
1262
|
+
for _x in r + repo_misses:
|
|
1263
|
+
REPO_OF[id(_x)] = repo
|
|
979
1264
|
gates += g; runs += r; sessions += s; commits += c
|
|
980
1265
|
per_repo.append({"repo": repo, "app": app_name(repo),
|
|
981
1266
|
"project_type": project_type(repo)[0],
|
|
@@ -996,9 +1281,19 @@ def analyse(repos):
|
|
|
996
1281
|
"commit_hook": has_commit_hook(repo)})
|
|
997
1282
|
|
|
998
1283
|
def seg(records):
|
|
1284
|
+
# An `FR` verdict grades a framework requirement line, not an application's
|
|
1285
|
+
# screen: it is written by a runner over a document the framework controls, so
|
|
1286
|
+
# it passes far more often than a REQ verified against a running app. Pooled
|
|
1287
|
+
# into an application segment it raises that segment's first-pass rate and
|
|
1288
|
+
# dilutes its escape rate, with nothing on the output to show it happened.
|
|
1289
|
+
# SCHEMA.md §3 states the rule -- "it never pools with the others" -- and this
|
|
1290
|
+
# is where it is applied. Tested by tests/regression/run.sh tf_020.
|
|
999
1291
|
d = defaultdict(list)
|
|
1000
1292
|
for r in records:
|
|
1001
|
-
|
|
1293
|
+
if r.get("req_class") == "FR":
|
|
1294
|
+
key = "framework-requirement"
|
|
1295
|
+
else:
|
|
1296
|
+
key = "unclassified" if r.get("project_type_inferred") else r.get("project_type", "app")
|
|
1002
1297
|
d[key].append(r)
|
|
1003
1298
|
return d
|
|
1004
1299
|
|
|
@@ -1031,6 +1326,10 @@ def analyse(repos):
|
|
|
1031
1326
|
|
|
1032
1327
|
out = {"per_repo": per_repo, "tainted_reqs": sorted("%s:%s" % x for x in tainted if x[1]),
|
|
1033
1328
|
"live": {}, "backfilled": {}, "pooled": {},
|
|
1329
|
+
# what a `run-void` took out of every figure above, and why (SCHEMA.md §2.7)
|
|
1330
|
+
"runs_voided_n": voided_runs,
|
|
1331
|
+
"runs_voided": void_reasons,
|
|
1332
|
+
"run_voids_orphaned_n": void_orphans,
|
|
1034
1333
|
"misses": analyse_misses(misses),
|
|
1035
1334
|
"phases": analyse_phases(runs)}
|
|
1036
1335
|
|
|
@@ -1069,10 +1368,12 @@ def analyse(repos):
|
|
|
1069
1368
|
# ---- poolable metrics: runs, cadence, tokens. Exempt from both separations.
|
|
1070
1369
|
build_runs = [r for r in runs if r.get("cmd") == "build-phase"]
|
|
1071
1370
|
fix_runs = [r for r in runs if r.get("mode") == "fix"]
|
|
1072
|
-
throughput = [float(r["reqs_count"]) / r
|
|
1073
|
-
for r in runs if r
|
|
1371
|
+
throughput = [float(r["reqs_count"]) / _dur(r)
|
|
1372
|
+
for r in runs if _dur(r) is not None and r.get("reqs_count")]
|
|
1074
1373
|
batch = [r["reqs_count"] for r in build_runs if r.get("reqs_count") is not None]
|
|
1075
1374
|
verified_transitions = sum(1 for g in gates if g.get("verdict") == "Verified")
|
|
1375
|
+
pooled_list = [p for p in (list_price(REPO_OF.get(id(r), ""), r) for r in runs)
|
|
1376
|
+
if p is not None]
|
|
1076
1377
|
tok = sum((s.get("input_tokens") or 0) + (s.get("output_tokens") or 0) for s in sessions)
|
|
1077
1378
|
days = {c.get("ts", "")[:10] for c in commits if c.get("ts")}
|
|
1078
1379
|
out["pooled"] = {
|
|
@@ -1086,7 +1387,16 @@ def analyse(repos):
|
|
|
1086
1387
|
"tokens_total": tok,
|
|
1087
1388
|
"tokens_per_verified_req": round(float(tok) / verified_transitions, 1)
|
|
1088
1389
|
if verified_transitions >= MIN_N and tok else None,
|
|
1089
|
-
"cost_usd": None, # never estimated — see SCHEMA.md §4
|
|
1390
|
+
"cost_usd": None, # what was BILLED is never estimated — see SCHEMA.md §4
|
|
1391
|
+
# What the same work costs at list price. A price, not a bill (SCHEMA.md §2.5b), and the
|
|
1392
|
+
# only money-shaped headline that works on a subscription — which is most of this
|
|
1393
|
+
# framework's runs. Summed over the runs that carry it, with that count published, so a
|
|
1394
|
+
# figure built on half the runs cannot be read as if it covered all of them.
|
|
1395
|
+
"list_usd_total": round(sum(pooled_list), 4),
|
|
1396
|
+
"list_usd_records": len(pooled_list),
|
|
1397
|
+
"list_usd_per_verified_req": (
|
|
1398
|
+
round(sum(pooled_list) / verified_transitions, 4)
|
|
1399
|
+
if verified_transitions >= MIN_N and len(pooled_list) >= MIN_N else None),
|
|
1090
1400
|
"commits": len(commits),
|
|
1091
1401
|
"commit_duplicates_collapsed": commit_dupes,
|
|
1092
1402
|
"session_duplicates_collapsed": session_dupes,
|
|
@@ -1173,6 +1483,20 @@ def print_report(a, repos):
|
|
|
1173
1483
|
print(" across project_type or across live/backfilled would produce a")
|
|
1174
1484
|
print(" number that cannot be defended. See SCHEMA.md §6.")
|
|
1175
1485
|
print("")
|
|
1486
|
+
if a.get("runs_voided_n"):
|
|
1487
|
+
print(" %d run record(s) VOIDED — named by a run-void record as wrong, so they are"
|
|
1488
|
+
% a["runs_voided_n"])
|
|
1489
|
+
print(" in no figure above. Both records stay on the stream; nothing was deleted:")
|
|
1490
|
+
for line in a.get("runs_voided", [])[:6]:
|
|
1491
|
+
print(" " + line)
|
|
1492
|
+
if len(a.get("runs_voided", [])) > 6:
|
|
1493
|
+
print(" ... and %d more" % (len(a["runs_voided"]) - 6))
|
|
1494
|
+
print("")
|
|
1495
|
+
if a.get("run_voids_orphaned_n"):
|
|
1496
|
+
print(" %d run-void record(s) name a run that is not on this stream — the void was"
|
|
1497
|
+
% a["run_voids_orphaned_n"])
|
|
1498
|
+
print(" written against a record that never arrived. Nothing was excluded for them.")
|
|
1499
|
+
print("")
|
|
1176
1500
|
if a["tainted_reqs"]:
|
|
1177
1501
|
print(" %d REQ(s) excluded from the LIVE first-pass rate because they carry"
|
|
1178
1502
|
% len(a["tainted_reqs"]))
|
|
@@ -1193,8 +1517,14 @@ def print_report(a, repos):
|
|
|
1193
1517
|
print(" REQ throughput : %s REQs/hour (median across runs)" %
|
|
1194
1518
|
(p["throughput_median_reqs_per_hour"] if p["throughput_median_reqs_per_hour"] is not None else "insufficient data"))
|
|
1195
1519
|
print(" sessions / tokens : %d sessions, %s total tokens" % (p["sessions"], "{:,}".format(p["tokens_total"])))
|
|
1196
|
-
print(" tokens per Verified : %s (
|
|
1520
|
+
print(" tokens per Verified : %s (what you were BILLED is never estimated — SCHEMA.md §4)" %
|
|
1197
1521
|
(p["tokens_per_verified_req"] if p["tokens_per_verified_req"] is not None else "insufficient data"))
|
|
1522
|
+
if p.get("list_usd_records"):
|
|
1523
|
+
print(" list price : $%s over %d priced run(s) — the published rate applied to"
|
|
1524
|
+
% (p["list_usd_total"], p["list_usd_records"]))
|
|
1525
|
+
print(" the tokens actually measured. A price, not a bill.")
|
|
1526
|
+
if p.get("list_usd_per_verified_req") is not None:
|
|
1527
|
+
print(" list $ per Verified : $%s" % p["list_usd_per_verified_req"])
|
|
1198
1528
|
print(" commit cadence : %s commits/active day (%d commits over %d days)" %
|
|
1199
1529
|
(p["commits_per_active_day"] if p["commits_per_active_day"] is not None else "—",
|
|
1200
1530
|
p["commits"], p["active_days"]))
|
|
@@ -1243,6 +1573,15 @@ def print_phases(p, W):
|
|
|
1243
1573
|
print("-" * W)
|
|
1244
1574
|
print(" %d live run record(s). Token-window coverage: %s"
|
|
1245
1575
|
% (p["runs_live"], " ".join("%s=%d" % kv for kv in p["scope_coverage"].items())))
|
|
1576
|
+
if any(p.get(k) for k in ("duration_impossible_n", "duration_absent_n", "duration_recomputed_n")):
|
|
1577
|
+
print(" duration: %d of %d record(s) usable"
|
|
1578
|
+
% (p.get("duration_measured_n", 0), p["runs_live"])
|
|
1579
|
+
+ (", %d impossible (start after end, discarded)" % p["duration_impossible_n"]
|
|
1580
|
+
if p.get("duration_impossible_n") else "")
|
|
1581
|
+
+ (", %d record no elapsed time" % p["duration_absent_n"]
|
|
1582
|
+
if p.get("duration_absent_n") else "")
|
|
1583
|
+
+ (", %d taken from the timestamps over a stored figure that disagreed"
|
|
1584
|
+
% p["duration_recomputed_n"] if p.get("duration_recomputed_n") else ""))
|
|
1246
1585
|
print(" A window is only as good as its scope: 'tree' saw the subagents, 'main'")
|
|
1247
1586
|
print(" did not look, 'none'/absent measured nothing at all and is excluded from")
|
|
1248
1587
|
print(" every token figure below rather than averaged in as a zero.")
|
|
@@ -1323,8 +1662,57 @@ def print_phases(p, W):
|
|
|
1323
1662
|
for h, c in m["cost_usd_by_harness"].items():
|
|
1324
1663
|
print(" cost (%s) : $%s over %d record(s) — MEASURED, never pooled "
|
|
1325
1664
|
"across harness" % (h, c["usd"], c["records"]))
|
|
1326
|
-
|
|
1327
|
-
|
|
1665
|
+
bmodes = m.get("billing_modes") or {}
|
|
1666
|
+
if bmodes:
|
|
1667
|
+
print(" paid for by : " +
|
|
1668
|
+
" · ".join("%s %d" % (k, v) for k, v in bmodes.items()))
|
|
1669
|
+
if m.get("list_usd_records"):
|
|
1670
|
+
print(" list price : $%s over %d run(s) — the published rate applied to"
|
|
1671
|
+
% (m["list_usd"], m["list_usd_records"]))
|
|
1672
|
+
print(" the tokens on the record, worked out HERE and never")
|
|
1673
|
+
print(" stored. A price, not a bill: it is what makes a")
|
|
1674
|
+
print(" subscription run comparable with a metered one.")
|
|
1675
|
+
if m.get("list_usd_unpriced_n"):
|
|
1676
|
+
print(" %d run(s) had no rate for their model and are"
|
|
1677
|
+
% m["list_usd_unpriced_n"])
|
|
1678
|
+
print(" outside it — never priced at zero.")
|
|
1679
|
+
if m.get("money_records"):
|
|
1680
|
+
print(" money billed : $%s over %d metered run(s). Only a metered run is"
|
|
1681
|
+
% (m["money_usd"], m["money_records"]))
|
|
1682
|
+
print(" money billed; a subscription's zero is a flat fee")
|
|
1683
|
+
print(" already paid, a local model has no bill (§2.5b).")
|
|
1684
|
+
if m.get("plan_allowance_records"):
|
|
1685
|
+
print(" plan allowance : $%s used over %d run(s) on a monthly plan. This is"
|
|
1686
|
+
% (m["plan_allowance_usd"], m["plan_allowance_records"]))
|
|
1687
|
+
print(" the provider's own meter against a PER-MODEL")
|
|
1688
|
+
print(" allowance, not an invoice — and it is what runs out")
|
|
1689
|
+
print(" and sends a tier to its fallback.")
|
|
1690
|
+
for mod, v in (m.get("plan_allowance_by_model") or {}).items():
|
|
1691
|
+
print(" %-30s $%s of allowance" % (mod, v))
|
|
1692
|
+
if m.get("money_mixed_usd"):
|
|
1693
|
+
print(" also billed : $%s on %d run(s) whose window mixed billing modes —"
|
|
1694
|
+
% (m["money_mixed_usd"], m["money_mixed_records"]))
|
|
1695
|
+
print(" real money, but not separable from the part a")
|
|
1696
|
+
print(" subscription had already paid for.")
|
|
1697
|
+
for mod, c in (m.get("models") or {}).items():
|
|
1698
|
+
bits = []
|
|
1699
|
+
if c.get("list_usd"):
|
|
1700
|
+
bits.append("list $%s" % c["list_usd"])
|
|
1701
|
+
sp = (m.get("spend_by_model") or {}).get(mod)
|
|
1702
|
+
if sp:
|
|
1703
|
+
bits.append("billed $%s" % sp["usd"])
|
|
1704
|
+
if bits:
|
|
1705
|
+
print(" %-30s %s" % (mod, " · ".join(bits)))
|
|
1706
|
+
un = m.get("spend_unattributed") or {}
|
|
1707
|
+
if un.get("records"):
|
|
1708
|
+
print(" %-30s billed $%-8s over %d run(s) — the window ran several"
|
|
1709
|
+
% ("(not attributed)", un["usd"], un["records"]))
|
|
1710
|
+
print(" models, and one window's dollars are not split by token share.")
|
|
1711
|
+
# `bm` used to be assigned inside `if m["reqs_touched_total"] or m["files_written_total"]:`
|
|
1712
|
+
# and read outside it, so --phases crashed with UnboundLocalError on any phase that
|
|
1713
|
+
# touched no REQ and wrote no file — which is every metrics-report and every render.
|
|
1714
|
+
# MISS-TechieFlow-20260910-03.
|
|
1715
|
+
bm = m.get("by_mode") or {}
|
|
1328
1716
|
if len(bm) > 1:
|
|
1329
1717
|
print(" by mode : (in the order they ran)")
|
|
1330
1718
|
for mode, v in bm.items():
|
|
@@ -1453,6 +1841,15 @@ def print_misses(m, W):
|
|
|
1453
1841
|
print(" Claude Code carries cost_usd:null permanently and is NEVER")
|
|
1454
1842
|
print(" priced from a rate card here (SCHEMA.md §4). Real dollars come from")
|
|
1455
1843
|
print(" OpenCode runs; token counts are the honest figure everywhere else.")
|
|
1844
|
+
if m.get("cost_list_usd_per_miss") is not None:
|
|
1845
|
+
print(" list price per miss : $%s (%d record(s)) — the repair's tokens at the"
|
|
1846
|
+
% (m["cost_list_usd_per_miss"], m["cost_list_usd_records"]))
|
|
1847
|
+
print(" published rate. Works on every harness, and is a price, not a bill.")
|
|
1848
|
+
if m.get("cost_usd_excluded_not_money_n"):
|
|
1849
|
+
print(" %d fix record(s) measured a cost that is NOT money — a subscription's"
|
|
1850
|
+
% m["cost_usd_excluded_not_money_n"])
|
|
1851
|
+
print(" zero or a local model's absent bill — and are outside that figure")
|
|
1852
|
+
print(" rather than averaged in as free repairs (SCHEMA.md §2.5b).")
|
|
1456
1853
|
print(" shared (apportioned): %d fix records — equal division, NOT a measurement"
|
|
1457
1854
|
% m["cost_shared_n"])
|
|
1458
1855
|
print(" tokens out per miss : %s (n=%d priced)" %
|