From f0804b6469dac395e8005e63db1bb080a2b6e8df Mon Sep 17 00:00:00 2001 From: Pauljd Date: Tue, 3 Nov 2026 23:29:32 +0000 Subject: [PATCH] Editorial audit: per-category airtime figures and per-block tallies (read-only) Co-Authored-By: Claude Opus 5.5 (1M context) --- .../tv/orbit/classic/ScheduleAuditTest.kt | 14 ++ docs/replays/editorial-audit-2026-11-04.md | 172 ++++++++++++++++++ docs/replays/editorial-categories.py | 42 +++++ 3 files changed, 228 insertions(+) create mode 100644 docs/replays/editorial-audit-2026-11-04.md create mode 100644 docs/replays/editorial-categories.py diff --git a/android/app/src/test/java/tv/orbit/classic/ScheduleAuditTest.kt b/android/app/src/test/java/tv/orbit/classic/ScheduleAuditTest.kt index 694326c..b66a5a4 100644 --- a/android/app/src/test/java/tv/orbit/classic/ScheduleAuditTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/ScheduleAuditTest.kt @@ -52,6 +52,7 @@ class ScheduleAuditTest { val md = StringBuilder("# Schedule audit replay (read-only)\n\nFour weeks from each channel's committed plan, with today's rules, on copies of the box's real pools. " + "\"Committed\" is the plan on the box now; \"New\" is what today's rules book after it.\n") val allPlans = mutableMapOf>() + val json = JSONObject() for (programming in SmartChannels) { val number = programming.number val pf = File(dir, "schedule-$number.json"); val sf = File(dir, "episodes-$number.json") @@ -98,6 +99,18 @@ class ScheduleAuditTest { }) md.append(".\n") } + // For the editorial audit: every title's airtime and starts, old week against new days, and by hour of day. + val titleOf = series.associate { it.id to it.title } + fun tally(es: List) = JSONObject().also { o -> es.groupBy { it.seriesId }.forEach { (id, xs) -> + o.put(titleOf[id] ?: guideTitleParts(xs.first().media.item.title).first, JSONObject().put("minutes", xs.sumOf { it.media.durationMs } / 60_000).put("starts", xs.size) + .put("hours", org.json.JSONArray(xs.map { java.time.Instant.ofEpochMilli(it.startMs).atZone(zone).let { l -> l.dayOfWeek.value * 100 + l.hour } }.distinct().sorted()))) } } + json.put(number.toString(), JSONObject().put("name", programming.name).put("pool", org.json.JSONArray(series.map { JSONObject().put("title", it.title).put("files", it.episodes.size).put("minutes", it.episodes.sumOf { e -> e.durationMs } / 60_000) })) + .put("committed", tally(committed.entries)).put("steady", tally(steady)).put("committedEnd", committedEnd).put("film", filmChannel) + .put("parts", org.json.JSONArray(programming.dayParts.map { p -> JSONObject().put("from", p.fromHour).put("to", p.toHour).put("days", org.json.JSONArray(p.days.map { it.name })).put("titles", org.json.JSONArray(p.titles)).put("group", p.group).put("key", p.key).put("onTime", p.onTime) })) + .put("searches", org.json.JSONArray(programming.searches)).put("finishTogether", programming.finishTogether).put("shuffleEpisodes", programming.shuffleEpisodes) + .put("mixed", org.json.JSONArray(series.filter(programming::mixes).map { it.title })) + .put("committedReasons", org.json.JSONArray(committed.entries.map { it.why.substringBefore(';') }.distinct())) + .put("byPart", org.json.JSONArray(programming.dayParts.map { part -> tally(steady.filter { e -> java.time.Instant.ofEpochMilli(e.startMs).atZone(zone).let { l -> part.covers(l.hour, l.dayOfWeek) } }) }))) // Fallbacks and deferrals the planner recorded. val fallbacks = steady.count { "fallback" in it.why }; val noBlock = steady.count { it.why.startsWith("no block") } if (fallbacks + noBlock > 0) md.append("- **Planner fallbacks (3 weeks):** $fallbacks fallback, $noBlock outside any block.\n") @@ -135,6 +148,7 @@ class ScheduleAuditTest { flat.values.forEach { es -> es.sortedBy { it.second.startMs }.zipWithNext().forEach { (a, b) -> if (a.first != b.first && b.second.startMs < a.second.endMs) clashes += "${a.second.media.item.title.take(40)} on ${a.first} and ${b.first} at ${java.time.Instant.ofEpochMilli(b.second.startMs).atZone(zone).toLocalDateTime()}" } } md.append("\n## Across channels\n\nThe same file on two channels at once (replay ignores the live clash check, so new bookings may show some): ${clashes.size}${if (clashes.isNotEmpty()) "\n\n" + clashes.take(20).joinToString("\n") { "- $it" } else ""}\n") File(dir, "schedule-audit.md").writeText(md.toString()) + File(dir, "schedule-audit.json").writeText(json.toString(1)) println("audit written: ${File(dir, "schedule-audit.md")}") } } diff --git a/docs/replays/editorial-audit-2026-11-04.md b/docs/replays/editorial-audit-2026-11-04.md new file mode 100644 index 0000000..a72099d --- /dev/null +++ b/docs/replays/editorial-audit-2026-11-04.md @@ -0,0 +1,172 @@ +# Editorial audit figures, 4 November 2026 (read-only) + +Airtime over three weeks of new bookings, by each brief's own categories (titles matched by name), from ScheduleAuditTest on copies of the box's pools and plans. + +``` + +### 611 Orbit Junior (pool 48, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + After school, 15:00 to 18:00 (core) titles 17 aired 17 share 56% files 1813 never: + Daytime, 09:00 to 15:00 titles 9 aired 9 share 23% files 1099 never: + Breakfast, 06:00 to 09:00 titles 15 aired 15 share 10% files 2300 never: + Teatime, 18:00 to 20:00 titles 5 aired 5 share 8% files 265 never: + (not in brief) titles 2 aired 2 share 1% files 604 never: + top: Shaun the Sheep 5% (Daytime, 0); The Story of Tracy Beaker 4% (After scho); Byker Grove 4% (After scho); Rentaghost 4% (After scho); Knightmare 4% (After scho); Grange Hill 4% (After scho) + +### 612 Wonder (pool 92, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 92 aired 92 share 100% files 92 never: + top: Princess Mononoke 1% (-); Castle in the Sky 1% (-); Spirited Away 1% (-); Pom Poko 1% (-); Howl's Moving Castle 1% (-); Nausicaä of the Valley of th 1% (-) + +### 613 PixelZ (pool 100, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 100 aired 100 share 100% files 100 never: + top: Raya and the Last Dragon 1% (-); Spider-Man: Across the Spide 1% (-); Epic 1% (-); How to Train Your Dragon 2 1% (-); Frozen II 1% (-); Kung Fu Panda 3 1% (-) + +### 614 Tots (pool 36, film=False, finishTogether=True, committedReasons=['']) + (not in brief) titles 30 aired 29 share 82% files 3326 never: Andy's Dinosaur Adventures + Programming (Tots.kt, every title hand-a titles 6 aired 5 share 17% files 815 never: Balamory (2026) + top: In The Night Garden 38% (-); Teletubbies 7% (Programmin); Clangers 6% (-); CBeebies Bedtime Stories 4% (-); BeddyByes 3% (-); Clifford the Big Red Dog 3% (Programmin) + +### 101 Orbit 1 (pool 46, film=False, finishTogether=True, committedReasons=['', 'block 6-18', 'block 18-0', 'block 0-6']) + Supporting titles 24 aired 24 share 55% files 3356 never: + Core titles 15 aired 14 share 38% files 2441 never: Not Going Out + Paul's favourites titles 1 aired 1 share 2% files 115 never: + (not in brief) titles 1 aired 1 share 1% files 68 never: + Surprises titles 5 aired 5 share 1% files 917 never: + top: The Golden Girls 8% (Supporting); Frasier 8% (Core); Everybody Loves Raymond 8% (Core); Happy Days 3% (Supporting); Kenan & Kel 2% (Supporting); The Wonder Years 2% (Paul's fav) + +### 201 Terra (pool 39, film=False, finishTogether=True, committedReasons=['']) + Core titles 23 aired 23 share 72% files 186 never: + Supporting titles 13 aired 13 share 19% files 206 never: + (not in brief) titles 3 aired 3 share 7% files 20 never: + top: Natural World 5% (Supporting); Life on Earth 5% (Core); The Living Planet 4% (Core); Life 4% (Core); Prehistoric Planet 4% (Core); The Trials of Life 4% (Core) + +### 102 Orbit 2 (pool 53, film=False, finishTogether=True, committedReasons=['', 'block 6-15', 'block 15-18', 'block 18-21']) + Supporting titles 28 aired 28 share 59% files 3293 never: + Core titles 15 aired 15 share 29% files 2777 never: + (not in brief) titles 5 aired 5 share 7% files 510 never: + Surprises titles 5 aired 5 share 3% files 371 never: + top: Robot Wars 11% (Core); MythBusters 10% (Supporting); Top Gear 6% (Supporting); How It's Made 4% (Supporting); The Gadget Show 3% (Supporting); Total Wipeout 2% (Supporting) + +### 103 Orbit Three (pool 45, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Supporting titles 20 aired 20 share 43% files 490 never: + Core titles 12 aired 8 share 42% files 346 never: Gavin & Stacey, The Mighty Boosh, Spaced, Phoenix Nights + Surprises titles 13 aired 13 share 13% files 637 never: + top: Still Game 10% (Core); Two Pints of Lager and a Pac 9% (Core); Limmy's Show! 6% (Core); The Inbetweeners 5% (Core); Rab C. Nesbitt 5% (Core); Cuckoo 3% (Supporting) + +### 301 Orbit Premiere (pool 98, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 98 aired 98 share 100% files 98 never: + top: Superman 1% (-); Avengers: Endgame 1% (-); Saving Private Ryan 1% (-); No Time to Die 1% (-); Avatar 1% (-); The Lord of the Rings: The T 1% (-) + +### 202 Odyssey (pool 36, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Core titles 14 aired 12 share 56% files 673 never: Cosmos, Wonders of the Universe + Supporting titles 9 aired 5 share 28% files 621 never: James May's Man Lab, The Ascent of Man, The Story of Maths, Walking with Dinosaurs + (not in brief) titles 5 aired 3 share 10% files 96 never: Wonders of the Solar System, Band of Brothers + Journeys titles 8 aired 3 share 4% files 94 never: Civilisation, The Civil War, The Story of India, Long Way Round, Around the World in 80 Days + top: The Universe 28% (Core); Mayday 11% (Supporting); Impossible Engineering 8% (-); How Do They Do It? 7% (Supporting); Megastructures 5% (Core); Horizon 5% (Core) + +### 104 Orbit Gold (pool 44, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Supporting titles 25 aired 25 share 61% files 1005 never: + Core titles 10 aired 10 share 29% files 313 never: + (not in brief) titles 6 aired 6 share 6% files 538 never: + Surprises titles 3 aired 3 share 1% files 266 never: + top: Only Fools and Horses 8% (Core); Dad's Army 7% (Core); Waiting for God 6% (Supporting); As Time Goes By 6% (Supporting); Are You Being Served? 6% (Supporting); Some Mothers Do 'Ave 'Em 4% (Supporting) + +### 203 Orbit Wild (pool 25, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Supporting titles 14 aired 14 share 53% files 1940 never: + Core titles 10 aired 9 share 46% files 733 never: Dual Survival + Surprise titles 1 aired 1 share 0% files 180 never: + top: Naked and Afraid 15% (Core); Alone 15% (Core); Whale Wars 7% (Supporting); Man vs. Wild 6% (Core); Swamp People 4% (Supporting); Deadliest Catch 4% (Supporting) + +### 302 Orbit Action (pool 104, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 104 aired 104 share 100% files 104 never: + top: Sicario 1% (-); Heat 1% (-); The Raid 2 1% (-); Bad Boys II 1% (-); Man on Fire 1% (-); The Rock 1% (-) + +### 401 Ring X (pool 89, film=False, finishTogether=True, committedReasons=['', 'event 0-24']) + Daytime, 06:00 to 23:00 outside those ev titles 7 aired 7 share 48% files 2055 never: + (not in brief) titles 71 aired 21 share 27% files 4817 never: All Elite Wrestling: Battle of the Belts, AEW Double or Nothing 2019, WWE WrestleMania 39 Sunday, NWA Starrcade 1983, WWE Survivor Series 1987, NJPW Wrestle Kingdom 11, TNA Unbreakable 2005, WCW Fall Brawl 1997 +42 + Late, 23:00 to 06:00 titles 9 aired 9 share 22% files 223 never: + Weeknights, 18:00 to 23:00 titles 2 aired 2 share 2% files 2034 never: + top: WCW Clash of the Champions 11% (Daytime, 0); WWF Prime Time Wrestling 9% (Daytime, 0); WWE NXT 9% (-); WCW Saturday Night 9% (Daytime, 0); Biography: WWE Legends 7% (Late, 23:0); ECW Hardcore TV 6% (Daytime, 0) + +### 105 Riot (pool 49, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Supporting titles 25 aired 25 share 51% files 2404 never: + Core titles 10 aired 10 share 29% files 2996 never: + Surprises titles 6 aired 6 share 9% files 477 never: + (not in brief) titles 8 aired 7 share 9% files 667 never: How I Met Your Mother + top: Futurama 6% (Core); King of the Hill 6% (Core); Bob's Burgers 6% (Supporting); American Dad! 6% (Core); The Cleveland Show 5% (Supporting); Duckman 5% (Supporting) + +### 204 Dark Files (pool 46, film=False, finishTogether=True, committedReasons=['', 'block day', 'block evening', 'block late']) + (not in brief) titles 38 aired 36 share 79% files 3685 never: The First 48, Dark Tourist + Core titles 8 aired 7 share 20% files 2340 never: Haunted + top: Making a Murderer 4% (-); Dateline NBC 4% (-); The Jinx 4% (-); Paranormal Witness 3% (Core); Most Haunted 3% (Core); City Confidential 3% (-) + +### 303 Orbit Sci-Fi (pool 98, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 98 aired 98 share 100% files 98 never: + top: Dune: Part Two 1% (-); Blade Runner 2049 1% (-); Dune 1% (-); Aliens 1% (-); Star Wars: The Last Jedi 1% (-); Tenet 1% (-) + +### 601 Vortex (pool 117, film=False, finishTogether=True, committedReasons=['', 'block day', 'block night']) + (not in brief) titles 97 aired 67 share 57% files 3575 never: Murder Princess, Pokémon: Origins, Karas, Now and Then, Here and There, Zakuro, Legend of the Galactic Heroes Gaiden, Dragon Ball: Mystical Adventure, Dragon Ball Super: Broly +22 + Core titles 14 aired 14 share 36% files 2843 never: + Supporting titles 6 aired 6 share 6% files 585 never: + top: Yu-Gi-Oh! Duel Monsters 3% (Core); Pokémon 3% (Core); Samurai Jack 3% (Core); ThunderCats 3% (Core); Cardcaptor Sakura 3% (Core); Dragon Ball 3% (Core) + +### 106 Paradox (pool 18, film=False, finishTogether=True, committedReasons=['block 6-18', 'block 18-23', 'block 23-6']) + Supporting titles 11 aired 11 share 60% files 1243 never: + Core titles 6 aired 6 share 33% files 1033 never: + Surprises titles 1 aired 1 share 5% files 48 never: + top: The X-Files 9% (Core); Doctor Who (2005) 7% (Core); Star Trek: The Next Generati 6% (Core); Sliders 6% (Supporting); Eureka 6% (Supporting); Stargate SG-1 6% (Supporting) + +### 304 Orbit Horror (pool 113, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 110 aired 110 share 96% files 110 never: + Slasher Saturday titles 3 aired 3 share 3% files 3 never: + top: It Chapter Two 1% (-); Doctor Sleep 1% (-); The Shining 1% (-); Rosemary's Baby 1% (-); The Conjuring 2 1% (-); It 1% (Slasher Sa) + +### 305 After Dark (pool 170, film=True, finishTogether=False, committedReasons=['']) + (not in brief) titles 169 aired 126 share 98% files 169 never: [REC]², Plan 9 from Outer Space, The Return of the Living Dead, Frankenhooker, Night of the Comet, Bad Taste, Them!, It Came from Outer Space +35 + Joe Bob Saturdays titles 1 aired 1 share 1% files 223 never: + top: Koi... Mil Gaya 2% (-); Mulholland Drive 1% (-); Mystic River 1% (-); Donnie Darko 1% (-); The Name of the Rose 1% (-); Starship Troopers 1% (-) + +### 602 Heroes (pool 19, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Core titles 8 aired 8 share 56% files 553 never: + Supporting titles 11 aired 11 share 43% files 469 never: + top: Teen Titans 11% (Core); Young Justice 10% (Supporting); Justice League 8% (Core); Static Shock 8% (Supporting); Batman: The Animated Series 8% (Core); Superman: The Animated Serie 6% (Core) + +### 107 Evidence (pool 30, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Core titles 9 aired 9 share 40% files 1992 never: + Supporting titles 10 aired 10 share 32% files 1551 never: + (not in brief) titles 11 aired 11 share 27% files 1557 never: + top: Columbo 9% (Core); CSI: Miami 5% (Supporting); CSI: Crime Scene Investigati 5% (-); NCIS 5% (Core); Law & Order 5% (Core); The Mentalist 5% (Supporting) + +### 603 Orbit X (pool 21, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Core titles 9 aired 9 share 69% files 1927 never: + (not in brief) titles 8 aired 6 share 22% files 528 never: Bucky O'Hare and the Toad Wars!, Visionaries: Knights of the Magical Light + Supporting titles 4 aired 4 share 8% files 266 never: + top: G.I. Joe 17% (Core); Power Rangers 15% (Core); Teenage Mutant Ninja Turtles 10% (Core); Digimon: Digital Monsters 6% (Core); Captain Planet and the Plane 6% (-); He-Man and the Masters of th 6% (-) + +### 306 Orbit VHS (pool 96, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 96 aired 96 share 100% files 96 never: + top: Coming to America 1% (-); Trading Places 1% (-); Hook 1% (-); The Mighty Ducks 1% (-); The Blues Brothers 1% (-); Caddyshack 1% (-) + +### 604 Rewind (pool 23, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Core titles 10 aired 10 share 62% files 1087 never: + Supporting titles 13 aired 13 share 37% files 2048 never: + top: The Powerpuff Girls 12% (Core); Ed, Edd n Eddy 9% (Core); Codename: Kids Next Door 8% (Core); Courage the Cowardly Dog 7% (Core); Rugrats 6% (Supporting); Doug 5% (Supporting) + +### 307 Orbit Romance (pool 98, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 98 aired 98 share 100% files 98 never: + top: Gone with the Wind 2% (-); Titanic 1% (-); The Sound of Music 1% (-); West Side Story 1% (-); Sense and Sensibility 1% (-); Love Actually 1% (-) + +### 605 ToonZ (pool 17, film=False, finishTogether=True, committedReasons=['', 'whole pool']) + Core titles 7 aired 7 share 72% files 1727 never: + Supporting titles 6 aired 6 share 19% files 334 never: + Surprises titles 3 aired 3 share 5% files 191 never: + (not in brief) titles 1 aired 1 share 2% files 17 never: + top: Looney Tunes 29% (Core); Tom and Jerry 23% (Core); The Jetsons 6% (Core); The Looney Tunes Show 5% (Supporting); Scooby-Doo, Where Are You! 5% (Core); The Flintstones 5% (Core) + +### 308 Detour (pool 88, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 88 aired 88 share 100% files 88 never: + top: The 40 Year Old Virgin 1% (-); Wedding Crashers 1% (-); Bridesmaids 1% (-); The Other Guys 1% (-); Get Him to the Greek 1% (-); Ted 1% (-) + +### 309 BritX (pool 90, film=True, finishTogether=False, committedReasons=['', 'whole pool']) + (not in brief) titles 90 aired 90 share 100% files 90 never: + top: Legend 1% (-); Pride 1% (-); Quadrophenia 1% (-); The Krays 1% (-); Local Hero 1% (-); Rise of the Footsoldier: Par 1% (-) +``` diff --git a/docs/replays/editorial-categories.py b/docs/replays/editorial-categories.py new file mode 100644 index 0000000..43b1a85 --- /dev/null +++ b/docs/replays/editorial-categories.py @@ -0,0 +1,42 @@ +import json, re, sys, glob, os +D='/home/pauljd/Documents/Codex/orbit-tv/docs/channels' +J=json.load(open('/tmp/claude-1000/-home-pauljd-Projects/c539a8ea-4816-4f5b-8d2a-3efd663b12ac/scratchpad/replay/schedule-audit.json')) +def norm(t): + t=t.lower().replace('&','and') + t=re.sub(r"\(\d{4}\)","",t); t=re.sub(r"[^a-z0-9 ]+"," ",t) + t=re.sub(r"\b(the|a|an)\b"," ",t) + return ' '.join(t.split()) +files={} +for f in glob.glob(D+'/*.md'): + m=re.search(r'\((\d{3})\)', open(f).readline()) + if m: files[m.group(1)]=f +out={} +for num,ch in J.items(): + f=files.get(num); txt=open(f).read() if f else '' + secs=re.findall(r'^\*\*([^*]+?):?\*\*:?\s*(.*)$', txt, re.M) + pool={p['title']:p for p in ch['pool']} + steady=ch['steady']; total=sum(v['minutes'] for v in steady.values()) or 1 + assign={} + for label,body in secs: + lab=label.strip().rstrip(':') + if lab.lower().startswith(('status','identity','history','order','new arrivals','crossover','catalogue','known limit','rest period','built in','future artwork','to measure','precedence','new suggestions','band of brothers','long episodes','thin copies','not added','the old','presentation','not built','possible crossover','library','0.87','time of day')): continue + nb=' '+norm(body)+' ' + for t in pool: + if t in assign: continue + k=norm(t) + if k and (' '+k+' ') in nb: assign[t]=lab + cats={} + for t,p in pool.items(): + c=assign.get(t,'(not in brief)') + d=cats.setdefault(c,{'titles':0,'aired':0,'minutes':0,'never':[], 'files':0}) + d['titles']+=1; d['files']+=p['files'] + m=steady.get(t,{}).get('minutes',0) + d['minutes']+=m + if m: d['aired']+=1 + else: d['never'].append(t) + # brief titles not in pool (approx): split by commas on core/supporting/surprises lines + print(f"\n### {num} {ch['name']} (pool {len(pool)}, film={ch['film']}, finishTogether={ch['finishTogether']}, committedReasons={ch['committedReasons'][:4]})") + for c,d in sorted(cats.items(), key=lambda x:-x[1]['minutes']): + print(f" {c[:40]:40s} titles {d['titles']:3d} aired {d['aired']:3d} share {100*d['minutes']//total:3d}% files {d['files']:5d} never: {', '.join(d['never'][:8])}{' +'+str(len(d['never'])-8) if len(d['never'])>8 else ''}") + top=sorted(steady.items(), key=lambda x:-x[1]['minutes'])[:6] + print(' top:', '; '.join(f"{t[:28]} {100*v['minutes']//total}% ({assign.get(t,'-')[:10]})" for t,v in top))