Commit Graph
336 Commits
Author SHA1 Message Date
wecom-fullstack b3f31f9ac9 fix(maps): 画布连线键与目标改名解耦(objective_id 锚定)+ nodeRefs 位置兜底 + 保守反错绑断言(P3)
ci/woodpecker/push/main Pipeline failed
- map_connections 读侧以 from/to_objective_id 反查当前 dimensions 树定位节点,改名/重排不再静默断线
- 悬停/拖拽期 map_objectives 未返回时按位置兜底(仅同层同序位有效),并记录 stale_objective_id
- 新增 tests/test_conn_key_rename_stability.py(含 2 条保守反错绑负测用例)
- 零 DDL、零 health_score 改动(问3=A)
2026-10-23 15:49:36 +08:00
wecom-fullstackandHermes CI Fix 986dec516e fix(maps): 画布因果连线零绘制(节点双键注册) + 战略回顾 total_hold 未初始化 500
ci/woodpecker/push/main Pipeline was successful
- StrategyLayer.vue: setNodeRef 双键注册(渲染键 layer-index 与数据键 layer:目标名),$el 归一化为 DOM,卸载时清理数据键
- maps.py: total_hold 初始化 0 + summary.hold 透出(修复 review?level=strategic 500 UnboundLocalError)
- tests: 新增 test_maps_review_hold_20261022.py(待核/普通/空/NULL/无level 五例,负对照已验证会失败)

任务: project-20261022-cma-map-canvas-connections-not-drawn-fullstack
阶段A候选件;未 push、未部署(等阶段B放行)
2026-10-22 16:50:10 +08:00
项目Bot 4cad2b73c4 fix(lead): 客户咨询通知改走系统总线8660→任总DM,替掉已下线的 ai-cs 通道
ci/woodpecker/push/main Pipeline was successful
- ai-cs 用户/profile 已不存在,旧 runuser 调用必然失败且被 except: pass 吞掉
- 线索只落 /root/leads/leads.json,任总侧零通知(线上活缺陷)
- 新通道:POST 127.0.0.1:8660/bus/send,notice_class=fallback,收件人不变(200653946)
- 新增 /root/leads/notify.log 落痕,杜绝静默失败
2026-10-22 16:47:25 +08:00
wecom-project 1efa6b7df9 refactor(lead): 退役 notify_wecom 8800 全员群推送(只保留 notify_boss DM)
ci/woodpecker/push/main Pipeline was successful
- 删除 notify_wecom()(JSON body 打 8800/send,中继只解析 form-encoded,从未发出过消息)与吞异常的 except: pass
- 客户咨询通知唯一通道:notify_boss 推任总 DM(经 ai-cs 分流)
- 去掉随之无用的 urllib.request 导入
- 零行为变更:删的是从不生效的代码;防止后人「修成 form-encoded」把客户个人信息推进全员群

关联决策:ASK-20261021-09(任总 2026-10-22 11:16:8800 仅限 notice_class=content)
派单:finance-20261022-cma-lead-retire-8800-project(父任务 yanxue-20261022-relay-8800-koujing-finance)
2026-10-22 12:49:06 +08:00
wecom-fullstack 9bbe3001bf fix(alerts): kpi_alerts 去重键改生成侧语义键 dedupe_key(A2)— 文案改动不再重复落库
ci/woodpecker/push/main Pipeline was successful
- 列 dedupe_key VARCHAR(160) NULL + 唯一索引 (entity_id,kpi_id,alert_type,dedupe_key)
- write_alerts 三级判重:dedupe_key 命中 / 历史 NULL 行反解兼容 / 无命中插入
- 并发兜底:savepoint 捕获 IntegrityError 计跳过
- dry-run would_insert 改用 count_would_insert(修复 len(hits) 虚高)
- DDL 幂等执行器 scripts/ddl_kpi_alerts_dedupe.py(check/apply/rollback)
- tests/test_dedupe_key.py 7 passed(T1-T7b);alert/overdue 回归 173 passed
2026-10-22 11:29:54 +08:00
wecom-fullstackandHermes CI Fix adcd118498 feat(maps): P1 四层因果结构化 map_connections 表 + 迁移脚本 + analysis 只读端点
- 新表 map_connections(entity_id/map_id/from_node/to_node/from_layer/to_layer/
  from_objective_id/to_objective_id/effect/label/sort_order),唯一键 (map_id,from,to) 保幂等
- 节点双格式兼容:层:中文目标名(生产存量 31 条)与 层-序号(前端现用)
- canvas_data 保留不动(双写期);POST/DELETE 写表 + 同步 JSON,失败仅告警不阻断
- GET /connections 表优先、表空回落 canvas_data;新增 GET /connections/analysis
  (层统计/跨层分布/断链/反向告警不拦截)
