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):
|
|
|
|
|
"""决策系统缓存按股票/动作,缺同依据的既有研判就必须要求重新回答。"""
|
|
|
|
|
keys = ('plan_basis', 'scan_params_hash', 'portfolio_context_hash')
|
|
|
|
|
return (not previous or any(previous.get(k) is None for k in keys)
|
|
|
|
|
or any(fingerprint(hard.get(k)) != fingerprint(previous.get(k)) for k in keys))
|