feat(entity-dim): 账套维度收口批 — subjects/analysis_results 加列 + 分析结论账套化 + 模板实例化构造点补账套
ci/woodpecker/push/main Pipeline failed
ci/woodpecker/push/main Pipeline failed
- ① kpi_templates instantiate 构造点显式传会话账套(原缺 entity_id ⇒ 静默落 e1),kpi_code 判重按账套收紧
- ② kpi_subject_map 3 个读取点加护栏注释(不加列、不改语义)
- ③ subjects 分层:读口 union(NULL=全局标准准则科目 / N=账套私有),UPSERT 唯一键改 (entity_id, subject_code)
- ④ analysis_results 账套化:读/写/删三端点按会话账套判定归属(列未落地时 kpi_code JOIN 止血),
无归属行进 unattributed 登记、删除一律 404
- 模型层按现网 DDL 补齐:KPIDefinition(template_id/is_system + uk_entity_kpi)、AnalysisResult.entity_id、Subject.entity_id
- 新增 migrate_{subjects,analysis_results}_entity_id_20260923.py(--check/--apply/--down)、
22 条对账用例 test_entity_dimension_convergence_20260923.py
DDL 已由 ops 受控通道落地(subjects 21:31:56 / analysis_results 21:31:59 CST,审批 1e9b4d9a/5ffb1640)
本提交为切片提交,未 push(发布须另取放行)
This commit is contained in:
@@ -66,12 +66,22 @@ class StrategicMap(Base):
|
||||
|
||||
|
||||
class KPIDefinition(Base):
|
||||
"""KPI字典"""
|
||||
"""KPI字典
|
||||
|
||||
账套维度:`kpi_code` 唯一性为 **`(entity_id, kpi_code)` 复合**(生产库 `uk_entity_kpi`,
|
||||
2026-09-23 账套维度收口批与模型对齐——原模型写 `unique=True`(全表唯一)与现网 DDL 不一致,
|
||||
会让 e1/e2 无法各建同码 KPI,也让 SQLite 测试库建不出真实形态)。
|
||||
"""
|
||||
__tablename__ = "kpi_definitions"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
entity_id = Column(Integer, default=1, comment="企业ID")
|
||||
map_id = Column(Integer, ForeignKey("strategic_maps.id"), nullable=True, comment="关联战略地图")
|
||||
kpi_code = Column(String(50), unique=True, nullable=False, comment="KPI编码")
|
||||
# 2026-09-23 账套维度收口批:生产库 kpi_definitions 有这两列、模型缺失 ⇒
|
||||
# `instantiate_template` 的 `KPIDefinition(template_id=…, is_system=…)` 必抛
|
||||
# TypeError(HTTP 500),「从模板实例化」实际不可用。按现网 DDL 补齐(模型↔库对齐)。
|
||||
template_id = Column(Integer, nullable=True, comment="来源模板ID (kpi_templates.id)")
|
||||
is_system = Column(Integer, default=0, comment="是否系统预置模板实例化(1=是)")
|
||||
kpi_code = Column(String(50), nullable=False, comment="KPI编码(唯一性=(entity_id, kpi_code))")
|
||||
kpi_name = Column(String(200), nullable=False, comment="KPI名称")
|
||||
dimension = Column(String(50), comment="所属维度: finance/customer/process/learning")
|
||||
objective = Column(String(200), comment="关联战略目标")
|
||||
@@ -104,6 +114,9 @@ class KPIDefinition(Base):
|
||||
created_by = Column(Integer, nullable=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
__table_args__ = (
|
||||
UniqueConstraint("entity_id", "kpi_code", name="uk_entity_kpi"),
|
||||
)
|
||||
|
||||
|
||||
class KPIValue(Base):
|
||||
@@ -558,9 +571,16 @@ class ReportHistory(Base):
|
||||
|
||||
|
||||
class AnalysisResult(Base):
|
||||
"""财务Bot分析结论 — 带置信度评分"""
|
||||
"""财务Bot分析结论 — 带置信度评分
|
||||
|
||||
账套维度(2026-09-23 账套化,任务 0-P1-project-20260923-entity-dimension-convergence §三④):
|
||||
`entity_id IS NULL` = 历史未归属行(存量 2 行)——**不参与任何账套过滤**,
|
||||
读取端点单独登记(`unattributed`),删除端点一律 404(不猜账套、不引入 403 探测面)。
|
||||
"""
|
||||
__tablename__ = "analysis_results"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
entity_id = Column(Integer, nullable=True, index=True,
|
||||
comment="企业ID(NULL=历史未归属行,不参与账套过滤)")
|
||||
period = Column(String(20), nullable=False, comment="期间 YYYY-MM")
|
||||
conclusion = Column(String(1000), nullable=False, comment="分析结论")
|
||||
confidence = Column(Integer, nullable=False, comment="置信度 0-100")
|
||||
@@ -608,10 +628,20 @@ class ScenarioSuggestion(Base):
|
||||
# ============================================================
|
||||
|
||||
class Subject(Base):
|
||||
"""会计科目 — 新30号准则分类"""
|
||||
"""会计科目 — 新30号准则分类
|
||||
|
||||
账套维度(2026-09-23 `subjects` 分层,任务 0-P1-project-20260923-entity-dimension-convergence):
|
||||
`entity_id IS NULL` = 全局标准准则科目(两账套共享)
|
||||
`entity_id = N` = 账套 N 私有明细(如酣客智享通 `remark='zxt-sync'` 明细)
|
||||
唯一性 = `(entity_id, subject_code)`:不同账套允许同码;同一账套内同码仍拒绝(应用层 UPSERT 判重)。
|
||||
⚠️ MySQL 唯一索引把 NULL 视为互不相同 ⇒ 全局行的同码重复只能靠写入口判重兜住,
|
||||
新增「全局科目」写入路径时必须显式查重(不可依赖索引)。
|
||||
"""
|
||||
__tablename__ = "subjects"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
subject_code = Column(String(20), nullable=False, unique=True, comment="科目编码")
|
||||
entity_id = Column(Integer, nullable=True, index=True,
|
||||
comment="企业ID(NULL=全局标准准则科目 / N=账套私有明细)")
|
||||
subject_code = Column(String(20), nullable=False, comment="科目编码(唯一性=(entity_id, subject_code))")
|
||||
subject_name = Column(String(200), nullable=False, comment="科目名称")
|
||||
parent_code = Column(String(20), nullable=True, comment="上级科目编码")
|
||||
level = Column(Integer, default=1, comment="科目级别 1-4")
|
||||
@@ -624,6 +654,9 @@ class Subject(Base):
|
||||
remark = Column(String(500), nullable=True, comment="备注")
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
__table_args__ = (
|
||||
UniqueConstraint("entity_id", "subject_code", name="uk_entity_subject"),
|
||||
)
|
||||
|
||||
|
||||
class VoucherDetail(Base):
|
||||
|
||||
Reference in New Issue
Block a user