- scripts/migrate_map_connections.py 四态(check/apply/idem/rollback)+ canvas 快照回滚点
- tests: 9 条(双格式解析、双写、回落、删除同步、分析统计与告警)
2026-10-22 05:19:45 +08:00
wecom-fullstack 0a6525edab feat(strategy): P0 战略基座 — strategy_charter + GET/PUT /api/cma/charter + P组入口
ci/woodpecker/push/main Pipeline was successful
- 表 strategy_charter(一账套一行,entity_id 唯一):mission/vision/vision_year/vision_measurable/values_text/value_items(JSON) 全 NOT NULL
- 迁移脚本 backend/scripts/migrate_strategy_charter_20261021.py:--check/--apply/--down(回滚先 mysqldump,有数据需 --force),零 INSERT
- API:GET 无行 → filled=false 空态(禁示例数据);PUT 字段白名单 + 账套隔离三道 + vision_year 4位年份 + value_items [{name,meaning}] 结构校验
- 前端:P 组「战略章程」入口 + MyDialog 编辑(ceo/finance 可写,其余只读);战略地图页顶部只读章程条
- 用例 43 条 + auth-surface 58 条 = 101 passed;vite build rc=0
2026-10-21 18:54:09 +08:00
wecom-fullstack 7022a05928 fix(ar): 单户逾期文案口径——账龄最老 N 天 / 逾期合计 ¥X(占该户 Y%)
ci/woodpecker/push/main Pipeline was successful
D2:%d 为最老逾期天数、%s 为 31 天以上逾期合计,旧文案「期末逾期 151 天以上 ¥300,120」
会被读成最老档即 30 万(HKJJ120 150+ 实为 65,115.00)。只改文案,判据/params/历史行零变更。
新增回归用例:HKJJ120 逐档实数 ⇒ 账龄最老 151 天,逾期合计 ¥300,120.00(占该户 85.14%)。
2026-10-21 16:01:08 +08:00
Hermes CI Fix a7a663f6cd feat(ar): 逾期预警判据 G5——90-150 前池 5pp/10万 + 责任人点名优先(阶段B 受批面)
ci/woodpecker/push/main Pipeline was successful
2026-10-21 11:21:22 +08:00
fullstack-botandHermes CI Fix 71867c4c7a feat(ar): 应收账龄看板 + 逾期预警 overdue 接入(阶段A)
T1 逾期预警(§6 财务 2026-10-20 23:23 口径):
- alert_rules 支持 rule_type=ar_overdue;阈值/责任人/排除名单全量走 params(代码零业务常量)
- 90 天占比红线 key 保留但默认 null 停用(不回落 0.25);黄线 15%-25% 保留
- 新增逐户红线「>30 天且 >5 万」+ 聚合红线「150+ 占比环比上升 = 结构恶化」
- assignee 派生:manual → 近 3 个月主业务员(zxt_masterbill emp_id → employee_dict.u_Name,
  缺表时降级同库摘要制单人姓名)→ 部门负责人(渠道 118 / 商务 114 / 直属酒窖 117);
  两路皆失败 ⇒ 该户不生成、进 skipped[],禁落空 assignee
- POST /run-ar-overdue(dry_run 默认 true)+ check-all 分支;只 INSERT 同一 message 幂等

T2 账龄看板:
- GET /api/cma/ar-aging/{meta,trend,overview,customers,customers/export};每条查询带 entity_id
- 三口径分列、caliber=all → 400;缺期 has_data=false 且金额 null(不补 0)
- ArAging.vue:口径后缀/截止期徽标/趋势断点/六档堆叠/逐户可排序导出

单测 21 项全通过(SQLite 夹具,不连生产库);vite build 通过。
阶段A 零生产写:未 push、未部署、未 --apply、未 dry_run=false。
2026-10-21 08:42:24 +08:00
wecom-fullstack 790be2d6b1 fix(strategy-map): 三处缺陷——D1 完备度/摘要路由先注册(修 /completeness/objectives 422)、D2 连线增删显式 flag_modified(修 DELETE 200 不落库)、D3 拖拽命中改 rect 判定(修窄屏知识面板遮挡导致拖拽失效)
ci/woodpecker/push/main Pipeline was successful
2026-10-20 20:35:28 +08:00
wecom-fullstack 86e24c6310 fix(strategy-map): 移除悬空 import kpi_target_reader(该模块全仓不存在,致 app.main 导入失败/部署自检中止)→ 目标值取 target_monthly or target_value
ci/woodpecker/push/main Pipeline was successful
2026-10-20 13:01:02 +08:00
wecom-fullstack c030fb7d18 fix(strategy-map): 口径违禁串代码级清零(9,492.6/83.9%/19/19/el-dialog 仅存于注释)+ components.d.ts 补 MapDimensionFilter 2026-10-20 11:33:58 +08:00
Hermes CI Fix 53cad7eaa9 merge: origin/main (A3 三维切片) 合入战略地图改造分支 2026-10-19 14:32:33 +08:00
Hermes CI Fix a8141e8879 wip(strategy-map): v4 口径落码(分线营收/新式毛利率/管理指标/C1 hold/57 归档) 2026-10-19 14:32:08 +08:00
wecom-fullstackandHermes CI Fix d85dd2a155 feat(strategy-map): A3 三维切片(时间/门店/产品)——切片重算 + 显式空态 + entity 隔离
ci/woodpecker/push/main Pipeline was successful
- 新增 /api/cma/maps/{id}/dimension-options 与 /{id}/slice(只读)
- 时间:kpi_values.period 月/季/年;flow 求和、ratio 均值、balance/status 取最新
- 产品:product_sales + 逐月源一致性闸门(偏差>1% 不切片)
- 门店:库内无门店级事实 ⇒ available=false + reason,绝不回退总额
- 前端 MapDimensionFilter 挂到 MapCanvas,切换即 emit 切片,清除恢复 /review
2026-10-19 13:32:49 +08:00
Hermes CI Fix 4d20c27de4 wip(strategy-map): V1+V3 合版 + v2/v3 口径(完备度/C1 hold/57 归档组) 2026-10-19 10:33:40 +08:00
Hermes CI Fix a67f4b688f feat(receivables): customer-level rows only + register hanke_ar as balance snapshot batch
ASK-20261001-33: api_receivables counts only rows with a non-blank counterparty (monthly aggregate rows stay in table but out of the collection page cards/aging/detail/owners); BALANCE_SNAPSHOT_SOURCES gains hanke_ar (default skip, no per-customer overdue alerts). New pytest file covers aggregate-row exclusion, e2 baseline digitwise, snapshot registration.
(cherry picked from commit 475f78bc8c5f67f2716c721172a88ff89ec58227)
2026-10-18 16:42:06 +08:00
Hermes CI Fix fa1267c323 feat(budget): effective-version caliber + process-order tabs + 4 missing rings
Task: CMA-20261003 (budget module governance). Local commit only; push is
gated on release approval (see report).

