package pipeline import ( "fmt" "strconv" "strings" ) // stopcontest.go: the POST-CALL half of «contested» — the question asked of a row AFTER the role has // answered and before anyone would pay a second model for a second opinion. // // It is a SEPARATE predicate from the pre-call one (terminology.Candidate.Contest) because it reads // different fields at a different moment, and folding them into one would make the population unreadable: // a share of «contested» that mixes «the drafts disagreed» with «the role was unsure» answers neither // question. The two also route differently — the pre-call one decides who is ASKED and with what context, // this one decides who is asked AGAIN. // // ⛔ WHAT THE POPULATION OF THESE SIGNALS IS ON EVERYTHING THIS PROJECT HAS BOUGHT, so a caller does not // mistake a threshold for a filter: on both cold runs the conflict signals are EMPTY — `contradicts` 0 of // 69 and 0 of 66, `bank_holds` the same, and no row came back unresolved — while `conf` is present on // every row and `invented` on 11 of 69 (A) and 17 of 66 (B). A predicate built on conflicts alone would // select nothing at all here and could not be checked against this material; that is a fact about the // material, not about the signal. // StopContestOpts are the post-call predicate's inputs. As with the pre-call opts, this file holds no // default for any of them: the numbers are what the $0 probe varies (cmd/tmbankprobe), and a paid consumer // brings its own with the measurement that chose it. type StopContestOpts struct { // UseConf turns the confidence arm ON; MaxConf is then the threshold a row's role-stated confidence // must be at or below to count as contested. // // ⛔ TWO FIELDS AND NOT A SENTINEL, because the sentinel made the ZERO VALUE of this struct an ACTIVE // arm: with «-1 disables», a caller that filled nothing selected every row whose reply stated 0 % — // the most urgent rows on the sheet — while believing it had asked for nothing. A file that declares // «no defaults live here» may not have a zero value with behaviour. // // A NEGATIVE Conf on the ROW means the reply carried no confidence at all, and never counts as low: // «the role said nothing» is not «the role said it was unsure». // // ⛔ THE RATIFIED LIMIT OF THIS NUMBER (D39.102 п.2), and it is narrow: the verbalized confidence may // order a review queue INSIDE one model's reply (measured AUC 0.77–0.85). It is not an arbiter, not a // weight, and not comparable between models. Using it here to decide WHO IS LOOKED AT AGAIN stays // inside that; letting it decide WHICH RENDERING WINS, or comparing it across two models' replies, // would not, and nothing in this file does either. UseConf bool MaxConf int // CountUnresolved: the role produced no rendering at all (declined, or nothing usable came back). CountUnresolved bool // CountInvented: the consolidated rendering is not one the drafts proposed. Legitimate — the role sees // the whole book and the drafts saw fragments — and the class an owner is told to read first. CountInvented bool // CountConflicts: the rendering contradicts another of this run's consolidations, or a row the bank // already holds for the same firing surface. CountConflicts bool // CountPreCall carries the pre-call verdict into the post-call one: the drafts disagreed and the role // has now picked a side. Off by default (the zero value) so the two populations can be measured apart. CountPreCall bool MinConventions int // used only when CountPreCall is set } // StopContest is the verdict plus the signals that produced it, in a fixed order so two runs of the probe // print the same row the same way. type StopContest struct { Contested bool Signals []string } // Contest applies the post-call predicate to a stop row. // // ⚠ A row the bank SETTLED (never asked) is never contested: there is no answer to doubt. Reading it as // contested would put the cheapest rows — the ones a seed already decided — at the head of the queue for a // second paid opinion. // // ⛔ A ROW THE BASIS SETTLED IS A DIFFERENT CASE AND IS NOT EXEMPT, and that is DECIDED here rather than // inherited from the name next to it. SettledByBank means the bank renders the surface and every draft // agreed — there is genuinely nothing to doubt. A basis row carries a real rendering, chosen by a paid // role in an earlier purchase, and neither half of the other sentence is true of it: the bank holds no // seed surface for it and the drafts were never asked. So it is judged on the same signals as any other // answered row — the doubt it deserves is the doubt its own rendering deserves. Exempting it by the // resemblance of the two field names would silence the queue for most of a settled book. func (r BankStopRow) Contest(o StopContestOpts) StopContest { var out StopContest if r.SettledByBank { return out } if o.CountUnresolved && strings.TrimSpace(r.Dst) == "" { out.Signals = append(out.Signals, "unresolved") } if o.UseConf && r.Conf >= 0 && r.Conf <= o.MaxConf { out.Signals = append(out.Signals, "conf<="+strconv.Itoa(o.MaxConf)) } if o.CountInvented && r.Invented { out.Signals = append(out.Signals, "invented") } if o.CountConflicts && (len(r.Contradicts) > 0 || len(r.BankHolds) > 0) { out.Signals = append(out.Signals, "conflict") } if o.CountPreCall && o.MinConventions > 0 && r.Conventions >= o.MinConventions { out.Signals = append(out.Signals, fmt.Sprintf("conventions>=%d", o.MinConventions)) } out.Contested = len(out.Signals) > 0 return out }