2026-10-15 10:27:12 +08:00
|
|
|
"""计划消费与提议版本核验的纯函数;不改变准入、仓位或自然语言判断。"""
|
|
|
|
|
import hashlib
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fingerprint(value):
|
|
|
|
|
return hashlib.sha256(json.dumps(value, ensure_ascii=False, sort_keys=True,
|
|
|
|
|
separators=(',', ':'), default=str).encode()).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def basis(row):
|
|
|
|
|
# 排名与审计时间不是资格/风险/数量依据。完整评析引用变化需要重新研判。
|
|
|
|
|
return {k: row.get(k) for k in ('score', 'tier', 'upside', 'heat', 'theme',
|
|
|
|
|
'verdict', 'risk', 'missing', 'reasons', 'logic_state', 'company_gate',
|
|
|
|
|
'company_review', 'company_review_text', 'events', 'pricing_state', 'news')}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def portfolio_context(view, commitments, stock=None):
|
|
|
|
|
"""持仓/资金/在途承诺变化使旧数量依据失效;价格漂移仍由执行终检管理。"""
|
|
|
|
|
totals = view.get('totals') or {}
|
2026-10-16 10:41:27 +08:00
|
|
|
inflight = {'names': sorted(commitments.get('names') or [])}
|
|
|
|
|
# 实时报价只改变预估占用,不能使同一份确认每次失效。
|
|
|
|
|
# 旧调用方缺少稳定依据时保留原比较;旧提议指纹变化仍须重核一次。
|
|
|
|
|
if isinstance(commitments.get('basis'), list):
|
|
|
|
|
inflight['basis'] = sorted(commitments['basis'])
|
|
|
|
|
else:
|
|
|
|
|
inflight['amount'] = commitments.get('amount')
|
2026-10-15 10:27:12 +08:00
|
|
|
return fingerprint({'held': sorted((r['ts_code'], r.get('total_qty'), r.get('sector'))
|
|
|
|
|
for r in view.get('held') or []),
|
|
|
|
|
'cash_source': totals.get('cash_source'), 'cash_avail': totals.get('cash_avail'),
|
|
|
|
|
'sector_ready': view.get('sector_ready'), 'stock': stock or {},
|
2026-10-16 10:41:27 +08:00
|
|
|
'inflight': inflight})
|
2026-10-15 10:27:12 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def compare(old, current, code):
|
|
|
|
|
"""返回 unavailable/material/unchanged。资格失效由调用方通过缺候选表达。"""
|
|
|
|
|
if not current or not (current.get('decision') or {}).get('id'):
|
|
|
|
|
return 'unavailable'
|
|
|
|
|
state = current.get('refresh_status') or {}
|
|
|
|
|
if code in (state.get('pending_codes') or []) or current.get('consumption_error'):
|
|
|
|
|
return 'unavailable'
|
|
|
|
|
if old is None or fingerprint(old) != fingerprint(basis(current)):
|
|
|
|
|
return 'material'
|
|
|
|
|
return 'unchanged'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def needs_new_judgement(hard, previous):
|
2026-10-20 11:31:02 +08:00
|
|
|
"""仅研究依据变化重问模型;组合及参数变化复算规则和数量。"""
|
|
|
|
|
return (not previous or previous.get('plan_basis') is None
|
|
|
|
|
or fingerprint(hard.get('plan_basis')) != fingerprint(previous.get('plan_basis')))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def confirmation_revision(qty, hard, verdict):
|
|
|
|
|
"""确认针对可见的建议内容;后台检查时间、名次及无关版本不让确认失效。"""
|
|
|
|
|
return fingerprint({'qty': qty, 'basis': hard.get('plan_basis'),
|
|
|
|
|
'advice': hard.get('advice'), 'reason': hard.get('reason'),
|
|
|
|
|
'verdict': verdict.get('verdict'), 'judge_reason': verdict.get('reason'),
|
|
|
|
|
'confidence': verdict.get('confidence')})
|