Backend
- app/utils/budget_status.py (new): single source for the effective-version
  caliber. EFFECTIVE_STATUSES = approved + active; effective_plan_clause()
  enforces entity isolation; pick_effective() priority active > approved >
  updated_at desc. Fixes: approved budgets disappeared from the execution
  surface because the three queries only read status='active'.
- budget.py: /comparison, /comparison/kpi/{id}, /deviation-check now read the
  effective surface; versions list gains is_effective / can_release fields;
  submit/approve are fail-closed (draft|active -> submitted -> approved).
- deviation_engine.get_budget_for_kpi: effective surface + deterministic pick.
- 4 missing rings, zero DDL, appended to the same router:
  POST /versions/release (approved -> active, idempotent by operation_logs),
  POST /adjustments (derive a new draft version, base rows untouched),
  GET /control/status (effective budget vs actual -> ok/warn/over),
  GET /assessment (achieve rate + A/B/C/D grade).
  Audit rows written through app.utils.oplog.log_op with entity_id.

Frontend
- BudgetManagement.vue: top-level tabs reordered to the process order
  (strategy, method, driver, input, decompose, versions, release, execution,
  deviation, adjust, assess, rolling). Pane name values unchanged, so old
  bookmarks keep working; routes unchanged.
- Cash classify rules / unclassified queue moved to /cash-plan
  (components/CashClassifyPanel.vue); budget actual-value collection moved to
  /data-manage (components/BudgetCollectPanel.vue). Mount points only: no
  change to cash_plans data, classify logic, collection logic or kpi_values.
- api/index.ts: 4 ring endpoints exposed.

Docs / tests
- docs/budget-module-governance-design.md: phase-0 design doc (status machine,
  tab order, domain boundaries, 4-ring design, cleanup write pack scope).
- tests/test_budget_status_machine.py (new): effective surface, version
  guards, release idempotency, adjustment, control and assessment.
- tests/test_budget.py: existing cases adapted to the fail-closed contract.

(cherry picked from commit 4267fc4d25ee45c5675a541af9777be96fe9f9c2)
2026-10-18 16:32:55 +08:00
Hermes CI Fix 75a48f55d2 feat(strategy-map): live board reading map_objectives + kpi_targets (read-only)
Task: finance-20261011-strategy-map-frontend-fullstack (P2, read-only integration)

- backend/app/utils/strategy_board.py: single source of truth for the level
  caliber (layer colors, inverse-kpi explicit whitelist, status class by
  unit, zero guard, period ranking, latest-actual picking). Pure functions.
- backend/app/api/maps.py: GET /api/cma/maps/{map_id}/strategy-board
  - nodes straight from map_objectives (sorted by sort_order, id)
  - node<->kpi binding with 4-step fallback, binding_source echoed back
  - targets from kpi_targets (target_type compatible: yearly + annual),
    deduped per (kpi_id, period) with yearly preferred
  - actuals from kpi_values actual-only scope, latest period
  - causality edges from kpi_causality, out-of-map endpoints dropped+counted
  - entity_id taken from the token only (no cross-tenant reads)
  - zero writes; nothing is persisted by this endpoint
- frontend/src/views/StrategyMap.vue: /strategy-map live board (4 swim lanes
  bottom-up, node cards, 20 causality arrows in SVG, period switch, A3 print).
  No emoji, null guards everywhere (no toFixed on null).
- api/index.ts + router/index.ts + permission.ts: route + menu registration
  (/strategy-map, zone A read-only, roles ceo/finance/business/it).
- backend/tests/test_strategy_board_20261011.py: 33 cases (spec section 4 /
  4.1 / 4.2 / 4.3 + section 7 fixtures + threshold boundaries + period rank).
- backend/tests/test_map_objectives_kpis_form_20261011.py: 14 cases for the
  four kpis column shapes (list, json string, object list, NULL/[]/weird).

Not included: push + backend restart (production write, separate gate).

(cherry picked from commit c675bde6b4beaf1d8a9d02a80f5434203a53f348)
2026-10-18 16:31:52 +08:00
Hermes CI Fix 2cff58ca6a feat(docs-center): report reading section (type=report) on /api/cma/docs
Increment #2 over PoC ede0524 (task yanxue-20260924-cma-report-source).

- backend derives reports from existing drop points (wisdom/_reports + every profile pending-tasks/_reports), read live per request, no cache, no rebuild

- gate = front-matter plus non-empty conclusion; missing either means silently out of the list (receipts/audits never enter); title/date/domain/sources missing are marked incomplete instead of rejected

- entity + role partitioning (ceo/it cross-entity; others own entity plus group/generic); kpi_refs resolve to a one-hop metric link

- new GET /api/cma/docs/report/{rid} detail endpoint; knowledge branch unchanged (type defaults to knowledge)

- frontend: CMAKnowledge adds a report reading pane (filters + detail dialog), reuses the existing markdown renderer, no new deps

(cherry picked from commit 7c6e0857e68133f5ba2a539fd3569aeb82547911)
2026-10-18 16:31:52 +08:00
Hermes CI Fix 57f9dbae15 fix(action-plans): 「已逾期」改日期粒度(口径 B,当天到期不算逾期)
- _apply_overdue():`ActionPlan.due_date < datetime.now()` → `... < 今天0点`
  (唯一实现,plan_stats 与 list_plans 共用,故一处改即两端点同改)
- 口径同步注释(口径 A→B 的沿革与分界式);行为改动仅此一处
- 测试夹具改到日粒度(整天偏移),新增「今日到期不算逾期」鉴别行与
  `test_overdue_caliber_is_date_granularity` 口径锁定用例(防回退 A)
