MISSING={None,"","MISSING","UNKNOWN","N/A"}
REQ=['processing_activity_id', 'purpose_id', 'personal_data_category', 'controller_entity', 'processor_entity', 'role_basis_ref', 'documented_instruction_ref', 'instruction_version', 'dpa_ref', 'dpa_version', 'subprocessor_id', 'subprocessor_authorization_state', 'transfer_context', 'retention_rule_ref', 'deletion_rule_ref', 'security_measure_ref', 'purpose_change_state', 'instruction_conflict_state', 'privacy_review_required', 'processing_state', 'audit_ref']
AUX_BOOL=['instruction_matches_purpose','instruction_current','retention_deletion_consistent','security_evidence_current','dpa_current','transfer_recomputed','baseline_decision_reused','baseline_instruction_reused']
AUX_OTHER=['role_evidence_state','decision_authority_state','subprocessor_evidence_ref','transfer_safeguard_ref','legal_review_state','special_category_flag','transfer_variant']
def missing(v): return v in MISSING
def evaluate(x):
    for f in REQ:
        if missing(x.get(f)): return 'HOLD_REQUIRED_EVIDENCE_MISSING:'+f
    for f in AUX_BOOL:
        if f not in x: return 'HOLD_AUX_EVIDENCE_MISSING:'+f
    for f in AUX_OTHER:
        if f not in x or missing(x.get(f)): return 'HOLD_AUX_EVIDENCE_MISSING:'+f
    if x.get('role_evidence_state')=='CONTRACT_LABEL_ONLY': return 'HOLD_CONTRACT_LABEL_NOT_ROLE_PROOF'
    if x.get('role_evidence_state') not in {'FACTUAL_ASSESSMENT','LAW_DEFINED','MIXED_EVIDENCE'}: return 'HOLD_ROLE_EVIDENCE_INVALID'
    if x.get('decision_authority_state') not in {'CONTROLLER_DETERMINES_PURPOSE','JOINT_DETERMINATION','PROCESSOR_TECHNICAL_MEANS_ONLY','PROCESSOR_DETERMINES_NEW_PURPOSE'}: return 'HOLD_DECISION_AUTHORITY_INVALID'
    if x.get('decision_authority_state')=='PROCESSOR_DETERMINES_NEW_PURPOSE' and x.get('processing_state')=='ALLOWED': return 'HOLD_ROLE_BOUNDARY_CONFLICT'
    if x.get('instruction_current') is not True: return 'HOLD_INSTRUCTION_STALE'
    if x.get('documented_instruction_ref') in {'NONE','NO_INSTRUCTION','UNKNOWN'}: return 'HOLD_DOCUMENTED_INSTRUCTION_MISSING'
    if x.get('instruction_matches_purpose') is not True: return 'HOLD_INSTRUCTION_PURPOSE_MISMATCH'
    if x.get('purpose_change_state') not in {'NONE','CHANGED_RECOMPUTED','CHANGED_PENDING_REVIEW'}: return 'HOLD_PURPOSE_CHANGE_STATE_INVALID'
    if x.get('purpose_change_state') in {'CHANGED_RECOMPUTED','CHANGED_PENDING_REVIEW'} and x.get('baseline_instruction_reused') is True: return 'HOLD_PURPOSE_CHANGE_BASELINE_INSTRUCTION_REUSE'
    if x.get('dpa_current') is not True: return 'HOLD_DPA_STALE'
    if x.get('subprocessor_authorization_state') not in {'NOT_APPLICABLE','APPROVED','PENDING','DENIED'}: return 'HOLD_SUBPROCESSOR_AUTH_STATE_INVALID'
    if x.get('subprocessor_id') not in {'NONE','NO_SUBPROCESSOR'}:
        if x.get('subprocessor_authorization_state')!='APPROVED': return 'HOLD_SUBPROCESSOR_NOT_AUTHORIZED'
        if x.get('subprocessor_evidence_ref') in {'NONE','NOT_APPLICABLE','NO_EVIDENCE','UNKNOWN'}: return 'HOLD_SUBPROCESSOR_AUTH_EVIDENCE_MISSING'
    if x.get('transfer_context') not in {'EEA_ONLY','SAME_JURISDICTION','THIRD_COUNTRY_TRANSFER','CROSS_BORDER_REVIEW'}: return 'HOLD_TRANSFER_CONTEXT_INVALID'
    if x.get('transfer_context') in {'THIRD_COUNTRY_TRANSFER','CROSS_BORDER_REVIEW'} and x.get('transfer_safeguard_ref') in {'NONE','NOT_APPLICABLE','NO_SAFEGUARD','UNKNOWN'}: return 'HOLD_TRANSFER_SAFEGUARD_MISSING'
    if x.get('retention_deletion_consistent') is not True: return 'HOLD_RETENTION_DELETION_CONFLICT'
    if x.get('security_evidence_current') is not True: return 'HOLD_SECURITY_EVIDENCE_STALE'
    if x.get('instruction_conflict_state') not in {'NONE','OPEN','CONTRADICTORY','RESOLVED'}: return 'HOLD_INSTRUCTION_CONFLICT_STATE_INVALID'
    if x.get('instruction_conflict_state') in {'OPEN','CONTRADICTORY'}:
        if x.get('processing_state')=='ALLOWED': return 'HOLD_INSTRUCTION_CONFLICT'
        return 'PRIVACY_HOLD_REVIEW_READY'
    if x.get('privacy_review_required') not in {'YES','NO'}: return 'HOLD_PRIVACY_REVIEW_STATE_INVALID'
    if x.get('special_category_flag') is True and x.get('privacy_review_required')!='YES': return 'HOLD_SPECIAL_CATEGORY_REVIEW_REQUIRED'
    if x.get('privacy_review_required')=='YES' and x.get('legal_review_state') not in {'APPROVED','REQUIRED_OPEN'}: return 'HOLD_LEGAL_REVIEW_STATE_INVALID'
    if x.get('privacy_review_required')=='YES' and x.get('legal_review_state')=='REQUIRED_OPEN': return 'PRIVACY_HOLD_REVIEW_READY'
    if x.get('processing_state') not in {'ALLOWED','REVIEW_REQUIRED','HOLD'}: return 'HOLD_PROCESSING_STATE_INVALID'
    if x.get('transfer_variant') not in {'BASELINE','NONE'}:
        if x.get('transfer_recomputed') is not True: return 'HOLD_TRANSFER_NOT_RECOMPUTED'
        if x.get('baseline_decision_reused') is True: return 'HOLD_TRANSFER_BASELINE_DECISION_REUSE'
        if x.get('baseline_instruction_reused') is True: return 'HOLD_TRANSFER_BASELINE_INSTRUCTION_REUSE'
        if x.get('processing_state') in {'ALLOWED','REVIEW_REQUIRED'}: return 'TRANSFER_PRIVACY_REVIEW_READY'
    if x.get('processing_state')=='ALLOWED': return 'PROCESSING_BOUNDARY_REVIEW_READY'
    if x.get('processing_state') in {'REVIEW_REQUIRED','HOLD'}: return 'PRIVACY_HOLD_REVIEW_READY'
    return 'PRIVACY_HOLD_REVIEW_READY'
