95 lines
4.8 KiB
Go
95 lines
4.8 KiB
Go
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.
|
||
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
|
||
}
|