- 依据:任总 2026-09-22 10:10 拍板口径 B;前端角标 ActionPlanLibrary.vue::isOverdue
  与 cash.py::_plan_dict 本就同粒度,本单未动
- 只改码:未 push / 未部署 / 未重启(发布须放行)

(cherry picked from commit 2da5b6da018013f860430913b5b0e0436f966685)
2026-10-18 16:31:52 +08:00
wecom-project 5fa83e2532 fix(knowledge): related_page 过滤精确化(子串包含 -> 归一候选键等值)
KBFF-v11c(派单 KBFF-v11c-relatedpage-exact-match-filter · A 侧 wecom-yanxue · 执行人 wecom-project)
本提交=代码面切片:未 push、未部署;生产部署另走发布单 + approval-gate(回滚 = git revert <本 sha>)。

- backend/app/api/knowledge_articles.py:related_page 过滤由 LIKE '%<v>%'(子串包含,短键/前缀超集命中)
  收敛为「归一候选键 IN ∪ 模板子路由 <键>/:<占位符>」;不可归一入参 -> 空集(不退化为全量)
- backend/app/utils/route_key.py(新增):匹配口径唯一实现处(归一化 / 候选键派生 / 模板模式)
- backend/tests/test_kbff_v11c_related_page_exact_match_20261012.py(新增):42 用例
  —— 生产 seed 7 值逐值回归(各中 1 行)、短键 /maps->0 行 /kpi->0 行、带参路由与尾斜杠/query/大小写兼容、
  改造前 contains 语义负对照、账套分区与 category 组合语义保持

(cherry picked from commit 2a5cb182c237ae724d4b506b68eb48810ba27b16)
2026-10-18 16:31:52 +08:00
fullstackandHermes CI Fix 60b6ef3bfe fix(alert-rules): dynamic-thresholds 迭代 Column 当行取值致 500
GET /api/cma/alert-rules/dynamic-thresholds 在 {c.name: getattr(c, c.name) for c in
DynamicThresholdCache.__table__.columns} 里把 Column 对象当 ORM 行取属性,触发
AttributeError: 'Comparator' object has no attribute 'id'。改为列名取自表元数据、
值取自 query 结果行;entity_id 过滤与 limit 50/kpi_id 分支不变。

(cherry picked from commit 4130726be411a74a3318d9dc6bbc518a6c60d0ff)
2026-10-18 16:31:52 +08:00
wecom-fullstackandHermes CI Fix a9dcbf22bc chore(security): 摘除 knowledge_service 硬编码默认 DeepSeek Key(缺 env 抛明确错误/503)
- knowledge_service: 移除 os.getenv 默认值占位,改 _require_deepseek_key() 必填,缺 env 抛 DeepSeekKeyMissing
- knowledge API: generate/{daily,weekly,monthly} 缺 Key 返回 503(可观测,不再静默走模板兜底)
- finance_bot / verify_zxt_vouchers_json / bot_bridge_push: 凭据改 env 优先(CMA_BOT_KEY_* / CMA_BOT_API_KEYS / CMA_BRIDGE_TOKEN),历史共享键仅作兼容期兜底并告警
- tests: 新增 5 例回归(缺 Key 服务层抛错、API 503;有 Key 正常 200)

(cherry picked from commit 859bd8276d7c5aad4bf10ee3399b6cccc188f064)
2026-10-18 16:31:52 +08:00
wecom-fullstack 885cfddbff feat(warning-p0a): 资金缺口阈值外置 + 复算对拍 + 讲人话(数字守恒) + 数据就绪核验
ci/woodpecker/push/main Pipeline failed
P0-A(任务 fullstack-20261017-warning-capability-build-p0):
- cash_thresholds.py:阈值迁 per-entity(复用 alert_rules.rule_type=cash_gap_threshold,
  默认 20/10 万不动;无配置/非法/异常一律回落默认,fail-safe 不改预警行为)
- cash_forecast_engine.py:阈值改从配置读取,响应新增 threshold_source 明写来源
- verify_cash_gap_replay.py:同输入双跑逐字段 diff=0 + 手算 3/3 + 端点逐字段对拍(--live)
- cash_gap_narrative.py:复用 ai_brief.call_deepseek,只写原因/建议;
  断言生成文本数字集合 ⊆ 规则层数字集合,违例即不带文案上线
- audit_warning_data_readiness.py:G0 只读核验(cma_reader SELECT only),
  行数/期间/枚举 + 数据质量缺口(期初全 0、负期末、单批次、无员工维度表)

未 push(push 即部署,须项目侧书面放行);未改生产库/未动 cron。
2026-10-17 23:33:51 +08:00
Hermes FullstackandHermes CI Fix a52faa90a6 fix(dashboard): bep-card terms 分支补字段别名映射(修 289cc8f 引入的 e2 500)
ci/woodpecker/push/main Pipeline was successful
289cc8f(§4-1 博海固定成本仅借方)在 dashboard._bep_sum_field 新增 terms 分支,
把 bep_caliber 的 terms 记法字段名(debit/credit)原样传给 getattr(VoucherDetail, field)
⇒ AttributeError: type object 'VoucherDetail' has no attribute 'debit'
⇒ GET /api/cma/dashboard/bep-card(entity=2 博海)稳定 500(entity=1 走 filters 分支不受影响)。

修复:terms 分支内做字段名映射 debit→debit_amount / credit→credit_amount(与
profit_lines.FIELD_MAP、kpi_bep_deriver._TERM_FIELD 同式),再递归聚合。

