259 lines
14 KiB
Go
259 lines
14 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"textmachine/backend/internal/llm"
|
|
"textmachine/backend/internal/store"
|
|
)
|
|
|
|
// paidtail.go: WHAT THE MONEY BOUGHT, decomposed — the largest single line of the first paid run and the
|
|
// one axis no $0 surface showed.
|
|
//
|
|
// Measured on the cold run of 31.08: the money that did not become shipped text was the biggest item in
|
|
// the run, larger than escalation, and both `status` and `report` published only a TOTAL. An operator was
|
|
// told what a book cost and never what part of it bought nothing (disclosure law §2.1).
|
|
//
|
|
// ⛔ WHY THIS IS NOT THE OBVIOUS QUERY, and the reason is measured rather than argued. The obvious slice is
|
|
// `request_log WHERE ok = 0 AND cost_usd > 0`. On that run it selects 11 rows and $0.12316089 — and THREE
|
|
// different things:
|
|
// - the classifier's three calls ($0.00918827), which SUCCEEDED. Their replies are healthy term tables
|
|
// (`三转蛊师<TAB>term …`, finish=stop), flagged because the CJK-echo exemption covered ONLY the
|
|
// terminologist AT THE TIME — it covers both bank roles now (terminologist.go, `isBankRole`) — so a
|
|
// bilingual table read as an echo of its own source;
|
|
// - one editor call ($0.01865424) whose text SHIPPED after a cosmetic sanitizer strip;
|
|
// - and the rest, which genuinely bought nothing.
|
|
// A surface built on that slice would have told the operator he lost 29.2% more than he did — a money
|
|
// figure with a false cause, which is the disease this pack is about, committed by its own cure.
|
|
//
|
|
// SO THE DECOMPOSITION IS BUILT FROM THE CHECKPOINTS, not from the verdict column. Checkpoints are the
|
|
// money (`committed == SUM(checkpoints)` is an invariant of the ledger), they are append-only, and their
|
|
// INSERTION ORDER is the only thing that distinguishes a superseded call from the one that stands — which
|
|
// is why CheckpointUsageForBook is documented as ordering by rowid and `attempt` explicitly cannot do it.
|
|
//
|
|
// FOUR classes, exhaustive over paid checkpoints, and each states something literally true:
|
|
// - SUPERSEDED — a later paid call for the SAME position replaced it. Retries and escalation hops both
|
|
// land here, from the same rule, with no need to read either column.
|
|
// - BANK — it bought the book's TERMINOLOGY rather than a chunk of its text.
|
|
// - WITHHELD — the position's last paid call produced nothing shippable (its row carries no final hash).
|
|
// - SHIPPED — it bought the bytes a reader gets.
|
|
// Nothing here re-derives money: every figure is a sum of costs the ledger already settled.
|
|
//
|
|
// ⛔ THE BANK CLASS EXISTS BECAUSE ITS ABSENCE MADE THIS FILE COMMIT THE DEFECT IT WAS BUILT AGAINST, and
|
|
// the acceptance caught it. The bank roles are checkpointed under a SYNTHETIC stage at chapter 0 and write
|
|
// NO chunk_status row at all — deliberately, they are not a wave — so `shipped` can never be true for
|
|
// them and the whole terminology contour fell into WITHHELD. On the cold run that is $0.04980482 of a
|
|
// $0.133 «loss», i.e. the surface overstated by 59.8%: worse than the 29.2% overstatement this file's own
|
|
// header rejects the naive `ok = 0` slice for. A live probe on a HEALTHY book printed «$0.010920 (85.7%)
|
|
// did NOT become shipped text» about a glossary pass that worked perfectly.
|
|
//
|
|
// The class is identified by the STAGE, which is the addressing label those calls carry
|
|
// (terminologyStageName). The REPAIR role is deliberately NOT in it: repair checkpoints carry their
|
|
// stage's real name, so they map to a chunk_status position like any other call and are classified by
|
|
// whether that position shipped — which is right for the repair that STANDS, and what it costs a chunk
|
|
// that needed several is spelled out below.
|
|
//
|
|
// ⛔ AND THE BANK ROLES NEED THE ROLE COLUMN TOO, which is why `pos` below is not the chunk_status triple
|
|
// for them. A chunk stage is addressed by (chapter, chunk, stage), and a retry or an escalation hop at
|
|
// that address is genuinely competing to produce the SAME bytes: the later one replaces the earlier, which
|
|
// is what `superseded` means. (⚠ The repair role shares that address WITHOUT sharing that property — a
|
|
// repair rewrites a bounded span, not the stage's whole output, and several repairs of one chunk buy
|
|
// provably DISJOINT spans while landing on one triple. So they file each other as superseded today. That
|
|
// is a defect of the same family as the one fixed below, it is NOT fixed here — a second money class in
|
|
// one landing — and it is what TestTheRoleSplitDoesNotMoveARepairsMoney pins as unmoved rather than as
|
|
// right.) The bank roles are a third case again: they share ONE synthetic stage at
|
|
// chapter 0 and number their BATCHES in chunk_idx, so the classifier's batch 0 and the terminologist's
|
|
// batch 0 arrive at the same triple having bought two different things, and the earlier of the two was
|
|
// reported as money that bought nothing. Measured on the run of 08.09: three ledger rows
|
|
// (`0 0 terminology classifier` · `0 0 terminology terminologist` · `0 1 terminology terminologist`)
|
|
// printed as «the book's TERMINOLOGY $0.004595 (2)» — the classifier's $0.009013, the largest of the
|
|
// three, filed under `superseded` while it was bought and used.
|
|
//
|
|
// The role is read for the bank stage ALONE and not folded into every position, and folding it in would
|
|
// not have been the cure anyway: it separates a repair from the stage call it follows — a different class,
|
|
// on a different row, moved unannounced — while leaving repairs of ONE chunk on top of each other, since
|
|
// every one of them carries the same role and only the span tells them apart.
|
|
//
|
|
// ⚠ A superseded BANK call is still superseded, and that is not a special case but the point: the whole
|
|
// contour is re-bought whenever the drafted set grows (backlog row 233), and that money genuinely bought
|
|
// nothing the second time. Only the call that STANDS is the bank's product.
|
|
|
|
// PaidTail is the decomposition of a book's committed spend by WHAT IT BOUGHT.
|
|
type PaidTail struct {
|
|
// ShippedUSD bought the text that ships. SupersededUSD was replaced by a later paid call for the same
|
|
// position. WithheldUSD bought a position that produced nothing shippable.
|
|
ShippedUSD float64 `json:"shipped_usd"`
|
|
SupersededUSD float64 `json:"superseded_usd"`
|
|
WithheldUSD float64 `json:"withheld_usd"`
|
|
// BankUSD bought the book's TERMINOLOGY — the consolidated glossary the editor injects — rather than a
|
|
// chunk of its text. It is a product, not a loss, and it is separated because the bank roles write no
|
|
// chunk_status row, so the "did it ship" question is not merely false for them: it is undefined.
|
|
BankUSD float64 `json:"bank_usd"`
|
|
// The call counts behind the money — a single expensive call and twenty cheap ones are different
|
|
// problems with the same dollar figure.
|
|
ShippedCalls int `json:"shipped_calls"`
|
|
SupersededCalls int `json:"superseded_calls"`
|
|
WithheldCalls int `json:"withheld_calls"`
|
|
BankCalls int `json:"bank_calls"`
|
|
// TotalUSD is the sum of the three, i.e. every paid checkpoint of the book. It is published so a
|
|
// reader can check the decomposition against the ledger's own committed figure instead of trusting it.
|
|
TotalUSD float64 `json:"total_usd"`
|
|
// WorstPosition names the single position that spent the most on money that did not ship, so the
|
|
// largest loss is a place an operator can go to rather than a number. Empty when nothing was lost.
|
|
WorstPosition string `json:"worst_position,omitempty"`
|
|
WorstUSD float64 `json:"worst_usd,omitempty"`
|
|
}
|
|
|
|
// LostUSD is the money that bought NOTHING — neither shipped text nor bank. It excludes BankUSD by
|
|
// construction: a glossary pass that worked is not a loss, and printing it as one is the exact shape of
|
|
// false-cause this whole pack exists to remove.
|
|
func (t PaidTail) LostUSD() float64 { return t.SupersededUSD + t.WithheldUSD }
|
|
|
|
// paidTail decomposes the book's paid checkpoints. Pure over its inputs and free of I/O so the
|
|
// classification can be tested without a database behind it.
|
|
//
|
|
// `usage` MUST be in insertion order (CheckpointUsageForBook guarantees rowid order) — the whole
|
|
// superseded/standing distinction is that order, and a caller that sorts it differently gets a different
|
|
// and wrong answer.
|
|
func paidTail(usage []store.CheckpointUsage, statuses []store.ChunkStatus) PaidTail {
|
|
shipped := make(map[pos]bool, len(statuses))
|
|
for _, cs := range statuses {
|
|
// A position ships when its row carries the hash the read models resolve text through. A flagged
|
|
// row carries none, and neither does a skipped one. chunk_status has no role column and needs
|
|
// none: only the bank roles share a stage, and they write no chunk_status row at all.
|
|
shipped[pos{chapter: cs.Chapter, chunkIdx: cs.ChunkIdx, stage: cs.Stage}] = cs.FinalHash != ""
|
|
}
|
|
// The LAST paid call at each position is the one that stands; every earlier one was replaced.
|
|
lastPaid := map[pos]int{}
|
|
for i, u := range usage {
|
|
if u.CostUSD <= 0 {
|
|
continue // derived $0 projections (banknote/sanitized exports) are not calls and cost nothing
|
|
}
|
|
lastPaid[posOf(u)] = i
|
|
}
|
|
var t PaidTail
|
|
lost := map[pos]float64{}
|
|
for i, u := range usage {
|
|
if u.CostUSD <= 0 {
|
|
continue
|
|
}
|
|
p := posOf(u)
|
|
t.TotalUSD += u.CostUSD
|
|
switch {
|
|
case lastPaid[p] != i:
|
|
t.SupersededUSD += u.CostUSD
|
|
t.SupersededCalls++
|
|
lost[p] += u.CostUSD
|
|
case u.Stage == terminologyStageName:
|
|
// The call that STANDS for a bank position bought the bank. It has no chunk_status row to
|
|
// have shipped, and asking «did it ship» of it is a category error, not a failure.
|
|
t.BankUSD += u.CostUSD
|
|
t.BankCalls++
|
|
case shipped[p]:
|
|
t.ShippedUSD += u.CostUSD
|
|
t.ShippedCalls++
|
|
default:
|
|
t.WithheldUSD += u.CostUSD
|
|
t.WithheldCalls++
|
|
lost[p] += u.CostUSD
|
|
}
|
|
}
|
|
// Deterministic worst-position: ties break on the position, never on map order.
|
|
for p, v := range lost {
|
|
name := p.name()
|
|
if v > t.WorstUSD || (v == t.WorstUSD && name < t.WorstPosition) {
|
|
t.WorstUSD, t.WorstPosition = v, name
|
|
}
|
|
}
|
|
return t
|
|
}
|
|
|
|
// pos is what a paid call BOUGHT, as an identity: two calls at the same pos are two purchases of one
|
|
// thing, and the later one replaces the earlier.
|
|
type pos struct {
|
|
chapter, chunkIdx int
|
|
stage string
|
|
// role is set for the BANK stage alone — see the file header. Empty everywhere else, so a chunk
|
|
// stage's calls keep colliding exactly as they must.
|
|
role string
|
|
}
|
|
|
|
// posOf addresses one billed call. The bank roles are the only calls whose stage is not their address.
|
|
func posOf(u store.CheckpointUsage) pos {
|
|
p := pos{chapter: u.Chapter, chunkIdx: u.ChunkIdx, stage: u.Stage}
|
|
if u.Stage == terminologyStageName {
|
|
p.role = u.Role
|
|
}
|
|
return p
|
|
}
|
|
|
|
// name is the operator-facing address. Chapter 0 is the BOOK level (the bank roles address their batches
|
|
// there), so it is spelled as such rather than as a chapter nobody has, and the role is spelled with it:
|
|
// two positions that print one name would send an operator to the wrong one of them.
|
|
func (p pos) name() string {
|
|
if p.chapter == 0 {
|
|
s := fmt.Sprintf("book/batch%d/%s", p.chunkIdx, p.stage)
|
|
if p.role != "" {
|
|
s += "/" + p.role
|
|
}
|
|
return s
|
|
}
|
|
return fmt.Sprintf("ch%d/chunk%d/%s", p.chapter, p.chunkIdx, p.stage)
|
|
}
|
|
|
|
// estimatedSpend is how much of a book's committed money is an ESTIMATE rather than a figure the
|
|
// provider reported, and over how many calls. It sits beside paidTail because it asks the same kind of
|
|
// question of the same rows: that one decomposes committed spend by what it BOUGHT, this one by how
|
|
// well it is KNOWN.
|
|
//
|
|
// It is the engine's half of PD-441 and the condition attached to D39.230 п.1. Without it the platform
|
|
// bills committed_usd and can say «at least this much», never «at least X, up to Y».
|
|
//
|
|
// ⛔ THE TEST IS «PAID WITH NO TOKENS TO SHOW FOR IT», not a list of causes. Cost is derived from usage
|
|
// everywhere else in the engine, so a settled row with zero tokens and a non-zero cost can only have
|
|
// come from a reservation estimate — whatever ended the call: a body that would not decode, a deadline
|
|
// of ours, a stopped run, a broken socket, a paid 2xx that reported no usage. A list of finish_reasons
|
|
// would answer the same question today and quietly stop answering it the day a sixth way to pay
|
|
// without a token count is added.
|
|
//
|
|
// Reading the checkpoints rather than request_log is what makes the pair a decomposition of
|
|
// committed_usd instead of a second, telemetry-shaped opinion about it: the two are the same money, and
|
|
// a resumed run writes no new estimate row while its money stands.
|
|
//
|
|
// ⛔ `committed == SUM(checkpoints)` HOLDS ONLY UNTIL A REDRIVE, and the difference is money this
|
|
// figure would otherwise disown. A redrive DELETES the checkpoints of the stages it re-attacks and
|
|
// leaves their spend committed — «after a redrive committed(spend) >= SUM(checkpoints), the safe
|
|
// direction» (store/chunkstatus.go). Derived from the surviving rows alone, the published share then
|
|
// falls to zero and tells the platform that money nobody can account for was measured. So the gap is
|
|
// carried INTO the estimate by the same rule the rows are: we hold a cost and have no token count to
|
|
// justify it. Measured on a live redrive: committed unchanged at $0.001056, estimated dropped to $0.
|
|
func estimatedSpend(usage []store.CheckpointUsage, committedUSD float64) (rows int, usd float64) {
|
|
var accounted float64
|
|
for _, u := range usage {
|
|
accounted += u.CostUSD
|
|
}
|
|
for _, u := range usage {
|
|
if u.CostUSD <= 0 {
|
|
continue // derived $0 checkpoints are not calls and cost nothing
|
|
}
|
|
var tok llm.Usage
|
|
if err := json.Unmarshal([]byte(u.UsageJSON), &tok); err != nil {
|
|
// Unreadable usage on a paid row is the same state as absent usage: a cost we cannot
|
|
// justify from tokens. Counting it as measured publishes the more comfortable answer.
|
|
rows, usd = rows+1, usd+u.CostUSD
|
|
continue
|
|
}
|
|
if tok.PromptTokens == 0 && tok.CompletionTokens == 0 && tok.ReasoningTokens == 0 {
|
|
rows, usd = rows+1, usd+u.CostUSD
|
|
}
|
|
}
|
|
// Committed money with no checkpoint behind it at all: a redrive threw the evidence away and kept
|
|
// the spend. It is counted as ONE more unaccounted line rather than as a per-call figure, because
|
|
// how many calls it stood for is exactly what was deleted. The float slack keeps a sum of prices
|
|
// from inventing a nanodollar of «unaccounted».
|
|
if gap := committedUSD - accounted; gap > 1e-9 {
|
|
rows, usd = rows+1, usd+gap
|
|
}
|
|
return rows, usd
|
|
}
|