Stage D second trial (briefs + descriptions) and phase 3-4 design
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"8009312":[],"8078448":["Dark Files"],"7998858":[],"8002849":[],"7982220":["Orbit Romance"],"8060672":[],"8005900":[],"7981213":["Dark Files"],"8063723":["Orbit Horror","After Dark"],"8036706":[],"8063799":["Orbit Romance","BritX"],"8065673":[],"8070609":[],"8056325":[],"8065631":["","Orbit Premiere"],"8054668":[],"7993465":[],"8002696":[],"7999478":["","PixelZ"],"7998882":[],"8014652":["","Orbit 2"],"7991575":["Orbit Romance"],"8057254":["Ring X"],"8002507":[],"8008372":["Orbit Romance"],"8067827":["Orbit Romance"],"8068916":[],"8064652":[],"8000477":["Orbit Action"],"8003324":[],"7978360":[],"7988199":["Evidence"],"8059718":[],"8068177":[],"8068803":["","Orbit Action"],"8001802":[],"8067524":[],"8020593":["Orbit Horror"],"8001326":["","Orbit 1"],"7980720":[],"8065517":[],"8055642":["BritX"],"7998439":["Orbit Horror","After Dark"],"8006374":[],"8064561":[],"7991863":[],"8019916":[],"8059814":["Orbit Romance"],"7998500":[],"8059092":["","Orbit Three","Orbit 1"],"8068802":[],"7980716":["Ring X"],"8069094":[],"7986681":["Orbit Romance"],"7993234":[],"8007968":["Vortex"],"8006205":["","Vortex"],"8015874":["Orbit Romance"],"7999723":[],"8068581":["Dark Files"]}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
import json,re,math,collections,sys,glob,sqlite3
|
||||
S,R=sys.argv[1],sys.argv[2]
|
||||
ev={}
|
||||
for l in open(f'{S}/evidence-0.jsonl'):
|
||||
r=json.loads(l); ev[r['id']]=r
|
||||
c=sqlite3.connect(f'file:{S}/cat-now/catalogue.db?mode=ro',uri=True)
|
||||
UK=re.compile(r'United Kingdom|England|Scotland|Wales|\bUK\b')
|
||||
UKSTUDIO=re.compile(r'\bBBC\b|\bITV\b|Channel 4|Film4|Sky\b|Working Title|Ealing|Hammer|Aardman',re.I)
|
||||
def W(*words): return re.compile(r'\b('+'|'.join(words)+r')',re.I)
|
||||
# Each channel's brief from docs/channels and Paul's 4 November definitions: what it carries, themes for and against, era, ratings, origin.
|
||||
B={
|
||||
'101':dict(name='Orbit 1',kind={'Series'},genres_any={'Comedy'},themes=W('sitcom','family','friends','comedy'),against=W('murder','killer','documentary'),era=(1970,2026),kids=False),
|
||||
'102':dict(name='Orbit 2',kind={'Series'},genres_any={'Game Show','Reality','Comedy','Talk'},themes=W('quiz','game show','contestants','panel','challenge','teams compete'),against=W('murder','documentary'),era=(1960,2015),kids=False,uk=True),
|
||||
'103':dict(name='Orbit Three',kind={'Series'},genres_any={'Comedy'},themes=W('sketch','cult comedy','surreal','dark comedy','scottish'),against=W('documentary'),era=(1990,2026),kids=False,uk=True),
|
||||
'104':dict(name='Orbit Gold',kind={'Series'},genres_any={'Comedy'},themes=W('sitcom','classic comedy'),against=W('documentary'),era=(1960,1999),kids=False,uk=True),
|
||||
'105':dict(name='Riot',kind={'Series'},genres_any={'Animation','Comedy'},need_any={'Animation'},themes=W('adult','irreverent','raunchy','satir','dysfunctional'),against=W('preschool','children learn'),era=(1988,2026),kids=False),
|
||||
'106':dict(name='Paradox',kind={'Series'},genres_any={'Science Fiction','Fantasy','Mystery','Horror'},themes=W('supernatural','paranormal','alien','time travel','parallel','ghost','vampire','demon','monster','mysterious','strange','unexplained','dimension','future','space','dystopia','cult','prophecy','witch'),against=W('documentary','reality','romance','cooking'),era=(1955,2026),kids=False),
|
||||
'107':dict(name='Evidence',kind={'Series'},genres_any={'Crime','Mystery'},themes=W('detective','inspector','police','forensic','investigat','murder','homicide','case','sergeant','DCI','sleuth'),against=W('documentary','true story of','real-life','archive footage','interviews'),era=(1965,2026),kids=False,scripted=True),
|
||||
'201':dict(name='Terra',kind={'Series'},genres_any={'Documentary'},themes=W('wildlife','animal','ocean','sea','reef','planet','nature','ecosystem','species','forest','jungle','predator','habitat','natural world','migration'),against=W('murder','killer','crime','war','music','celebrity','royal','politic'),era=(1975,2026),kids=False),
|
||||
'202':dict(name='Odyssey',kind={'Series'},genres_any={'Documentary','History'},themes=W('space','universe','astronom','science','engineer','scientist','history','ancient','explor','discover','technolog','travel','civilisation','civilization'),against=W('murder','killer','crime','celebrity','reality'),era=(1950,2026),kids=False),
|
||||
'203':dict(name='Orbit Wild',kind={'Series'},genres_any={'Reality','Documentary','Adventure'},themes=W('surviv','wilderness','expedition','endurance','extreme','remote','frontier','adventure','island','alone','hunt','fishing'),against=W('murder','dating','cooking','makeover'),era=(1985,2026),kids=False),
|
||||
'204':dict(name='Dark Files',kind={'Series'},genres_any={'Documentary','Crime','Mystery'},need_any={'Documentary','Reality'},themes=W('murder','killer','disappear','missing','true crime','unsolved','investigat','case','paranormal','haunt','unexplained','cult','conspiracy','serial','detective','victim','trial'),against=W('wildlife','cooking'),era=(1975,2026),kids=False),
|
||||
'301':dict(name='Orbit Premiere',kind={'Movie'},genres_any={'Action','Adventure','Science Fiction','Fantasy'},themes=W('epic','hero','battle','world','mission','superhero','franchise'),against=W('documentary','concert'),era=(1975,2026),kids=False,current=True),
|
||||
'302':dict(name='Orbit Action',kind={'Movie'},genres_any={'Action'},themes=W('cop','agent','assassin','heist','fight','mercenar','revenge','martial','spy','chase','soldier'),against=W('documentary','concert','UFC','WWE'),era=(1960,2026),kids=False,current=True),
|
||||
'303':dict(name='Orbit Sci-Fi',kind={'Movie'},genres_any={'Science Fiction'},need_any={'Science Fiction'},themes=W('alien','space','future','robot','planet','time','dystopia','android','galaxy'),against=W('documentary'),era=(1950,2026),kids=False,current=True),
|
||||
'304':dict(name='Orbit Horror',kind={'Movie'},genres_any={'Horror'},need_any={'Horror'},themes=W('haunt','demon','possess','killer','evil','terror','ghost','curse','slasher','nightmare'),against=W('documentary'),era=(1960,2026),kids=False,current=True),
|
||||
'305':dict(name='After Dark',kind={'Movie'},genres_any={'Horror','Thriller','Science Fiction'},themes=W('cult','bizarre','strange','exploitation','b-movie','grindhouse','creature','mutant','zombie','cannibal','obscure','surreal','underground'),against=W('documentary','romcom','family'),era=(1955,2018),kids=False,intl_bonus=True),
|
||||
'306':dict(name='Orbit VHS',kind={'Movie'},genres_any={'Comedy','Adventure','Fantasy','Family','Science Fiction'},themes=W('kids','teen','adventure','summer','gang','treasure'),against=W('documentary'),era=(1980,1995),kids=False),
|
||||
'307':dict(name='Orbit Romance',kind={'Movie'},genres_any={'Romance'},need_any={'Romance'},themes=W('love','romance','falls for','wedding','relationship','heart'),against=W('documentary','murder','horror'),era=(1930,2026),kids=False,current=True),
|
||||
'308':dict(name='Detour',kind={'Movie'},genres_any={'Comedy'},need_any={'Comedy'},themes=W('road trip','party','college','bachelor','friends','wild night','spring break'),against=W('documentary','animated'),era=(1990,2026),kids=False,current=True),
|
||||
'309':dict(name='BritX',kind={'Movie'},genres_any={'Crime','Comedy','Drama','Thriller'},themes=W('london','gangster','british','manchester','glasgow','council estate','hooligan','criminal'),against=W('documentary'),era=(1965,2026),kids=False,uk=True,current=True),
|
||||
'401':dict(name='Ring X',kind={'Movie','Series'},genres_any={'Sport','Action','Documentary'},themes=W('wrestl','WWE','AEW','TNA','UFC','ring','champion','title match','pay-per-view','fight'),against=W('romance'),era=(1980,2026),kids=False,current=True),
|
||||
'601':dict(name='Vortex',kind={'Series','Movie'},genres_any={'Animation','Action','Anime'},need_any={'Animation','Anime'},themes=W('anime','fight','power','warrior','battle','hero','martial','saiyan','ninja','mecha','demon'),against=W('preschool','sitcom'),era=(1980,2026),kids=False),
|
||||
'602':dict(name='Heroes',kind={'Series'},genres_any={'Animation','Action'},need_any={'Animation'},themes=W('superhero','batman','spider','x-men','justice','villain','powers'),against=W('preschool'),era=(1980,2015),kids=True),
|
||||
'603':dict(name='Orbit X',kind={'Series'},genres_any={'Animation','Action','Adventure'},need_any={'Animation','Children','Family'},themes=W('rangers','robot','team','evil','power','adventure','toy'),against=W('preschool','adult'),era=(1980,2002),kids=True),
|
||||
'604':dict(name='Rewind',kind={'Series'},genres_any={'Animation','Comedy'},need_any={'Animation'},themes=W('cartoon','kids','adventures','friends'),against=W('adult','preschool'),era=(1990,2015),kids=True),
|
||||
'605':dict(name='ToonZ',kind={'Series'},genres_any={'Animation','Comedy'},need_any={'Animation'},themes=W('cartoon','classic'),against=W('adult'),era=(1928,2005),kids=True),
|
||||
'611':dict(name='Orbit Junior',kind={'Series'},genres_any={'Children','Family','Animation','Comedy'},need_any={'Children','Kids','Family'},themes=W('children','kids','school','friends','adventure'),against=W('adult','murder'),era=(1955,2026),kids=True,uk=True),
|
||||
'612':dict(name='Wonder',kind={'Movie'},genres_any={'Animation','Family','Fantasy'},need_any={'Animation'},themes=W('princess','magic','fairy','kingdom','adventure','tale'),against=W('adult','horror'),era=(1935,2026),kids=True),
|
||||
'613':dict(name='PixelZ',kind={'Movie'},genres_any={'Animation','Family','Comedy'},need_any={'Animation'},themes=W('adventure','friends','family','toy','journey'),against=W('adult','horror'),era=(1995,2026),kids=True,current=True),
|
||||
'614':dict(name='Tots',kind={'Series'},genres_any={'Children','Animation','Family'},need_any={'Children','Kids'},themes=W('preschool','little','learn','friends','gentle','toddler','young children'),against=W('adult','violence','fight','battle'),era=(1980,2026),kids=True),
|
||||
}
|
||||
KIDS_OK={'G','PG','TV-Y','TV-Y7','TV-G','TV-PG','U','TV-Y7-FV','E','0+','3+','6+','7+'}
|
||||
ADULT={'R','TV-MA','NC-17','18','18+','X','NR18','SG-R21','SG-M18','GB-18'}
|
||||
SYN={'Anime':'Animation','Martial Arts':'Action','Sci-Fi & Fantasy':'Science Fiction','Action & Adventure':'Action','Kids':'Children','Suspense':'Thriller','Sports':'Sport'}
|
||||
def gen(r): return {SYN.get(g,g) for g in (r.get('genres') or [])}
|
||||
# Channel text profiles: TF-IDF of their titles' descriptions, so a candidate is also compared with what the channel actually shows.
|
||||
STOP=set('a an the and or of to in on for with his her their its is are was be by as at from this that he she they it into who when after one two new life series film show story world'.split())
|
||||
def toks(t): return [w for w in re.findall(r"[a-z][a-z'-]{2,}",(t or '').lower()) if w not in STOP]
|
||||
pools={}
|
||||
for f in glob.glob(f'{R}/episodes-*.json'):
|
||||
ch=f.split('-')[-1][:-5]
|
||||
if ch in B: pools[ch]=[s['id'] for s in json.load(open(f))['series']]
|
||||
docs={ch:[ev[i] for i in ids if i in ev and ev[i].get('overview')] for ch,ids in pools.items()}
|
||||
df=collections.Counter(w for ch in docs for d in docs[ch] for w in set(toks(d['overview'])))
|
||||
N=sum(len(v) for v in docs.values())
|
||||
def vec(text):
|
||||
tf=collections.Counter(toks(text)); return {w:(1+math.log(n))*math.log((N+1)/(df.get(w,0)+1)) for w,n in tf.items()}
|
||||
def norm(v):
|
||||
s=math.sqrt(sum(x*x for x in v.values())) or 1; return {k:x/s for k,x in v.items()}
|
||||
cent={}
|
||||
for ch,ds in docs.items():
|
||||
acc=collections.Counter()
|
||||
for d in ds:
|
||||
for k,x in norm(vec(d['overview'])).items(): acc[k]+=x
|
||||
cent[ch]=norm(dict(acc))
|
||||
def cos(a,b): return sum(x*b.get(k,0) for k,x in a.items())
|
||||
ENGLISH=re.compile(r'United States|United Kingdom|Canada|Australia|Ireland|New Zealand|England|Scotland|Wales')
|
||||
def original(i):
|
||||
row=c.execute('select original_title,title from items where id=?',(i,)).fetchone()
|
||||
return row or ('','')
|
||||
def foreign(r):
|
||||
o,t=original(r['id'])
|
||||
nonlatin=bool(re.search(r'[^\x00-\u024f\s\d\W]',o or ''))
|
||||
countries=' '.join(r.get('countries') or [])
|
||||
if nonlatin: return True,'original title not in English'
|
||||
if countries and not ENGLISH.search(countries): return True,'made in '+countries.split()[0]
|
||||
if o and t and o.strip().lower()!=t.strip().lower() and not re.search(r'[A-Za-z]{3}',o): return True,'original title differs'
|
||||
return False,''
|
||||
INTL={'305','601','401'}
|
||||
def score(r,ch):
|
||||
b=B[ch]; e=[]; typ=r.get('type'); g=gen(r); ov=r.get('overview') or ''; year=r.get('year'); rating=r.get('rating') or ''
|
||||
text=ov+' '+(r.get('name') or '')+' '+' '.join(r.get('taglines') or [])
|
||||
if typ not in b['kind']: return 0,['wrong kind']
|
||||
if b.get('kids') and rating in ADULT: return 0,[f'rated {rating}']
|
||||
s=0.
|
||||
# Genres: only as one part of the evidence; missing genres are not held against it.
|
||||
if g:
|
||||
if b.get('need_any') and not (g & b['need_any']): return 0,[f'not {"/".join(sorted(b["need_any"]))}']
|
||||
gh=len(g & b['genres_any'])
|
||||
s+=.25*min(1,gh/1.5); e.append(f'genres {", ".join(sorted(g & b["genres_any"])) or "none of its"}')
|
||||
else: s+=.1; e.append('no genres given (not held against it)')
|
||||
# Themes from the description, for and against.
|
||||
pos=sorted(set(m.group(0).lower() for m in b['themes'].finditer(text)))
|
||||
neg=sorted(set(m.group(0).lower() for m in b['against'].finditer(text)))
|
||||
if ov:
|
||||
s+=.35*min(1,len(pos)/2); e.append(('themes: '+', '.join(pos[:4])) if pos else 'no brief themes in its description')
|
||||
if neg: s-=.25*min(1,len(neg)/2); e.append('against: '+', '.join(neg[:3]))
|
||||
sim=cos(norm(vec(ov)),cent.get(ch,{})) if ch in cent else 0
|
||||
s+=.4*min(1,sim/.25); e.append(f'description like its titles ({sim:.2f})')
|
||||
else: s+=.15; e.append('no description (not held against it)')
|
||||
if year:
|
||||
lo,hi=b['era']
|
||||
if year<lo-3 or year>hi+3: s*=.3 if not b.get('current') else 1; e.append(f'{year} outside its era {lo}-{hi}' if not b.get('current') else '')
|
||||
if b.get('uk'):
|
||||
uk=bool(UK.search(' '.join(r.get('countries') or []))) or bool(UKSTUDIO.search(' '.join(r.get('studios') or [])))
|
||||
if uk: s+=.1; e.append('British')
|
||||
elif r.get('countries') or r.get('studios'): s*=.6; e.append('not British by its studios or countries')
|
||||
if b.get('kids') and rating and rating not in KIDS_OK: s*=.5; e.append(f'rated {rating}')
|
||||
if b.get('kids') and not rating: s*=.8; e.append('no rating to confirm it suits children')
|
||||
f,why=foreign(r)
|
||||
if f and ch not in INTL: s*=.5; e.append(why+' (this channel is English-language)')
|
||||
return max(0,s),[x for x in e if x]
|
||||
def place(r):
|
||||
sc=sorted(((score(r,ch),ch) for ch in B),key=lambda x:-x[0][0])
|
||||
(s1,e1),c1=sc[0]; (s2,e2),c2=sc[1]
|
||||
if s1<.45: v='no clear home'
|
||||
elif s2>=s1*.85: v='ambiguous'
|
||||
else: v='suggested'
|
||||
tier='routine' if v=='suggested' and s1>=.7 and s2<s1*.7 else ('editorial' if v=='suggested' else ('needs evidence' if v=='ambiguous' else 'no action'))
|
||||
return dict(id=r['id'],title=r.get('name'),year=r.get('year'),type=r.get('type'),genres=sorted(gen(r)),verdict=v,tier=tier,top=B[c1]['name'],s1=round(s1,2),second=B[c2]['name'],s2=round(s2,2),evidence='; '.join(e1))
|
||||
if __name__=='__main__':
|
||||
ids=[row[0] for row in c.execute("select id from items where premiere between '2026-09-05' and '2026-11-04' and provider_ids like '%Imdb%'")]
|
||||
seen=set(); out=[]
|
||||
carried={i for v in pools.values() for i in v}
|
||||
for i in ids:
|
||||
if i not in ev or i in carried: continue
|
||||
r=ev[i]; key=(r.get('name'),r.get('year'))
|
||||
if key in seen: continue
|
||||
seen.add(key); out.append(place(r))
|
||||
json.dump(out,open(f'{S}/staged2.json','w'),indent=0,ensure_ascii=False)
|
||||
print(len(out),collections.Counter(o['verdict'] for o in out),collections.Counter(o['tier'] for o in out))
|
||||
print(collections.Counter(o['top'] for o in out if o['verdict']=='suggested').most_common())
|
||||
@@ -0,0 +1,133 @@
|
||||
# Stage D, second trial: briefs and descriptions (read only, 4 November 2026)
|
||||
|
||||
Paul approved phase 1 (evidence) and phase 2 (brief-based scoring, read only) on 4 November 2026 at 23:01. Nothing is booked, changed or approved automatically. Script: `docs/replays/stage-d-trial-2.py`. Output: `docs/replays/stage-d-trial-2.json`.
|
||||
|
||||
## Evidence read on the box (0.108.4, 23:11)
|
||||
|
||||
2,431 titles: every film and series on the 30 channels' lists, plus every library title premiered in the last 60 days. One read-only pass of about 60 requests.
|
||||
|
||||
| Field | Present |
|
||||
|---|---|
|
||||
| Description | 99% |
|
||||
| Genres | 99% |
|
||||
| Studios | 97% |
|
||||
| Leading cast | 95% |
|
||||
| Rating | 85% |
|
||||
| Directors | 54% |
|
||||
| Countries | 54% |
|
||||
| Tags or keywords | **0%**, as this library carries none |
|
||||
|
||||
A missing field is stored as missing and never counts against a title.
|
||||
|
||||
## How trial 2 scores
|
||||
|
||||
Each channel has a **brief**, taken from docs/channels and Paul's 4 November definitions. A brief sets out:
|
||||
- what the channel carries (films, series or both);
|
||||
- the genres it accepts, and any it needs, such as Documentary for Dark Files or Horror for Orbit Horror;
|
||||
- its **themes for and against**, read from the description. Dark Files looks for murder, disappearance, unsolved, paranormal. Evidence looks for detective, police, forensic, and counts against documentary or real-life. Terra looks for wildlife, ocean, ecosystem, and counts against crime, celebrity or royal;
|
||||
- its era (kept for nostalgia channels; current releases welcome on Premiere, Action, Sci-Fi, Horror, Romance, Detour, BritX and PixelZ);
|
||||
- its rating policy for children's channels;
|
||||
- whether it's British, for Orbit 2, Gold, Three, Junior and BritX.
|
||||
|
||||
A candidate's description is also compared with the descriptions of what the channel actually shows. This is local TF-IDF similarity, with no cloud AI.
|
||||
|
||||
**Language:** a title made outside the English-speaking world, or with a non-English original title, counts against English-language channels (not After Dark, Vortex or Ring X).
|
||||
|
||||
**The tiers, designed for phase 3:**
|
||||
- **Routine:** a strong, clear fit.
|
||||
- **Editorial:** a likely fit worth a look.
|
||||
- **Needs evidence:** two channels are close.
|
||||
- **No action:** nothing fits.
|
||||
|
||||
## Measured against a hand-checked sample
|
||||
|
||||
60 of the new titles were chosen at random, and I labelled each one by reading its description against the briefs: which channel fits, any acceptable alternatives, or no home. **The labels are my editorial judgement and are in `stage-d-trial-2-sample-labels.json`, for Paul to spot-check.** 20 of the 60 have a clear home, and 33 have none.
|
||||
|
||||
| | Trial 1 (genres only) | Trial 2, routine tier (score ≥ 0.45) | Trial 2, routine plus editorial (≥ 0.35) |
|
||||
|---|---|---|---|
|
||||
| Suggestions made | 15 | 10 | 18 |
|
||||
| **Precision** (suggestion correct) | **53%** | **90%** | 78% |
|
||||
| Clear fits found | 7 of 20 | 8 of 20 | 12 of 20 |
|
||||
| Titles with no home given a channel anyway | 4 of 33 | **0 of 33** | 1 of 33 |
|
||||
| Paradox picked for plain dramas | yes (54 across the trial) | no (1 Paradox suggestion in all 392) | no |
|
||||
|
||||
**Across all 392 new works, trial 2's tiers are:**
|
||||
|
||||
| Tier | Works |
|
||||
|---|---|
|
||||
| Routine | 29 |
|
||||
| Editorial | 63 |
|
||||
| Needs evidence | 5 |
|
||||
| No action | 295 |
|
||||
|
||||
Most of the 295 genuinely don't belong on any channel. They're international dramas, stand-up specials, reality formats and music documentaries.
|
||||
|
||||
**Routine** (29 titles), the strongest:
|
||||
|
||||
| Title | Channel (score) | Next | Evidence |
|
||||
|---|---|---|---|
|
||||
| Dragon Ball Super: Beerus (2026) | Vortex (0.99) | Ring X (0.21) | genres Action, Animation; themes: battle, power, saiyan; description like its titles (0.24) |
|
||||
| TNA Bound for Glory 2026 (2026) | Ring X (0.88) | Orbit Premiere (0.57) | genres Action; themes: champion, tna, wrestl; description like its titles (0.23) |
|
||||
| WWE Money in the Bank 2026 (2026) | Ring X (0.88) | Orbit Premiere (0.23) | genres Action; themes: wrestl, wwe; description like its titles (0.23) |
|
||||
| AEW All Out 2026 (2026) | Ring X (0.85) | Orbit Premiere (0.33) | no genres given (not held against it); themes: aew, champion, pay-per-view, title match; description like its titles (0.32) |
|
||||
| AEW WrestleDream 2026 (2026) | Ring X (0.85) | Orbit Premiere (0.32) | no genres given (not held against it); themes: aew, champion, pay-per-view, wrestl; description like its titles (0.30) |
|
||||
| Blue Planet III (2026) | Terra (0.81) | Odyssey (0.23) | genres Documentary; themes: ocean, planet; description like its titles (0.18) |
|
||||
| A Tale of the Secret Saint (2026) | Vortex (0.79) | Paradox (0.2) | genres Action, Animation; themes: battle, demon, power; description like its titles (0.12) |
|
||||
| So What's Wrong with Getting Reborn as a Goblin? (2026) | Vortex (0.78) | Paradox (0.21) | genres Action, Animation; themes: demon, hero; description like its titles (0.11) |
|
||||
|
||||
**Editorial** (63 titles), the strongest:
|
||||
|
||||
| Title | Channel (score) | Next | Evidence |
|
||||
|---|---|---|---|
|
||||
| Best of the Best (2026) | Detour (0.7) | Ring X (0.31) | genres Comedy; themes: college, friends; description like its titles (0.11) |
|
||||
| Suikoden: The Anime (2026) | Vortex (0.7) | Ring X (0.39) | genres Action, Animation; themes: anime, fight; description like its titles (0.06) |
|
||||
| Magic Repo Man: Dumped by My Party, I'll Cash In with a Cute Support Fairy to Become the Strongest! (2026) | Vortex (0.69) | Riot (0.12) | genres Animation; themes: hero, power; description like its titles (0.11) |
|
||||
| Wildwood (2026) | Orbit Premiere (0.69) | Vortex (0.4) | genres Adventure, Fantasy; themes: mission, world; description like its titles (0.06) |
|
||||
| UFC Fight Night 288: Silva vs. Delgado (2026) | Ring X (0.68) | Vortex (0.54) | no genres given (not held against it); themes: champion, fight, ufc; description like its titles (0.15) |
|
||||
| The Blame (2026) | Evidence (0.68) | Paradox (0.06) | genres Crime; themes: detective, investigat, murder; description like its titles (0.10) |
|
||||
| Magical Explorer (2026) | Vortex (0.68) | Ring X (0.18) | genres Action, Animation; themes: battle, hero; description like its titles (0.05) |
|
||||
| The Search for Leah Croucher: A Parent's Nightmare (2026) | Dark Files (0.68) | Terra (0.43) | genres Documentary; themes: case, missing; description like its titles (0.10) |
|
||||
|
||||
## The remaining failures, from the sample
|
||||
|
||||
- **Romance TV films** (A Knight to Remember, A Very Arctic Christmas, A Second Chance Christmas) score below the line. Orbit Romance's list is classic cinema, and their descriptions read differently. **Fix:** give Romance a "seasonal TV romance" theme, or decide whether that kind of film belongs there at all. That's Paul's call.
|
||||
- **Short horror descriptions** (Fresh Meat, Other Mommy) fall just under the line. **Fix:** let the defining genre (Horror) carry more weight when the description is short.
|
||||
- **Buzzkill**, a comedy-horror, goes to Detour rather than Horror or After Dark, because "sorority" and "friends" read as Detour. **Fix:** horror outranks comedy themes when Horror is a genre.
|
||||
- **Spidey and the Avengers** goes to Wonder. It's a preschool TV film with no home on a film channel. **Fix:** preschool themes count against Wonder.
|
||||
- **The Stunt Driver** goes to BritX because it's partly a British production, though it's not crime or grit. **Fix:** BritX needs its themes, not only British origin.
|
||||
- **Drive Through Fire** goes to Ring X because "fight" appears in its description. **Fix:** Ring X's themes need the wrestling or MMA brand words, not "fight" alone.
|
||||
|
||||
Each fix is small and explainable. None needs cloud AI.
|
||||
|
||||
---
|
||||
|
||||
# Phases 3 and 4: design only (not built, not authorised to run)
|
||||
|
||||
## Confidence tiers (phase 3)
|
||||
|
||||
| Tier | What happens | Paul's workload |
|
||||
|---|---|---|
|
||||
| Routine | Recorded as Proposed with its evidence. Later, *if Paul switches it on per channel*, approved automatically within that channel's policy. Every decision is logged, can be undone, and there's a global off switch | None by default. A count on the Desk |
|
||||
| Editorial | A handful a week, the strongest first, shown on the Desk with one-line reasons | A few decisions a week, not hundreds |
|
||||
| Needs evidence | Held until more evidence arrives (a fuller description, a second source) | None |
|
||||
| No action | Recorded, and not shown again unless its evidence changes | None |
|
||||
|
||||
**Always kept for approval:** premieres, new programming identities, events and anything unusual. Committed bookings are never rewritten. The four "new" signals stay separate: newly added, newly released, newly playable, first on Orbit. "Playable" means the box's decoder check passed, never just a file extension.
|
||||
|
||||
## The Director's Desk (phase 4)
|
||||
|
||||
One compact screen, not a list of hundreds:
|
||||
- **This week:** new to the library, by count, with a few named.
|
||||
- **Strong matches:** the routine tier as counts per channel, opened only on request.
|
||||
- **Worth a look:** 3 to 5 editorial picks, each with its reason and Approve, Not for Orbit or Later.
|
||||
- **Premieres and specials:** at most two, such as Blue Planet III for Terra.
|
||||
- **Needs your eye:** ambiguous titles with their two candidate channels.
|
||||
- **Recently decided:** approvals and rejections, and anything undone.
|
||||
|
||||
Familiar, Balanced and Surprise Me shape how many picks are shown and how adventurous they are, with Balanced the default. The director learns from Paul's decisions, but always keeps a share of new ground, so his taste doesn't close into a bubble.
|
||||
|
||||
## Next steps, each for Paul's approval
|
||||
|
||||
1. Apply the six fixes above, then rerun the trial on a fresh sample. That keeps the measurement honest.
|
||||
2. Build the Desk as read-only: the tiers and counts, with no approve buttons yet.
|
||||
3. Only after that, approval actions, with the lifecycle Detected → Verified → Proposed → Approved → Booked → Aired.
|
||||
Reference in New Issue
Block a user