回归测试:tests/test_profit_summary_real_caliber_20261014.py 新增
test_dashboard_card_e2_terms_mode_field_alias(修复前 RED:复现同一 AttributeError;
修复后 GREEN:e2 固定成本 98,000 元 / 卡面 9.80 万)。
2026-10-16 18:28:50 +08:00
Hermes CI Fix 289cc8f2cc fix(profit-caliber): 博海口径 §4-1/§4-2/§4-5 落地 —— 固定成本仅借方 + 6403 进变动 + 财务收益单列 + 批次备注
ci/woodpecker/push/main Pipeline was successful
ASK-20261014-04(任总 2026-10-14 11:20「放行4」)批后口径:
- §4-1 博海固定成本弃净额:6601/6602/6603 仅借方(133.98 万,2026-01..09);2026-03 单月由 -9.02 万转正;
  6603 贷方(现金折扣收益 64.40 万)改 finance_income_terms 单列备查,不进固定成本/末行/勾稽。
- §4-2 博海变动成本追加 6403 营业税金及附加(530.17 万)。
- §4-3/§4-4 维持现行(酣客收入/边际贡献基数不动)。
- §4-5 管理利润表页加「数据批次/导入时刻」纯展示备注(import_logs)。
- 口径唯一源仍为 bep_caliber.ENTITY_CALIBERS;报表/卡面/派生器一律经 fixed_cost_spec() 解析(读口写口一致)。
2026-10-16 12:32:52 +08:00
wecom-fullstack e3e6b3ba8a fix(reports): 全表真实取数——清零示例兜底(BS_DEMO/CF_DEMO/_get_demo_block_total/0.50) + 上期同比/勾稽/预测标注 + CF官方口径单位收口
ci/woodpecker/push/main Pipeline failed
2026-10-15 04:13:40 +08:00
hermes 2244cd79b7 fix(reports): profit-summary 真实科目取数重建(去 KPI 比率倒推/硬编码 0.50 + 勾稽自检 + entity 隔离)
ci/woodpecker/push/main Pipeline was canceled
2026-10-14 23:14:41 +08:00
wecom-fullstack 424c33c085 feat(kpi,review): KPI direction 列 + KPITarget 模型 + 复盘产出方入库(R1 收口)
ci/woodpecker/push/main Pipeline failed
R1 收口批次(A 侧裁定 p12-ruling-input-r1-batch.RULING.md 主路径)· 单个快进提交:
- backend/app/models/__init__.py(+42/−2):CMA-KPI-20261003 阶段1 `direction`(KPIDefinition/KPITemplate)
  + ② KPITarget(kpi_targets,11 列 + uk_entity_kpi_period_type + 2 索引)
- 复盘产出方三件(review_builder 含 getattr(direction) 硬化 / review_run / 测试件),原始提交件 26dc242 的重铸件

收口边界申报:本提交**不含** D-1 批次(VoucherDetail.row_uid / ImportLog uq_import_logs_entity_batch_type)——
只读补证显示 prod `cma` 无该列/该唯一键(证据 out/r1-t1-batches.json + out/r1-t1-d1-placement.json),
携行会导致 deploy 后 voucher_details 整实体 SELECT/INSERT 报 1054;D-1 另单(先 DDL 后 ORM)。
2026-10-13 20:33:42 +08:00
Hermes CI Fix 4cdd30e321 feat(lead): 网站线索额外推送任总DM(经 ai-cs 实盘分流脚本)
ci/woodpecker/push/main Pipeline failed
- 新增 notify_boss():POST /api/cma/lead 时,除既有全员群 notify_wecom 外,
  额外经 ai-cs 实盘分流脚本(routing-live.py)通知 wecom:200653946(加性,不改动既有逻辑)
- 跨 profile 调用必须显式传 HOME/HERMES_HOME=/var/lib/wecom-ai-cs/profile:
  cma-backend.service Environment 为空,缺省会解析到非 ai-cs profile(实测无 wecom 目标)
- 失败静默(capture_output + except pass),不影响主流程
任务: ai-cs-20261013-website-lead-to-boss-and-routing-hook-ops
2026-10-13 17:48:00 +08:00
Hermes CI Fix 501322a8b9 feat(maps): 战略地图按 objective 粒度合并(granular 通道),不同 objective 并发不再互相 409
ci/woodpecker/push/main Pipeline failed
2026-10-13 11:37:24 +08:00
wecom-fullstackandwecom-yanxue f786ff9ae3 test(docs-center): 金丝雀分类断言改「必备分类在场+相对顺序」(分类扩展不再留假红)
ci/woodpecker/push/main Pipeline failed
- test_prod_content_readable 原硬编码 [term,formula,practice,faq],与生产 categories.json 全等比较;
  2026-10-12 14:05 内容目录新增 manual(模块手册,order 5) 后期望值失鲜 ⇒ 基线/改后两棵树同红。
- 改为子集式断言(必备分类在场 + 过滤后相对顺序),不再硬编码全集;分类扩展不再红。
- 同族断言盘点:本文件其余分类断言走 tmp content_dir 自持 categories.json,不依赖生产内容目录,无需改。
2026-10-13 07:48:18 +08:00
Hermes CI Fix cfd6a13ca4 fix(budget): 年度行(budget_month=0)不回落 1 月 + 同族 R1/R2/R3
ci/woodpecker/push/main Pipeline failed
- R-A2 sync-cash-plans: 年度行形态判定(_is_annual_budget_row)跳过 + skipped_annual 披露
- R2 POST /plans 期间形态校验(YYYY-MM / YYYY-00 显式接受)
- R3 budget_roll_forward 排除年度行
- R1 cash_classify 归类路径年度行判据 + 披露
- 新增 2 个回归件(14+10 例)

