#!/usr/bin/env python3
"""Reproduce the situation taxonomy and conservative, source-grounded crosswalk.
All source record text is retained. Crosswalk assignments are editorial proposals,
not observed labels or empirically validated classifiers.
"""
from pathlib import Path
from collections import Counter,defaultdict
from html import escape
import json,re,hashlib
from bs4 import BeautifulSoup
ROOT=Path(__file__).resolve().parents[1]
WORK=ROOT.parent
PRIMARY=ROOT/'research/situation-taxonomy-primary.json'
ORIGINAL=WORK/'PERFORMANCENET_3_1_MASTER_REFERENCE_2026-10-16.html'
URL='https://github.com/LAION-AI/Voice-Acting-Pipeline/blob/e3b0d5281558f7f73e1118c6a9b4c149bb8c0621/data/situation_taxonomy.json'
base=json.loads(PRIMARY.read_text())
# Re-extract supplied appendices so the complete build is reproducible.
soup=BeautifulSoup(ORIGINAL.read_text(),'html.parser')
records=[]
for letter in 'DEF':
    heading=next(h for h in soup.find_all('h2') if h.get_text().startswith('Appendix '+letter+'.'))
    for node in heading.next_siblings:
        if node.name=='h2':break
        if node.name!='details':continue
        family=node.summary.get_text(' ',strip=True)
        for article in node.select('article.record'):
            key,title=article.h4.get_text(' ',strip=True).split(' — ',1)
            records.append({'id':key,'title':title,'collection':letter,'source_family':family,'source_text':[p.get_text(' ',strip=True) for p in article.find_all('p')]})
