69 lines
3.4 KiB
Python
69 lines
3.4 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""人工裁决理由的码表与拼句 (2026-09-14 系统自决包, 台账 015)。纯逻辑, 零外部依赖, 可单测。
|
||
|
|
|
||
|
|
系统自决把绝大多数「该不该做」接了过去, 剩下真交人的那几张提议, 人裁决时从预设理由里选一条,
|
||
|
|
选「其他」再补一句 —— 不再每次手写一整段。这里放两张码表 (采纳 / 驳回) 与一个拼句函数,
|
||
|
|
把「决定 + 码 + 补充 + 自由文本」拼成落进评审账本的一句话, 并回出用到的码 (进硬数字备查)。
|
||
|
|
|
||
|
|
页面拿这两张表铺预设按钮 (GET /api/proposals/decide-reasons); 后端在裁决入口再校验一次 ——
|
||
|
|
选「其他」不填补充、或码与决定对不上, 一律回中文错误, 不落账 (与研判不可用交人同一层的严)。
|
||
|
|
旧的自由文本 reason 仍然接受 (向后兼容: 脚本、旧缓存页面照发照收)。
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
# 驳回理由码 → 中文标签。最后一个是「其他」, 选它必须填补充。
|
||
|
|
DECLINE_REASONS = {
|
||
|
|
"DECLINE_CONSENSUS_MISMATCH": "与研判结论不符",
|
||
|
|
"DECLINE_TECH_WEAK": "技术面已转弱",
|
||
|
|
"DECLINE_POSITION_FULL": "仓位已达上限",
|
||
|
|
"DECLINE_SECTOR_CONCENTRATED": "行业集中度过高",
|
||
|
|
"DECLINE_WAIT_PULLBACK": "等待回调再议",
|
||
|
|
"DECLINE_MARKET_RISK": "大盘风险偏高",
|
||
|
|
"DECLINE_OTHER": "其他",
|
||
|
|
}
|
||
|
|
|
||
|
|
# 采纳理由码 → 中文标签。最后一个是「其他」, 选它必须填补充。
|
||
|
|
ACCEPT_REASONS = {
|
||
|
|
"ACCEPT_CONSENSUS": "合议放行,按系统建议执行",
|
||
|
|
"ACCEPT_MANUAL_JUDGE": "人工判断机会成立",
|
||
|
|
"ACCEPT_PER_COMMAND": "按既有命令执行",
|
||
|
|
"ACCEPT_RISK_CONTROL": "风控需要",
|
||
|
|
"ACCEPT_OTHER": "其他",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def reasons_for(decision) -> dict:
|
||
|
|
"""按决定取对应的码表。ACCEPTED 用采纳表, 其余 (DECLINED) 用驳回表。"""
|
||
|
|
return ACCEPT_REASONS if str(decision or "").upper() == "ACCEPTED" else DECLINE_REASONS
|
||
|
|
|
||
|
|
|
||
|
|
def compose(decision, reason_code=None, reason_note=None, reason=None):
|
||
|
|
"""把裁决理由拼成一句话。返回 (账本句, 用到的码或 None)。
|
||
|
|
|
||
|
|
decision: ACCEPTED / DECLINED, 决定用哪张码表。
|
||
|
|
reason_code: 预设理由码 (可空)。给了就必须落在对应码表里, 否则报错。
|
||
|
|
reason_note: 补充说明 (选「其他」时必填)。
|
||
|
|
reason: 旧的自由文本 (向后兼容)。只有它、没有码时, 原样当账本句。
|
||
|
|
|
||
|
|
码与自由文本都空 → 报错 (空理由不收)。码不在表里 / 选「其他」不填补充 → 报错。
|
||
|
|
错误一律是能直接给人看的中文 ValueError。
|
||
|
|
"""
|
||
|
|
decision = str(decision or "").upper()
|
||
|
|
table = reasons_for(decision)
|
||
|
|
code = (reason_code or "").strip() or None
|
||
|
|
note = (reason_note or "").strip()
|
||
|
|
free = (reason or "").strip()
|
||
|
|
label_side = "采纳" if decision == "ACCEPTED" else "驳回"
|
||
|
|
|
||
|
|
if not code and not free:
|
||
|
|
raise ValueError("采纳或驳回都要给理由:选一个预设理由,或写一句自由说明")
|
||
|
|
if code:
|
||
|
|
if code not in table:
|
||
|
|
raise ValueError(f"理由码 {code} 不适用于本次{label_side}")
|
||
|
|
if code.endswith("_OTHER") and not note:
|
||
|
|
raise ValueError("选「其他」时必须填补充说明")
|
||
|
|
sentence = table[code] + (f";补充:{note}" if note else "")
|
||
|
|
return sentence[:500], code
|
||
|
|
# 只有自由文本 (旧字段兼容): 原样当账本句, 不带码。
|
||
|
|
return free[:500], None
|