2026-07-31 14:10:24 +08:00
|
|
|
"""PPT/海报优化规则回归测试。"""
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "api"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_currency_aliases_and_unknown_are_not_fabricated():
|
|
|
|
|
from insurance.ppt.normalizer import _normalize_currency
|
|
|
|
|
|
|
|
|
|
assert _normalize_currency("USB") == "USD"
|
|
|
|
|
assert _normalize_currency("RMB") == "CNY"
|
|
|
|
|
assert _normalize_currency("¥") == "CNY"
|
|
|
|
|
assert _normalize_currency("XYZ") is None
|
|
|
|
|
assert _normalize_currency(None) is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_optional_review_fields_are_normalized_without_becoming_required():
|
|
|
|
|
from insurance.ppt.normalizer import normalize_savings_plan
|
|
|
|
|
|
|
|
|
|
plan = normalize_savings_plan({
|
|
|
|
|
"product_name": "储蓄计划",
|
|
|
|
|
"insured": {"age": 35, "gender": "女", "smoker": "否"},
|
|
|
|
|
"policy": {
|
|
|
|
|
"currency": "RMB",
|
|
|
|
|
"annual_premium": 10000,
|
|
|
|
|
"premium_payment_period": 5,
|
|
|
|
|
"basic_plan_annual_premium": 9000,
|
|
|
|
|
"basic_sum_insured": 500000,
|
|
|
|
|
"first_year_amount_due": 9800,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert plan["insured"]["smoker"] == "no"
|
|
|
|
|
assert plan["policy"]["currency"] == "CNY"
|
|
|
|
|
assert plan["policy"]["basicPlanAnnualPremium"] == 9000
|
|
|
|
|
assert plan["policy"]["basicSumInsured"] == 500000
|
|
|
|
|
assert plan["policy"]["firstYearAmountDue"] == 9800
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_benefit_and_withdrawal_issues_never_block_savings_generation():
|
|
|
|
|
from insurance.ppt.validator import validate_formal_savings_plan
|
|
|
|
|
|
|
|
|
|
issues = validate_formal_savings_plan({
|
|
|
|
|
"productName": "储蓄计划",
|
|
|
|
|
"insured": {"age": 35, "smoker": "no"},
|
|
|
|
|
"policy": {"currency": "USD", "annualPremium": 10000, "payYears": 5},
|
|
|
|
|
"benefitRows": [
|
|
|
|
|
{
|
|
|
|
|
"policyYear": 1,
|
|
|
|
|
"guaranteedCashValue": 100,
|
|
|
|
|
"totalSurrenderValue": 50,
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"withdrawalRows": [{"policyYear": 1}, {"policyYear": 3}],
|
|
|
|
|
"source": {"pdfHash": "hash"},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
affected = [
|
|
|
|
|
issue for issue in issues
|
|
|
|
|
if issue.section in ("benefitRows", "withdrawalRows")
|
|
|
|
|
]
|
|
|
|
|
assert affected
|
|
|
|
|
assert all(issue.level == "warn" for issue in affected)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_brand_policy_controls_company_product_and_logo_independently():
|
|
|
|
|
from insurance.ppt.masking import apply_brand_policy, build_brand_policy
|
|
|
|
|
|
|
|
|
|
company = {
|
|
|
|
|
"id": "c1",
|
|
|
|
|
"displayName": "真实保司",
|
|
|
|
|
"maskedDisplayName": "保X",
|
|
|
|
|
"maskingEnabled": True,
|
|
|
|
|
"logoEnabled": False,
|
|
|
|
|
"logoUrl": "/logo.png",
|
|
|
|
|
}
|
|
|
|
|
product = {
|
|
|
|
|
"id": "p1",
|
|
|
|
|
"displayName": "真实产品",
|
|
|
|
|
"maskedDisplayName": "产X",
|
|
|
|
|
"maskingEnabled": False,
|
|
|
|
|
}
|
|
|
|
|
policy = build_brand_policy(company, [product])
|
|
|
|
|
masked_company, masked_product = apply_brand_policy(company, product, policy)
|
|
|
|
|
|
|
|
|
|
assert masked_company["displayName"] == "保X"
|
|
|
|
|
assert masked_company["logoUrl"] == ""
|
|
|
|
|
assert masked_product["displayName"] == "真实产品"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_all_six_core_poster_profiles_have_distinct_content_budgets():
|
|
|
|
|
from insurance.poster.field_profiles import get_field_profile
|
|
|
|
|
|
|
|
|
|
profiles = {
|
|
|
|
|
(plan_type, output_mode): get_field_profile(plan_type, output_mode)
|
|
|
|
|
for plan_type in ("savings", "ci", "iul")
|
|
|
|
|
for output_mode in ("single", "long")
|
|
|
|
|
}
|
|
|
|
|
assert len(profiles) == 6
|
|
|
|
|
for plan_type in ("savings", "ci", "iul"):
|
|
|
|
|
assert profiles[(plan_type, "single")]["maxFeatureCount"] == 3
|
|
|
|
|
assert profiles[(plan_type, "long")]["maxFeatureCount"] == 6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_single_poster_never_contains_full_benefit_table():
|
|
|
|
|
from insurance.poster.content_builder import build_poster_content
|
|
|
|
|
|
|
|
|
|
rows = [
|
|
|
|
|
{"policy_year": year, "total_surrender": year * 1000}
|
|
|
|
|
for year in range(1, 31)
|
|
|
|
|
]
|
|
|
|
|
content = build_poster_content(
|
|
|
|
|
{"benefit_illustration": rows},
|
|
|
|
|
{"features": [{"title": str(index)} for index in range(8)]},
|
|
|
|
|
output_mode="single",
|
|
|
|
|
plan_type="savings",
|
|
|
|
|
)
|
|
|
|
|
assert len(content["benefit_table"]) <= 1
|
|
|
|
|
assert len(content["features"]) <= 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_scenario_is_compatible_only_with_its_calculation_mode(monkeypatch):
|
|
|
|
|
from insurance.ppt import scenarios
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(scenarios, "get_scenario_config", lambda _code: {
|
|
|
|
|
"generationMode": "single",
|
|
|
|
|
"baseScenario": None,
|
|
|
|
|
})
|
|
|
|
|
assert scenarios.template_scenario_compatible("retirement", "generic_single")
|
|
|
|
|
assert not scenarios.template_scenario_compatible("retirement", "generic_compare")
|
2026-07-31 15:20:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_poster_case_mapping_reads_nested_insured_and_policy_fields():
|
|
|
|
|
from insurance.poster.tasks import _map_extract_plan_fields
|
|
|
|
|
|
|
|
|
|
mapped = _map_extract_plan_fields({
|
|
|
|
|
"insured": {"age": 35, "gender": "female"},
|
|
|
|
|
"policy": {
|
|
|
|
|
"currency": "RMB",
|
|
|
|
|
"sum_insured": 500000,
|
|
|
|
|
"annual_premium": 100000,
|
|
|
|
|
"premium_payment_period": 5,
|
|
|
|
|
"coverage_period": "终身",
|
|
|
|
|
},
|
|
|
|
|
"benefit_illustration": [{"policy_year": 10, "total_surrender_value": 800000}],
|
|
|
|
|
}, "savings", "success")
|
|
|
|
|
|
|
|
|
|
assert mapped["age"] == 35
|
|
|
|
|
assert mapped["gender"] == "女"
|
|
|
|
|
assert mapped["currency"] == "CNY"
|
|
|
|
|
assert mapped["sum_assured"] == 500000
|
|
|
|
|
assert mapped["annual_premium"] == 100000
|
|
|
|
|
assert mapped["premium_term"] == 5
|
|
|
|
|
assert mapped["coverage_period"] == "终身"
|
|
|
|
|
assert mapped["benefit_table"][0]["policy_year"] == 10
|
|
|
|
|
assert mapped["meta"]["status"] == "parsed"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_poster_case_mapping_does_not_report_empty_result_as_parsed():
|
|
|
|
|
from insurance.poster.tasks import _map_extract_plan_fields
|
|
|
|
|
|
|
|
|
|
mapped = _map_extract_plan_fields({}, "savings", "partial")
|
|
|
|
|
|
|
|
|
|
assert mapped["meta"]["status"] == "failed"
|
|
|
|
|
assert mapped["meta"]["validFieldCount"] == 0
|
|
|
|
|
assert "age" in mapped["meta"]["missingFields"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_poster_compliance_returns_all_character_ranges_and_revision():
|
|
|
|
|
from insurance.poster.compliance import check_copy_compliance
|
|
|
|
|
|
|
|
|
|
result = check_copy_compliance({
|
|
|
|
|
"headline": "保证收益",
|
|
|
|
|
"body": "并非无风险,也不是零风险。",
|
|
|
|
|
"call_to_action": "立即咨询",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert result["status"] == "block"
|
|
|
|
|
assert result["revision"]
|
|
|
|
|
assert [(issue["field"], issue["text"]) for issue in result["issues"]] == [
|
|
|
|
|
("headline", "保证"),
|
|
|
|
|
("body", "无风险"),
|
|
|
|
|
("body", "零风险"),
|
|
|
|
|
]
|
|
|
|
|
assert result["issues"][0]["start"] == 0
|
|
|
|
|
assert result["issues"][0]["end"] == 2
|
2026-07-31 15:41:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_poster_compliance_warns_and_returns_direct_replacement():
|
|
|
|
|
from insurance.poster.compliance import check_copy_compliance
|
|
|
|
|
|
|
|
|
|
result = check_copy_compliance({
|
|
|
|
|
"headline": "行业领先的保障方案",
|
|
|
|
|
"body": "具体内容以正式合同为准。",
|
|
|
|
|
"call_to_action": "了解详情",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert result["status"] == "warn"
|
|
|
|
|
assert result["issues"][0]["severity"] == "warn"
|
|
|
|
|
assert result["issues"][0]["replacement"] == "具有特色"
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 16:19:33 +08:00
|
|
|
def test_poster_generation_requires_template_before_other_processing():
|
|
|
|
|
from insurance.poster.service import PosterService
|
|
|
|
|
|
|
|
|
|
result = PosterService().generate_poster("user-1", {
|
|
|
|
|
"copyContent": {
|
|
|
|
|
"headline": "保障方案",
|
|
|
|
|
"body": "具体内容以正式合同为准。",
|
|
|
|
|
"call_to_action": "了解详情",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert result == {"code": 1001, "message": "请选择海报模板", "data": None}
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 15:41:58 +08:00
|
|
|
def test_poster_case_mapping_preserves_parse_diagnostics():
|
|
|
|
|
from insurance.poster.tasks import _map_extract_plan_fields
|
|
|
|
|
|
|
|
|
|
mapped = _map_extract_plan_fields({
|
|
|
|
|
"insured": {"age": 35},
|
|
|
|
|
"policy": {"currency": "USD", "annual_premium": 10000, "premium_payment_period": 5},
|
|
|
|
|
"_meta": {"method": "regex+ocr", "low_quality_pages": [3, 8]},
|
|
|
|
|
"_provenance": {"insured.age": {"source": "ocr", "confidence": 0.8}},
|
|
|
|
|
}, "savings", "partial")
|
|
|
|
|
|
|
|
|
|
assert mapped["meta"]["method"] == "regex+ocr"
|
|
|
|
|
assert mapped["meta"]["lowQualityPages"] == [3, 8]
|
|
|
|
|
assert mapped["meta"]["provenance"]["insured.age"]["confidence"] == 0.8
|