Files
2026-08-15 09:23:26 +12:00

131 lines
4.9 KiB
Go

package credits
import "testing"
// Confidence, and the stability rule that decides whether anything is written at all.
//
// The governing principle under test throughout: prefer no marker to a wrong marker. Almost
// every case here is about the combination refusing to answer.
func visual(startMs int64, confidence float64) Detection {
return Detection{Found: true, StartMs: startMs, Confidence: confidence, Method: MethodVisual}
}
func cluster(startMs int64, users int, next bool) BehaviourEvidence {
nextCount := 0
if next {
nextCount = users
}
return BehaviourEvidence{
Found: true, StartMs: startMs, UserCount: users,
NextEpisodeCount: nextCount, SpreadMs: 4000,
}
}
// The brief's worked example: a visual detector at 44:01 and a Tracearr cluster at 44:05
// should produce a marker around 44:03 with very high confidence.
func TestAgreeingSignalsStrengthenEachOther(t *testing.T) {
detection, ok := Combine(visual(2_641_000, 0.80), cluster(2_645_000, 3, true))
if !ok {
t.Fatal("two agreeing signals were rejected")
}
if detection.Method != MethodCombined {
t.Fatalf("method = %q, want %q", detection.Method, MethodCombined)
}
if detection.Confidence <= 0.80 {
t.Fatalf("confidence %.2f; agreement must beat either signal alone", detection.Confidence)
}
// The earlier of the two. A skip that starts at the first frame of the roll is right;
// one that starts a few seconds in has already shown what it was asked to skip.
if detection.StartMs != 2_641_000 {
t.Fatalf("StartMs = %d, want the earlier reading", detection.StartMs)
}
}
func TestDisagreeingSignalsUsuallyProduceNothing(t *testing.T) {
// Three minutes apart is not two readings of one transition.
_, ok := Combine(visual(2_400_000, 0.80), cluster(2_580_000, 3, true))
if ok {
t.Fatal("contradicting signals produced a marker; nothing is the right answer")
}
}
func TestWeakVisualAloneIsRejected(t *testing.T) {
if _, ok := Combine(visual(2_600_000, 0.55), BehaviourEvidence{}); ok {
t.Fatal("a detection below the confidence threshold was accepted")
}
}
func TestBehaviourAloneCanWriteAMarker(t *testing.T) {
// The whole reason the behavioural half is worth having: on a well-watched show it
// settles the question with no media access at all.
detection, ok := Combine(Detection{}, cluster(2_645_000, 4, true))
if !ok {
t.Fatal("a four-viewer cluster produced no marker")
}
if detection.Method != MethodBehaviour {
t.Fatalf("method = %q, want %q", detection.Method, MethodBehaviour)
}
}
func TestThinBehaviourAloneIsRejected(t *testing.T) {
if _, ok := Combine(Detection{}, cluster(2_645_000, 2, false)); ok {
t.Fatal("two plain stops wrote a marker with nothing else agreeing")
}
}
func TestNothingFoundIsNotAMarker(t *testing.T) {
if _, ok := Combine(Detection{}, BehaviourEvidence{}); ok {
t.Fatal("no evidence at all produced a marker")
}
}
// Marker stability: readings of the same episode wobble by a few seconds, and rewriting the
// row each time would break the "one write per episode, ever" promise.
func TestSmallVariationDoesNotRewrite(t *testing.T) {
existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.90, DetectionMethod: MethodCombined}
for _, offset := range []int64{-4000, -1000, 2000, 3000} {
if ShouldRewrite(existing, visual(existing.CreditsStartMs+offset, 0.91)) {
t.Fatalf("a %dms difference triggered a rewrite", offset)
}
}
}
func TestSubstantiallyBetterEvidenceRewrites(t *testing.T) {
existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.71, DetectionMethod: MethodVisual}
better := Detection{
Found: true, StartMs: 2_644_500, Confidence: 0.95, Method: MethodCombined,
}
if !ShouldRewrite(existing, better) {
t.Fatal("a much stronger reading of the same position did not rewrite")
}
}
func TestADifferentPositionNeedsBetterEvidence(t *testing.T) {
existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.90, DetectionMethod: MethodCombined}
// A different answer that is no better must not win, or two detectors that disagree
// would rewrite the row past each other for ever.
if ShouldRewrite(existing, visual(2_500_000, 0.85)) {
t.Fatal("a weaker reading at a different position overwrote a strong marker")
}
if !ShouldRewrite(existing, Detection{
Found: true, StartMs: 2_500_000, Confidence: 0.96, Method: MethodCombined,
}) {
t.Fatal("stronger evidence at a different position should win")
}
}
func TestManualMarkersAreNeverOverwritten(t *testing.T) {
existing := Marker{CreditsStartMs: 2_600_000, Confidence: 0.5, DetectionMethod: MethodManual}
if ShouldRewrite(existing, visual(2_400_000, 0.98)) {
t.Fatal("an automatic detection overwrote an operator's correction")
}
}
func TestBelowThresholdNeverRewrites(t *testing.T) {
existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.72, DetectionMethod: MethodVisual}
if ShouldRewrite(existing, visual(2_644_100, 0.50)) {
t.Fatal("a sub-threshold detection was written over an accepted marker")
}
}