Stage D read-only trial: channel fit for newly released titles, with evidence and confidence
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
import sqlite3,json,glob,collections,re,math,sys
|
||||
S,R=sys.argv[1],sys.argv[2]
|
||||
c=sqlite3.connect(f'file:{S}/cat-now/catalogue.db?mode=ro',uri=True)
|
||||
names={}
|
||||
for m in re.finditer(r'ChannelPreset\((\w+|\d+), "([^"]+)"',open('/home/pauljd/Documents/Codex/orbit-tv/android/app/src/main/java/tv/orbit/classic/ChannelPresets.kt').read()): names[m[1]]=m[2]
|
||||
names.update({'305':'After Dark','601':'Vortex','614':'Tots','300':'Drive-In'})
|
||||
KIDS={'602','603','604','605','611','612','613','614'}
|
||||
ADULT={'R','TV-MA','NC-17','18','18+','X'}
|
||||
prof={}; carried=set(); works_on={}
|
||||
SYN={'Anime':'Animation','Martial Arts':'Action','Sci-Fi & Fantasy':'Science Fiction','Action & Adventure':'Action','War & Politics':'War','Kids':'Children','Suspense':'Thriller','Sport':'Sport','Sports':'Sport','Reality-TV':'Reality'}
|
||||
def norm(gs): return sorted({SYN.get(x,x) for x in gs})
|
||||
def work(pids,title,year):
|
||||
p=json.loads(pids) if pids and pids.startswith('{') else {}
|
||||
p={k.lower():v for k,v in p.items()}
|
||||
return ('tmdb:'+p['tmdb']) if p.get('tmdb') else ('imdb:'+p['imdb']) if p.get('imdb') else f'title:{title.lower()}:{year}'
|
||||
for f in sorted(glob.glob(f'{R}/episodes-*.json')):
|
||||
ch=f.split('-')[-1][:-5]
|
||||
if not ch.isdigit() or int(ch)>999 or ch=='300': continue
|
||||
recs=[]
|
||||
for s in json.load(open(f))['series']:
|
||||
r=c.execute("select type,title,year,rating,genres,studios,provider_ids from items where id=?",(s['id'],)).fetchone()
|
||||
if r: recs.append(r); w=work(r[6],r[1],r[2]); carried.add(w); works_on.setdefault(w,set()).add(ch)
|
||||
if not recs: continue
|
||||
g=collections.Counter(x for r in recs for x in norm([y for y in (r[4] or '').split('|') if y]))
|
||||
n=len(recs)
|
||||
prof[ch]=dict(n=n,genres={k:v/n for k,v in g.items()},types=collections.Counter(r[0] for r in recs),
|
||||
years=sorted(r[2] for r in recs if r[2]),ratings=collections.Counter(r[3] for r in recs if r[3]),
|
||||
adult=sum(1 for r in recs if r[3] in ADULT)/n)
|
||||
DF=None
|
||||
def idf(g):
|
||||
global DF
|
||||
if DF is None: DF=collections.Counter(x for p in prof.values() for x,v in p['genres'].items() if v>=.25)
|
||||
return math.log((1+len(prof))/(1+DF[g]))+.3
|
||||
def score(cand,ch):
|
||||
p=prof[ch]; typ,title,year,rating,genres,studios=cand[:6]
|
||||
gs=norm([x for x in (genres or '').split('|') if x])
|
||||
ev=[]
|
||||
if p['types'][typ]==0: return 0,['wrong kind']
|
||||
if not gs: return 0,['no genres']
|
||||
gm=sum(p['genres'].get(x,0)*idf(x) for x in gs)/sum(idf(x) for x in gs)
|
||||
defining=[x for x,v in p['genres'].items() if v>=.6 and DF[x]<=6]
|
||||
if defining and not set(defining)&set(gs): gm*=.35; ev.append('lacks its defining '+'/'.join(sorted(defining)))
|
||||
ev.append('genres '+', '.join(f"{x} ({round(p['genres'].get(x,0)*100)}% of its list)" for x in gs[:3]))
|
||||
s=gm
|
||||
ys=p['years']
|
||||
if ys:
|
||||
lo,hi=ys[len(ys)//20],ys[-1-len(ys)//20]
|
||||
if year and (year<lo-5 or year>hi+5): s*=.45; ev.append(f'{year} outside its era {lo}-{hi}')
|
||||
else: ev.append(f'{year} within its era {lo}-{hi}')
|
||||
if rating in ADULT and (ch in KIDS or p['adult']<.05): return 0,[f'rated {rating}, not for this channel']
|
||||
if ch in KIDS and not rating: s*=.5; ev.append('no rating to confirm it suits children')
|
||||
if ch=='401' and re.search(r'\b(UFC|WWE|AEW|TNA|NXT|Bellator|ROH|NJPW|boxing|wrestl)',title,re.I): s=max(s,.9); ev.append('a wrestling or fight event by its title')
|
||||
if rating and p['ratings'][rating]: ev.append(f'rated {rating} like {p["ratings"][rating]} of its titles')
|
||||
return s,ev
|
||||
idf('Drama')
|
||||
cands=c.execute("select type,title,year,rating,genres,studios,provider_ids,premiere,id from items where premiere between '2026-09-05' and '2026-11-04' and provider_ids like '%Imdb%'").fetchall()
|
||||
seen=set(); out=[]
|
||||
for r in cands:
|
||||
w=work(r[6],r[1],r[2])
|
||||
if w in seen: continue
|
||||
seen.add(w)
|
||||
sc=sorted(((score(r,ch),ch) for ch in prof),key=lambda x:-x[0][0])
|
||||
(s1,ev1),c1=sc[0]; (s2,ev2),c2=sc[1]
|
||||
on=sorted(works_on.get(w,[]))
|
||||
if on: verdict='already on a channel'
|
||||
elif s1<.3: verdict='no clear home'
|
||||
elif s2>=s1*.8 and s2>=.3: verdict='ambiguous'
|
||||
elif len(norm([x for x in (r[4] or '').split('|') if x]))<2 and s1<.9: verdict='ambiguous'
|
||||
else: verdict='suggested'
|
||||
conf=round(min(.99,s1)*(1-(s2/s1 if s1 else 1))*2,2) if s1 else 0
|
||||
out.append(dict(title=r[1],year=r[2],type=r[0],rating=r[3],genres=r[4],premiere=r[7][:10],id=r[8],verdict=verdict,top=names.get(c1,c1),score=round(s1,2),second=names.get(c2,c2),score2=round(s2,2),evidence='; '.join(ev1),already=[names.get(x,x) for x in on],conf=conf))
|
||||
json.dump(out,open(f'{S}/staged.json','w'),indent=0)
|
||||
cnt=collections.Counter(o['verdict'] for o in out); print(len(out),cnt)
|
||||
print(collections.Counter(o['top'] for o in out if o['verdict']=='suggested').most_common(20))
|
||||
for t in ['Blue Planet III','Dragon Ball Super: Beerus']:
|
||||
for o in out:
|
||||
if o['title']==t: print(o)
|
||||
@@ -0,0 +1,105 @@
|
||||
# Stage D trial: where should new titles go? (read only, 4 November 2026)
|
||||
|
||||
**What this is:** the first piece of the intelligence layer, run off the box against copies. It books and changes nothing. It follows Paul's Stage D principles:
|
||||
- evidence and confidence, not genre matching alone;
|
||||
- ambiguous titles flagged, not placed;
|
||||
- New to Emby, Newly released and New to Orbit kept separate.
|
||||
|
||||
## How it works
|
||||
|
||||
**Each channel's profile** comes from what it actually carries: the 1,899 series and films in the 30 pools, matched to the library catalogue, which holds 132,674 items as of today. A profile has four parts:
|
||||
- the share of its list in each genre;
|
||||
- its era, the middle 90% of its years;
|
||||
- its ratings;
|
||||
- whether it carries films, series or both.
|
||||
|
||||
**Each candidate** is scored against every channel on five things:
|
||||
- **Genres:** the genres it shares with the channel, weighted so that common genres (Drama, Comedy) count for less than distinctive ones (Documentary, Horror, Reality).
|
||||
- **Defining genre:** a channel's defining genre is one that is at least 60% of its list and rare across channels. A candidate without it scores far lower there.
|
||||
- **Era:** whether its year falls within the channel's era.
|
||||
- **Rating:** adult ratings never go to children's channels, and a children's channel needs a rating to confirm the fit.
|
||||
- **Small rules with evidence:** for example, a wrestling or fight-event title goes to Ring X.
|
||||
|
||||
**The verdict:**
|
||||
- **Suggested:** a clear best fit.
|
||||
- **Ambiguous:** the runner-up is within 80% of the best, or the only metadata is one broad genre.
|
||||
- **No clear home:** nothing fits well.
|
||||
- **Already on a channel:** the title is already carried.
|
||||
|
||||
Every line keeps its evidence. Script: `docs/replays/stage-d-trial-2026-11-04.py`. Full output: `docs/replays/stage-d-trial-2026-11-04.json`.
|
||||
|
||||
## The result for the library's newly released titles
|
||||
|
||||
There are 394 distinct works with an IMDb ID, premiered 5 September to 4 November 2026.
|
||||
|
||||
| Verdict | Works |
|
||||
|---|---|
|
||||
| Suggested | 114 |
|
||||
| Ambiguous, so it needs Paul's call | 130 |
|
||||
| No clear home | 149 |
|
||||
| Already on a channel (Dragon Ball Super: Beerus, on Vortex) | 1 |
|
||||
|
||||
**The suggestions by channel:** Paradox 54, Orbit Wild 13, Orbit Romance 11, Dark Files 11, Orbit Horror 6, Terra 5, PixelZ 5, Ring X 4, Orbit Premiere 3, Riot 1, After Dark 1.
|
||||
|
||||
Examples, each with its evidence:
|
||||
|
||||
**suggested**
|
||||
|
||||
| Title | Kind | Genres | Best fit (score) | Next (score) | Evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| Blue Planet III (2026) | Series | Documentary, Mini-Series | Terra (0.59) | Dark Files (0.38) | genres Documentary (100% of its list), Mini-Series (31% of its list); 2026 within its era 1983-2024 |
|
||||
| OJ Unseen: Exploitation of Evil (2026) | Series | Documentary, Crime | Dark Files (0.67) | Terra (0.52) | genres Crime (50% of its list), Documentary (82% of its list); 2026 within its era 1988-2021 |
|
||||
| The Serial Killer Connection (2026) | Series | Documentary, Crime | Dark Files (0.67) | Terra (0.52) | genres Crime (50% of its list), Documentary (82% of its list); 2026 within its era 1988-2021 |
|
||||
| WWE Money in the Bank 2026 (2026) | Movie | Action, Drama | Ring X (0.9) | Orbit Premiere (0.46) | genres Action (76% of its list), Drama (30% of its list); 2026 within its era 1987-2023; a wrestling or fight event by its title; rated TV-PG like 15 |
|
||||
| UFC 332: Silva vs. Wang (2026) | Movie | Action, Drama | Ring X (0.9) | Orbit Premiere (0.46) | genres Action (76% of its list), Drama (30% of its list); 2026 within its era 1987-2023; a wrestling or fight event by its title |
|
||||
| Bankzitters: Surviving in the Wild (2026) | Series | Adventure, Reality | Orbit Wild (0.69) | Paradox (0.14) | genres Adventure (52% of its list), Reality (76% of its list); 2026 within its era 1997-2022 |
|
||||
| VisionQuest (2026) | Series | Adventure, Drama, Science Fiction | Paradox (0.71) | Heroes (0.3) | genres Adventure (50% of its list), Drama (83% of its list), Science Fiction (72% of its list); 2026 within its era 1959-2024; rated TV-14 like 4 of i |
|
||||
| Yaga (2026) | Series | Drama, Fantasy | Paradox (0.73) | Vortex (0.18) | genres Drama (83% of its list), Fantasy (61% of its list); 2026 within its era 1959-2024 |
|
||||
|
||||
**ambiguous**
|
||||
|
||||
| Title | Kind | Genres | Best fit (score) | Next (score) | Evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| The Search for Leah Croucher: A Parent's Nightmare (2026) | Series | Documentary | Terra (1.0) | Dark Files (0.82) | genres Documentary (100% of its list); 2026 within its era 1983-2024 |
|
||||
| Buckingham Palace: A Year in the Life (2026) | Series | Documentary | Terra (1.0) | Dark Files (0.82) | genres Documentary (100% of its list); 2026 within its era 1983-2024 |
|
||||
| The Pandemic Prophet (2026) | Series | Documentary | Terra (1.0) | Dark Files (0.82) | genres Documentary (100% of its list); 2026 within its era 1983-2024 |
|
||||
| UFC Fight Night 290: Allen vs. Duncan (2026) | Movie | Action | Ring X (0.9) | Orbit Premiere (0.73) | genres Action (76% of its list); 2026 within its era 1987-2023; a wrestling or fight event by its title |
|
||||
| Agatha Christie’s Tommy & Tuppence (2026) | Series | Drama | Paradox (0.83) | Ring X (0.3) | genres Drama (83% of its list); 2026 within its era 1959-2024 |
|
||||
| A Tale of Two Cities (2026) (2026) | Series | Drama | Paradox (0.83) | Ring X (0.3) | genres Drama (83% of its list); 2026 within its era 1959-2024 |
|
||||
|
||||
**no clear home**
|
||||
|
||||
| Title | Kind | Genres | Best fit (score) | Next (score) | Evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| Resident Evil (2026) | Movie | Horror, Science Fiction, Adventure | Orbit Premiere (0.28) | After Dark (0.22) | genres Adventure (74% of its list), Horror (1% of its list), Science Fiction (38% of its list); 2026 within its era 1989-2021; rated R like 8 of its t |
|
||||
| The Deputy (2026) | Movie | Action, Crime, Thriller | Orbit Premiere (0.3) | Orbit Action (0.3) | genres Action (73% of its list), Crime (8% of its list), Thriller (17% of its list); 2026 within its era 1989-2021; rated R like 8 of its titles |
|
||||
| Klara and the Sun (2026) | Movie | Science Fiction, Drama | Orbit Premiere (0.29) | Orbit Sci-Fi (0.28) | genres Drama (20% of its list), Science Fiction (38% of its list); 2026 within its era 1989-2021; rated PG-13 like 79 of its titles |
|
||||
| R.L. Stine's Pumpkinhead 2 (2026) | Movie | Fantasy, Horror | Orbit Horror (0.28) | After Dark (0.25) | genres Fantasy (8% of its list), Horror (97% of its list); 2026 outside its era 1968-2019 |
|
||||
| The Final Problem (2026) | Series | Crime, Drama, History, Mini-Series, Mystery, Thriller | Paradox (0.27) | Evidence (0.21) | genres Crime (22% of its list), Drama (83% of its list), History (0% of its list); 2026 within its era 1959-2024; rated TV-14 like 4 of its titles |
|
||||
|
||||
## What the trial shows
|
||||
|
||||
1. **Where genre evidence is clear, it works:**
|
||||
- true-crime documentaries go to Dark Files rather than Terra;
|
||||
- nature documentaries such as Blue Planet III go to Terra;
|
||||
- wrestling and UFC events go to Ring X;
|
||||
- survival reality goes to Orbit Wild.
|
||||
2. **Genre metadata alone isn't enough.** This is exactly Paul's point.
|
||||
- **Documentaries:** a documentary tagged only Documentary can't be told apart between Terra, Odyssey and Dark Files. The Search for Leah Croucher, a disappearance, belongs on Dark Files, but its metadata doesn't say so, so it's rightly flagged ambiguous.
|
||||
- **Drama:** dramas tagged only Drama drift towards Paradox, which is drama-heavy. So "suggested" for Paradox is the weakest group: 54 suggestions, many of them period or legal dramas that don't fit its science-fiction identity.
|
||||
- **Crime and mystery:** The Final Problem, a crime mystery, lands nearer Paradox than Evidence. That's another case for better evidence.
|
||||
3. **New films mostly have no clear home.** The film channels are tightly curated lists, many with an era that ends before 2026, as VHS and After Dark do. That's right for VHS. For Premiere, Action and Horror, recent titles should be welcome, so their era limits need a recency allowance.
|
||||
|
||||
## Proposed next steps, each for Paul's approval
|
||||
|
||||
- **More evidence, all read-only:**
|
||||
- add each work's overview, tags and keywords, and its leading cast and director, to the catalogue reads;
|
||||
- then use them, for example disappearance or murder in the overview for Dark Files, wildlife or ocean for Terra, a shared lead or director with a channel's titles, and a franchise (title stem) shared with a channel's titles.
|
||||
- **Fixes to the scoring:**
|
||||
- weaken broad genres further;
|
||||
- give Paradox's defining genres (Science Fiction and Fantasy) more weight than Drama;
|
||||
- give Premiere, Action, Sci-Fi and Horror a recency allowance.
|
||||
- **Then a Suggestions screen in Settings:**
|
||||
- the lifecycle Detected → Verified → Proposed → Approved → Booked → Aired;
|
||||
- Approve, Not for Orbit and Ask me later on each title;
|
||||
- nothing added to a channel without approval.
|
||||
Reference in New Issue
Block a user