(ROOT/'research/source-situation-records.json').write_text(json.dumps(records,ensure_ascii=False,indent=2)+'\n')
voice_legend=json.loads((WORK/'edition/data/voice-code-legend.json').read_text())
performance_legend=json.loads((WORK/'edition/data/performance-code-legend.json').read_text())
# Every E record receives an explicit reviewed domain assignment. '-' means the
# title does not establish a domain from the eleven-domain source; it is not 'other'.
EDGE_ROWS='''
ACTV;ACTV;ACTV FATG;ACTV;ACTV;POSE ACTV;FATG ACTV;HLTH;ACTV ENVI HLTH;SOCT;HLTH;CLMT ACTV;CLMT;CLMT;CLMT;CLMT ENVI;CLMT HLTH;CLMT PAIN;ENVI TRGT;SOCT ACTV ENVI;SOCT ENVI;-;GEAR;TRGT SOCT;ENVI;SOCT
HLTH;HLTH;HLTH;ACTV;ACTV;ACTV;ACTV FATG;HLTH;PAIN;PAIN CLMT;PAIN POSE;PAIN;PAIN SOCT;PAIN;PAIN HLTH;ACTV;ACTV;PAIN ACTV;SUBST;SUBST;HLTH;SUBST;SUBST;-;HLTH;ACTV;HLTH;PAIN;HLTH SOCT;HLTH;HLTH SOCT
-;-;SOCT;ENVI;FATG;FATG;FATG;SOCT;FATG;HLTH;HLTH;POSE HLTH;HLTH;HLTH;HLTH SUBST;HLTH;SOCT;HLTH;HLTH;HLTH SOCT;HLTH;HLTH;HLTH;HLTH;HLTH
SOCT TRGT;SOCT TRGT;SOCT ENVI;SOCT TRGT;SOCT TRGT;SOCT;SOCT ACTV;SOCT TRGT;POSE ENVI TRGT;HLTH SOCT
-;-;-;-;TRGT;SOCT;SOCT;SOCT;-;-;-;-;SOCT;SOCT;SOCT;-;-;SOCT;-;SOCT;-;-;-;-;-;-
-;-;-;ACTV;-;SOCT;-;SOCT;ACTV;ACTV;-;-;SOCT;HLTH;SOCT;HLTH;HLTH;SOCT;SOCT;SOCT;SOCT;SOCT;-;SOCT;SOCT;SOCT;SOCT;-;-;SOCT;SOCT;SOCT;SOCT;-;-;SOCT;-;ACTV;ACTV;SOCT;ACTV;ACTV;SOCT;SOCT;-;-;TRGT;SOCT;ACTV SOCT;SOCT;ACTV;HLTH;ACTV;-;-;ACTV;ACTV;ACTV TRGT
PAIN HLTH;SOCT;SOCT;SOCT;SOCT;SOCT;SOCT;SOCT;SOCT;FATG SOCT;SOCT;SOCT;SOCT;SOCT;SOCT;TRGT;SUBST TRGT;TRGT;SOCT TRGT;TRGT;SOCT;SOCT;SOCT;TRGT;SOCT;-;-;SOCT;-;-;-;-
TRGT;TRGT;-;TRGT;TRGT;TRGT;SOCT TRGT;TRGT;TRGT;TRGT;-;-;SOCT;ACTV;ACTV;-;-;-;-;-;-;-;-;-;HLTH;-;-;-;ACTV;SOCT;-;FATG;-;-;ACTV;SOCT;HLTH GEAR;-;TRGT SOCT;TRGT SOCT;SOCT;-;-;TRGT;TRGT;TRGT;TRGT;TRGT;TRGT;-;-;SOCT;TRGT;TRGT;-;-;-;-
'''
EDGE_DOMAINS=[[x for x in cell.split() if x!='-'] for cell in ';'.join(line.strip() for line in EDGE_ROWS.strip().splitlines()).split(';')]
assert len(EDGE_DOMAINS)==266,len(EDGE_DOMAINS)
# Pure affect does not establish an interaction; medical events in another person
# do not establish the focal actor's health state.
for index in [105,106,109,141,232]: EDGE_DOMAINS[index]=[]
EDGE_DOMAINS[185]=['SOCT']
EDGE_DOMAINS[91]=['SOCT']
F_DOMAINS={
'TENS':['ACTV','ACTV','ACTV','POSE ACTV','POSE ACTV','ACTV POSE','ACTV','POSE','ACTV','ACTV FATG'],
'BRTH':['FATG ACTV','ACTV ENVI HLTH','HLTH','HLTH','SOCT','CLMT HLTH','CLMT','SOCT','HLTH','ACTV ENVI'],
'PAIN':['PAIN','PAIN','PAIN','PAIN','PAIN','PAIN','PAIN HLTH','PAIN POSE','PAIN','PAIN SOCT'],
'TEMP':['CLMT ENVI','CLMT','PAIN','CLMT HLTH','CLMT PAIN','CLMT','CLMT','CLMT PAIN','CLMT','HLTH'],
'TASTE':['ACTV','ACTV','ACTV PAIN','ACTV','ACTV','ACTV PAIN','ACTV','ACTV SUBST','ACTV PAIN','HLTH ACTV'],
'SHOCK':['','ENVI','','ENVI','SOCT','SOCT','','','ENVI',''],
}
HUMX_DOMAINS={
'first_kill_after':'SOCT','duress_script':'SOCT','forced_confession':'SOCT','witness_atrocity':'SOCT','survivor_guilt':'SOCT','perpetrator_flat':'SOCT','arousal_onset':'ACTV','arousal_plateau':'ACTV','arousal_peak_approach':'ACTV','arousal_climax':'ACTV','refractory_oversens':'ACTV','drowning_terminal':'HLTH ENVI','burning':'PAIN CLMT','traumatic_amputation':'PAIN HLTH','anaphylaxis':'HLTH','cardiac_event':'HLTH','overdose_onset':'SUBST HLTH','exsanguination':'HLTH','load_still':'ACTV','load_athletic':'ACTV','thoracic_weighted':'POSE','tract_geometry_forced':'POSE','sustained_hold':'ACTV','opioid_withdrawal':'SUBST','delirium_tremens':'SUBST','craving_negotiation':'SUBST SOCT','relapse_shame':'SUBST SOCT','silence_constrained':'ACTV','muffled_covered':'GEAR','exposure_vigilance':'SOCT','audio_only_performed':'ACTV','starvation':'HLTH','extreme_thirst':'HLTH','solitary_confinement':'SOCT','sensory_deprivation':'','sleep_psychosis_72h':'FATG HLTH','state_interrupted':'ACTV','performance_failure':'ACTV','pain_intrusion':'PAIN ACTV','consent_stop':'SOCT','dissociation_in_state':'SOCT','tracheostomy_valve':'HLTH GEAR','electrolarynx':'HLTH GEAR','ventilator_assisted':'HLTH GEAR','esophageal_speech':'HLTH','aphonia_functional':'HLTH','post_peak_crying':'ACTV','afterglow':'ACTV','post_peak_dysphoria':'ACTV SOCT','delayed_drop_receptive':'SOCT','delayed_drop_leading':'SOCT','diagnosis_received':'HLTH SOCT','diagnosis_delivered':'SOCT','dying_declaration':'HLTH TRGT','terminal_calm':'HLTH','first_encounter':'SOCT','familiar_routine':'SOCT','intoxicated_context':'SUBST SOCT','boundary_negotiation':'SOCT','possession_nonhuman':'','creature_adr':'','aged_decades':''}
# Missing contextual facets. Each leaf has an explicit inclusion criterion;
# regex evidence is restricted to the title and explicit F subcategory, never
# the generic instruction or the source's unvalidated acoustic prediction.
EXTENSION_ROWS=[
('STIM','capsaicin','Capsaicin / chili stimulus',r'capsaicin|chili|superhot'),
('STIM','sour_taste','Sour-taste stimulus',r'sourness|sour (?:candy|taste)|lemon|lime juice'),
('STIM','bitter_taste','Bitter-taste stimulus',r'bitterness|bitter (?:taste|medicine|liquid)'),
('STIM','oral_temperature','Hot or cold oral stimulus',r'brain freeze|ice overload|scalding food|scalding.*drink|hot (?:coffee|tea|soup)|frozen.*tongue'),
('STIM','nasal_irritant','Nasal irritant stimulus',r'wasabi|horseradish|nasal assault'),
('STIM','oral_texture','Oral texture / contact stimulus',r'oral textures|sensory violations|mouthful|food in the mouth'),
('STIM','chemical_contact','Chemical contact / ingestion stimulus',r'solvent|chemical oral|cleaning fluid|bleach|ammonia|acid|chemical burn'),
('STIM','electrical_contact','Electrical contact stimulus',r'electrocut|electrical current|electric shock|power line'),
('STIM','gas_medium','Changed breathing-gas medium',r'helium voice|deep-gas|breathing[- ]gas'),
('STIM','sudden_signal','Sudden perceptual stimulus',r'jump scare|sudden appearance|sudden loud|explosion'),
('STIM','sensory_absence','Sensory deprivation',r'sensory deprivation'),
('STIM','tickle','Tickling stimulus',r'tickl'),
('EVENT','airway_emergency','Respiratory / airway / immersion emergency',r'choking|near-drowning|drowning|water inhalation|airway emergency|anaphylaxis|respiratory emergency'),
('EVENT','injury','Explicit injury event',r'gunshot wounds?|fracture|broken bone|impalement|penetrating wound|crush injur|amputation|traumatic limb|blood.loss|\bburns\b|burn injury'),
('EVENT','medical_procedure','Medical / dental procedure',r'\bsurgery\b|\bsurgical (?:and medical agony|procedure|operation)|medical agony|medical and chemical oral|\bdental (?:procedure|surgery|extraction)|\bnovocaine\b|\banesthesia\b'),
('EVENT','medical_onset','Medical emergency context, current or historical',r'medical emergency onset|cardiac|chest.pain emergency|\bseizure\b|(?<!heat )(?<!heat-)\bstroke\b|fainting|syncope'),
('EVENT','catastrophe_witness','Witnessed death / catastrophe',r'discovering a body|catastrophic scene|witnessing catastrophic|witnessing.*violence'),
('EVENT','news_revelation','Consequential information / revelation',r'devastating news|news received|digital and information shocks|diagnosis|results|winning ticket|betrayal|realization'),
('EVENT','intrusion','Intrusion / unexpected encounter',r'intruder|home invasion|stranger|unexpected encounter|sudden appearance|jump scare'),
('EVENT','animal_encounter','Animal encounter / attack',r'animal attack|animal and wildlife|wildlife encounter'),
('EVENT','collision','Collision / near miss',r'car accident|near-miss|(?<!adrenaline )crash|collision'),
('EVENT','structural_failure','Structural / environmental failure',r'structural failure|environmental collapse|(?:building|roof|bridge|ceiling|floor|tunnel|wall|scaffolding|balcony|mine|structure)[^|.;]{0,45}\bcollaps|\bcollaps\w*[^|.;]{0,45}(?:building|roof|bridge|ceiling|floor|tunnel|wall|scaffolding|balcony|mine|structure)|cave.in|wreckage|rubble'),
('EVENT','intimacy','Adult intimacy context',r'adult intima|adult sexual|orgasm|post.coital|afterglow|edging|seduction|flirtation|climax|moaning'),
('EVENT','social_ritual','Social rite / public milestone',r'wedding|vows|eulogy|funeral|retirement|farewell|coming out|toast|birthday|ceremony'),
('EVENT','care_response','Care / emergency coordination',r'911 dispatcher|emergency caller|CPR coaching|calming.*child|talking someone off|surgeon narrating|mayday|captain going down|new parent|lullaby'),
('EVENT','coercion','Coercion / interrogation context',r'coerci|coerced|duress|hostage|interrogation|torture|deliberate.*pain|forced confession'),
('CONSTRAINT','restraint','Physical restraint / entrapment',r'restraint|restrained|trapped|pinned|zip.tied'),
('CONSTRAINT','communication_barrier','Communication barrier / mediation',r'through (?:glass|a door)|phone call|voicemail|phoning|by phone|radio host|interpreter|interpreting'),
('CONSTRAINT','privacy','Privacy / quietness requirement',r'privacy|stay quiet|hidden child|library whisper|silence.constrained|whispered urgency'),
('CONSTRAINT','resource_deprivation','Explicit food / water / sleep deprivation',r'(?<!oxygen )\bstarvation\b|food deprivation|severe thirst|sleep deprivation|sleep.deprived'),
('CONSTRAINT','bodily_urge','Unmet bodily need / urge',r'hangry|needing the bathroom|craving|desperately.*bathroom'),
('CONSTRAINT','capacity_limit','Explicit exertional capacity limit',r'impossible lift|beyond capacity|edge of collapse|maximum|endurance at the edge'),
('CONSTRAINT','social_isolation','Explicit social isolation',r'solitary confinement|prolonged social isolation'),
('CONSTRAINT','consent_boundary','Explicit consent / boundary condition',r'consent|boundary|boundaries|explicit.*stop'),
('CONSTRAINT','performance_pressure','Performance / evaluation pressure',r'audition|take 20|freestyle under pressure|bombing on stage|heckler|rehearsed courage|job interview'),
('TASK','display_regulation','Deliberate display / expression regulation',r'masking|unmasked|pretend|hiding[^|.;]{0,30}(?:anger|emotion|fear|grief|nerv|pain)|confident outside|polite tone|suppress(?:ing|ed)[^|.;]{0,25}(?:rage|anger|fear|laughter|sneeze|emotion)|concealing|practiced.*fine|fake crying|trying to cry|forced emotion|love.bomb|false bravado|delighted by a terrible gift'),
('TASK','delivery_style','Explicit delivery-style task',r'cartoon|audiobook|news anchor|breaking.news|teacher explaining|\bhype\b|meditation.*guide|formal gravity|rapid patter|baby.talk|motherese|shakespearean|heightened dramatic|deadpan|youtube voice|ad read|ASMR'),
('TASK','vocal_technique','Deliberate vocal-production technique',r'ventriloquism|falsetto|head.voice|operatic|metal scream|controlled distortion|beatboxing|vocal percussion'),
('TASK','singing','Singing / melodic task',r'singing|humming|lullaby|operatic|happy birthday'),
('TASK','vocal_event','Non-speech vocal event task',r'sigh|throat.clear|groan|gasp|tutting|sucking teeth|tongue clicking|whistling|belly laugh|chuckle|snicker|moaning'),
('TASK','body_sound','Body-generated sound task',r'applause|self.slap|table.thump'),
('TASK','language_mediation','Language formulation / mediation task',r'second language|interpreting|interpreter|mother tongue|unknown language'),
('TASK','accent_adjustment','Accent accommodation / imitation',r'accent|code.switch'),
('TASK','identity_voice','Authored vocal-identity presentation',r'gender.affirming|imitating another specific person|impersonation|widely separated ages|aged decades'),
('TASK','text_relation','Read / memorised / improvised text relation',r'memorized|prepared statement|\brecit\w*|reading aloud|improvis(?:ing|ation|ed dialogue|ed speech)|cold read|losing the line|take 20|\bscript\b|freestyle'),
('TASK','articulation','Explicit fluency / articulation challenge',r'stutter|cluttering|mumbling|slurred|unclear articulation|lisp|retainer|rushing.*speech|pressured speech|voice cracking'),
('TASK','ritual_utterance','Ritual / prayer / oath task',r'prayer|liturgy|speaking in tongues|oaths|blessings|curses'),
('TASK','fictional_nonhuman','Fictional non-human / altered-identity task',r'possession|non.human|creature|séance|channeling'),
('TASK','audience_exchange','Performance-audience interaction',r'heckler|bombing on stage|campfire.*story|public.*speech'),
('TASK','performance_calibration','Deliberate intensity / authenticity comparison',r'exaggerated vs subtle|genuine vs performed|fake crying vs genuine|forced emotion|freshness on command'),
('PHASE','onset','Explicit onset / initial change',r'\bonset\b|building arousal|beginning|first encounter|dawning|mounting'),
('PHASE','sustained','Explicit sustained phase',r'sustained|plateau|endurance|holding out|ongoing'),
('PHASE','interruption','Explicit interruption / intrusion',r'interruption|interrupted|intrusion|mid.sentence|mid.speech|mid.eulogy|mid.scene'),
('PHASE','aftermath','Explicit aftermath / post-event phase',r'aftermath|\bpost[- ]|afterglow|after a scare|morning.after|after shouting|after running|refractory|adrenaline crash'),
('PHASE','recovery','Explicit participant recovery / relational repair',r'\brecover(?:y|ing)\b(?! room)|thawing|aftercare|making amends'),
('PHASE','transition','Explicit state / delivery transition',r'→|graded|transition|code.switch|register shift|possession switch|thawing|waking|dawning'),
('PHASE','anticipation','Explicit anticipation / approach',r'anticipation|anticipatory|approach to peak|night before'),
]
# Taxonomy extensions are orthogonal facets, not new mutually exclusive classes.
FACET_RATIONALES={
'STIM':'The eleven source domains do not consistently distinguish the sensory material or trigger (for example sourness, capsaicin, texture or changed gas). This facet preserves that specific circumstance without treating the response as a medical diagnosis.',
'EVENT':'Room, bodily state and social setting do not fully identify the discrete event confronting the actor. Event type is needed to distinguish source stress tests that would otherwise share a broad domain.',
'CONSTRAINT':'A named limitation can cut across posture, activity and social setting. This facet records the explicit constraint without inferring an unspecified cause.',
'TASK':'Several Edge Cases specify what kind of performance or communication is requested, rather than a bodily circumstance. These task facets preserve those instructions without inventing a situation or duplicating a VoiceNet acoustic scale.',
'PHASE':'The authored stage of an event matters for constructing a test. This facet distinguishes that scene condition from an observed temporal trajectory in PerformanceNet.',
}
# Broad source-scenario analogues. These never assert an identical event or
# diagnose a real performer. Domain compatibility must already be established.
LEAF_RULES=[
('ACTV','climbing_rope_rock',r'climbing|rope climbing'),('ACTV','lifting_heavy',r'lifting|lift\b|carrying heavy'),('ACTV','climbing_stairs',r'stairs'),('ACTV','sprinting_max',r'sprint'),('ACTV','jogging_steady',r'running'),('ACTV','fighting_struggle',r'grappling|struggle|combat'),('ACTV','rowing_pulling',r'pulling'),('ACTV','treading_water',r'treading water'),('ACTV','swimming_surface',r'swimming'),('ACTV','shivering_cold',r'shivering'),('ACTV','crying_sobbing',r'crying|tears|sobbing'),('ACTV','laughing_fit',r'laugh|chuckle|snicker'),('ACTV','whispering_quiet',r'whisper'),('ACTV','vomiting_gagging',r'vomit|gagg|nauseous'),('ACTV','coughing_fit',r'cough'),('ACTV','sneezing_recovery',r'sneez'),('ACTV','hiccuping_spasm',r'hiccup'),('ACTV','yawning_stretching',r'yawn'),('ACTV','eating_soft',r'eating soft|soft food'),
('POSE','choked_restrained',r'restraint|restrained'),('POSE','crushed_weight',r'pinned|crush injur|trapped.*wreckage'),('POSE','limp_faint',r'fainting|syncope'),
('HLTH','hlth_asthma_attack',r'asthma'),('HLTH','hlth_fever_high',r'fever'),('HLTH','hlth_altitude_sickness',r'altitude sickness'),('HLTH','hlth_dental_numbed',r'novocaine|dental.*numb'),('HLTH','hlth_pregnancy_late',r'third.trimester'),('HLTH','hlth_stroke_aphasia',r'(?<!heat )stroke|aphasia'),('HLTH','hlth_parkinsons_tremor',r'parkinson'),('HLTH','hlth_tourettes_vocal',r'tourette'),
('CLMT','clmt_freezing_bitter',r'cold|freezing|cryogenic|frostbite'),('CLMT','clmt_blizzard_whiteout',r'polar storm|blizzard'),('CLMT','clmt_extreme_heat',r'heat stroke|hyperthermia|sweltering|desert heat'),('CLMT','clmt_smoke_filled_room',r'smoke'),('CLMT','clmt_strong_wind',r'strong wind'),('CLMT','clmt_high_altitude_thin',r'high altitude|thin air'),
('SUBST','subst_caffeine_high',r'coffee|caffeine'),('SUBST','subst_alcohol_heavily_drunk',r'drunk'),('SUBST','subst_anesthesia_waking',r'anesthesia|sedation'),
('FATG','fatg_just_woken',r'just waking|sleepy|waking'),('FATG','fatg_complete_exhaustion',r'exhaustion|collapse'),('FATG','fatg_oxygen_debt',r'oxygen debt|after running'),('FATG','fatg_adrenaline_crash',r'adrenaline crash'),('FATG','fatg_deep_meditation',r'meditation|trance'),('FATG','fatg_multi_day_deprivation',r'severe sleep deprivation'),
('PAIN','pain_wound_bleeding',r'gunshot|penetrating wound|blood.loss'),('PAIN','pain_burn_scald',r'burn|scald'),('PAIN','pain_broken_bone',r'fracture|broken bone'),('PAIN','pain_torture_extreme',r'torture|deliberate.*pain'),('PAIN','pain_labor_contractions',r'labor.*childbirth|contraction'),('PAIN','pain_dental_toothache',r'tooth|dental pain'),('PAIN','pain_headache_migraine',r'migraine'),
('GEAR','gear_surgical_mask',r'surgical mask'),('GEAR','gear_scarf_wrapped',r'scarf'),
('TRGT','target_animal',r'talking to the dog'),('TRGT','target_infant',r'baby.talk|motherese|lullaby'),('TRGT','target_broadcaster',r'news.*anchor'),('TRGT','target_podcaster',r'radio host'),('TRGT','target_telephonic_remote',r'phone|voicemail'),('TRGT','target_classroom_lecture',r'teacher'),('TRGT','target_self_mumble',r'mirror|musing aloud'),('TRGT','target_divine_prayer',r'prayer'),('TRGT','target_stage_actor',r'shakespearean|stage|audition'),
('SOCT','social_job_interview',r'job interview'),('SOCT','social_interrogation_police',r'interrogation'),('SOCT','social_panic_attack',r'panic attack|panic spiral'),('SOCT','social_dissociation',r'dissociation|depersonalization|numb grief'),('SOCT','social_manic_episode',r'manic episode'),('SOCT','social_religious_ecstasy',r'religious ecstasy'),('SOCT','social_grief_mourning',r'grief|grave|eulogy|deathbed'),('SOCT','social_jealousy_envy',r'jealousy|envy'),('SOCT','social_nostalgia_longing',r'nostalgia|longing'),('SOCT','social_relief_catharsis',r'relief flooding'),('SOCT','social_shame_humiliation',r'humiliation'),('SOCT','social_betrayal_discovery',r'betrayal'),('SOCT','social_passive_aggressive',r'passive aggression'),('SOCT','social_plea_desperate',r'begging|pleading'),('SOCT','social_bullying_intimidation',r'intimidation|veiled threat'),('SOCT','social_deceptive_lying',r'memorized lie'),('SOCT','social_mockery_sarcasm',r'sarcasm'),('SOCT','social_confession_guilt',r'confession\b.*guilt|guilt.*confession\b'),('SOCT','social_goodbye_permanent',r'goodbye|farewell'),('SOCT','social_therapy_session',r'therapist'),('SOCT','social_legal_bureaucratic',r'testimony|cross.examination'),('SOCT','social_emergency_panic',r'panicked emergency caller'),('SOCT','social_consent_negotiation',r'consent|boundar'),('SOCT','social_seduction_calculated',r'seduction|flirtation'),
]
# Explicit shared-descriptor crosslinks; these do not manufacture situation context.
EMO={'gratitude':'GRA','pride':'PRI','curiosity':'INT','interest':'INT','concentration':'FOC','contemplation':'THO','relief':'REL','longing':'LON','teasing':'TEA','impatience':'IRR','doubt':'DOU','disappointment':'DISA','disgust':'DSG','jealousy':'ENV','envy':'ENV','contentment':'CON','affection':'AFF','infatuation':'INF','awe':'AWE','malice':'MAL','shame':'SHA','fear':'FEA','anger':'ANG','sadness':'SAD','triumph':'TRI','hope':'HOP','helplessness':'HLP','numbness':'NUM','contempt':'CTM'}
clinical=re.compile(r'psychotic|delirium|post.seizure|stroke|parkinson|tourette|autistic|adhd|dementia|mutism|ptsd|manic|depress|paranoia|anaphylaxis|overdose|cardiac|withdrawal',re.I)
MANUAL_NOTES={
'EDGE-009':'Hyperventilation does not contain the delivery-style word hype. Panic is an authored premise; the source places panic in SOCT, a historically mixed domain.',
'EDGE-016':'Heat stroke is not a cerebrovascular stroke. Retain the explicit heat/health premise; do not map to stroke/aphasia or to hype delivery.',
'EDGE-021':'Helium establishes a changed gas medium. It does not establish intoxication or a SUBST category.',
'EDGE-026':'Hoarseness is explicit. Sore throat and its cause are not established, so no common-cold or sore-throat leaf is assigned.',
'EDGE-028':'Congestion is explicit. A common cold is only one possible cause and is not assigned.',
'EDGE-043':'Brain freeze is an oral cold stimulus and pain premise. It is not mapped to the migraine leaf.',
'EDGE-047':'A hangover is an aftermath context; current heavy drunkenness is not established.',
'EDGE-063':'Adrenaline crash is not a vehicle collision. The title explicitly supplies an aftermath phase.',
'EDGE-076':'The source explicitly authors autistic masking. This is not a diagnosis made from vocal qualities; retain the display-regulation task separately.',
'EDGE-082':'Calm emergency dispatch does not imply the dispatcher is panicking. Retain the emergency-care context without the source emergency-panic leaf.',
'EDGE-091':'The surgeon is the speaking participant; a patient-related health event does not establish the surgeon’s own health condition.',
'EDGE-092':'Gratitude identifies affect, not who is speaking to whom or what event occurred. Situation context remains unspecified.',
'EDGE-112':'Loneliness does not establish physical or social isolation. Keep the feeling and request actual scene context.',
'EDGE-113':'The title explicitly distinguishes guilt from shame. The word shame therefore does not create a SHA reference.',
'EDGE-116':'The pain belongs to a witnessed other person. Do not transfer PAIN to the focal speaker or equate compassion with pain.',
'EDGE-141':'Suppressed rage gives a display-regulation task; it does not by itself establish an interaction.',
'EDGE-145':'Whistling past the graveyard can be an idiom. Do not require a literal whistling sound from this title alone.',
'EDGE-185':'A new parent at 3 a.m. supplies a care context, not sufficient evidence that the parent is fatigued.',
'EDGE-200':'A reality-TV confessional need not involve guilt; the source guilt-confession leaf is not assigned.',
'EDGE-205':'Gender-affirming voice work is an authored presentation task. It is not a biological classification or an inferred identity.',
'EDGE-224':'A gasp is a vocal event. Shock/awe does not specify an external perceptual stimulus, so no stimulus is invented.',
'EDGE-232':'Blocks and repetitions identify an articulation challenge. A chronic condition is not established by this authored title.',
'XPHY-TEMP-04-01':'The entire Desert Heat Stroke subcategory is excluded from the stroke/aphasia leaf. Heat-related context remains explicit.',
'XPHY-SHOCK-08-01':'Medical emergency is witnessed in another participant. All ten records in this subcategory keep that subject distinction.',
'XPHY-SHOCK-10-01':'Digital/information shocks identify a consequential information event; they do not establish an acoustic environment or a diagnosis.',
'SIT-HUMX-humx_diagnosis_delivered':'Delivering a diagnosis identifies a communication event. The diagnosed person is not automatically the speaker.',
'SIT-HUMX-humx_electrolarynx':'The source explicitly names an assistive speech device. Health and equipment context are retained without predicting actual vocal performance.',
}
REJECTED_MATCHES={
 'SIT-ACTV-breath_play':('X-EVENT-airway_emergency','The source names a consensual activity; that alone does not establish a respiratory emergency.'),
 'XPHY-TASTE-04-01':('X-EVENT-medical_procedure','Accidentally is not the word dental.'),
 'XPHY-TEMP-04-01':('X-EVENT-structural_failure','A person collapsing in heat is not a collapsing structure.'),
 'XPHY-BRTH-01-01':('X-EVENT-injury','A burning airfield does not establish an injured participant.'),
 'XPHY-SHOCK-02-02':('X-EVENT-injury','A discharged gun does not establish an injury.'),
 'XPHY-SHOCK-08-02':('X-EVENT-structural_failure','A companion collapsing is a medical event, not structural failure.'),
 'XPHY-SHOCK-08-06':('X-EVENT-structural_failure','A participant collapsing is a medical event, not structural failure.'),
 'SIT-GEAR-gear_surgical_mask':('X-EVENT-medical_procedure','A surgical mask does not establish that a procedure occurs.'),
 'SIT-ACTV-brushing_teeth':('X-EVENT-medical_procedure','Dental hygiene is not a medical procedure.'),
 'SIT-PAIN-pain_dental_toothache':('X-EVENT-medical_procedure','Toothache is not a procedure.'),
 'XPHY-TASTE-10-09':('X-EVENT-injury','A described metallic burning sensation does not establish injury.'),
 'XPHY-TENS-02-01':('X-CONSTRAINT-restraint','Pulling an immovable object does not establish actor restraint.'),
 'XPHY-TENS-05-01':('X-CONSTRAINT-restraint','Isometric effort does not establish actor restraint.'),
 'XPHY-BRTH-08-01':('X-TASK-display_regulation','Hiding physically is not masking emotional expression.'),
 'XPHY-PAIN-07-02':('X-TASK-text_relation','Improvised pain management is not improvised dialogue.'),
 'XPHY-TEMP-06-09':('X-STIM-gas_medium','Contact with liquid helium does not establish a changed breathing gas.'),
 'XPHY-SHOCK-09-07':('X-TASK-vocal_event','The dam groans; that is an environmental sound, not the actor’s vocalization.'),
 'XPHY-TENS-08-08':('X-PHASE-transition','A kill switch is equipment, not a state or delivery transition.'),
 'XPHY-TENS-10-09':('X-PHASE-transition','A work shift is not a state or delivery transition.'),
 'XPHY-BRTH-07-09':('X-PHASE-transition','A security shift is not a state or delivery transition.'),
 'XPHY-TENS-05-05':('X-PHASE-recovery','A repair crew fixes equipment; participant recovery is not established.'),
 'SIT-POSE-standing_one_leg':('X-PHASE-transition','Shifted weight describes a posture, not necessarily a temporal transition.'),
 'XPHY-BRTH-09-07':('X-PHASE-recovery','A recovery room is a location, not evidence that recovery is occurring.'),
 'XPHY-SHOCK-04-08':('X-PHASE-recovery','Recovered vehicle control is not participant or relational recovery.'),
 'SIT-FATG-fatg_hitting_wall':('X-EVENT-structural_failure','Hitting the wall is an endurance idiom, not structural failure.'),
 'XPHY-TEMP-06-10':('X-TASK-display_regulation','A gas suppression system does not suppress emotional expression.'),
}
for identifier,(_,reason) in REJECTED_MATCHES.items():
    MANUAL_NOTES[identifier]=(MANUAL_NOTES.get(identifier,'')+' '+reason).strip()

