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)。
This commit is contained in:
wecom-fullstack
2026-10-13 20:33:42 +08:00
parent 4cdd30e321
commit 424c33c085
4 changed files with 914 additions and 2 deletions
+42 -2
View File
@@ -1,5 +1,5 @@
"""管理会计OS 数据模型"""
from sqlalchemy import Column, Integer, String, Text, Float, DateTime, ForeignKey, Boolean, JSON, func, UniqueConstraint, Numeric, Date
from sqlalchemy import Column, Integer, String, Text, Float, DateTime, ForeignKey, Boolean, JSON, func, text, UniqueConstraint, Index, Numeric, Date
from app.database import Base
from app.models.budget_plan import BudgetPlan
@@ -93,7 +93,13 @@ class KPIDefinition(Base):
data_owner = Column(String(100), default="待指定", comment="数据责任人")
frequency = Column(String(20), default="monthly", comment="daily/weekly/monthly/quarterly/yearly")
unit = Column(String(50), default="%", comment="单位")
target_value = Column(Float, nullable=True, comment="目标值(兼容旧字段)")
# 2026-10-03 KPI字典分层治理(任务 CMA-KPI-20261003)阶段1:direction 结构列。
# 枚举 higher_better(越大越好)/lower_better(越小越好),NULL=未定。
# 既有行由 migrate_kpi_direction_20261003.py 按 threshold_green 比较符**结构推定**回填
# (非口径裁定);口径/阈值语义仍属 finance/DAMA 在途批次。
direction = Column(String(16), nullable=True,
comment="指标方向: higher_better越大越好/lower_better越小越好/NULL未定")
target_value = Column(Float, nullable=True, comment="目标值(NULL=未设目标;0=真零目标;兼容旧字段)")
target_monthly = Column(Float, nullable=True, comment="月度目标值")
target_quarterly = Column(Float, nullable=True, comment="季度目标值")
target_yearly = Column(Float, nullable=True, comment="年度目标值")
@@ -491,6 +497,10 @@ class KPITemplate(Base):
formula = Column(Text)
formula_desc = Column(String(500))
unit = Column(String(50))
# 2026-10-03 KPI字典分层治理(任务 CMA-KPI-20261003)阶段1:字典层同加 direction;
# 回填规则 = 继承同 kpi_code 实例的已定方向(见 docs/kpi-dictionary-layering-design.md §2.2)。
direction = Column(String(16), nullable=True,
comment="指标方向: higher_better越大越好/lower_better越小越好/NULL未定")
target_value = Column(Float)
description = Column(Text)
is_system = Column(Integer, default=0)
@@ -864,6 +874,36 @@ class ObjectiveKPI(Base):
__table_args__ = (UniqueConstraint("objective_id", "kpi_id", name="uk_obj_kpi"),)
class KPITarget(Base):
"""KPI 期间目标(考核型·方案 A)—— ③ 读点收敛 CMA-KPI-20261003
唯一真源(考核型):`(entity_id, kpi_id, period, target_type)` 唯一;
承诺型目标只写 `krs`(裁定 §二 边界声明:同一 KPI 不得两处均有 target)。
**列定义与 ② DDL 出件包 `up.sql` 逐字一致**(11 列 + uk_entity_kpi_period_type + 2 索引);
`entity_id` 无 DDL DEFAULT(写入口必须显式传值,防静默默认把实体隔离降级为约定);
`updated_at` 无 `ON UPDATE CURRENT_TIMESTAMP`(由 ORM/写入口维护,与现网 kpi_definitions 同风格)。
"""
__tablename__ = "kpi_targets"
id = Column(Integer, primary_key=True, autoincrement=True)
entity_id = Column(Integer, nullable=False, comment="账套(企业ID);写入口必须显式传值(实体隔离)")
kpi_id = Column(Integer, nullable=False, comment="指标实例 → kpi_definitions.id")
period = Column(String(20), nullable=False, comment="期间: 2026-08(月)/2026Q3(季)/2026(年)")
target_type = Column(String(16), nullable=False, comment="目标粒度: monthly/quarterly/yearly")
target_value = Column(Numeric(15, 2), nullable=True, comment="NULL=该期未设;0=真零")
source = Column(String(20), nullable=False, server_default=text("'manual'"),
comment="manual/okr/budget/import")
note = Column(String(255), nullable=True, comment="备注")
created_by = Column(String(64), nullable=True, comment="写入者(用户/Bot标识)")
created_at = Column(DateTime, nullable=False, server_default=func.now())
updated_at = Column(DateTime, nullable=False, server_default=func.now())
__table_args__ = (
UniqueConstraint("entity_id", "kpi_id", "period", "target_type", name="uk_entity_kpi_period_type"),
Index("ix_kpi_targets_entity_period", "entity_id", "period"),
Index("ix_kpi_targets_kpi", "kpi_id"),
)
class KR(Base):
"""关键结果KR — OKR完整化 (O→KR→KPI)"""
__tablename__ = "krs"