Stage D trial 4 on a fourth fresh sample; four regression samples kept
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 2m6s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m26s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-04 23:44:27 +00:00
co-authored by Claude Opus 5.5
parent 8772be27d8
commit 4ea4e19a95
4 changed files with 6925 additions and 0 deletions
@@ -0,0 +1 @@
{"8019496":[],"8020674":[],"8032453":["","Orbit Action"],"8014886":["","Orbit Action"],"8005758":["Dark Files"],"8070429":[],"8060115":["","Odyssey"],"7997994":["Evidence"],"8061381":[],"8063798":[],"8070559":[],"7997791":[],"8060401":["Wonder","PixelZ"],"8005448":["Vortex"],"8014088":["Vortex"],"8020594":["Orbit Horror","After Dark"],"7985943":["","Orbit Wild"],"8014195":[],"8068447":["Paradox"],"8019422":[],"8006592":[],"7996047":["Orbit Horror","After Dark"],"8014004":["Dark Files"],"7998951":["Orbit Action"],"8056349":[],"8014275":[],"8062599":[],"8008975":["Paradox"],"7993266":["Ring X"],"8001036":["","Vortex","Rewind"],"7998036":[],"8006415":[],"8001551":[],"8005690":[],"7991705":[],"8061386":[],"7997825":["Orbit 1"],"8070470":[],"8058485":[],"8060107":[],"8019013":["Ring X"],"7986905":[],"8014336":["","Orbit Three","Orbit 1","Paradox"],"8017315":["Terra"],"7994924":["","Orbit Sci-Fi"],"8055366":[],"8004050":[],"8026256":[],"8003283":[],"8018832":[],"7981186":[],"8018809":["Ring X"],"8014525":[],"8019603":["","After Dark","Orbit Horror"],"8016061":["Evidence"],"8019883":["","Paradox"],"8066243":[],"8057252":["Ring X"],"7985161":[],"8020570":[]}
File diff suppressed because it is too large Load Diff
+158
View File
@@ -0,0 +1,158 @@
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','AI','robot','superhero'),era=(1975,2026),kids=False,need_theme=True),
'202':dict(name='Odyssey',kind={'Series'},genres_any={'Documentary','History'},themes=W('space','universe','astronom','science','engineer','scientist','history of','historian','ancient','archaeolog','exploration','explorer','technolog','invention','journey across','civilisation','civilization','planet earth'),against=W('murder','killer','crime','celebrit','reality','athlete','boxer','comedian','sitcom','band','singer','fame','star of'),era=(1950,2026),kids=False,need_theme=True),
'203':dict(name='Orbit Wild',kind={'Series'},genres_any={'Reality','Documentary','Adventure'},themes=W('surviv','wilderness','expedition','endurance','remote','frontier','alone in','hunt','fishing','mountain','arctic','jungle','desert'),against=W('murder','dating','cooking','makeover','celebrit','villa','luxury','cash prize','contestants compete'),era=(1985,2026),kids=False,need_theme=True),
'204':dict(name='Dark Files',kind={'Series'},genres_any={'Documentary','Crime','Mystery'},need_any={'Documentary'},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,need_theme=True),
'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,need_theme=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','wild night','spring break','frat','hangover','vegas'),against=W('documentary','animated'),era=(1990,2026),kids=False,current=True,need_theme=True),
'309':dict(name='BritX',kind={'Movie'},genres_any={'Crime','Comedy','Drama','Thriller'},themes=W('gangster','gang','criminal','crime','heist','drug','hooligan','council estate','thug','con','robbery','dealer'),need_theme=True,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','MMA','mixed martial','title match','pay-per-view','bout'),against=W('romance'),era=(1980,2026),kids=False,current=True,need_theme=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,need_theme=True),
'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','preschool','junior','Disney Jr','Spidey'),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 ('','')
BOARD=re.compile(r'^(SG|BR|PL|KR|JP|DE|FR|IT|ES|NL|TR|IN|MX|CN|HK|TW|TH|PH|MY|ID|RU|SE|NO|DK|FI|AR|CL|CO|PT|CZ|HU|RO|GR|IL|EG|SA|AE|VN)-',re.I)
def foreign(r):
if BOARD.search(r.get('rating') or ''): return True,'rated by a non-English-language board ('+r['rating']+')'
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'}
ANIMATED_CH={'105','601','602','603','604','605','611','612','613','614'}
DOC_CH={'201','202','203','204','401'}
FICTION_CH={'101','103','104','105','106','107','301','302','303','304','305','306','307','308','309','601','602','603','604','605','612','613'}
LIVE=re.compile(r'\b(stand-up|standup|comedian|comedy special|unruly set|in this set|live from|in concert|tour film|takes aim at)\b',re.I)
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.
# Format, as a hard limit: animation, documentary and fiction each have their own channels; live specials have none but Ring X's events.
animated='Animation' in g
doc=bool(g & {'Documentary'})
if animated and ch not in ANIMATED_CH: return 0,['animation, not for this channel']
if not animated and ch in ANIMATED_CH and g and ch not in ('611','601'): return 0,['live action, not for this channel']
if doc and ch in FICTION_CH: return 0,['a documentary, not fiction']
if g and not doc and ch in DOC_CH and ch!='401' and not (g & {'Reality'} and ch in ('203','204')): return 0,['fiction, not factual']
if ch=='107' and (doc or g & {'Reality'}): return 0,['factual, and Evidence is scripted only']
if LIVE.search(ov) and ch!='401': return 0,['a live or stand-up special']
# 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
short=len(ov)<200
# A short description says little: lean on the defining genre and its themes instead of word similarity.
if short and g and (g & (b.get('need_any') or b['genres_any'])) and (pos or b.get('need_any')): s+=.4*max(min(1,sim/.25),.6); e.append(f'short description, matched on genre ({sim:.2f})')
else: 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')
if 'Horror' in g and ch in ('304','305') : s+=.12; e.append('a horror film')
if 'Horror' in g and ch in ('308','307','612','613'): s*=.5; e.append('horror is not for this channel')
if b.get('need_any') and g & b['need_any'] and len(ov)<160: s+=.1; e.append('short description, its defining genre carries more')
if b.get('need_theme') and not pos: s*=.5; e.append('none of its themes')
# Paul, 4 November 2026: made-for-TV romances (the Hallmark kind) aren't Orbit material.
if 'TV Movie' in g: s*=.4; e.append('a made-for-TV film')
if ch=='301' and 'Action' in g and not re.search(r'superhero|epic|franchise|saga|blockbuster',text,re.I): s*=.8; e.append('action, but not an event film')
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}/staged4.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())
@@ -107,3 +107,33 @@ That learning stays local and explainable, keeps Familiar, Balanced and Surprise
4. Then interactive decisions and learning, presented for approval first.
Auto-approval stays off.
## Trial 4 (23:55): the trial 3 fixes, measured on a fourth fresh sample
**Fixes:**
- Odyssey needs science, history or exploration themes, and counts against celebrities, athletes, bands and sitcoms.
- A rating from another country's board (`SG-`, `BR-`, `PL-` and others) counts as foreign-language.
- Premiere marks down action films that aren't event films.
- Orbit Wild needs survival or expedition themes, and counts against celebrity villas and contests.
- Detour needs road-trip and party themes, not "friends".
**Regression, on samples 1 to 3 (routine):** 100%, 100% and 91% precision. Titles with no home placed anyway: 0, 0 and 0.
**Sample 4 is fresh** (seed 37, 60 titles, labelled before scoring): 18 clear homes, 9 either-way, 33 none.
| Model | Suggestions | Precision | Recall of clear fits | Titles with no home placed anyway |
|---|---|---|---|---|
| Trial 1, genre only | 19 | 47% | 7 of 18 (39%) | 6 of 33 |
| **Trial 4, routine** | 21 | **76%** | **11 of 18 (61%)** | **3 of 33** |
| Trial 4, routine plus editorial | 24 | 71% | 11 of 18 | 5 of 33 |
**Across the two fresh samples since trial 1:**
- **Precision:** 60% → 76%.
- **Recall:** 50% → 61%.
- **Trial 1 (genre only), for comparison:** 33 to 47% precision.
**Still wrong on sample 4:**
- **BritX took a US action film (The Deputy).** **Fix:** British origin becomes a hard limit for BritX, not a markdown.
- **Legal dramas (WAR, Pierre) went to Evidence.** **Fix:** "case" isn't a police theme; lawyers, solicitors and divorce count against it.
- **A comedy (How to Ruin Diwali) went to Orbit 1,** and **a western (Angel and the Badman) went to Romance**. **Fix:** Romance needs romance as the main theme; Orbit 1 needs a family-sitcom or British-comedy fit.
- **Recall misses where the right channel came top but under the line:** S.W.A.T. Exiles (Evidence), Carrie and Dead-End Job (Paradox), A Different World (Orbit 1). Their descriptions are short or unusual. Verified franchise links would settle S.W.A.T. and A Different World, both continuations of known shows, which is why the collections read matters.