仅含本族 hunk(禁整文件提交): 开发区其余 14 hunk 属他会话在途件,未纳入。
2026-10-13 03:41:39 +08:00
Hermes CI Fix d08a8490eb fix(maps): 覆写通道扩面清点——同契约调用点补基准(3 例静默降级 + 2 例新增失败)
ci/woodpecker/push/main Pipeline failed
缺陷 cma-strategy-map-autosave-stale-overwrite 的护栏(PUT /api/cma/maps/{id} 覆写
dimensions/canvas_data 须带 base_updated_at,否则 409 fail-closed)改变了同一写通道的
契约 ⇒ 全仓清点调用点,在调用点取基准(写前读 updated_at),**不放宽护栏**:

- tests/test_map_versions.py: 新增 _base_updated_at(db, map_id) 助手;3 处覆写 dimensions 的
  PUT 补基准(test_rollback_backs_up_current_and_restores_content 会直接失败;
  test_rollback_again_uses_next_free_backup_version 两处此前已静默降级——PUT 被拒但用例仍绿,
  即「改了内容再回滚」的前提实际未发生)。
- tests/test_kr_name_pollution_20260916.py: 2 处 KR 透传护栏用例补基准。

改前/改后全量对拍(同树 pythonpath):
  baseline 22 failed / fixed2 18 failed;diff 仅剩 tests/test_doc_center_20260924.py::
  TestProdContentCanary::test_prod_content_readable —— 基线树同样失败(/srv/hermes/shared/
  cma-content 于 14:05 被写入 manual 分类,共享内容目录变化),与本单无关。
  本单新增的 5 例在基线树全红、改后全绿。

任务: yanxue-20261012-cma-map-autosave-stale-overwrite-defect-fullstack
2026-10-12 17:06:07 +08:00
wecom-fullstack 03254bd4c7 fix(maps): 战略地图写通道乐观并发 + 画布自动保存仅脏时写
生产缺陷 cma-strategy-map-autosave-stale-overwrite(D4 批次 19 行 map_objectives 被过期快照覆写):
- 后端 update_map: 覆写 dimensions/canvas_data 须带 base_updated_at,与服务端 updated_at 不符 → 409;
  校验在任何写入之前完成(校验不过时一行都不删/不改)
- 连线写通道(POST/DELETE connections)响应补 updated_at,供前端采纳新基准、避免自伤 409
- 前端 MapCanvas: 自动保存仅在 draft + isDirty 时触发;请求体带 base_updated_at;收到 409 停自动保存不重试
- 修复 async onMounted 竞态:await 期间被卸载后其续段仍注册定时器(清理钩子先跑完、无人回收)
  → 泄漏写者永久在后台用过期快照覆写(生产 referer=/maps 仍每 30–60s 写)。加 disposed 守卫 + stopAutoSave
- 测试: backend/tests/test_map_concurrency_20261012.py(8 例,改前 5 败);前端行为台架 11/11(改前 3/11)

任务: yanxue-20261012-cma-map-autosave-stale-overwrite-defect-fullstack
2026-10-12 11:42:56 +08:00
Hermes CI Fix 1613f7f961 fix(cpa): sales bill-type whitelist for customer revenue caliber (P0)
ci/woodpecker/push/main Pipeline failed
Customer revenue/concentration now filters bill_type IN (1,4,14) (XSD/XSTHD/XSHHD).

Non-sales docs (JHD purchase, YFKD prepay, YSKD accept, QTRK, SKD, YBFYD) carry SUPPLIER unit_id and were polluting 'customer concentration' (old Top1/Top2 were our biggest suppliers; root cause of 6.04x base gap).

Unify sign filter to (sum_money+sum_tax_money)>0 (equivalent to money_only on current data, diff 0 rows) and correct the withdrawn 407-row/17.2% wording in cpa_caliber docstring.

Deriver: anchors from cpa_caliber single source; new --concentration overwrite and --only concentration (minimal 9-row write face) with pre-overwrite backup.

Tests: fixture gains bill_type/sum_tax_money; +5 whitelist cases (44 passed targeted).
2026-10-09 17:00:37 +08:00
Hermes CI Fix 451c994a86 feat(cpa): customer profitability MVP P0-a/b - revenue caliber single source + snapshot table + 9-card tri-state
Task: finance-20261003-cpa-customer-profitability-mvp-fullstack (P0-a / P0-b)

- backend/app/utils/cpa_caliber.py (new): single source of truth for
  customer-level revenue aggregation from zxt_masterbill. Reused by
  alert_rules._customer_revenue_shares (dedupe: no second implementation).
- backend/scripts/migrate_cpa_snapshot_20261004.py (new): idempotent DDL for
  customer_profitability_snapshot (UNIQUE entity_id+period+unit_id),
  --check / --apply / --rollback / --status / --show-create + journal.
- backend/scripts/kpi_customer_profitability_deriver.py (new): monthly
  snapshot + kpi_values upsert (single transaction, idempotent).
  fail-closed: cogs_alloc / chan_sub / contrib / contrib_rate stay NULL and
  are never written as 0 or as an apportioned value.
- backend/scripts/sql/cpa_kpi_defs_20261004.sql (new): C_CUST_REVENUE and
  C_CUST_COUNT active; 5 P1 codes registered inactive with fail-closed reason.
- backend/app/api/customer_dashboard.py: new read-only endpoint
  GET /api/cma/customer-dashboard/standard-indicators returning the 9-card
  tri-state (value / no_data / undefined). Reads kpi_definitions via raw SQL
  to stay decoupled from the in-flight KPI direction DDL.
- frontend/src/views/CustomerDashboard.vue: 9-card panel rendering the three
  states (never renders an empty value as 0).

Anchor check 2026-01..09 (offline sqlite rehearsal, ALL_PASS=true):
73 customers / 4883.52 wan / top1 25.3835 / top2 23.4905 / top4 63.3249 /
top10 81.7675 - all six match the task acceptance anchors.