def domain_assignments(r):
    if r['collection']=='D':
        code=r['id'].split('-')[1]
        if code!='HUMX':return [code],'exact_source_membership'
        return HUMX_DOMAINS[r['id'].split('humx_',1)[1]].split(),'reviewed_title_crosswalk'
    if r['collection']=='E':return EDGE_DOMAINS[int(r['id'].split('-')[1])],'reviewed_title_crosswalk'
    _,group,subcategory,_=r['id'].split('-')
    return F_DOMAINS[group][int(subcategory)-1].split(),'explicit_source_subcategory_crosswalk'

def map_record(r):
    domains,method=domain_assignments(r)
    for code in domains:assert code in base,(r['id'],code)
    source_group=r['source_family'].split(' — ',1)[-1]
    context=r['title'] + (' | '+source_group if r['collection']=='F' else '')
    evidence_field='title + explicit source subcategory' if r['collection']=='F' else 'title'
    out=dict(r)
    out.update(source_group=source_group,context_evidence=context,stress_test_role='archetypal_challenge_point' if r['collection'] in 'EF' else 'inherited_context_record',evidence_status='authored_or_source_proposed_not_observed',observed_evidence=[],mapping_method=method)
    out['base_domains']=[{'id':code,'label':base[code]['_meta']['dimension_name'],'relation':'source_member' if method=='exact_source_membership' else 'domain_compatible_not_leaf_equivalent','evidence':r['source_family'] if method=='exact_source_membership' else context,'rationale': 'Original source family and ID establish membership.' if method=='exact_source_membership' else f'The explicit source premise/subcategory concerns {base[code]["_meta"]["dimension_name"].lower()}; this is a coarse editorial compatibility assignment, not evidence that a specific source leaf or recorded behaviour occurred.'} for code in domains]
    out['base_scenarios']=[]
    if method=='exact_source_membership':
        _,code,key=r['id'].split('-',2)
        assert key in base[code],r['id']
        out['base_scenarios']=[{'id':r['id'],'label':base[code][key]['name'],'relation':'exact_source_identity','evidence':r['id']}]
    else:
        for code,key,pattern in LEAF_RULES:
            match=re.search(pattern,context,re.I)
            if code in domains and match:
                out['base_scenarios'].append({'id':f'SIT-{code}-{key}','label':base[code][key]['name'],'relation':'supported_broad_analogue_not_identical_scenario','evidence':match.group(),'rationale':'The quoted title/subcategory wording supports a comparison with this inherited leaf. Subtype, degree, target and causal details not stated in the premise remain unspecified.'})
    out['extension_tags']=[]
    for facet,key,label,pattern in EXTENSION_ROWS:
        match_context=context
        match_field=evidence_field
        if key=='structural_failure' and r['id'].startswith('XPHY-TENS-08-'):
            # This source heading says machinery OR wreckage. Only the individual
            # title may establish a failed structure; machinery alone does not.
            match_context=r['title'];match_field='title; disjunctive family heading excluded'
        match=re.search(pattern,match_context,re.I)
        excluded=(key=='vocal_event' and r['id'] in ['EDGE-145','XPHY-SHOCK-09-07']) or (key=='structural_failure' and r['id']=='SIT-FATG-fatg_hitting_wall') or (key=='airway_emergency' and r['id']=='SIT-ACTV-breath_play')
        if match and not excluded:
            out['extension_tags'].append({'id':f'X-{facet}-{key}','label':label,'kind':facet.lower(),'grounding':'explicit_source_wording; taxonomy_leaf_is_editorial_proposal','evidence':match.group(),'evidence_field':match_field,'participant_scope':'Scene context; resolve the named subject before using as a focal-actor condition.','rationale':FACET_RATIONALES[facet]+(' The title may describe a current event or its history; this tag does not assert present onset.' if key=='medical_onset' else '')})
    out['shared_descriptor_refs']=[]
    for word,code in EMO.items():
        match=re.search(r'\b'+word+r'\b',r['title'],re.I)
        if match and not (r['id']=='EDGE-113' and code=='SHA'):out['shared_descriptor_refs'].append({'namespace':'EmoNet/forty-label source vocabulary','code':code,'evidence':match.group(),'relation':'authored_label_reference_not_observed_emotion'})
    for paragraph in r['source_text']:
        if 'Starting axes:' in paragraph or 'Relevant starting axes:' in paragraph:
            for code in dict.fromkeys(re.findall(r'\b[A-Z]{3}-[A-Z]{3}\b',paragraph)):
                if code in performance_legend:out['shared_descriptor_refs'].append({'namespace':'PerformanceNet','code':code,'label':performance_legend[code],'evidence':code,'relation':'source_suggested_starting_axis_not_an_assigned_score'})
        if paragraph.startswith('Source detector cues:'):
            for code in dict.fromkeys(re.findall(r'\b[A-Z][A-Z0-9_]{2,}\b',paragraph)):
                if code in voice_legend:out['shared_descriptor_refs'].append({'namespace':'VoiceNet/source cue vocabulary','code':code,'label':voice_legend[code]['label'],'evidence':code,'relation':'source_suggested_descriptor_not_measured_output','definition_status':voice_legend[code]['status']})
    unresolved={'EDGE-110':'Boredom / ennui','EDGE-113':'Guilt, explicitly distinct from shame','EDGE-114':'Determination / grit','EDGE-115':'Defiance','EDGE-116':'Compassion toward another person’s pain','EDGE-151':'Existential dread'}
    out['unresolved_concepts']=[{'label':unresolved[r['id']],'status':'Exact equivalence to a core forty-label emotion category is not established; source proxies and voice targets are not synonyms.'}] if r['id'] in unresolved else []
    out['participant_scope']='Authored scene context only; attach each condition to an explicit participant before assigning actor-state labels.'
    out['related_context_domains']=[]
    if r['id'].startswith('XPHY-SHOCK-08-') or r['id'] in ['EDGE-091','SIT-HUMX-humx_diagnosis_delivered']:
        out['related_context_domains']=[{'id':'HLTH','subject':'another_participant','relation':'external_context_not_focal_actor_health','evidence':r['title']}]
    if r['id']=='EDGE-116':out['related_context_domains']=[{'id':'PAIN','subject':'another_participant','relation':'witnessed_context_not_focal_actor_pain','evidence':r['title']}]
    out['uncertainties']=[]
    if method!='exact_source_membership':out['uncertainties'].append('Crosswalk is an editorial proposal based on source text; no recording, participant distribution or inter-rater validation was supplied.')
    if out['base_scenarios'] and method!='exact_source_membership':out['uncertainties'].append('A broad leaf analogue does not establish the leaf’s full subtype or its source acoustic predictions.')
    if clinical.search(r['title']):out['uncertainties'].append('The clinical or identity-related wording belongs to the authored source premise; this mapping must not be used to diagnose or infer a real person’s condition from performance.')
    if not domains:out['uncertainties'].append('No compatible original base domain is assigned. Proposed facets and related-participant context retain any explicit circumstances; complete the focal participant, setting, task and interval before generation or evaluation.')
    if method=='exact_source_membership':status='base_source_identity'
    elif out['base_scenarios']:status='base_leaf_analogue'
    elif domains:status='base_domain_only'
    elif out['extension_tags']:status='proposed_context_facet_only'
    elif out['shared_descriptor_refs']:status='shared_descriptor_only_situation_unspecified'
    else:status='unmapped_requires_context'
    out['mapping_status']=status
    out['context_completeness']='source_record_only_not_full_scene'
    if not domains and not out['extension_tags']:
        out['context_completeness']='focal_situation_incomplete_related_context_present' if out['related_context_domains'] else 'situation_context_unspecified'
    out['review_status']='deterministic_crosswalk_with_explicit_editorial_domain_decisions; not_independent_validation'
    out['editorial_review_notes']=[MANUAL_NOTES[r['id']]] if r['id'] in MANUAL_NOTES else []
    return out

