fix(bep): 派生器不再静默降级已转正行(ASK-20260924-10 任总放行)
ci/woodpecker/push/main Pipeline failed

- upsert 分支原无条件 existing.data_status='pending' ⇒ 人工转正次日 06:35 被打回 pending
- 值未变/值变一律不写 data_status(保留既有,含 verified)
- 值变:remark 追加「值已更新(batch=…, 旧X → 新Y) 待财务复核」+ 落复核提示件(_reports/,不进队列根)
- 新期间仍写 pending(9-14 复核流入口不变);算式/TARGET_KPIS/阈值逻辑不动
This commit is contained in:
wecom-fullstack
2026-09-25 07:07:01 +08:00
parent 23f12fc866
commit c3ff345d0a
+70 -3
View File
@@ -17,7 +17,10 @@
结账信号优先:DB 损益结转凭证(carry_forward=1 & subject_code='3103') → check.md 文件;
两者皆无时才退回旧「销售额阈值」兜底(销售低 ≠ 未结账,仅保底用,日志标注依据)
- 单位: voucher 金额为元,聚合后 /10000 → 万元
- 幂等: 同 kpi_id+period+source_type='derived' 存在则更新,可安全重跑
- 幂等: 同 kpi_id+period+source_type='derived' 存在则更新,可安全重跑;
**不降级**(ASK-20260924-10 / 2026-09-24):值未变或值变都不回写 data_status,
保留既有状态(含人工/任总放行的 verified),避免「人工转正 → 次日派生静默打回 pending」。
值变时 remark 留痕并落复核提示件(见 _reports/fullstack-<YYYYMMDD>-bep-value-changed-review.md)。
- 只写 source_type='derived' 行;不触碰 auto_collect/manual/excel/ledger 行;不改 collector 逻辑
用法:
@@ -68,6 +71,54 @@ CARRY_SUBJECT = "3103" # 本年利润(carry_forward=1 且该科目 = 该
# 智享通落盘目录(与 app/api/data.py 的 ZXT_LANDING_DIR 一致)——结账信号次选来源
ZXT_LANDING_DIR = "/root/WB_WS/_data/zxt-hanke"
# ── ASK-20260924-10(2026-09-24 任总放行):派生器不得静默降级已转正行 ──────────
# 缺陷(原实现):upsert 分支无条件 `existing.data_status = "pending"`(与值是否变化无关)
# ⇒ 人工/任总放行的值转正为 verified 后,次日 06:35 派生器一跑就被打回 pending,
# 看板 / 预警 / Bot 全链路再次读不到,交付在 24 小时内静默消失。
# 修正口径(本单指定,属待财务复核的裁定点):
# · 值未变 → 不写 data_status(保留既有,含 verified);source_batch/remark/actual_value 照旧更新
# · 值变 → 同样不写 data_status(不降级,避免看板数字忽现忽隐)
# + remark 追加「值已更新(batch=…, 旧X → 新Y) 待财务复核」+ 落复核提示件
# · 新期间(原本无行)→ 维持 pending(9-14 复核流入口不变)
# 提示件为**报告类**,落 `_reports/` 而非对方队列根(避免误唤醒其他 Bot);
# 落点可用环境变量 BEP_REVIEW_NOTE_DIR 覆盖(沙箱/测试用)。
DEFAULT_REVIEW_NOTE_DIR = "/root/.hermes/profiles/wecom-finance/pending-tasks/_reports"
def _review_note_dir() -> str:
return os.environ.get("BEP_REVIEW_NOTE_DIR") or DEFAULT_REVIEW_NOTE_DIR
def _write_value_changed_note(entity_id: int, period: str, batch: str, changed: list):
"""值变时落「待财务复核」提示件。写失败只告警,绝不阻断派生主流程。"""
if not changed:
return None
try:
d = _review_note_dir()
os.makedirs(d, exist_ok=True)
path = os.path.join(d, "fullstack-%s-bep-value-changed-review.md" % datetime.now().strftime("%Y%m%d"))
lines = [
"# 保本派生值变动 · 待财务复核(派生器自动生成;报告类产物,非队列任务)",
"",
"- 生成器: `backend/scripts/kpi_bep_deriver.py`(值变路径,ASK-20260924-10 口径)",
"- entity_id: %s | period: %s | batch: %s" % (entity_id, period, batch),
"- 生成时刻: %s" % datetime.now().strftime("%F %T"),
"- 口径: 值变**不降级** data_status(保留既有,含 verified),仅 remark 留痕 + 请财务复核",
"",
"| kpi_code | 旧值 | 新值 | data_status(写后) |",
"|---|---|---|---|",
]
for c in changed:
lines.append("| %s | %s | %s | %s |" % (c["code"], c["old"], c["new"], c["status"]))
lines += ["", "> 本件落在 `_reports/`(报告类),不进队列根,避免误唤醒其他 Bot。", ""]
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(lines))
logger.warning("值变留痕:复核提示件已落 %s(%d 个 KPI)", path, len(changed))
return path
except Exception as e: # 提示件失败不影响派生落库
logger.error("值变复核提示件写入失败(不阻断派生): %s", e)
return None
def _month_range(period: str):
"""返回该期间当年1月1日到月末的日期边界(用于 voucher_date 过滤兜底)"""
@@ -282,6 +333,7 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
batch = f"bep-{datetime.now().strftime('%Y%m%d%H%M%S')}"
written = {}
value_rows = {}
changed = []
for code, _ in TARGET_KPIS:
kpi = kpis[code]
if code in CASH_CODES:
@@ -297,10 +349,22 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
if write_kpi:
is_new = existing is None
if existing:
old_value = float(existing.actual_value) if existing.actual_value is not None else None
new_value = float(values[code])
value_changed = old_value is None or abs(old_value - new_value) > 1e-9
existing.actual_value = values[code]
existing.source_batch = batch
existing.remark = remark
existing.data_status = "pending"
if value_changed:
existing.remark = (f"{remark}|值已更新(batch={batch}, "
f"旧{old_value} → 新{new_value}) 待财务复核")
changed.append({"code": code, "old": old_value, "new": new_value,
"status": existing.data_status})
else:
existing.remark = remark
# 2026-09-24 ASK-20260924-10:**不再**无条件 `existing.data_status = "pending"`。
# 值未变 / 值变 一律不写 data_status ⇒ 保留既有状态(含人工放行的 verified)。
# 原写法会让「人工转正」在次日 06:35 静默被打回 pending(看板数字忽现忽隐)。
# 新期间(下方 else 分支)仍写 pending,复核流入口不变。
else:
existing = KPIValue(
entity_id=entity_id,
@@ -359,6 +423,8 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
status="pending",
))
db.commit()
# 值变留痕(ASK-20260924-10):落复核提示件(报告类 → _reports/,不进队列根)
_write_value_changed_note(entity_id, period, batch, changed)
return {
"entity_id": entity_id,
@@ -377,6 +443,7 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
"cm_pct": round(cm_pct, 2),
"values": values,
"written": written,
"value_changed": changed,
}