No production write and no deploy in this commit; release is gated.
2026-10-09 17:00:37 +08:00
wecom-fullstack a48a116e06 fix(dupont): insight.detail 叙述段字面 None 收口(表现层 _fmt_val,仅文案)
ci/woodpecker/push/main Pipeline failed
- 恒等式叙述段 5 处裸 f-string 插值(三因子 value / ROE / identity.diff)改走 _fmt_val:
  None → 「-」(与同段四列 _fmt 同族),有值原样输出(不经 :.2f,保证有值路径逐字节不变)
- 结构化字段 roe / factors[*].value / identity.diff 仍为 null;check_identity 语义、dupont_basis.py 未改
- 基线 origin/main acf179a;本单零生产写(不推远端/不同步部署树/不重启/不连库)
2026-10-06 23:36:06 +08:00
Hermes CI Fix acf179a199 fix(subjects): migrate 脚本口径乙 fail-closed(排除白名单逐码 + 双轨锚点)
ci/woodpecker/push/main Pipeline failed
2026-10-06 17:03:44 +08:00
wecom-fullstack 4f9bab6bc0 fix(dupont): reject out-of-domain window + bidirectional clip + suffix discipline (R1-R4/M2)
ci/woodpecker/push/main Pipeline failed
Per finance ruling 2026-10-05 (project-20261005-dupont-oob-window-fix):

- R1: no intersection with available month domain => explicit error (endpoint 400), no window fields

- R2: with intersection => intersection window, months>=1 (kills inversion and months<=0)

- R3: quarter/half/... suffix kept only when returned window == nominal window (clipped flag)

- R4: symmetric lower-bound clip note; M2: TTM nominal window = [anchor-11, anchor] (12 months)

- M1: docstring correction (lower-bound overflow is clipped, not 400)

No push / no deploy / no restart / no DB write.
2026-10-05 02:02:21 +08:00
Hermes CI Fix 3ed9fa0611 fix(cvp): O1 residue - e2 rebate_policy_pct 置 null(口径单一源 = bep_caliber)
ci/woodpecker/push/main Pipeline failed
问题:/bep-card 端点对 e2(博海)回落模块内硬编码常量 BEP_REBATE_POLICY_PCT=84.61,
而 bep_caliber.ENTITY_CALIBERS[2]["policy_pct"] 为 None(博海无渠补科目)⇒ 下游 API
消费者会把 payload 里的 rebate_policy_pct=84.61 误读为「博海有 84.61% 政策渠补率」。

改动(严格限定 dashboard.py 一个模块 + 1 条守护用例):
- 删除模块级常量 BEP_REBATE_POLICY_PCT;四处回落分支改为直接取口径值
  (未注册口径分支置 None;其余取 cal.get("policy_pct") / 已算出的 policy_pct)。
- 口径单一事实源维持 app/utils/bep_caliber.py,端点不做任何回落。
- 新增 tests/test_bep_rebate_actual_caliber.py::test_o1_policy_pct_single_source_no_hardcoded_fallback
  (改前必红 / 改后必绿,e1=84.61 作同端点负对照)。

改前/改后(端点函数直调,生产库只读,e1/e2 同批):
  改前 e2 inputs.rebate_policy_pct = 84.61 → 改后 null;e1 恒为 84.61(负对照不变)。
2026-10-04 22:37:20 +08:00
wecom-fullstackandHermes CI Fix 587d5b3983 fix(cvp): BEP ⑤ 口径统一落地(变动成本率=完整口径)
ci/woodpecker/push/main Pipeline failed
- 派生器/口径:F_BEP_VC_RATE 新增;卡面/公式行文案改「月固定成本 ÷ 边际贡献率」
- 保本额 = 月固定成本 ÷ (1 − 变动成本率);实测渠补率(F_BEP_REBATE)降为 e1 专用 chip
- 报告卡口径说明文字同步(固定成本=固定费用合计)

任务: finance-20261003-cvp-fix-and-bohai-coverage-fullstack ⑤
2026-10-04 19:53:12 +08:00
Hermes CI Fix 4fe956fcf9 fix(dupont): resolve YYYY-Qn / YYYY-Hn period windows (500 on quarter/half)
ci/woodpecker/push/main Pipeline failed
Root cause: resolve_window() matched quarter/half with p[4] == "q" / "h"
(always '-' for "2026-q2"/"2026-h1"), and the branches sat after the
single-month branch -> dead code. Real input fell into the month branch,
so months_between() raised ValueError on int('q2') -> HTTP 500 for all
18 quarter/half options in the period selector.

Fix: judge on p[5] in ("q","h") with a digit guard on p[6], evaluated
before the single-month branch; month branch tightened to p[5:].isdigit().
Existing error-branch wording unchanged.

Tests: backend/tests/test_dupont_period_options.py (18 defect options,
case equivalence, month/year/ytd/ttm/custom non-regression, malformed
input returns error dict instead of raising).
2026-10-04 18:34:05 +08:00
Hermes CI Fix 40d2ffe0f7 fix(dupont): None guard for raw formatting in /reports/dupont insight detail
ci/woodpecker/push/main Pipeline failed
Non-ending stock windows (e.g. ?period=2026-Q2 / 2026-05 / custom) return raw total_assets/equity as None when the window has no subject_balances level-1 rows; identity guard reports status 'unavailable' (not 'identity_broken') and execution continues into the insight detail f-string, where .2f on None raised TypeError -> HTTP 500. Add explicit fmt() ('-' for None) for the four raw columns in detail.

Task: fullstack-20261004-1544 (P1; proposer wecom-fullstack; ASK-20261004-07 follow-up). No DDL. Single file.
2026-10-04 18:03:34 +08:00
Hermes CI Fix 38a6de9325 feat(dupont): period-basis design (cumulative flow x average stock, optional annualize, identity guard)
ci/woodpecker/push/main Pipeline failed
Task: finance-20261003-dupont-period-basis-design-fullstack (P1; proposer wecom-finance)
Basis ruling: finance 2026-10-03 (Boss 09:49 CST DM approval of the design task)

