diff options
Diffstat (limited to 'server/cmd')
| -rw-r--r-- | server/cmd/listen-score/main.go | 105 | ||||
| -rw-r--r-- | server/cmd/listend/main.go | 7 |
2 files changed, 111 insertions, 1 deletions
diff --git a/server/cmd/listen-score/main.go b/server/cmd/listen-score/main.go new file mode 100644 index 0000000..f4b0ba7 --- /dev/null +++ b/server/cmd/listen-score/main.go @@ -0,0 +1,105 @@ +/* +Command listen-score measures the detectors against what the listener actually heard. + +Every other number in this project comes from the declared-versus-served length gap, which says +how much advertising a copy carries and nothing about where. This is the only check on *where*: +each label is the listener pressing a button at a moment in an episode, saying "this is an +advertisement" where the detectors missed one or "this is not" where they cut something that +was not. + +Read-only. It opens the server's state file, scores what is published against the labels in it, +and prints. It changes nothing and is safe to run against a live server. +*/ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/terst/listen/server/internal/score" + "github.com/terst/listen/server/internal/store" +) + +func main() { + data := flag.String("data", "/site/listen.terst.org/data", "server data directory") + verbose := flag.Bool("v", false, "print every label and how it scored") + asJSON := flag.Bool("json", false, "print the summary as JSON") + flag.Parse() + + state, err := store.Load(filepath.Join(*data, "state.json")) + if err != nil { + fmt.Fprintf(os.Stderr, "listen-score: %v\n", err) + os.Exit(1) + } + + var total score.Summary + matched, unmatched := 0, 0 + + for _, label := range state.Labels() { + episode := state.Match(label.FeedURL, label.GUID, "", "") + if episode == nil { + // The mirror keeps eight episodes a show, so a label outlives the audio it + // describes. Counted rather than ignored: a corpus that has mostly aged out is a + // different thing from a corpus the detectors do badly on. + unmatched++ + continue + } + matched++ + + // Scored against this episode's own breaks. Pooling them would credit one episode's + // detection to another episode's label. + breaks := make([]score.Span, 0, 4) + for _, b := range episode.AllBreaks() { + breaks = append(breaks, score.Span{StartMs: b.StartMs, EndMs: b.EndMs}) + } + one := score.Score([]score.Label{{ + Span: score.Span{StartMs: label.StartMs, EndMs: label.EndMs}, + IsAd: label.Verdict == store.LabelAd, + EndMeasured: label.EndMeasured, + }}, breaks) + + total.AdsFound += one.AdsFound + total.AdsMissed += one.AdsMissed + total.ProgrammeCut += one.ProgrammeCut + total.ProgrammeKept += one.ProgrammeKept + + if *verbose { + fmt.Printf("%-6s %-9s %6.0fs-%-6.0fs %d break(s) %.44s\n", + outcome(one), label.Verdict, + float64(label.StartMs)/1000, float64(label.EndMs)/1000, + len(breaks), episode.Title) + } + } + + if *asJSON { + out := struct { + score.Summary + Labels int `json:"labels"` + Matched int `json:"matched"` + Unmatched int `json:"unmatched"` + }{total, matched + unmatched, matched, unmatched} + blob, _ := json.MarshalIndent(out, "", " ") + fmt.Println(string(blob)) + return + } + + fmt.Printf("\n%d label(s): %d against an episode still mirrored, %d outlived their audio\n", + matched+unmatched, matched, unmatched) + fmt.Println(total.String()) +} + +func outcome(s score.Summary) string { + switch { + case s.AdsFound > 0: + return "found" + case s.AdsMissed > 0: + return "MISSED" + case s.ProgrammeCut > 0: + return "CUT" + default: + return "kept" + } +} diff --git a/server/cmd/listend/main.go b/server/cmd/listend/main.go index 6cbfb65..377fedc 100644 --- a/server/cmd/listend/main.go +++ b/server/cmd/listend/main.go @@ -656,7 +656,12 @@ func (w *worker) findRecurring(show *store.Show) { published, refused := 0, 0 for i, target := range corpus { - if target.episode.MasterCues > 0 { + // Skipped only where subtraction actually produced something, not merely where a + // master exists. A refetch master that carried the same campaign yields no candidates + // at all, and treating "has a master" as "has a better answer" left those episodes + // with nothing -- Tangle's "The Israeli elections" has a 229-second gap, a refetch + // master, no subtraction candidates, and was excluded from this. + if len(target.episode.WordBreaks) > 0 { continue } others := make([][]transcript.Cue, 0, len(corpus)-1) |
