diff --git a/deploy/profile-scripts/mofin_db.py b/deploy/profile-scripts/mofin_db.py index b3262770..0350e33b 100644 --- a/deploy/profile-scripts/mofin_db.py +++ b/deploy/profile-scripts/mofin_db.py @@ -2131,7 +2131,7 @@ def write_holding_strategy(conn, code: str, name: str, data: dict, data['stop_loss'] = float(_op[2]) data['take_profit'] = float(_op[3]) print(f" [AUTHORITY-POS] {code} 保护系统仓位'{_old_pos_val[:30]}'", flush=True) - elif _old_ra: + elif _old_ra: try: from datetime import datetime as _ddt3, timedelta as _dtd3 _ra_dt3 = _ddt3.fromisoformat(str(_old_ra)[:19]) diff --git a/deploy/profile-scripts/mofin_health.py b/deploy/profile-scripts/mofin_health.py index 6c4fe9cf..1c24e239 100644 --- a/deploy/profile-scripts/mofin_health.py +++ b/deploy/profile-scripts/mofin_health.py @@ -940,6 +940,24 @@ def build_report(): flow_status = "read_only" else: flow_status = "orphan" + wcnt, rcnt = len(writers), len(readers) + # 2026-08-19 多写方风险(老莫:数据篡改风险的根源) + # 写方>=3 → 高风险(多个模块竞争写同一实体,可能互相覆写/篡改) + if wcnt >= 3: + risk = "high" + risk_note = f"⚠️多写方{wcnt}(竞争写,可能篡改)" + elif wcnt == 0: + risk = "orphan" + risk_note = "无写方(死实体)" + elif rcnt == 0: + risk = "write_only" + risk_note = "写无读(幽灵)" + elif wcnt == 2: + risk = "watch" + risk_note = f"{wcnt}写方(需关注)" + else: + risk = "ok" + risk_note = "" entities.append({ "name": tname, "desc": TABLES_DESC.get(tname, ""), @@ -947,11 +965,16 @@ def build_report(): "rows": cnt, "readers": readers[:10], "writers": writers[:10], + "writer_count": wcnt, + "reader_count": rcnt, + "multi_writer": wcnt >= 3, + "risk": risk, + "risk_note": risk_note, "has_input": has_input, "has_output": has_output, "orphan": flow_status in ("orphan", "read_only", "write_only"), "flow_status": flow_status, - "warn": flow_status != "healthy", + "warn": flow_status != "healthy" or wcnt >= 3, "flow_detail": flow_detail, }) diff --git a/static/mofin_health.html b/static/mofin_health.html index 4630f6b7..84923ce8 100644 --- a/static/mofin_health.html +++ b/static/mofin_health.html @@ -269,6 +269,14 @@ function renderDataFlow(entities, jsonFiles, architecture) { } html += `显示 ${filtered.length} 个数据表 + ${jf.length} 个JSON文件的读写流向。绿色→写入,蓝色→读取。`; + // 2026-08-19 多写方风险汇总 + const risky = filtered.filter(e => (e.risk==='high'||(e.writers||[]).length>=3)); + if (risky.length > 0) { + html += `
`; + html += `
🚨 篡改风险:${risky.length} 个数据实体有多写方竞争
`; + html += `
多写方=多个模块写同一实体,可能互相覆写/篡改。重点:holding_strategies(策略参数)、candidates(候选池)、todos等。
`; + html += `
`; + } // 按写入方数量排序,最活跃的排前面 filtered.sort((a,b) => (b.writers||[]).length - (a.writers||[]).length); @@ -282,7 +290,18 @@ function renderDataFlow(entities, jsonFiles, architecture) { const rDesc = fd.readers || {}; html += '
'; - html += `
🗄️ ${e.name}
`; + // 2026-08-19 多写方风险徽标(老莫:数据篡改风险) + const wcnt = e.writer_count || (e.writers||[]).length; + const rcnt = e.reader_count || (e.readers||[]).length; + let riskBadge = ''; + if (e.risk === 'high' || wcnt >= 3) { + riskBadge = `⚠️多写方${wcnt}·篡改风险`; + } else if (wcnt === 0) { + riskBadge = `☠️无写方·死实体`; + } else if (wcnt === 2) { + riskBadge = `${wcnt}写方·关注`; + } + html += `
🗄️ ${e.name}${riskBadge} (${wcnt}写/ ${rcnt}读)
`; html += `
${e.desc||''}
`; // 综合总结 if (fd.summary) {