- /reports/dupont accepts period/start/end/annualize (default YTD), returns
  period_start/period_end/basis/annualized metadata; identity guard
  |ROE - npm x turnover x multiplier| <= 0.05 pp, else identity_broken (no values).
- new app/utils/dupont_basis.py = single source of truth (window resolve, flow
  cumulative via voucher_details, stock average via subject_balances level=1,
  fail-closed basis_fallback, pure calc + identity check).
- scripts/kpi_dupont_deriver.py (idempotent derived writer, dry-run/check/write),
  scripts/apply_dupont_kpi_defs.py + sql/dupont_kpi_defs_20261003.sql (seed:
  F_ASSET_TOTAL/F_EQUITY_TOTAL/F_EQUITY_MULTIPLIER as derived; F_ASSET_TURNOVER
  manual -> derived). Seed already applied under approval 296cc1fd.
- scripts/verify_dupont_period_basis.py: read-only acceptance battery A1-A9
  (independent SQL recompute). BEFORE (base tree) 1/10 PASS -> AFTER 10/13 PASS;
  remaining 3 need the deriver value write (approval-gated).
- frontend DupontAnalysis.vue: period selector (YTD default) + annualize switch
  + caliber metadata bar + identity_broken alert.

No DDL. No ERP direct connection (old register_dupont_kpis.py caliber dropped).
2026-10-04 13:35:25 +08:00
Hermes CI Fix 1f1385ebbf feat(cvp): CM≤0 fail-closed 禁写0.0 + 博海BEP扩面(新口径) + 影子CVP标注is_mock + 前端NULL守卫/不适用
ci/woodpecker/push/main Pipeline failed
- kpi_bep_deriver.py: 实体口径注册表(app/utils/bep_caliber.py 单一事实源);博海(6001/6401/6601-03/1602);
  fail-closed② 边际贡献非正/固定成本退化 ⇒ 五条保本值 NULL+pending+remark'不适用'(禁回落0.0)
- dashboard.py /bep-card: 多实体口径 + not_applicable 态返回 label/reason
- predict.py /cvp-detailed: 显式 source=scenario_simulation + is_mock=true(影子CVP标注)
- 前端 BEPBreakEvenCard.vue: 全量数值判空 + '不适用'态;PredictDashboard.vue: 情景模拟·非实测标签

验收 accept-bep-cvp-20261003.py: BEFORE 2/11 → AFTER 11/11 PASS
任务 finance-20261003-cvp-fix-and-bohai-coverage-fullstack
2026-10-03 16:03:12 +08:00
Hermes CI Fix 8631ce718f feat(profitability): real ROI (op profit / invested capital), enable EVA, add RI
ci/woodpecker/push/main Pipeline failed
Kaliber: wecom-finance 2026-10-02 ruling (Boss 11:59 CST "li; but (1) add real ROI").

- kpi_definitions seed scripts/sql/profitability_kpi_defs_20261003.sql:
  F_ROI renamed to F_ROA (same kpi_id=20, all 8 historical values kept), new F_ROI
  (real IMA caliber = operating profit / invested capital), F_EVA activated with unit
  and thresholds aligned to 万元, new F_RI (residual income).
- parameters are NOT hardcoded in formula text: they live in
  kpi_definitions.data_source_config.params (tax_rate / wacc / min_return_rate /
  include_factory_subsidy / include_channel_subsidy / include_sales_discount /
  annualize / capital_basis); single reader = app/utils/profitability_params.py.
- deriver scripts/kpi_profitability_deriver.py: YTD operating profit from
  voucher_details (carry_forward=0 net), invested capital from subject_balances
  level-1 rows only, idempotent upsert into kpi_values (source_type=derived, no
  data_status downgrade, fail-closed when invested capital is 0).
- read-only endpoint GET /reports/value-management reads the derived rows only
  (single source of truth, no recomputation) and exposes the parameter block.
- DupontAnalysis.vue: value management section (ROI / ROA / ROE / EVA / RI).
- caliber_units.py: RATIO_CALIBER_TABLE key F_ROI renamed to F_ROA, new F_ROI row.
- tests: backend/tests/test_profitability_indicators.py (21 cases).
2026-10-03 02:10:47 +08:00
Hermes CI Fix 1da0a74568 fix(h2-forecast): 预估区残余读路径 P2 收口(3 类 6 处)
任总 2026-10-02 11:35 直令「对外 /api/cma/** 读 KPIValue 默认只取实测区」同族残余
(ac4bbc6 范围外的 3 类 6 处)。6 处一律引 app.utils.kpi_value_scope.actual_only_clause(),
零新写字面量口径:
- customer_dashboard.py:76    年视图汇总(预估禁与实测相加)
- budget.py:260               按去年实绩加权派生基数(批量 auto-decompose)
- budget.py:333               同上(单 KPI auto-decompose)
- cash_forecast_engine.py:204 get_entity_kpi_history 历史/趋势序列
- cash_forecast_engine.py:259 get_current_cash_balance 最新值
- cash_forecast_engine.py:471 calculate_accuracy 实际值序列(预测输入)

证据:同脚本双树 A/B 15/15(改前 5/15,真库 kpi-forecast history 去掉 2026-H2、
next_target 336.32→250.99);分类计数 33/41/9/3 → 39 covered/38 period_guarded/9 write/0 unguarded;
pytest 3 failed/1529 passed,失败集与基线逐条同 ID。不 push / 不部署 / 不重启 / 不写库。
Task: project-20261002-h2-forecast-residual-reads-p2-fullstack
2026-10-02 23:41:17 +08:00