"""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_iul_key_fields_accept_raw_and_normalized_names_without_data_loss(): from insurance.ppt.normalizer import normalize_iul_plan plan = normalize_iul_plan({ "productName": "Manulife SIUL 3", "insured": {"age": 48, "gender": "female", "smoker": "non-smoker"}, "policy": { "currency": "USD", "sumInsured": 3000000, "annualPremium": 80060, "payYears": 5, "coverage_period": "终身", }, "benefit_illustration": [ {"policy_year": 1, "total_surrender_value": 31600}, {"policy_year": 10, "total_surrender_value": 76800}, ], }) assert plan["productName"] == "Manulife SIUL 3" assert plan["insured"]["age"] == 48 assert plan["insured"]["smoker"] == "no" assert plan["policy"]["sumInsured"] == 3000000 assert plan["policy"]["annualPremium"] == 80060 assert plan["policy"]["payYears"] == 5 assert plan["benefitRows"][0]["age"] == 48 assert plan["benefitRows"][1]["age"] == 57 assert plan["benefitRows"][1]["totalSurrenderValue"] == 76800 def test_iul_implausibly_tiny_benefit_values_block_generation(): from insurance.ppt.validator import validate_formal_iul_plan issues = validate_formal_iul_plan({ "productName": "Manulife SIUL 3", "insured": {"age": 48, "smoker": "no"}, "policy": { "currency": "USD", "sumInsured": 3000000, "annualPremium": 80060, "paymentPeriod": "5", }, "indexAccounts": [{"name": "S&P 500"}], "benefitRows": [ {"policyYear": year, "age": 48 + year - 1, "totalSurrenderValue": value} for year, value in [(1, 56), (10, 85), (20, 114), (30, 122)] ], "source": {"pdfHash": "hash"}, }) assert any( issue.code == "IUL_BENEFIT_VALUE_IMPLAUSIBLE" and issue.level == "error" for issue in issues ) def test_ppt_generation_preserves_company_selected_for_uploaded_file(): source = ( Path(__file__).resolve().parents[1] / "api/insurance/generation/celery_tasks.py" ).read_text(encoding="utf-8") assert 'normalized["companyId"] = ext.get("companyId") or company_id or ""' in source assert "if not company_id:" in source 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") 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 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"] == "具有特色" 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} 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