baodan/tests/plan_snapshot_test.py

307 lines
11 KiB
Python
Raw Normal View History

前工程开发已经推进到 Phase 6 基础能力,但正式验收还没有完成。更准确地说:Phase 0~5 的主要代码链路已经落地,Phase 6 完成了治理框架,尚缺生产化和真实数据验收。 阶段 当前状态 说明 Phase 0~3 基本完成 Document IR、证据链、人工确认、不可变 PlanData Snapshot、海报/PPT 投影已实现 Phase 4 代码完成 场景、策略、模板版本,校验/发布门禁,确定性 resolver 和严格槽位合并已实现 Phase 5 代码完成 输入冻结、PPTX/海报对账、失败关闭、幂等、心跳、重试和冻结输入重放已实现 Phase 6 基础完成 质量看板、结构化告警、Golden 审批、留存清理、孤儿检查和灰度开关已实现 正式上线 未完成 缺真实样本、生产模板、业务规则签字和灰度观察 目前验证基线: PPT/海报专项测试:107 passed, 1 skipped Vue TypeScript 检查:通过 前端生产构建:通过 代码变更仍在工作区,尚未提交 仓库全量测试仍有既有失败/挂起项,暂时不能宣称全仓测试完全绿色 仍未完成的代码任务主要有: 影子解析差异流水线 目前有灰度开关,但还没有完整的“新旧解析同时运行、字段差异入库、按保司/profile 聚合”的影子比较任务。 自动视觉回归 目前实现的是 DOM 模块、溢出、尺寸、文本和数值检查;还缺基于真实模板和标准图片的像素差异、字体缺失、遮挡和裁切回归。 告警通道接入 后台已经能产生结构化质量告警,但尚未自动推送到邮件、企微或其他通知通道。 留存任务生产化 dry-run、实删服务和失败审计已经具备,但尚未接入周期性 Celery/定时任务,也没有自动重试失败清理批次。 旧链路最终下线 旧 PPT 解析器和海报紧凑解析仍保留为回滚路径。需要全量灰度稳定后才能删除或彻底关闭写入口。 全仓测试收口 需要处理现有无关失败和挂起测试,建立真正全绿的 CI 基线。 仍需外部输入和生产环境完成的事项: 至少 30 份脱敏 Golden PDF,并完成双人标注和精确率验收。 业务专家确认派生公式、缺失值、可比较性和结论策略。 上传并标注真实生产 PPTX 的语义 shape、页面类型和容量。 安装生产字体并建立视觉基准图片。 实际执行数据库迁移 038~040。 完成留存 dry-run、实删演练以及 10% → 30% → 100% 灰度。 观察期通过后开启 SCENARIO_ENGINE_V2,目前它仍默认关闭;真实清理开关也默认关闭。 完整状态记录在 [PPT与海报Phase4至6补充实施记录](D:/work/code/python/coding/baodanagent/docs/PPT与海报Phase4至6补充实施记录_20260802.md)。
2026-08-02 16:34:06 +08:00
import json
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "api"))
2026-08-04 10:44:34 +08:00
def test_generate_ppt_does_not_shadow_module_jsonify():
import symtable
route_path = Path(__file__).resolve().parents[1] / "api/insurance/ppt/routes.py"
module = symtable.symtable(route_path.read_text(encoding="utf-8"), str(route_path), "exec")
generate_ppt = next(child for child in module.get_children() if child.get_name() == "generate_ppt")
jsonify = generate_ppt.lookup("jsonify")
assert jsonify.is_global()
assert not jsonify.is_local()
前工程开发已经推进到 Phase 6 基础能力,但正式验收还没有完成。更准确地说:Phase 0~5 的主要代码链路已经落地,Phase 6 完成了治理框架,尚缺生产化和真实数据验收。 阶段 当前状态 说明 Phase 0~3 基本完成 Document IR、证据链、人工确认、不可变 PlanData Snapshot、海报/PPT 投影已实现 Phase 4 代码完成 场景、策略、模板版本,校验/发布门禁,确定性 resolver 和严格槽位合并已实现 Phase 5 代码完成 输入冻结、PPTX/海报对账、失败关闭、幂等、心跳、重试和冻结输入重放已实现 Phase 6 基础完成 质量看板、结构化告警、Golden 审批、留存清理、孤儿检查和灰度开关已实现 正式上线 未完成 缺真实样本、生产模板、业务规则签字和灰度观察 目前验证基线: PPT/海报专项测试:107 passed, 1 skipped Vue TypeScript 检查:通过 前端生产构建:通过 代码变更仍在工作区,尚未提交 仓库全量测试仍有既有失败/挂起项,暂时不能宣称全仓测试完全绿色 仍未完成的代码任务主要有: 影子解析差异流水线 目前有灰度开关,但还没有完整的“新旧解析同时运行、字段差异入库、按保司/profile 聚合”的影子比较任务。 自动视觉回归 目前实现的是 DOM 模块、溢出、尺寸、文本和数值检查;还缺基于真实模板和标准图片的像素差异、字体缺失、遮挡和裁切回归。 告警通道接入 后台已经能产生结构化质量告警,但尚未自动推送到邮件、企微或其他通知通道。 留存任务生产化 dry-run、实删服务和失败审计已经具备,但尚未接入周期性 Celery/定时任务,也没有自动重试失败清理批次。 旧链路最终下线 旧 PPT 解析器和海报紧凑解析仍保留为回滚路径。需要全量灰度稳定后才能删除或彻底关闭写入口。 全仓测试收口 需要处理现有无关失败和挂起测试,建立真正全绿的 CI 基线。 仍需外部输入和生产环境完成的事项: 至少 30 份脱敏 Golden PDF,并完成双人标注和精确率验收。 业务专家确认派生公式、缺失值、可比较性和结论策略。 上传并标注真实生产 PPTX 的语义 shape、页面类型和容量。 安装生产字体并建立视觉基准图片。 实际执行数据库迁移 038~040。 完成留存 dry-run、实删演练以及 10% → 30% → 100% 灰度。 观察期通过后开启 SCENARIO_ENGINE_V2,目前它仍默认关闭;真实清理开关也默认关闭。 完整状态记录在 [PPT与海报Phase4至6补充实施记录](D:/work/code/python/coding/baodanagent/docs/PPT与海报Phase4至6补充实施记录_20260802.md)。
2026-08-02 16:34:06 +08:00
def _evidence(document_id=1, page=2):
return {
"documentId": document_id,
"pageNumber": page,
"bbox": [10, 20, 100, 40],
"tableId": None,
"rowId": None,
"columnId": None,
"textHash": "a" * 64,
}
def _legacy_data(document_id=1):
return {
"product_name": "储蓄计划 A",
"product_type": "savings",
"insured": {"age": 35, "gender": "male", "smoker": "no"},
"policy": {
"currency": "HKD",
"annual_premium": 100000,
"premium_payment_period": 5,
"total_premium": None,
},
"benefit_illustration": [{
"policy_year": 10,
"scenario_type": "base",
"total_surrender_value": 650000,
}],
"meta": {"evidence": [
{"fieldPath": "policy.annual_premium", "evidence": _evidence(document_id, 2)},
{"fieldPath": "benefit.base.10.default.total_surrender_value", "evidence": _evidence(document_id, 8)},
]},
}
def test_candidate_reconciliation_preserves_conflicts_and_context_hint_cannot_override():
from insurance.plan_data.field_values import make_candidate, reconcile_candidates
evidence = [_evidence()]
result = reconcile_candidates([
make_candidate("policy.annualPremium", "100,000", normalized_value=100000, source="pdf_table", evidence=evidence),
make_candidate("policy.annualPremium", 120000, source="llm", confidence=0.99),
make_candidate("identity.productName", "文件名产品", source="context_hint"),
make_candidate("identity.productName", "PDF 产品", source="regex", evidence=evidence),
])
assert result["policy.annualPremium"]["status"] == "conflict"
assert result["policy.annualPremium"]["conflictCandidates"] == [100000, 120000]
assert result["identity.productName"]["status"] == "conflict"
assert set(result["identity.productName"]["conflictCandidates"]) == {"PDF 产品", "文件名产品"}
def test_legacy_conversion_uses_field_values_and_never_derives_contract_total():
from insurance.plan_data.conversion import legacy_to_plan_data
plan_data = legacy_to_plan_data(_legacy_data())
assert plan_data["policy"]["annualPremium"]["value"] == 100000
assert plan_data["policy"]["annualPremium"]["status"] == "extracted"
assert plan_data["policy"]["contractualTotalPremium"]["status"] == "missing"
assert plan_data["policy"]["contractualTotalPremium"]["value"] is None
assert plan_data["benefitScenarios"][0]["rows"][0]["totalSurrenderValue"]["evidence"]
2026-08-04 10:44:34 +08:00
def test_poster_flat_fields_survive_snapshot_conversion():
from insurance.plan_data.conversion import legacy_to_plan_data
from insurance.plan_data.snapshot_validation import validate_plan_data
poster_data = {
"product_name": "测试重疾计划",
"plan_type": "ci",
"age": 38,
"gender": "female",
"smoking_status": "no",
"currency": "HKD",
"sum_assured": 1_000_000,
"annual_premium": 25_000,
"premium_term": 20,
"meta": {"evidence": []},
}
plan_data = legacy_to_plan_data(poster_data)
result = validate_plan_data(plan_data, for_confirmation=True)
assert plan_data["identity"]["insuredAge"]["value"] == 38
assert plan_data["identity"]["insuredGender"]["value"] == "female"
assert plan_data["identity"]["insuredSmoker"]["value"] == "no"
assert plan_data["policy"]["currency"]["value"] == "HKD"
assert plan_data["policy"]["sumAssured"]["value"] == 1_000_000
assert plan_data["policy"]["premiumPaymentPeriod"]["value"] == 20
assert result["blockingCount"] == 0
前工程开发已经推进到 Phase 6 基础能力,但正式验收还没有完成。更准确地说:Phase 0~5 的主要代码链路已经落地,Phase 6 完成了治理框架,尚缺生产化和真实数据验收。 阶段 当前状态 说明 Phase 0~3 基本完成 Document IR、证据链、人工确认、不可变 PlanData Snapshot、海报/PPT 投影已实现 Phase 4 代码完成 场景、策略、模板版本,校验/发布门禁,确定性 resolver 和严格槽位合并已实现 Phase 5 代码完成 输入冻结、PPTX/海报对账、失败关闭、幂等、心跳、重试和冻结输入重放已实现 Phase 6 基础完成 质量看板、结构化告警、Golden 审批、留存清理、孤儿检查和灰度开关已实现 正式上线 未完成 缺真实样本、生产模板、业务规则签字和灰度观察 目前验证基线: PPT/海报专项测试:107 passed, 1 skipped Vue TypeScript 检查:通过 前端生产构建:通过 代码变更仍在工作区,尚未提交 仓库全量测试仍有既有失败/挂起项,暂时不能宣称全仓测试完全绿色 仍未完成的代码任务主要有: 影子解析差异流水线 目前有灰度开关,但还没有完整的“新旧解析同时运行、字段差异入库、按保司/profile 聚合”的影子比较任务。 自动视觉回归 目前实现的是 DOM 模块、溢出、尺寸、文本和数值检查;还缺基于真实模板和标准图片的像素差异、字体缺失、遮挡和裁切回归。 告警通道接入 后台已经能产生结构化质量告警,但尚未自动推送到邮件、企微或其他通知通道。 留存任务生产化 dry-run、实删服务和失败审计已经具备,但尚未接入周期性 Celery/定时任务,也没有自动重试失败清理批次。 旧链路最终下线 旧 PPT 解析器和海报紧凑解析仍保留为回滚路径。需要全量灰度稳定后才能删除或彻底关闭写入口。 全仓测试收口 需要处理现有无关失败和挂起测试,建立真正全绿的 CI 基线。 仍需外部输入和生产环境完成的事项: 至少 30 份脱敏 Golden PDF,并完成双人标注和精确率验收。 业务专家确认派生公式、缺失值、可比较性和结论策略。 上传并标注真实生产 PPTX 的语义 shape、页面类型和容量。 安装生产字体并建立视觉基准图片。 实际执行数据库迁移 038~040。 完成留存 dry-run、实删演练以及 10% → 30% → 100% 灰度。 观察期通过后开启 SCENARIO_ENGINE_V2,目前它仍默认关闭;真实清理开关也默认关闭。 完整状态记录在 [PPT与海报Phase4至6补充实施记录](D:/work/code/python/coding/baodanagent/docs/PPT与海报Phase4至6补充实施记录_20260802.md)。
2026-08-02 16:34:06 +08:00
def test_projection_uses_confirmed_table_milestones_and_keeps_unknown_null():
from insurance.plan_data.conversion import legacy_to_plan_data
from insurance.plan_data.projections import to_deck_contract, to_poster_projection
plan_data = legacy_to_plan_data(_legacy_data())
poster = to_poster_projection(plan_data)
deck = to_deck_contract(plan_data)
assert poster["surrender_value_10"] == 650000
assert poster["surrender_value_20"] is None
assert poster["total_premium"] is None
assert deck["policy"]["contractualTotalPremium"] is None
assert deck["benefitRows"][0]["totalSurrenderValue"] == 650000
def test_migration_037_and_snapshot_lifecycle_are_versioned_and_owned():
from flask import Flask
from sqlalchemy import inspect
from insurance.db.database import db
from insurance.db.migrate_036 import migrate as migrate_036
from insurance.db.migrate_037 import migrate as migrate_037
from insurance.models.insurance_document import InsuranceDocument
from insurance.plan_data.service import (
SnapshotError,
confirm_snapshot,
create_draft_from_legacy,
patch_draft,
)
app = Flask(__name__)
app.config.update(
SQLALCHEMY_DATABASE_URI="sqlite:///:memory:",
SQLALCHEMY_TRACK_MODIFICATIONS=False,
)
db.init_app(app)
with app.app_context():
migrate_036()
migrate_037()
document = InsuranceDocument(
user_id="user-1",
sha256="b" * 64,
storage_key="/private/plan.pdf",
original_name="plan.pdf",
mime_type="application/pdf",
file_size=100,
page_count=8,
pdf_kind="native",
status="parsed",
parser_version="test-v1",
)
db.session.add(document)
db.session.commit()
legacy = _legacy_data(document.id)
draft = create_draft_from_legacy(document.id, "user-1", legacy)
confirmed = confirm_snapshot(draft.id, "user-1")
assert draft.snapshot_version == 1
assert confirmed.snapshot_version == 2
assert confirmed.previous_snapshot_id == draft.id
assert confirmed.status == "confirmed"
assert confirmed.snapshot_hash != draft.snapshot_hash
assert confirmed.validation()["blockingCount"] == 0
with pytest.raises(SnapshotError) as stale:
patch_draft(
confirmed.id,
"user-1",
expected_hash="wrong",
changes=[{
"fieldPath": "policy.annualPremium",
"value": 110000,
"overrideReason": "客户确认修正",
}],
)
assert stale.value.code == "SNAPSHOT_VERSION_CONFLICT"
edited = patch_draft(
confirmed.id,
"user-1",
expected_hash=confirmed.snapshot_hash,
changes=[{
"fieldPath": "policy.annualPremium",
"value": 110000,
"overrideReason": "客户确认修正",
}],
)
assert edited.snapshot_version == 3
assert edited.plan_data()["policy"]["annualPremium"]["status"] == "confirmed"
assert confirmed.plan_data()["policy"]["annualPremium"]["value"] == 100000
with pytest.raises(SnapshotError):
confirm_snapshot(edited.id, "user-2")
tables = set(inspect(db.engine).get_table_names())
assert {
"insurance_plan_snapshots",
"insurance_field_evidence",
"insurance_plan_overrides",
} <= tables
2026-08-04 10:44:34 +08:00
def test_confirmation_warns_when_critical_amount_evidence_is_missing():
前工程开发已经推进到 Phase 6 基础能力,但正式验收还没有完成。更准确地说:Phase 0~5 的主要代码链路已经落地,Phase 6 完成了治理框架,尚缺生产化和真实数据验收。 阶段 当前状态 说明 Phase 0~3 基本完成 Document IR、证据链、人工确认、不可变 PlanData Snapshot、海报/PPT 投影已实现 Phase 4 代码完成 场景、策略、模板版本,校验/发布门禁,确定性 resolver 和严格槽位合并已实现 Phase 5 代码完成 输入冻结、PPTX/海报对账、失败关闭、幂等、心跳、重试和冻结输入重放已实现 Phase 6 基础完成 质量看板、结构化告警、Golden 审批、留存清理、孤儿检查和灰度开关已实现 正式上线 未完成 缺真实样本、生产模板、业务规则签字和灰度观察 目前验证基线: PPT/海报专项测试:107 passed, 1 skipped Vue TypeScript 检查:通过 前端生产构建:通过 代码变更仍在工作区,尚未提交 仓库全量测试仍有既有失败/挂起项,暂时不能宣称全仓测试完全绿色 仍未完成的代码任务主要有: 影子解析差异流水线 目前有灰度开关,但还没有完整的“新旧解析同时运行、字段差异入库、按保司/profile 聚合”的影子比较任务。 自动视觉回归 目前实现的是 DOM 模块、溢出、尺寸、文本和数值检查;还缺基于真实模板和标准图片的像素差异、字体缺失、遮挡和裁切回归。 告警通道接入 后台已经能产生结构化质量告警,但尚未自动推送到邮件、企微或其他通知通道。 留存任务生产化 dry-run、实删服务和失败审计已经具备,但尚未接入周期性 Celery/定时任务,也没有自动重试失败清理批次。 旧链路最终下线 旧 PPT 解析器和海报紧凑解析仍保留为回滚路径。需要全量灰度稳定后才能删除或彻底关闭写入口。 全仓测试收口 需要处理现有无关失败和挂起测试,建立真正全绿的 CI 基线。 仍需外部输入和生产环境完成的事项: 至少 30 份脱敏 Golden PDF,并完成双人标注和精确率验收。 业务专家确认派生公式、缺失值、可比较性和结论策略。 上传并标注真实生产 PPTX 的语义 shape、页面类型和容量。 安装生产字体并建立视觉基准图片。 实际执行数据库迁移 038~040。 完成留存 dry-run、实删演练以及 10% → 30% → 100% 灰度。 观察期通过后开启 SCENARIO_ENGINE_V2,目前它仍默认关闭;真实清理开关也默认关闭。 完整状态记录在 [PPT与海报Phase4至6补充实施记录](D:/work/code/python/coding/baodanagent/docs/PPT与海报Phase4至6补充实施记录_20260802.md)。
2026-08-02 16:34:06 +08:00
from insurance.plan_data.conversion import legacy_to_plan_data
from insurance.plan_data.snapshot_validation import validate_plan_data
data = _legacy_data()
data["meta"]["evidence"] = []
result = validate_plan_data(legacy_to_plan_data(data), for_confirmation=True)
2026-08-04 10:44:34 +08:00
assert result["blockingCount"] == 0
assert result["warningCount"] == 1
前工程开发已经推进到 Phase 6 基础能力,但正式验收还没有完成。更准确地说:Phase 0~5 的主要代码链路已经落地,Phase 6 完成了治理框架,尚缺生产化和真实数据验收。 阶段 当前状态 说明 Phase 0~3 基本完成 Document IR、证据链、人工确认、不可变 PlanData Snapshot、海报/PPT 投影已实现 Phase 4 代码完成 场景、策略、模板版本,校验/发布门禁,确定性 resolver 和严格槽位合并已实现 Phase 5 代码完成 输入冻结、PPTX/海报对账、失败关闭、幂等、心跳、重试和冻结输入重放已实现 Phase 6 基础完成 质量看板、结构化告警、Golden 审批、留存清理、孤儿检查和灰度开关已实现 正式上线 未完成 缺真实样本、生产模板、业务规则签字和灰度观察 目前验证基线: PPT/海报专项测试:107 passed, 1 skipped Vue TypeScript 检查:通过 前端生产构建:通过 代码变更仍在工作区,尚未提交 仓库全量测试仍有既有失败/挂起项,暂时不能宣称全仓测试完全绿色 仍未完成的代码任务主要有: 影子解析差异流水线 目前有灰度开关,但还没有完整的“新旧解析同时运行、字段差异入库、按保司/profile 聚合”的影子比较任务。 自动视觉回归 目前实现的是 DOM 模块、溢出、尺寸、文本和数值检查;还缺基于真实模板和标准图片的像素差异、字体缺失、遮挡和裁切回归。 告警通道接入 后台已经能产生结构化质量告警,但尚未自动推送到邮件、企微或其他通知通道。 留存任务生产化 dry-run、实删服务和失败审计已经具备,但尚未接入周期性 Celery/定时任务,也没有自动重试失败清理批次。 旧链路最终下线 旧 PPT 解析器和海报紧凑解析仍保留为回滚路径。需要全量灰度稳定后才能删除或彻底关闭写入口。 全仓测试收口 需要处理现有无关失败和挂起测试,建立真正全绿的 CI 基线。 仍需外部输入和生产环境完成的事项: 至少 30 份脱敏 Golden PDF,并完成双人标注和精确率验收。 业务专家确认派生公式、缺失值、可比较性和结论策略。 上传并标注真实生产 PPTX 的语义 shape、页面类型和容量。 安装生产字体并建立视觉基准图片。 实际执行数据库迁移 038~040。 完成留存 dry-run、实删演练以及 10% → 30% → 100% 灰度。 观察期通过后开启 SCENARIO_ENGINE_V2,目前它仍默认关闭;真实清理开关也默认关闭。 完整状态记录在 [PPT与海报Phase4至6补充实施记录](D:/work/code/python/coding/baodanagent/docs/PPT与海报Phase4至6补充实施记录_20260802.md)。
2026-08-02 16:34:06 +08:00
assert any(item["code"] == "CRITICAL_EVIDENCE_MISSING" for item in result["issues"])
2026-08-04 10:44:34 +08:00
def test_optional_benefit_and_withdrawal_data_do_not_block_confirmation():
from insurance.plan_data.conversion import legacy_to_plan_data
from insurance.plan_data.snapshot_validation import validate_plan_data
data = _legacy_data()
data["meta"]["evidence"] = [
{"fieldPath": "policy.annual_premium", "evidence": _evidence(1, 2)},
]
data["withdrawal_illustration"] = [{
"policy_year": 10,
"annual_withdrawal": 50000,
"surrender_value_after": 300000,
}]
result = validate_plan_data(legacy_to_plan_data(data), for_confirmation=True)
assert result["blockingCount"] == 0
assert not any(
item["code"] == "CRITICAL_EVIDENCE_MISSING"
and (
".benefitScenarios." in item["path"]
or ".withdrawals." in item["path"]
)
for item in result["issues"]
)
def test_missing_optional_projection_sections_do_not_block_confirmation():
from insurance.plan_data.conversion import legacy_to_plan_data
from insurance.plan_data.snapshot_validation import validate_plan_data
data = _legacy_data()
data["benefit_illustration"] = []
data["withdrawal_illustration"] = []
result = validate_plan_data(legacy_to_plan_data(data), for_confirmation=True)
assert result["blockingCount"] == 0
2026-08-03 10:45:25 +08:00
def test_confirmation_does_not_treat_source_labels_as_amounts():
from insurance.plan_data.conversion import legacy_to_plan_data
from insurance.plan_data.snapshot_validation import validate_plan_data
data = _legacy_data()
data["policy"]["annual_premium_source_label"] = "IUL利益表保费列"
result = validate_plan_data(legacy_to_plan_data(data), for_confirmation=True)
assert not any(
item["path"].endswith("annualPremiumSourceLabel")
for item in result["issues"]
)
前工程开发已经推进到 Phase 6 基础能力,但正式验收还没有完成。更准确地说:Phase 0~5 的主要代码链路已经落地,Phase 6 完成了治理框架,尚缺生产化和真实数据验收。 阶段 当前状态 说明 Phase 0~3 基本完成 Document IR、证据链、人工确认、不可变 PlanData Snapshot、海报/PPT 投影已实现 Phase 4 代码完成 场景、策略、模板版本,校验/发布门禁,确定性 resolver 和严格槽位合并已实现 Phase 5 代码完成 输入冻结、PPTX/海报对账、失败关闭、幂等、心跳、重试和冻结输入重放已实现 Phase 6 基础完成 质量看板、结构化告警、Golden 审批、留存清理、孤儿检查和灰度开关已实现 正式上线 未完成 缺真实样本、生产模板、业务规则签字和灰度观察 目前验证基线: PPT/海报专项测试:107 passed, 1 skipped Vue TypeScript 检查:通过 前端生产构建:通过 代码变更仍在工作区,尚未提交 仓库全量测试仍有既有失败/挂起项,暂时不能宣称全仓测试完全绿色 仍未完成的代码任务主要有: 影子解析差异流水线 目前有灰度开关,但还没有完整的“新旧解析同时运行、字段差异入库、按保司/profile 聚合”的影子比较任务。 自动视觉回归 目前实现的是 DOM 模块、溢出、尺寸、文本和数值检查;还缺基于真实模板和标准图片的像素差异、字体缺失、遮挡和裁切回归。 告警通道接入 后台已经能产生结构化质量告警,但尚未自动推送到邮件、企微或其他通知通道。 留存任务生产化 dry-run、实删服务和失败审计已经具备,但尚未接入周期性 Celery/定时任务,也没有自动重试失败清理批次。 旧链路最终下线 旧 PPT 解析器和海报紧凑解析仍保留为回滚路径。需要全量灰度稳定后才能删除或彻底关闭写入口。 全仓测试收口 需要处理现有无关失败和挂起测试,建立真正全绿的 CI 基线。 仍需外部输入和生产环境完成的事项: 至少 30 份脱敏 Golden PDF,并完成双人标注和精确率验收。 业务专家确认派生公式、缺失值、可比较性和结论策略。 上传并标注真实生产 PPTX 的语义 shape、页面类型和容量。 安装生产字体并建立视觉基准图片。 实际执行数据库迁移 038~040。 完成留存 dry-run、实删演练以及 10% → 30% → 100% 灰度。 观察期通过后开启 SCENARIO_ENGINE_V2,目前它仍默认关闭;真实清理开关也默认关闭。 完整状态记录在 [PPT与海报Phase4至6补充实施记录](D:/work/code/python/coding/baodanagent/docs/PPT与海报Phase4至6补充实施记录_20260802.md)。
2026-08-02 16:34:06 +08:00
def test_generation_task_exposes_frozen_versions_without_server_paths():
from insurance.models.generation_task import GenerationTask
task = GenerationTask(
id="task-1",
user_id="user-1",
artifact_type="ppt",
operation="generate",
workspace_id="session-1",
input_revision=7,
submit_revision=7,
snapshot_id=3,
snapshot_hash="c" * 64,
renderer_version="renderer-v2",
result_state="stale",
output_json=json.dumps({"filePath": "/private/output.pptx", "downloadUrl": "/download/1"}),
)
payload = task.to_dict()
assert payload["snapshotId"] == 3
assert payload["submitRevision"] == 7
assert payload["rendererVersion"] == "renderer-v2"
assert payload["resultState"] == "stale"
assert payload["output"] == {"downloadUrl": "/download/1"}