159 lines
6.4 KiB
Go
159 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"textmachine/backend/internal/pipeline"
|
|
)
|
|
|
|
// passes.go: the §4.4 instrument — ONE book, its TWO purchases of the bank role, asking what actually
|
|
// differed in a row's own input where the role's decision differed.
|
|
//
|
|
// ⛔ IT READS THE STOP TABLES, and the first version of it read the signature maps instead. That version
|
|
// was WRONG and the wrongness is worth keeping written down: the map's note names the chunk COUNT of the
|
|
// top draft proposal but never its TEXT — only the other proposals are quoted — so a comparison built on
|
|
// it folds the CONSOLIDATED rendering into the evidence and reports «the evidence moved» for every single
|
|
// row whose rendering moved. It produced 0 of 12 and 0 of 14 «identical», which is not a fact about the
|
|
// runs but a fact about the reader. The stop table carries the drafts, their counts, the frequency, the
|
|
// type and the source contexts — the row's whole input, as the role was shown it.
|
|
//
|
|
// ⚠ WHAT IS STILL OUTSIDE THE ARTIFACT, and it is what the residue of this question is made of: the BATCH
|
|
// the row travelled in (its neighbours) and the canon anchor built for that batch. Two rows with identical
|
|
// blocks can therefore still have been asked in different company, and the artifacts cannot say so.
|
|
|
|
// rowInput renders everything the stop table records about a row's INPUT — never the answer. The
|
|
// consolidated rendering, the confidence and the INVENTED mark are the role's OUTPUT and are deliberately
|
|
// absent: folding an answer into the evidence is exactly how a comparison comes to say «the evidence
|
|
// changed» every time the answer did.
|
|
func rowInput(r pipeline.BankStopRow, withCtx bool) string {
|
|
parts := make([]string, 0, len(r.Variants))
|
|
for _, v := range r.Variants {
|
|
parts = append(parts, v.Label())
|
|
}
|
|
sort.Strings(parts)
|
|
s := fmt.Sprintf("type=%s origin=%s freq=%d drafts=[%s] evidence=[%s]",
|
|
r.Type, r.Origin, r.Freq, strings.Join(parts, " | "), strings.Join(r.Evidence, "; "))
|
|
if withCtx {
|
|
ctx := append([]string(nil), r.Contexts...)
|
|
sort.Strings(ctx)
|
|
s += " ctx=[" + strings.Join(ctx, " ¦ ") + "]"
|
|
}
|
|
return s
|
|
}
|
|
|
|
// printPassComparison joins a book's two stop tables and reports the numbers the economics of «do not
|
|
// re-ask a settled row» stands on.
|
|
func printPassComparison(name, first, second string) error {
|
|
a, err := loadSheetRows(first)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b, err := loadSheetRows(second)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
idx := func(rows []pipeline.BankStopRow) map[string]pipeline.BankStopRow {
|
|
out := make(map[string]pipeline.BankStopRow, len(rows))
|
|
for _, r := range rows {
|
|
out[r.Src] = r
|
|
}
|
|
return out
|
|
}
|
|
ai, bi := idx(a), idx(b)
|
|
var shared []string
|
|
for src := range ai {
|
|
if _, ok := bi[src]; ok {
|
|
shared = append(shared, src)
|
|
}
|
|
}
|
|
sort.Strings(shared)
|
|
fmt.Printf("\n== %s: ONE BOOK, TWO PURCHASES OF THE BANK ROLE\n", name)
|
|
fmt.Printf(" first : %s (%d rows)\n second: %s (%d rows)\n", first, len(a), second, len(b))
|
|
fmt.Printf(" shared surfaces: %d | only in the first: %d | only in the second: %d\n",
|
|
len(shared), len(ai)-len(shared), len(bi)-len(shared))
|
|
// CONTROLS, printed rather than assumed: a comparison of two documents that carry no drafts and no
|
|
// contexts would find everything identical and mean nothing by it.
|
|
withDrafts, withCtx := 0, 0
|
|
for _, src := range shared {
|
|
if len(ai[src].Variants) > 0 && len(bi[src].Variants) > 0 {
|
|
withDrafts++
|
|
}
|
|
if len(ai[src].Contexts) > 0 && len(bi[src].Contexts) > 0 {
|
|
withCtx++
|
|
}
|
|
}
|
|
fmt.Printf(" control: shared rows carrying draft proposals on BOTH sides: %d of %d · carrying source contexts on both: %d of %d\n",
|
|
withDrafts, len(shared), withCtx, len(shared))
|
|
|
|
var changed, sameBlock, sameBlockAndCtx, settled, unresolvedBoth int
|
|
var examples []string
|
|
for _, src := range shared {
|
|
x, y := ai[src], bi[src]
|
|
// ⛔ EVERY SHARED ROW IS CLASSIFIED, and the two classes below are pulled out of «unchanged»
|
|
// deliberately. A row the bank settled was never asked in the second purchase, and a row left
|
|
// unresolved in BOTH was never decided in either: counting them among «rows a do-not-re-ask rule
|
|
// would have saved» would credit the rule with rows nobody re-asked and nobody answered.
|
|
if x.SettledByBank || y.SettledByBank {
|
|
settled++
|
|
continue
|
|
}
|
|
if strings.TrimSpace(x.Dst) == "" && strings.TrimSpace(y.Dst) == "" {
|
|
unresolvedBoth++
|
|
continue
|
|
}
|
|
if x.Dst == y.Dst {
|
|
continue
|
|
}
|
|
changed++
|
|
blockSame := rowInput(x, false) == rowInput(y, false)
|
|
ctxSame := rowInput(x, true) == rowInput(y, true)
|
|
if blockSame {
|
|
sameBlock++
|
|
}
|
|
if ctxSame {
|
|
sameBlockAndCtx++
|
|
}
|
|
if len(examples) < 12 {
|
|
verdict := "the row's own input MOVED"
|
|
switch {
|
|
case ctxSame:
|
|
verdict = "input IDENTICAL, contexts included"
|
|
case blockSame:
|
|
verdict = "block identical, CONTEXTS moved"
|
|
}
|
|
examples = append(examples, fmt.Sprintf("%s: %q → %q — %s", src, x.Dst, y.Dst, verdict))
|
|
if !blockSame {
|
|
examples = append(examples,
|
|
fmt.Sprintf(" before: %s", rowInput(x, false)),
|
|
fmt.Sprintf(" after : %s", rowInput(y, false)))
|
|
}
|
|
}
|
|
}
|
|
fmt.Printf(" rows a purchase never asked about (the bank had settled them): %d — excluded below, they are not re-decisions\n", settled)
|
|
fmt.Printf(" rows left UNRESOLVED in both purchases: %d — also excluded: nobody decided them either time\n", unresolvedBoth)
|
|
fmt.Printf(" rows whose rendering CHANGED between the two purchases: %d of %d shared\n", changed, len(shared))
|
|
fmt.Printf(" of those, the row's own BLOCK was identical (drafts, freq, type, evidence): %d\n", sameBlock)
|
|
fmt.Printf(" of those, identical INCLUDING the source contexts the role was shown: %d\n", sameBlockAndCtx)
|
|
for _, e := range examples {
|
|
fmt.Printf(" · %s\n", e)
|
|
}
|
|
// The other half of the population, and it is the one the economics is actually about.
|
|
unchanged := len(shared) - changed - settled - unresolvedBoth
|
|
fmt.Printf(" rows whose rendering did NOT change: %d of %d — these are the ones a «do not re-ask what is settled» rule would have saved\n", unchanged, len(shared))
|
|
return nil
|
|
}
|
|
|
|
func loadSheetRows(path string) ([]pipeline.BankStopRow, error) {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows, err := pipeline.ParseBankStopTable(raw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %w", path, err)
|
|
}
|
|
return rows, nil
|
|
}
|