from __future__ import annotations
REQUIRED=['recommendation_id','decision_context','candidate_id','candidate_rank','evidence_ref','evidence_freshness','evidence_quality','supporting_evidence','contradicting_evidence','missing_evidence','confidence_label','confidence_basis','calibration_scope','uncertainty_reason','abstain_condition','alternative_action','review_required','trace_ref','decision_state','review_due']

def _blank(v): return v is None or (isinstance(v,str) and not v.strip())
def _listish(v):
    if isinstance(v,list): return v
    if _blank(v) or str(v).upper() in {'NONE','NONE_DECISIVE'}: return []
    return [x.strip() for x in str(v).split('|') if x.strip()]

def evaluate(r):
    for k in REQUIRED:
        if k not in r or _blank(r[k]):
            return 'UNKNOWN_DECISIVE_EVIDENCE'
    if r.get('transfer_case') and r.get('baseline_decision_reused'):
        return 'HOLD_RECOMPUTE_REQUIRED'
    if r.get('transfer_case') and r.get('baseline_candidate_id')==r.get('candidate_id') and r.get('changed_evidence_requires_rank_change'):
        return 'HOLD_RECOMPUTE_REQUIRED'
    basis=str(r.get('confidence_basis','')).lower()
    if r.get('model_percentage_only') or ('model' in basis and '%' in basis and 'evidence' not in basis):
        return 'HOLD_UNSUPPORTED_CONFIDENCE'
    if str(r.get('calibration_scope','')).upper()=='STATISTICALLY_CALIBRATED' and not r.get('calibration_evidence_ref'):
        return 'HOLD_UNSUPPORTED_CALIBRATION'
    miss=_listish(r.get('missing_evidence'))
    if r.get('missing_decisive') or any(str(x).startswith('DECISIVE:') for x in miss):
        return 'HOLD_MISSING_EVIDENCE'
    if str(r.get('evidence_freshness')).upper() in {'STALE','EXPIRED','UNKNOWN'} and r.get('freshness_decisive',True):
        return 'HOLD_STALE_EVIDENCE'
    if str(r.get('evidence_quality')).upper() in {'LOW','UNKNOWN'} and r.get('quality_decisive',True):
        return 'HOLD_LOW_QUALITY_EVIDENCE'
    support_count=int(r.get('support_count',len(_listish(r.get('supporting_evidence')))))
    contradiction_count=int(r.get('contradiction_count',len(_listish(r.get('contradicting_evidence')))))
    label=str(r.get('confidence_label')).upper()
    if label=='STRONG' and (support_count<2 or contradiction_count>0):
        return 'HOLD_UNSUPPORTED_CONFIDENCE'
    if label=='MODERATE' and support_count<2 and contradiction_count>0:
        return 'HOLD_UNSUPPORTED_CONFIDENCE'
    if r.get('contradiction_decisive'):
        if not bool(r.get('review_required')):
            return 'HOLD_CONTRADICTION_REVIEW_REQUIRED'
        return 'RECOMMEND_WITH_REVIEW'
    if label in {'UNSUPPORTED','UNKNOWN'}:
        return 'HOLD_UNSUPPORTED_CONFIDENCE'
    if bool(r.get('review_required')):
        return 'RECOMMEND_WITH_REVIEW'
    return 'RECOMMEND'

def check_case(case):
    got=evaluate(case['record'])
    return got, got==case['expected']

def invariant_checks(case):
    r=case['record']; got=evaluate(r); checks=[]
    checks.append(got==case['expected'])
    checks.append(bool(str(r.get('recommendation_id','')).strip()))
    checks.append(bool(str(r.get('trace_ref','')).strip()))
    checks.append(bool(str(r.get('confidence_basis','')).strip()))
    checks.append(str(r.get('calibration_scope','')).strip()!='')
    checks.append(not (got=='RECOMMEND' and (r.get('missing_decisive') or str(r.get('evidence_freshness')).upper()=='STALE')))
    return checks
