# No-answer and low-confidence policy for PB-RAG-06-02.
# Load this file at start-up. Never hard-code thresholds in application code.
# Every ${PLACEHOLDER} is set from the calibration run (step 4) and agreed with compliance (step 6).
version: 1
policy_owner: ${POLICY_OWNER_ROLE}          # a role, not a person
approved_on: ${APPROVAL_DATE}               # YYYY-MM-DD, from the change record
change_record: ${CHANGE_RECORD_ID}

# Thresholds are set per ranking mode. Scores from different modes are not comparable.
ranking_mode: semantic                      # semantic | vector | hybrid_no_semantic
signals:
  semantic:
    score_field: "@search.rerankerScore"    # range 0.00 to 4.00
    refuse_below: "${SEMANTIC_REFUSE_BELOW}"
    caveat_below: "${SEMANTIC_CAVEAT_BELOW}"
  vector:
    score_field: "@search.score"            # range depends on the similarity metric
    refuse_below: "${VECTOR_REFUSE_BELOW}"
    caveat_below: "${VECTOR_CAVEAT_BELOW}"
  hybrid_no_semantic:
    score_field: null                       # RRF scores are too volatile to threshold
    refuse_below: null
    caveat_below: null
  min_hits_after_filter: 1                  # counted after security trimming and score thresholds
  require_model_answerable: true            # answerable flag from the structured output (PB-RAG-06-01)
  runtime_groundedness_check: ${RUNTIME_GROUNDEDNESS_ENABLED}   # true | false

# Order of checks: restricted_topic -> no_hits -> low_score_refuse -> generate
# -> not_answerable -> ambiguous -> ungrounded -> low_score_caveat -> answer.
behaviour:
  no_hits:          {action: refuse,              wording: R1}
  low_score_refuse: {action: refuse,              wording: R1}
  not_answerable:   {action: refuse,              wording: R2}
  ambiguous:        {action: ask_follow_up,       wording: F1}
  ungrounded:       {action: refuse,              wording: R3}
  low_score_caveat: {action: answer_with_caveat,  wording: C1}
  restricted_topic: {action: escalate,            wording: E1, route_to: "${ESCALATION_QUEUE}"}
  answer:           {action: answer,              wording: null}

restricted_topics: []                       # e.g. legal advice; agreed with compliance

targets:                                    # team defaults; confirm or change in step 6
  answer_rate_on_answerable: 0.90           # share answered or answered with caveat
  non_answer_rate_on_unanswerable: 0.90     # share refused, followed up or escalated
  tolerance_questions_per_set: 1            # dry-run may differ from calibration by one question per set

logging:
  log_decision_reason: true                 # store the rule that fired, never the user's raw data
  log_scores: true

review:
  cadence: monthly
  owner: ${POLICY_OWNER_ROLE}
  sample_size: "${REVIEW_SAMPLE_SIZE}"