mapped=[map_record(r) for r in records]
byid={r['id']:r for r in mapped}
extensions=[]
for facet,key,label,pattern in EXTENSION_ROWS:
    used=[r['id'] for r in mapped if any(t['id']==f'X-{facet}-{key}' for t in r['extension_tags'])]
    extensions.append({'id':f'X-{facet}-{key}','facet':facet,'label':label,'status':'proposed_compatible_extension','need':FACET_RATIONALES[facet],'inclusion_rule':f'Use only when the source or authored scene explicitly names {label.lower()}.','exclusion_rule':'Do not infer this circumstance from a proposed acoustic signature, observed cue, emotion label or generic template language. Absence of this tag means not specified, not absent in the world.','rule_pattern':pattern,'record_count':len(used),'example_ids':used[:5]})
source_count=sum(len(v)-1 for v in base.values())
assert source_count==289
assert len(mapped)==1217 and len(byid)==1217
assert Counter(r['collection'] for r in mapped)=={'D':351,'E':266,'F':600}
summary={'records':len(mapped),'collections':dict(Counter(r['collection'] for r in mapped)),'base_domains':len(base),'base_scenarios':source_count,'inherited_humx_records':62,'proposed_extension_leaves':len(extensions),'mapping_status':dict(Counter(r['mapping_status'] for r in mapped)),'by_collection':{c:dict(Counter(r['mapping_status'] for r in mapped if r['collection']==c)) for c in 'DEF'},'base_domain_counts':dict(Counter(d['id'] for r in mapped for d in r['base_domains'])),'extension_facet_counts':dict(Counter(t['kind'] for r in mapped for t in r['extension_tags'])),'residual_ids':[r['id'] for r in mapped if r['mapping_status'] in ['unmapped_requires_context','shared_descriptor_only_situation_unspecified']],'validation_status':'Structural inventory and auditable text-based assignments only. Not empirical validity, exhaustive real-world coverage, population/distribution coverage or calibrated predictive accuracy.'}
taxonomy={'schema_version':'1.0','name':'Humaneness — Situation Taxonomy integration','source_provenance':{'primary_url':URL,'revision':'e3b0d5281558f7f73e1118c6a9b4c149bb8c0621','primary_sha256':hashlib.sha256(PRIMARY.read_bytes()).hexdigest(),'master_reference':ORIGINAL.name,'master_sha256':hashlib.sha256(ORIGINAL.read_bytes()).hexdigest()},'base_domains':[{'id':code,'label':v['_meta']['dimension_name'],'source_metadata':v['_meta'],'interpretation_status':'Historical source category; acoustic-only and causal wording retained as attributed claims, not validated inference rules.','scenarios':[{'id':f'SIT-{code}-{key}','source_key':key,**value} for key,value in v.items() if key!='_meta']} for code,v in base.items()],'inherited_humx':[r for r in mapped if r['id'].startswith('SIT-HUMX-')],'extension_facets':[{'id':code,'rationale':text} for code,text in FACET_RATIONALES.items()],'extensions':extensions,'interpretation_rules':['Use scene/script/reference evidence for circumstance labels. A sound alone does not establish a bodily, medical or social cause.','Compose compatible context facets with VoiceNet acoustic descriptors, EmoNet affect labels and PerformanceNet visible/temporal/observer descriptors. Do not flatten these different variable types into interchangeable labels.','D supplies context records. E and F supply archetypal challenge points in the joint descriptor space; they are not additional mutually exclusive dimensions.','Mapping statuses preserve the difference between source identity, broad analogue, domain compatibility, proposed facet and genuinely missing context.','Any field absent from the source remains unspecified; no catch-all other category is used.','Coverage counts describe the supplied inventory and proposed representation only. Empirical validation and distribution coverage require separate data and studies.']}
coverage={'schema_version':'1.0','summary':summary,'records':mapped}
for name,data in [('situation-taxonomy.json',taxonomy),('situation-coverage.json',coverage)]:
 (ROOT/'data'/name).write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n')
