Stage D: a horror-comedy is After Dark's, whatever its year
Paul, 5 November 2026, on Mum, I'm Alien Pregnant: context over category. Horror with comedy (or body horror, gross-out, splatter) now goes to After Dark, free of its era, and counts less on Orbit Horror and Sci-Fi. Four of 392 titles move (Buzzkill, Deathgasm 2, Bunker Heights, Mum, I'm Alien Pregnant); the five regression samples hold or improve. Kotlin matches the new Python reference on all 392 titles. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,7 @@ private val Uk = Regex("United Kingdom|England|Scotland|Wales|\\bUK\\b")
|
||||
private val UkStudio = Regex("\\bBBC\\b|\\bITV\\b|Channel 4|Film4|Sky\\b|Working Title|Ealing|Hammer|Aardman", RegexOption.IGNORE_CASE)
|
||||
private val EnglishCountries = Regex("United States|United Kingdom|Canada|Australia|Ireland|New Zealand|England|Scotland|Wales")
|
||||
private val ForeignBoard = Regex("^(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)-", RegexOption.IGNORE_CASE)
|
||||
private val HorrorComedy = Regex("\\b(body horror|body-horror|gross-out|splatter|horror-comedy|horror comedy)", RegexOption.IGNORE_CASE)
|
||||
private val LiveSpecial = Regex("\\b(stand-up|standup|comedian|comedy special|unruly set|in this set|live from|in concert|tour film|takes aim at)\\b", RegexOption.IGNORE_CASE)
|
||||
private val EventFilmWords = Regex("superhero|epic|franchise|saga|blockbuster", RegexOption.IGNORE_CASE)
|
||||
private val International = setOf(305, 601, 401)
|
||||
@@ -198,7 +199,10 @@ internal fun channelScore(e: TitleEvidence, b: Brief, text: ChannelText, links:
|
||||
s += .4 * maxOf(minOf(1.0, sim / .25), .6); ev += "short description, matched on genre (%.2f)".format(sim)
|
||||
} else { s += .4 * minOf(1.0, sim / .25); ev += "description like its titles (%.2f)".format(sim) }
|
||||
} else { s += .15; ev += "no description (not held against it)" }
|
||||
e.year?.let { year ->
|
||||
// Paul, 5 November 2026: context over category. Horror played for laughs (a horror-comedy, body horror, gross-out) is After
|
||||
// Dark's cult territory, whatever its year, rather than mainstream Horror or Sci-Fi.
|
||||
val horrorComedy = "Horror" in g && ("Comedy" in g || HorrorComedy.containsMatchIn(all))
|
||||
if (!(horrorComedy && ch == 305)) e.year?.let { year ->
|
||||
val (lo, hi) = b.era
|
||||
if (year < lo - 3 || year > hi + 3) { if (!b.current) { s *= .3; ev += "$year outside its era $lo-$hi" } }
|
||||
}
|
||||
@@ -214,6 +218,8 @@ internal fun channelScore(e: TitleEvidence, b: Brief, text: ChannelText, links:
|
||||
if (b.kids && rating.isEmpty()) { s *= .8; ev += "no rating to confirm it suits children" }
|
||||
if ("Horror" in g && (ch == 304 || ch == 305)) { s += .12; ev += "a horror film" }
|
||||
if ("Horror" in g && ch in setOf(308, 307, 612, 613)) { s *= .5; ev += "horror is not for this channel" }
|
||||
if (horrorComedy && ch == 305) { s += .15; ev += "a horror-comedy, After Dark's cult territory" }
|
||||
if (horrorComedy && (ch == 303 || ch == 304)) { s *= .7; ev += "a horror-comedy, not mainstream horror or sci-fi" }
|
||||
if (b.needAny.isNotEmpty() && (g intersect b.needAny).isNotEmpty() && ov.length < 160) { s += .1; ev += "short description, its defining genre carries more" }
|
||||
if (b.needTheme && pos.isEmpty()) { s *= .5; ev += "none of its themes" }
|
||||
// Paul, 4 November 2026: made-for-TV romances (the Hallmark kind) aren't Orbit material.
|
||||
|
||||
@@ -129,6 +129,18 @@ class DirectorChairTest {
|
||||
assertThrows(IllegalArgumentException::class.java) { placeFromChair(store, user, "5000003", "A Season", "Season", null, 201, 201) }
|
||||
}
|
||||
|
||||
@Test fun aHorrorComedyIsAfterDarksWhateverItsYear() {
|
||||
// Paul, 5 November 2026, on Mum, I'm Alien Pregnant: context over category.
|
||||
val alien = TitleEvidence.of(line("hc", "Mum, I'm Alien Pregnant", "Movie", listOf("Horror", "Science Fiction", "Comedy"),
|
||||
"When a messy underachiever accidentally gets alien-pregnant, she must overcome skeptical doctors and her oversharing mum to survive.", countries = listOf("New Zealand")))
|
||||
val straight = TitleEvidence.of(line("sh", "The Haunting Below", "Movie", listOf("Horror"), "A demon haunts a family; a curse and a killer in the night."))
|
||||
val text = ChannelText(emptyMap(), mapOf("hc" to alien, "sh" to straight))
|
||||
val placed = placeTitle(alien, text)
|
||||
assertEquals(305, placed.top.number)
|
||||
assertTrue(placed.why.any { "horror-comedy" in it })
|
||||
assertNotEquals(305, placeTitle(straight, text).top.number) // straight horror isn't moved
|
||||
}
|
||||
|
||||
@Test fun aReportReadsBackAsItWasSaved() {
|
||||
val r = DeskReport(10, 2, 9, 1, listOf("Terra" to 1), 1, listOf(DeskPick("A", "Terra", "STRONG MATCH", "Line")), listOf(DeskAsk("B", "Sci-Fi", "Horror")), 0, false, 0, 5, 6)
|
||||
val back = DeskReport.of(r.toJson())
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
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','household','neighbou?r','roommate','parents','dad','mum','mom','sisters','brothers','college','workplace'),against=W('murder','killer','documentary','wedding of','engagement'),era=(1970,2026),kids=False,need_theme=True),
|
||||
'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','pathologist','homicide','murder','sergeant','DCI','sleuth','constable','cop','FBI','NCIS','agent','crime scene'),against=W('documentary','true story of','real-life','archive footage','interviews','lawyer','law firm','solicitor','attorney','barrister','courtroom','divorce','legal'),need_theme=True,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','romantic','falls for','fall in love','wedding','sweetheart','kiss','date'),against=W('documentary','murder','horror','outlaw','gunfight','sheriff','war'),need_theme=True,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'}
|
||||
HORRORCOMEDY=re.compile(r'\b(body horror|body-horror|gross-out|splatter|horror-comedy|horror comedy)',re.I)
|
||||
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)')
|
||||
# Paul, 5 November 2026: context over category. Horror played for laughs (a horror-comedy, body horror, gross-out) is After
|
||||
# Dark's cult territory, whatever its year, rather than mainstream Horror or Sci-Fi.
|
||||
hc='Horror' in g and ('Comedy' in g or bool(HORRORCOMEDY.search(text)))
|
||||
if year and not (hc and ch=='305'):
|
||||
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 ch=='101' and g & {'Game Show','Reality','Talk'}: return 0,['a game, reality or talk show, not a sitcom']
|
||||
if ch=='309':
|
||||
verified=bool(UK.search(' '.join(r.get('countries') or []))) or bool(UKSTUDIO.search(' '.join(r.get('studios') or [])))
|
||||
if not verified: return 0,['British origin not verified' if not (r.get('countries') or r.get('studios')) else 'not British by its countries or studios']
|
||||
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 hc and ch=='305': s+=.15; e.append("a horror-comedy, After Dark's cult territory")
|
||||
if hc and ch in ('303','304'): s*=.7; e.append('a horror-comedy, not mainstream horror or sci-fi')
|
||||
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}/staged6.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())
|
||||
Reference in New Issue
Block a user