"""外部输入台账 — 三张**外部输入**业务表(能力评价 M2 准入3 派生,2026-09-15) 背景:能力评价 6 项缺口值经只读实测确认**无一项**能从既有系统合法取证;其中 3 项 只能来自新的人工登记台账(PRD v1.3 §4.0)。本模块只做「建表 + 结构化字段」, 录入/派生分别见 app/api/ext_input_ledgers.py 与 app/services/ext_input_derive.py。 tax_filing_log 申报行为台账 → P_TAX_TIMELY (A4 税务申报及时率, 月频) hr_monthly_ledger 人事月度台账 → L_EMPLOYEE_TURNOVER (B5 员工流失率, 月频) licence_ledger 证照台账 → P_LICENCE_OK (B2 经营许可合规率, 季频) 纪律(任务 0-P0-project-20260915-cma-eval-m2-ext-input-tables): - 本模块**只新增表**,不改既有表、不改任何 KPI 定义(kpi_definitions 属准入2/4 同表串行) - `on_time` 是**服务端派生**字段(唯一实现 app/services/ext_input_derive.derive_on_time), 录入方手填值一律被忽略并回报 on_time_ignored - 每表 entity_id NOT NULL + 唯一键以 entity_id 打头(多租户隔离 + 索引可用性) """ from sqlalchemy import Column, Integer, String, Date, DateTime, UniqueConstraint, func from app.database import Base # 证照年检状态枚举(PRD v1.3 §4.0 原文口径,录入时白名单校验) ANNUAL_CHECK_STATUSES = ("未到期", "已年检", "待年检", "过期") # 期次格式(period) MONTH_PERIOD_RE = r"^\d{4}-(0[1-9]|1[0-2])$" # 2026-08 QUARTER_PERIOD_RE = r"^\d{4}-Q[1-4]$" # 2026-Q2 class TaxFilingLog(Base): """申报行为台账 — P_TAX_TIMELY 的唯一取证来源 唯一键 (entity_id, period, tax_type):同一账套同一属期同一税种只允许一条登记行。 """ __tablename__ = "tax_filing_log" id = Column(Integer, primary_key=True, index=True) entity_id = Column(Integer, nullable=False, index=True, comment="企业ID(账套)") period = Column(String(20), nullable=False, comment="属期 YYYY-MM(申报所属月份)") tax_type = Column(String(50), nullable=False, comment="税种: 增值税/附加税/企业所得税/印花税…") legal_deadline = Column(Date, nullable=True, comment="法定申报期限") filed_date = Column(Date, nullable=True, comment="实际申报日(未申报为 NULL)") on_time = Column(Integer, nullable=True, comment="是否按时申报 1/0(**服务端派生**,录入方不得手填)") evidence_no = Column(String(100), nullable=True, comment="申报回执/截图编号") registered_by = Column(String(100), nullable=True, comment="登记人(职能岗位)") 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", "period", "tax_type", name="uq_tax_filing_log_entity_period_type"), ) class HrMonthlyLedger(Base): """人事月度台账 — L_EMPLOYEE_TURNOVER 的唯一取证来源 唯一键 (entity_id, period)。 硬校验:headcount_end == headcount_begin + hires - separations, 不通过时**拒绝写入并回报**(见 app/services/ext_input_derive.check_headcount_equation)。 """ __tablename__ = "hr_monthly_ledger" id = Column(Integer, primary_key=True, index=True) entity_id = Column(Integer, nullable=False, index=True, comment="企业ID(账套)") period = Column(String(20), nullable=False, comment="期间 YYYY-MM") headcount_begin = Column(Integer, nullable=False, comment="期初在职人数") hires = Column(Integer, nullable=False, default=0, comment="入职人数") separations = Column(Integer, nullable=False, default=0, comment="离职人数") headcount_end = Column(Integer, nullable=False, comment="期末在职人数(须= 期初+入职-离职)") registered_by = Column(String(100), nullable=True, comment="登记人(职能岗位)") 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", "period", name="uq_hr_monthly_ledger_entity_period"), ) class LicenceLedger(Base): """证照台账 — P_LICENCE_OK 的唯一取证来源 唯一键 (entity_id, licence_name, licence_no)。 有效性口径(派生侧):valid_to >= 季末 且 年检状态 != '过期'; valid_to 为空 = 长期有效(仍受状态约束)。 """ __tablename__ = "licence_ledger" id = Column(Integer, primary_key=True, index=True) entity_id = Column(Integer, nullable=False, index=True, comment="企业ID(账套)") licence_name = Column(String(200), nullable=False, comment="证照名称") licence_no = Column(String(100), nullable=False, comment="证照编号") valid_from = Column(Date, nullable=True, comment="有效期起") valid_to = Column(Date, nullable=True, comment="有效期止(空=长期有效)") annual_check_status = Column(String(20), nullable=False, default="未到期", comment="年检状态: 未到期/已年检/待年检/过期") issuer = Column(String(200), nullable=True, comment="发证/主管机关") owner_role = Column(String(100), nullable=True, comment="责任岗位(职能,不指定自然人)") attachment_no = Column(String(100), nullable=True, comment="附件/扫描件编号") registered_by = Column(String(100), nullable=True, comment="登记人(职能岗位)") 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", "licence_name", "licence_no", name="uq_licence_ledger_entity_name_no"), )