# Audits include exact inventory preservation, grounded evidence spans and all residuals.
assert all(r['source_text']==next(x['source_text'] for x in records if x['id']==r['id']) for r in mapped)
assert all(t['evidence'] in r['context_evidence'] for r in mapped for t in r['extension_tags'])
assert all(d['id'] in base for r in mapped for d in r['base_domains'])
assert all(any(k!='_meta' and f'SIT-{c}-{k}'==leaf['id'] for c,v in base.items() for k in v) for r in mapped for leaf in r['base_scenarios'])
family_review=[]
for family in sorted(set(r['source_family'] for r in mapped)):
 rows=[r for r in mapped if r['source_family']==family]
 family_review.append({'family':family,'count':len(rows),'statuses':dict(Counter(r['mapping_status'] for r in rows)),'domains':sorted(set(d['id'] for r in rows for d in r['base_domains'])),'residual_ids':[r['id'] for r in rows if r['mapping_status'] in ['unmapped_requires_context','shared_descriptor_only_situation_unspecified']]})
audit={'source_counts':summary['collections'],'all_1217_ids_unique_and_preserved':True,'all_source_paragraphs_preserved':True,'primary_289_identity_matches':sum(r['mapping_status']=='base_source_identity' for r in mapped),'all_extension_evidence_spans_found_in_source_context':True,'all_domain_and_leaf_references_valid':True,'no_catch_all_other_category':True,'all_60_extreme_subcategories_explicitly_crosswalked':len([f for f in family_review if f['family'].startswith('XPHY-')])==60,'family_review':family_review,'residual_records':[r for r in mapped if r['mapping_status'] in ['unmapped_requires_context','shared_descriptor_only_situation_unspecified']]}
(ROOT/'qa/situation-audit.json').write_text(json.dumps(audit,ensure_ascii=False,indent=2)+'\n')
def has_leaf(identifier,leaf):return any(x['id']==leaf for x in byid[identifier]['base_scenarios'])
def has_tag(identifier,tag):return any(x['id']==tag for x in byid[identifier]['extension_tags'])
assert not has_leaf('EDGE-016','SIT-HLTH-hlth_stroke_aphasia')
assert all(not has_leaf(r['id'],'SIT-HLTH-hlth_stroke_aphasia') for r in mapped if r['id'].startswith('XPHY-TEMP-04-'))
assert all(not has_tag(key,'X-TASK-delivery_style') for key in ['EDGE-009','EDGE-016','EDGE-134'])
assert not has_tag('EDGE-063','X-EVENT-collision')
assert not has_tag('EDGE-112','X-CONSTRAINT-social_isolation')
assert not has_tag('EDGE-224','X-STIM-sudden_signal')
assert not any(x['code']=='SHA' for x in byid['EDGE-113']['shared_descriptor_refs'])
assert all(not r['base_domains'] and r['related_context_domains'][0]['subject']=='another_participant' for r in mapped if r['id'].startswith('XPHY-SHOCK-08-'))
assert all(not byid[x]['base_domains'] for x in ['EDGE-021','EDGE-205','EDGE-232'])
assert all(x['record_count']>0 for x in extensions)
for identifier,(tag,reason) in REJECTED_MATCHES.items():assert not has_tag(identifier,tag),(identifier,tag,reason)
assert all(not has_tag(r['id'],'X-CONSTRAINT-resource_deprivation') for r in mapped if r['id'].startswith('XPHY-BRTH-06-'))
for suffix in ['03','04','06','08','09']:assert not has_tag('XPHY-TENS-08-'+suffix,'X-EVENT-structural_failure')
review={'review_kind':'Editorial source-text review by the document-preparation agent; not independent annotation validation.',
 'scope':'Every Edge Case title, all 62 HUMX titles, all 60 Physical Extremes subcategory headings, all residuals, and all unique extension match spans were inspected. Selected ambiguous records are documented below. Full manual annotation of every F scenario was not performed; subcategory assignments and text rules remain proposals.',
 'family_count':len(family_review),'physical_extreme_subcategories':60,
 'negative_match_regression_checks_passed':True,
 'independent_editorial_review':{'status':'targeted_semantic_correction_review_complete','reviewer':'Separate collaborating document agent','scope':'All 60 Physical Extreme subgroups sampled; all 22 Edge Case residuals reviewed; targeted participant/observer, medical/injury, substring and phase matches checked. Reported false positives corrected and regression-tested.','limit':'Not exhaustive independent record annotation, inter-rater reliability testing or empirical validation.'},
 'reviewer_reported_regressions':[{'id':key,'rejected_tag':value[0],'reason':value[1],'passed':True} for key,value in REJECTED_MATCHES.items()],
 'whole_subgroup_regressions':[{'scope':'XPHY-BRTH-06 (10 records)','rejected_tag':'X-CONSTRAINT-resource_deprivation','reason':'Oxygen starvation is outside the food/water/sleep definition.','passed':True},{'scope':'XPHY-TENS-08-03,04,06,08,09','rejected_tag':'X-EVENT-structural_failure','reason':'Machinery OR wreckage heading does not establish structural failure in every item.','passed':True}],
 'selected_ambiguities':[{'id':key,'title':byid[key]['title'],'review_note':value,'final_status':byid[key]['mapping_status']} for key,value in MANUAL_NOTES.items()],
 'residual_review':[{'id':key,'title':byid[key]['title'],'decision':'Retain source descriptor references; no scene circumstance is established. Supply participant, event, setting, task and interval as appropriate before creating a situation bucket.'} for key in summary['residual_ids']],
 'open_limitations':['Broad leaf analogues are retrieval comparisons, not equivalent scene labels.','Per-record source text is preserved, but the mapping extracts only explicit title/subcategory context; richer authored scenes may justify additional context.','All combinations, empirical label reliability and real-world distribution coverage remain untested.']}
affected_tags=sorted({value[0] for value in REJECTED_MATCHES.values()}|{'X-EVENT-medical_onset','X-EVENT-airway_emergency','X-CONSTRAINT-resource_deprivation'})
review['all_remaining_affected_rule_matches']=[{'tag':tag,'records':[{'id':r['id'],'title':r['title'],'evidence':t['evidence'],'evidence_field':t['evidence_field']} for r in mapped for t in r['extension_tags'] if t['id']==tag]} for tag in affected_tags]
(ROOT/'qa/situation-review.json').write_text(json.dumps(review,ensure_ascii=False,indent=2)+'\n')
(ROOT/'qa/coverage-review.json').write_text(json.dumps(review,ensure_ascii=False,indent=2)+'\n')
from prepare_situations_volume import build_volume
build_volume(ROOT)
print(json.dumps(summary,ensure_ascii=False,indent=2))
