summaryrefslogtreecommitdiff
path: root/server/internal
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-10-27 07:54:25 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-10-27 07:54:25 +0000
commit50b52d58e9865ac01e5364ec68247edb323db9b7 (patch)
tree868cb610482705138a32c07ffce95cae698b332b /server/internal
parentaa046a7ee11a20914a5851dd0e5a89d129683b28 (diff)
Use the labels: seed signatures from them, and take them back
The labels have been arriving and sitting there. Now the shadow run acts on all three kinds. A report is advertising pointed at where detection found nothing, so it becomes a signature in its own right -- that being the whole gap the library exists to close, since the scan accounts for about 38% of what the feeds imply is there. A mark saying a break is right adds no audio, because the break is harvested anyway. What it adds is that a person vouched for it. A mark saying a break is wrong does two things. It stops that audio being harvested, and it takes back any signature already held that matches it. That second half is the one that matters: a signature built from programme would cut the programme in every episode carrying it, until somebody noticed. So his "no" removes it outright rather than lowering a score. Being pointed at outranks partners and occasions both. Those are ways of inferring what a stretch of audio is; he was there. A confirmed signature is trusted at once and stays confirmed -- he does not un-hear it next week. A report whose end was never corrected is trimmed to its first fifteen seconds. An uncorrected end is wherever the 25-second jump landed rather than where the advertising stopped, so its tail may be programme -- and one ad unit is already far more audio than a landmark fingerprint needs to be distinctive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'server/internal')
-rw-r--r--server/internal/signature/library.go45
-rw-r--r--server/internal/signature/library_test.go102
-rw-r--r--server/internal/signature/shadow.go138
-rw-r--r--server/internal/signature/shadow_test.go134
4 files changed, 410 insertions, 9 deletions
diff --git a/server/internal/signature/library.go b/server/internal/signature/library.go
index 92bf77b..0606c95 100644
--- a/server/internal/signature/library.go
+++ b/server/internal/signature/library.go
@@ -42,6 +42,10 @@ type Observation struct {
// Partners is how many episodes in this one batch carried the audio, as detection
// measured it. Within-batch corroboration, which is not the same thing as independence.
Partners int
+ // Confirmed is set when the listener said so himself -- pressing "this is an ad", or
+ // marking a break the detector found as right. The strongest evidence there is: detection
+ // infers from repetition, and he heard it.
+ Confirmed bool
// Occasion is what makes two sightings independent. A whole batch of episodes mirrored in
// one minute is one occasion, however many of them carried the audio -- they all ran
// whatever campaign was running that minute, which is exactly why eight agreeing episodes
@@ -57,6 +61,9 @@ type Entry struct {
DurationMs int64 `json:"durationMs"`
// Partners is the most episodes ever seen sharing this in a single batch.
Partners int `json:"partners,omitempty"`
+ // Confirmed is whether the listener ever said this is what it is. Never unset once set --
+ // he does not un-hear it next week.
+ Confirmed bool `json:"confirmed,omitempty"`
Fingerprint Fingerprint `json:"fingerprint"`
@@ -83,6 +90,11 @@ func (e *Entry) Confidence() int { return len(e.Occasions) }
// only ever propose advertising that detection had already found twice, which is precisely when
// it has nothing to add.
func (e *Entry) Trusted(minOccasions int) bool {
+ // Nothing outranks the listener. Detection and the occasion count are both ways of
+ // inferring what this audio is; he was there.
+ if e.Confirmed {
+ return true
+ }
if e.Confidence() >= minOccasions {
return true
}
@@ -126,6 +138,9 @@ func (l *Library) Observe(o Observation, atUnix int64) (*Entry, bool) {
if o.Partners > existing.Partners {
existing.Partners = o.Partners
}
+ if o.Confirmed {
+ existing.Confirmed = true
+ }
return existing, false
}
e := &Entry{
@@ -133,6 +148,7 @@ func (l *Library) Observe(o Observation, atUnix int64) (*Entry, bool) {
Kind: o.Kind,
DurationMs: o.DurationMs,
Partners: o.Partners,
+ Confirmed: o.Confirmed,
Fingerprint: o.Fingerprint,
Occasions: []string{o.Occasion},
FirstSeenUnix: atUnix,
@@ -213,6 +229,35 @@ func (l *Library) Age(nowUnix int64) int {
return dropped
}
+// Disown drops every signature matching this audio, and returns how many went.
+//
+// For audio the listener says is the programme. It is the worst failure this system has: a
+// signature built from programme would cut the programme, in every episode carrying it, until
+// somebody noticed. So his "no" removes it outright rather than lowering a score.
+func (l *Library) Disown(fp Fingerprint) int {
+ if len(fp.Hashes) == 0 {
+ return 0
+ }
+ doomed := map[string]bool{}
+ for _, hit := range l.index.Search(fp) {
+ if hit.Votes >= matchVotes {
+ doomed[hit.ID] = true
+ }
+ }
+ if len(doomed) == 0 {
+ return 0
+ }
+ kept := l.entries[:0]
+ for _, e := range l.entries {
+ if !doomed[e.ID] {
+ kept = append(kept, e)
+ }
+ }
+ l.entries = kept
+ l.reindex()
+ return len(doomed)
+}
+
// Forget drops the cues belonging to a show, for when it is unsubscribed. Copies are untouched:
// an advertisement heard in that show is not the show's.
func (l *Library) Forget(feedURL string) int {
diff --git a/server/internal/signature/library_test.go b/server/internal/signature/library_test.go
index 8290b19..8498c8d 100644
--- a/server/internal/signature/library_test.go
+++ b/server/internal/signature/library_test.go
@@ -269,3 +269,105 @@ func TestTheMostEpisodesEverSharingItIsWhatCounts(t *testing.T) {
t.Fatalf("partners %d -- a quieter week does not unmake what was measured before", e.Partners)
}
}
+
+// --- what the listener said -----------------------------------------------------------------
+
+// A listener pointing at advertising is the strongest evidence in the system. Detection infers
+// from repetition; he heard it.
+func TestAdvertisingTheListenerPointedAtIsTrustedAtOnce(t *testing.T) {
+ l := signature.NewLibrary()
+
+ e := observe(l, signature.Observation{
+ Fingerprint: fpOf(42, 20), Kind: signature.KindCopy, DurationMs: 20_000,
+ Occasion: "batch-1", Confirmed: true,
+ }, day)
+
+ if !e.Trusted(2) {
+ t.Fatal("he heard it; there is nothing stronger to wait for")
+ }
+}
+
+func TestBeingPointedAtOutranksHavingNoCorroborationAtAll(t *testing.T) {
+ l := signature.NewLibrary()
+ e := observe(l, signature.Observation{
+ Fingerprint: fpOf(42, 20), Kind: signature.KindCopy, DurationMs: 20_000,
+ Occasion: "batch-1",
+ }, day)
+ if e.Trusted(2) {
+ t.Fatal("precondition: an uncorroborated sighting is not trusted")
+ }
+
+ observe(l, signature.Observation{
+ Fingerprint: fpOf(42, 20), Kind: signature.KindCopy, DurationMs: 20_000,
+ Occasion: "batch-2", Confirmed: true,
+ }, 8*day)
+
+ if !e.Trusted(2) {
+ t.Fatal("confirming a signature already held should make it trusted")
+ }
+}
+
+func TestConfirmationIsNotForgottenOnALaterUnconfirmedSighting(t *testing.T) {
+ l := signature.NewLibrary()
+ observe(l, signature.Observation{
+ Fingerprint: fpOf(42, 20), Kind: signature.KindCopy, DurationMs: 20_000,
+ Occasion: "batch-1", Confirmed: true,
+ }, day)
+
+ e := observe(l, signature.Observation{
+ Fingerprint: fpOf(42, 20), Kind: signature.KindCopy, DurationMs: 20_000,
+ Occasion: "batch-2",
+ }, 8*day)
+
+ if !e.Confirmed {
+ t.Fatal("he does not un-hear it next week")
+ }
+}
+
+// Furniture gets no shortcut from partners; being pointed at is different, because the listener
+// is not inferring from a batch that shares one campaign -- he is reporting what he heard.
+func TestFurnitureTheListenerPointedAtIsAlsoTrusted(t *testing.T) {
+ l := signature.NewLibrary()
+
+ e := observe(l, signature.Observation{
+ Fingerprint: fpOf(7, 8), Kind: signature.KindCue, FeedURL: "https://x/f.xml",
+ DurationMs: 8_000, Occasion: "batch-1", Confirmed: true,
+ }, day)
+
+ if !e.Trusted(2) {
+ t.Fatal("a listener saying 'this is the show's own' is evidence, not inference")
+ }
+}
+
+// --- taking something back ------------------------------------------------------------------
+
+// The worst failure this system has: a signature built from audio that turns out to be the
+// programme would cut the programme, everywhere it appears, forever.
+func TestAudioTheListenerSaysIsTheProgrammeIsForgotten(t *testing.T) {
+ programme := tone(42, seconds(20))
+ l := signature.NewLibrary()
+ observe(l, signature.Observation{
+ Fingerprint: signature.Take(programme), Kind: signature.KindCopy,
+ DurationMs: 20_000, Occasion: "batch-1", Partners: 6,
+ }, day)
+
+ dropped := l.Disown(signature.Take(programme))
+
+ if dropped != 1 || l.Len() != 0 {
+ t.Fatalf("dropped %d, %d left -- a signature he says is the programme must go", dropped, l.Len())
+ }
+}
+
+func TestDisowningDoesNotTouchUnrelatedSignatures(t *testing.T) {
+ l := signature.NewLibrary()
+ observe(l, signature.Observation{
+ Fingerprint: fpOf(42, 20), Kind: signature.KindCopy, DurationMs: 20_000,
+ Occasion: "batch-1", Partners: 6,
+ }, day)
+
+ dropped := l.Disown(fpOf(99, 20))
+
+ if dropped != 0 || l.Len() != 1 {
+ t.Fatalf("dropped %d of something else", dropped)
+ }
+}
diff --git a/server/internal/signature/shadow.go b/server/internal/signature/shadow.go
index 412597c..928743a 100644
--- a/server/internal/signature/shadow.go
+++ b/server/internal/signature/shadow.go
@@ -22,11 +22,25 @@ type ShadowBreak struct {
Kind string
}
-// ShadowEpisode is one episode of a batch and what detection made of it.
+// ShadowLabel is something the listener said about a stretch of this episode.
+type ShadowLabel struct {
+ StartMs int64
+ EndMs int64
+ // Advertising is true when he said this is an ad -- the notification button, or marking a
+ // break the detector found as right. False when he said it is the programme.
+ Advertising bool
+ // EndMeasured is false when the end is wherever the jump landed rather than where he heard
+ // the advertising stop. See [GuessedEndSeedMs].
+ EndMeasured bool
+}
+
+// ShadowEpisode is one episode of a batch, what detection made of it, and what the listener
+// said about it.
type ShadowEpisode struct {
ID string
FeedURL string
Breaks []ShadowBreak
+ Labels []ShadowLabel
}
// ShadowBatch is one detection pass over one show.
@@ -51,11 +65,22 @@ type Proposal struct {
Novel bool
}
+// GuessedEndSeedMs is how much of a reported advertisement is kept when its end was never
+// measured.
+//
+// An uncorrected end is wherever the 25-second jump happened to land, not where the advertising
+// stopped, so the tail of that range may be programme -- and a signature carrying programme is
+// the one thing this must never build. One ad unit from the start is far more audio than a
+// landmark fingerprint needs to be distinctive, and it is the part most certainly advertising.
+const GuessedEndSeedMs = int64(15_000)
+
// Report is what one shadow run saw.
type Report struct {
Harvested int
Reinforced int
- WouldCut []Proposal
+ // Disowned counts signatures dropped because the listener said that audio is the programme.
+ Disowned int
+ WouldCut []Proposal
// Skipped counts episodes whose audio could not be read. Reported rather than failing the
// run: one unreadable file must not cost the whole batch's learning.
Skipped int
@@ -116,11 +141,43 @@ func (s *Shadow) Run(ctx context.Context, batch ShadowBatch) (Report, error) {
report.Skipped = len(unreadable)
return report, ctx.Err()
}
+ // His "no" first, and it does two things: it stops the harvest below from storing
+ // audio he says is the programme, and it takes back anything already held that matches
+ // it. A signature built from programme would cut the programme in every episode
+ // carrying it, until somebody noticed.
+ for _, label := range ep.Labels {
+ if label.Advertising {
+ continue
+ }
+ if !s.disown(ctx, ep, label, &report) {
+ unreadable[ep.ID] = true
+ }
+ }
for _, br := range ep.Breaks {
+ if disowned(ep.Labels, br.StartMs, br.EndMs) {
+ continue
+ }
// A break this cannot read is counted and stepped over. Every other per-item loop
// in this server isolates a single failure the same way, and the first real run
// lost a whole show's learning to one episode whose path was wrong.
- if !s.harvest(ctx, batch, ep, br, &report) {
+ if !s.harvest(ctx, batch, ep, seed{
+ startMs: br.StartMs,
+ endMs: br.EndMs,
+ kind: kindFor(br.Kind),
+ partners: br.Partners,
+ confirmed: vouchedFor(ep.Labels, br.StartMs, br.EndMs),
+ }, &report) {
+ unreadable[ep.ID] = true
+ break
+ }
+ }
+ // Then what he pointed at himself. Advertising detection missed is the gap the library
+ // exists to close, so a report is harvested whether or not a break was found there.
+ for _, label := range ep.Labels {
+ if !label.Advertising || coveredByBreak(ep.Breaks, label) {
+ continue
+ }
+ if !s.harvest(ctx, batch, ep, seedFromLabel(label), &report) {
unreadable[ep.ID] = true
break
}
@@ -130,14 +187,76 @@ func (s *Shadow) Run(ctx context.Context, batch ShadowBatch) (Report, error) {
return report, nil
}
-// harvest stores one break's audio as a signature, and reports whether the episode could be
+// seed is one stretch about to become a signature.
+type seed struct {
+ startMs int64
+ endMs int64
+ kind string
+ partners int
+ confirmed bool
+}
+
+func seedFromLabel(label ShadowLabel) seed {
+ end := label.EndMs
+ if !label.EndMeasured && end-label.StartMs > GuessedEndSeedMs {
+ end = label.StartMs + GuessedEndSeedMs
+ }
+ // Always a copy: he presses the button when he hears an advertisement, and a show's own
+ // furniture is not what that button is for.
+ return seed{startMs: label.StartMs, endMs: end, kind: KindCopy, confirmed: true}
+}
+
+func overlaps(aStart, aEnd, bStart, bEnd int64) bool { return aStart < bEnd && aEnd > bStart }
+
+// disowned says whether the listener called this stretch the programme.
+func disowned(labels []ShadowLabel, startMs, endMs int64) bool {
+ for _, l := range labels {
+ if !l.Advertising && overlaps(startMs, endMs, l.StartMs, l.EndMs) {
+ return true
+ }
+ }
+ return false
+}
+
+// vouchedFor says whether the listener marked this break right.
+func vouchedFor(labels []ShadowLabel, startMs, endMs int64) bool {
+ for _, l := range labels {
+ if l.Advertising && overlaps(startMs, endMs, l.StartMs, l.EndMs) {
+ return true
+ }
+ }
+ return false
+}
+
+// coveredByBreak says whether detection already found this label's audio, in which case
+// harvesting the break covered it and the label only had to vouch for it.
+func coveredByBreak(breaks []ShadowBreak, label ShadowLabel) bool {
+ for _, br := range breaks {
+ if overlaps(label.StartMs, label.EndMs, br.StartMs, br.EndMs) {
+ return true
+ }
+ }
+ return false
+}
+
+// disown forgets every signature matching audio the listener says is the programme.
+func (s *Shadow) disown(ctx context.Context, ep ShadowEpisode, label ShadowLabel, report *Report) bool {
+ samples, err := s.audio.Range(ctx, ep.ID, label.StartMs, label.EndMs-label.StartMs)
+ if err != nil {
+ return false
+ }
+ report.Disowned += s.library.Disown(Take(samples))
+ return true
+}
+
+// harvest stores one stretch of audio as a signature, and reports whether the episode could be
// read at all. A false means give up on this episode, not on the batch.
-func (s *Shadow) harvest(ctx context.Context, batch ShadowBatch, ep ShadowEpisode, br ShadowBreak, report *Report) bool {
- duration := br.EndMs - br.StartMs
+func (s *Shadow) harvest(ctx context.Context, batch ShadowBatch, ep ShadowEpisode, sd seed, report *Report) bool {
+ duration := sd.endMs - sd.startMs
if duration <= 0 {
return true
}
- samples, err := s.audio.Range(ctx, ep.ID, br.StartMs, duration)
+ samples, err := s.audio.Range(ctx, ep.ID, sd.startMs, duration)
if err != nil {
return false
}
@@ -147,10 +266,11 @@ func (s *Shadow) harvest(ctx context.Context, batch ShadowBatch, ep ShadowEpisod
}
_, isNew := s.library.Observe(Observation{
Fingerprint: fp,
- Kind: kindFor(br.Kind),
+ Kind: sd.kind,
FeedURL: ep.FeedURL,
DurationMs: duration,
- Partners: br.Partners,
+ Partners: sd.partners,
+ Confirmed: sd.confirmed,
Occasion: batch.Occasion,
}, batch.AtUnix)
if isNew {
diff --git a/server/internal/signature/shadow_test.go b/server/internal/signature/shadow_test.go
index f6c6676..03fcab9 100644
--- a/server/internal/signature/shadow_test.go
+++ b/server/internal/signature/shadow_test.go
@@ -233,3 +233,137 @@ func TestOneUnreadableEpisodeDoesNotCostTheBatchItsLearning(t *testing.T) {
t.Fatalf("skipped %d, want 1 -- an unreadable file should be counted, not silent", report.Skipped)
}
}
+
+// --- acting on what the listener said --------------------------------------------------------
+
+// The gap the library exists to close. Detection accounts for roughly 38% of the advertising
+// the feeds imply, so most of what gets heard was never found -- and a report is the listener
+// pointing straight at a piece of it.
+func TestHarvestsAdvertisingTheListenerReportedWhereDetectionFoundNothing(t *testing.T) {
+ ad := tone(42, seconds(20))
+ audio := fakeAudio{"e1": planted(1, ad, 300, 60)}
+ l := signature.NewLibrary()
+
+ report, err := signature.NewShadow(l, audio).Run(context.Background(), batch(
+ signature.ShadowEpisode{ID: "e1", Labels: []signature.ShadowLabel{
+ {StartMs: 60_000, EndMs: 80_000, Advertising: true, EndMeasured: true},
+ }},
+ ))
+ if err != nil {
+ t.Fatalf("run: %v", err)
+ }
+
+ if report.Harvested != 1 {
+ t.Fatalf("harvested %d from a report, want 1", report.Harvested)
+ }
+ if !l.Confident(1)[0].Confirmed {
+ t.Fatal("a reported advertisement is confirmed, which is what makes it trusted at once")
+ }
+}
+
+// An uncorrected end is wherever the 25-second jump happened to land, not where the advertising
+// stopped. A signature cut to it would carry programme audio into the thing meant to identify
+// advertising, so only the opening is taken -- enough to recognise, short enough to be safe.
+func TestAReportWithAGuessedEndIsTrimmedToItsOpening(t *testing.T) {
+ ad := tone(42, seconds(25))
+ audio := fakeAudio{"e1": planted(1, ad, 300, 60)}
+ l := signature.NewLibrary()
+
+ signature.NewShadow(l, audio).Run(context.Background(), batch(
+ signature.ShadowEpisode{ID: "e1", Labels: []signature.ShadowLabel{
+ {StartMs: 60_000, EndMs: 85_000, Advertising: true, EndMeasured: false},
+ }},
+ ))
+
+ got := l.Confident(1)[0].DurationMs
+ if got != signature.GuessedEndSeedMs {
+ t.Fatalf("stored %dms of a guessed range, want the %dms opening", got, signature.GuessedEndSeedMs)
+ }
+}
+
+func TestAReportWhoseEndWasHeardIsTakenWhole(t *testing.T) {
+ ad := tone(42, seconds(20))
+ audio := fakeAudio{"e1": planted(1, ad, 300, 60)}
+ l := signature.NewLibrary()
+
+ signature.NewShadow(l, audio).Run(context.Background(), batch(
+ signature.ShadowEpisode{ID: "e1", Labels: []signature.ShadowLabel{
+ {StartMs: 60_000, EndMs: 80_000, Advertising: true, EndMeasured: true},
+ }},
+ ))
+
+ if got := l.Confident(1)[0].DurationMs; got != 20_000 {
+ t.Fatalf("stored %dms, want the whole 20000ms he measured", got)
+ }
+}
+
+// The worst failure this system has. A signature built from programme would cut the programme
+// in every episode carrying it, so his "no" has to stop the harvest, not merely score it down.
+func TestABreakTheListenerCalledTheProgrammeIsNotHarvested(t *testing.T) {
+ programme := tone(42, seconds(20))
+ audio := fakeAudio{"e1": planted(1, programme, 300, 60)}
+ l := signature.NewLibrary()
+
+ report, _ := signature.NewShadow(l, audio).Run(context.Background(), batch(
+ signature.ShadowEpisode{
+ ID: "e1",
+ Breaks: []signature.ShadowBreak{{StartMs: 60_000, EndMs: 80_000, Kind: "ad", Partners: 6}},
+ Labels: []signature.ShadowLabel{{StartMs: 60_000, EndMs: 80_000, Advertising: false}},
+ },
+ ))
+
+ if report.Harvested != 0 || l.Len() != 0 {
+ t.Fatalf("harvested %d and holds %d -- he said it is the programme", report.Harvested, l.Len())
+ }
+}
+
+func TestSayingABreakIsTheProgrammeTakesBackASignatureAlreadyHeld(t *testing.T) {
+ programme := tone(42, seconds(20))
+ l := signature.NewLibrary()
+
+ // Week one: detection finds it and the library learns it.
+ signature.NewShadow(l, fakeAudio{"e1": planted(1, programme, 300, 60)}).Run(
+ context.Background(), signature.ShadowBatch{Occasion: "week-1", AtUnix: day,
+ Episodes: []signature.ShadowEpisode{{ID: "e1",
+ Breaks: []signature.ShadowBreak{{StartMs: 60_000, EndMs: 80_000, Kind: "ad", Partners: 6}}}}})
+ if l.Len() != 1 {
+ t.Fatalf("precondition: expected one signature, got %d", l.Len())
+ }
+
+ // Week two: he taps the mark and says it is the programme.
+ report, _ := signature.NewShadow(l, fakeAudio{"e2": planted(3, programme, 300, 120)}).Run(
+ context.Background(), signature.ShadowBatch{Occasion: "week-2", AtUnix: 8 * day,
+ Episodes: []signature.ShadowEpisode{{ID: "e2",
+ Labels: []signature.ShadowLabel{{StartMs: 120_000, EndMs: 140_000, Advertising: false}}}}})
+
+ if l.Len() != 0 {
+ t.Fatalf("still holds %d signatures he has disowned", l.Len())
+ }
+ if report.Disowned != 1 {
+ t.Fatalf("reported %d disowned, want 1", report.Disowned)
+ }
+}
+
+// Marking a break right is agreement, not new audio: the break is harvested anyway, and what
+// the mark adds is that a person vouched for it.
+func TestMarkingABreakRightConfirmsTheSignatureItCameFrom(t *testing.T) {
+ ad := tone(42, seconds(20))
+ audio := fakeAudio{"e1": planted(1, ad, 300, 60)}
+ l := signature.NewLibrary()
+
+ signature.NewShadow(l, audio).Run(context.Background(), batch(
+ signature.ShadowEpisode{
+ ID: "e1",
+ Breaks: []signature.ShadowBreak{{StartMs: 60_000, EndMs: 80_000, Kind: "ad", Partners: 1}},
+ Labels: []signature.ShadowLabel{{StartMs: 60_000, EndMs: 80_000, Advertising: true, EndMeasured: true}},
+ },
+ ))
+
+ entries := l.Confident(1)
+ if len(entries) != 1 {
+ t.Fatalf("got %d signatures, want one", len(entries))
+ }
+ if !entries[0].Confirmed {
+ t.Fatal("he vouched for this break; that is what the mark is for")
+ }
+}