MISSING={None,"","MISSING","UNKNOWN","N/A"}
REQ=['contract_suite_id', 'framework_agreement_ref', 'statement_of_work_ref', 'service_level_agreement_ref', 'scope_version', 'scope_item_id', 'deliverable_id', 'acceptance_criteria_ref', 'acceptance_state', 'service_metric_id', 'service_target', 'measurement_source', 'change_request_id', 'change_approval_state', 'document_priority_rule_ref', 'conflict_state', 'legal_review_required', 'evidence_ref', 'audit_ref']
AUX_BOOL=['delivery_schedule_current', 'change_required', 'change_scope_moved', 'measurement_window_current', 'metric_result_recomputed', 'document_conflict_detected', 'priority_rule_applied', 'remedy_invoked', 'transfer_recomputed', 'baseline_decision_reused']
AUX_OTHER=['governing_document_type', 'request_scope_state', 'scope_origin', 'deliverable_owner', 'period_of_performance', 'delivery_schedule_ref', 'customer_responsibility_ref', 'provider_responsibility_ref', 'customer_dependency_ref', 'customer_dependency_state', 'change_impact', 'measurement_window', 'measurement_result_state', 'remedy_ref', 'legal_review_state', '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('governing_document_type') not in {'MSA','SOW','SLA','CHANGE_ORDER'}: return 'HOLD_GOVERNING_DOCUMENT_TYPE_INVALID'
    if x.get('request_scope_state') not in {'IN_SCOPE','OUT_OF_SCOPE','DISPUTED'}: return 'HOLD_SCOPE_STATE_INVALID'
    if x.get('scope_origin') not in {'SOW','APPROVED_CHANGE','FRAMEWORK_TERM','SLA_ONLY'}: return 'HOLD_SCOPE_ORIGIN_INVALID'
    if x.get('acceptance_state') not in {'NOT_APPLICABLE','PENDING','APPROVED','REJECTED','DISPUTED'}: return 'HOLD_ACCEPTANCE_STATE_INVALID'
    if x.get('change_approval_state') not in {'NOT_APPLICABLE','PENDING','APPROVED','REJECTED'}: return 'HOLD_CHANGE_APPROVAL_STATE_INVALID'
    if x.get('conflict_state') not in {'CLEAR','OPEN','DISPUTED','RESOLVED'}: return 'HOLD_CONFLICT_STATE_INVALID'
    if x.get('legal_review_required') not in {'YES','NO'}: return 'HOLD_LEGAL_REVIEW_REQUIRED_INVALID'
    if x.get('legal_review_state') not in {'NOT_REQUIRED','REQUIRED_OPEN','APPROVED'}: return 'HOLD_LEGAL_REVIEW_STATE_INVALID'
    if x.get('customer_dependency_state') not in {'DECLARED','NOT_APPLICABLE','MISSING','BLOCKING'}: return 'HOLD_CUSTOMER_DEPENDENCY_STATE_INVALID'
    if x.get('measurement_result_state') not in {'CURRENT','STALE_RECOMPUTE_REQUIRED','NOT_APPLICABLE'}: return 'HOLD_MEASUREMENT_RESULT_STATE_INVALID'
    # Scope authority boundary
    if x.get('scope_origin')=='SLA_ONLY' and x.get('request_scope_state')=='IN_SCOPE': return 'HOLD_SLA_NOT_SCOPE_AUTHORITY'
    if x.get('request_scope_state')=='OUT_OF_SCOPE' and x.get('change_required') is not True: return 'HOLD_OUT_OF_SCOPE_CHANGE_REQUIRED'
    if x.get('request_scope_state')=='OUT_OF_SCOPE' and x.get('change_request_id') in {'NONE','NOT_APPLICABLE'}: return 'HOLD_CHANGE_REQUEST_MISSING'
    if x.get('change_scope_moved') is True and x.get('change_approval_state')!='APPROVED' and x.get('acceptance_state')=='APPROVED': return 'HOLD_UNAPPROVED_CHANGE_ACCEPTANCE_CONFLICT'
    if x.get('request_scope_state')=='IN_SCOPE' and x.get('acceptance_state')=='APPROVED' and x.get('acceptance_criteria_ref') in {'NONE','NOT_APPLICABLE'}: return 'HOLD_ACCEPTANCE_CRITERIA_MISSING'
    # Delivery and dependency evidence
    if x.get('delivery_schedule_current') is not True: return 'HOLD_DELIVERY_SCHEDULE_STALE'
    if x.get('customer_dependency_state')=='MISSING': return 'HOLD_CUSTOMER_DEPENDENCY_MISSING'
    # SLA evidence is performance evidence, not scope authority
    if x.get('measurement_window_current') is not True: return 'HOLD_SLA_MEASUREMENT_WINDOW_CHANGED'
    if x.get('measurement_result_state')=='STALE_RECOMPUTE_REQUIRED' or x.get('metric_result_recomputed') is not True: return 'HOLD_SLA_METRIC_RECOMPUTE_REQUIRED'
    # Conflict and priority
    if x.get('document_conflict_detected') is True and x.get('conflict_state')=='CLEAR': return 'HOLD_CONFLICT_NOT_RECORDED'
    if x.get('priority_rule_applied') is True and x.get('document_priority_rule_ref') in {'NONE','NO_RULE','UNKNOWN'}: return 'HOLD_PRIORITY_RULE_AUTHORITY_MISSING'
    if x.get('conflict_state') in {'OPEN','DISPUTED'}:
        if x.get('legal_review_required')!='YES': return 'HOLD_CONFLICT_LEGAL_REVIEW_REQUIRED'
        if x.get('legal_review_state')=='REQUIRED_OPEN': return 'CONTRACT_CONFLICT_REVIEW_REQUIRED'
    # Remedy does not invent legal meaning
    if x.get('remedy_invoked') is True and x.get('remedy_ref') in {'NONE','NOT_APPLICABLE','UNKNOWN'}: return 'HOLD_REMEDY_AUTHORITY_MISSING'
    # Transfer recomputation
    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('customer_dependency_state') not in {'DECLARED','NOT_APPLICABLE'}: return 'HOLD_TRANSFER_CUSTOMER_DEPENDENCY_UNRESOLVED'
        return 'TRANSFER_CONTRACT_REVIEW_READY'
    if x.get('request_scope_state')=='OUT_OF_SCOPE' and x.get('change_approval_state') in {'PENDING','REJECTED'}: return 'SCOPE_CHANGE_REVIEW_READY'
    return 'CONTRACT_SUITE_REVIEW_READY'
