200 lines
10 KiB
Go
200 lines
10 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"textmachine/backend/internal/obs"
|
|
"textmachine/backend/internal/store"
|
|
)
|
|
|
|
// paidtail_test.go: A0 — what the money bought, and specifically NOT the query that looks obvious.
|
|
|
|
// TestPaidTailSplitsMoneyByWhatItBought pins the three classes on a hand-built ledger, because the
|
|
// classification is pure arithmetic over insertion order and deserves to be tested without a book.
|
|
func TestPaidTailSplitsMoneyByWhatItBought(t *testing.T) {
|
|
// ch1/chunk0/draft: paid twice — a retry. The first is superseded by the second, which shipped.
|
|
// ch2/chunk0/draft: paid once, and its row carries no final hash: it bought nothing shippable.
|
|
// ch2/chunk0/edit : a $0 derived projection — not a call, and must not appear anywhere.
|
|
usage := []store.CheckpointUsage{
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "draft", CostUSD: 0.10},
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "draft", CostUSD: 0.20},
|
|
{Chapter: 2, ChunkIdx: 0, Stage: "draft", CostUSD: 0.05},
|
|
{Chapter: 2, ChunkIdx: 0, Stage: "edit", CostUSD: 0},
|
|
}
|
|
statuses := []store.ChunkStatus{
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "draft", FinalHash: "h1"},
|
|
{Chapter: 2, ChunkIdx: 0, Stage: "draft", FinalHash: ""},
|
|
}
|
|
got := paidTail(usage, statuses)
|
|
if got.ShippedUSD != 0.20 || got.ShippedCalls != 1 {
|
|
t.Errorf("the call that stands and ships is the only shipped money: %+v", got)
|
|
}
|
|
if got.SupersededUSD != 0.10 || got.SupersededCalls != 1 {
|
|
t.Errorf("the earlier paid call at the same position was replaced: %+v", got)
|
|
}
|
|
if got.WithheldUSD != 0.05 || got.WithheldCalls != 1 {
|
|
t.Errorf("a position whose last paid call left no final hash bought nothing shippable: %+v", got)
|
|
}
|
|
// Float addition, so the comparison is a tolerance rather than an equality — the assertion is that
|
|
// the three classes are EXHAUSTIVE over paid checkpoints, not that IEEE-754 sums in a nice order.
|
|
if d := got.TotalUSD - 0.35; d > 1e-9 || d < -1e-9 {
|
|
t.Errorf("the three classes must be exhaustive over PAID checkpoints: %+v", got)
|
|
}
|
|
if d := (got.ShippedUSD + got.SupersededUSD + got.WithheldUSD) - got.TotalUSD; d > 1e-9 || d < -1e-9 {
|
|
t.Errorf("the parts must sum to the whole: %+v", got)
|
|
}
|
|
if got.WorstPosition != "ch1/chunk0/draft" || got.WorstUSD != 0.10 {
|
|
t.Errorf("the largest single loss must be a place, not only a number: %+v", got)
|
|
}
|
|
}
|
|
|
|
// TestTheDecompositionIsNotTheOkColumn is the finding that shaped this surface, reproduced as the shape
|
|
// the cold run actually had.
|
|
//
|
|
// The obvious slice — `request_log WHERE ok = 0 AND cost_usd > 0` — selected three different things there:
|
|
// classifier calls that SUCCEEDED (flagged only because the CJK-echo rule is exempt for the terminologist
|
|
// and not for the classifier, so a bilingual term table reads as an echo of its own source), an editor
|
|
// call whose text SHIPPED after a cosmetic strip, and money that really did buy nothing. A surface built
|
|
// on it would have reported 29.2% more loss than there was — a money figure with a false cause.
|
|
//
|
|
// Here both of those rows are present and BOTH must land in `shipped`, because both bought bytes that
|
|
// stand: the classification asks what the money bought, never what a verdict column says about it.
|
|
func TestTheDecompositionIsNotTheOkColumn(t *testing.T) {
|
|
usage := []store.CheckpointUsage{
|
|
{Chapter: 0, ChunkIdx: 0, Stage: "terminology", CostUSD: 0.009}, // classifier: flagged, succeeded
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "edit", CostUSD: 0.018}, // editor: flagged, text shipped
|
|
}
|
|
statuses := []store.ChunkStatus{
|
|
// The classifier writes NO chunk_status row at all (it is a bank role) — so its position is
|
|
// unknown to `shipped`, and the classification must not therefore call its money a loss on the
|
|
// strength of a missing row it was never going to have.
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "edit", FinalHash: "stripped-hash"},
|
|
}
|
|
got := paidTail(usage, statuses)
|
|
if got.ShippedUSD != 0.018 {
|
|
t.Errorf("a cosmetic strip still SHIPS its text, so its money is not a loss: %+v", got)
|
|
}
|
|
// ⛔ THE ASSERTION THAT USED TO STAND HERE WAS WRONG, and it pinned the defect rather than the cure:
|
|
// it required the classifier's SUCCESSFUL call to be reported as `withheld`, on the reasoning that a
|
|
// bank role «cannot be shown to have shipped». That is a category error dressed as caution — the bank
|
|
// roles buy the book's TERMINOLOGY and never a chunk of its text, so «did it ship» is undefined for
|
|
// them, not false. Left as it was, the surface called the whole glossary contour a loss and overstated
|
|
// by 59.8% on the cold run's own ledger — worse than the naive `ok = 0` slice this file rejects.
|
|
// Found by the acceptance; the CHANGE OF THIS ASSERTION is sanctioned explicitly by the orchestrator
|
|
// (the test pinned behaviour the acceptance ruled incorrect), and that sanction is recorded in the
|
|
// pack report so it is not read as a test bent to fit a fix.
|
|
if got.BankUSD != 0.009 || got.BankCalls != 1 {
|
|
t.Errorf("a bank role's standing call bought the BOOK'S TERMINOLOGY — a product, not a loss: %+v", got)
|
|
}
|
|
if got.WithheldUSD != 0 {
|
|
t.Errorf("nothing here bought nothing: %+v", got)
|
|
}
|
|
if got.LostUSD() != 0 {
|
|
t.Errorf("both calls bought something real, so the reported loss must be zero: %+v", got)
|
|
}
|
|
if got.SupersededUSD != 0 {
|
|
t.Errorf("nothing was replaced here: %+v", got)
|
|
}
|
|
}
|
|
|
|
// TestTheReportPublishesWhatTheMoneyBought is the end-to-end half: the split must reach the $0 surface an
|
|
// operator reads, not merely exist in a struct.
|
|
//
|
|
// Mutation this catches: drop the paidTail call from Quality() and the report goes back to publishing a
|
|
// TOTAL and nothing about what it bought — which is the defect.
|
|
func TestTheReportPublishesWhatTheMoneyBought(t *testing.T) {
|
|
rec := &reqRec{}
|
|
srv := newJSONProvider(rec, draftEdit)
|
|
defer srv.Close()
|
|
bookPath := volumeBook(t, srv.URL, 2)
|
|
ctx := obs.WithReqInfo(context.Background(), obs.ReqInfo{TraceID: obs.NewTraceID()})
|
|
|
|
r := newRunner(t, bookPath)
|
|
defer r.Close()
|
|
if _, err := r.TranslateBook(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
q, err := r.QualityReport()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if q.PaidTail == nil {
|
|
t.Fatal("a book that spent money must publish what that money bought — the total alone is the " +
|
|
"surface the cold run found blind (A0)")
|
|
}
|
|
if q.PaidTail.TotalUSD <= 0 || q.PaidTail.ShippedUSD <= 0 {
|
|
t.Fatalf("a clean book's money bought shipped text: %+v", *q.PaidTail)
|
|
}
|
|
// And the decomposition must agree with the ledger it claims to decompose — otherwise it is a second,
|
|
// drifting definition of the book's spend.
|
|
committed, _, err := r.Store.SpentUSD("test-book")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if diff := q.PaidTail.TotalUSD - committed; diff > 1e-9 || diff < -1e-9 {
|
|
t.Fatalf("the decomposition must sum to the committed ledger, got %.9f vs %.9f", q.PaidTail.TotalUSD, committed)
|
|
}
|
|
}
|
|
|
|
// TestAHealthyGlossaryPassIsNotReportedAsALoss is the acceptance's blocker, turned into a landing.
|
|
//
|
|
// The bank roles are checkpointed under a synthetic stage at chapter 0 and write NO chunk_status row, so
|
|
// the first version of this surface could never see them as shipped and put the WHOLE terminology contour
|
|
// into «bought nothing shippable». Measured on the cold run's ledger that is $0.04980482 of a $0.133
|
|
// «loss» — a 59.8% overstatement, worse than the naive `ok = 0` slice this file was built to replace. A
|
|
// live probe on a HEALTHY book printed «85.7% did NOT become shipped text» about a glossary that worked.
|
|
//
|
|
// Mutation this catches: drop the `u.Stage == terminologyStageName` arm and the contour falls back into
|
|
// withheld → LostUSD becomes non-zero on a book where nothing was lost → RED.
|
|
func TestAHealthyGlossaryPassIsNotReportedAsALoss(t *testing.T) {
|
|
// One clean draft+edit position that ships, and one bank-role call that stands. Nothing was lost.
|
|
usage := []store.CheckpointUsage{
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "draft", CostUSD: 0.02},
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "edit", CostUSD: 0.03},
|
|
{Chapter: 0, ChunkIdx: 0, Stage: terminologyStageName, CostUSD: 0.011},
|
|
{Chapter: 0, ChunkIdx: 1, Stage: terminologyStageName, CostUSD: 0.009},
|
|
}
|
|
statuses := []store.ChunkStatus{
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "draft", FinalHash: "d"},
|
|
{Chapter: 1, ChunkIdx: 0, Stage: "edit", FinalHash: "e"},
|
|
}
|
|
got := paidTail(usage, statuses)
|
|
if got.LostUSD() != 0 {
|
|
t.Fatalf("this book lost NOTHING: every call bought either text or terminology. Reporting the "+
|
|
"glossary as a loss is the false-cause defect this surface exists to remove: %+v", got)
|
|
}
|
|
if d := got.BankUSD - 0.02; d > 1e-9 || d < -1e-9 || got.BankCalls != 2 {
|
|
t.Fatalf("both bank-role calls stand and bought the book's terminology: %+v", got)
|
|
}
|
|
if d := got.ShippedUSD - 0.05; d > 1e-9 || d < -1e-9 {
|
|
t.Fatalf("the two text positions shipped: %+v", got)
|
|
}
|
|
if got.WorstPosition != "" {
|
|
t.Fatalf("there is no «largest single loss» on a book with no losses — naming one would point an "+
|
|
"operator at a healthy glossary batch: %q at $%.6f", got.WorstPosition, got.WorstUSD)
|
|
}
|
|
if d := (got.ShippedUSD + got.BankUSD + got.SupersededUSD + got.WithheldUSD) - got.TotalUSD; d > 1e-9 || d < -1e-9 {
|
|
t.Fatalf("the four classes must still be exhaustive: %+v", got)
|
|
}
|
|
}
|
|
|
|
// TestAReboughtGlossaryBatchIsStillALoss keeps the new class from becoming an amnesty: the terminology
|
|
// contour is re-bought in full 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.
|
|
func TestAReboughtGlossaryBatchIsStillALoss(t *testing.T) {
|
|
usage := []store.CheckpointUsage{
|
|
{Chapter: 0, ChunkIdx: 0, Stage: terminologyStageName, CostUSD: 0.011}, // first purchase
|
|
{Chapter: 0, ChunkIdx: 0, Stage: terminologyStageName, CostUSD: 0.013}, // re-bought, replaces it
|
|
}
|
|
got := paidTail(usage, nil)
|
|
if d := got.SupersededUSD - 0.011; d > 1e-9 || d < -1e-9 || got.SupersededCalls != 1 {
|
|
t.Fatalf("the replaced batch bought nothing that survives: %+v", got)
|
|
}
|
|
if d := got.BankUSD - 0.013; d > 1e-9 || d < -1e-9 || got.BankCalls != 1 {
|
|
t.Fatalf("only the standing batch is the bank's product: %+v", got)
|
|
}
|
|
if d := got.LostUSD() - 0.011; d > 1e-9 || d < -1e-9 {
|
|
t.Fatalf("a re-bought contour IS a loss, and the class must not amnesty it: %+v", got)
|
|
}
|
|
}
|