241 lines
18 KiB
Python
241 lines
18 KiB
Python
"""Build the deterministic 120-case multiscene evaluation inventory.
|
|
|
|
This module prepares synthetic test transactions only. It performs no model,
|
|
database, service, publication, or legal-approval action.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from yuqei_contract_api.contract_templates import list_contract_templates, template_document_kind
|
|
from yuqei_contract_api.template_evaluation_evidence import scene_model_cases
|
|
from yuqei_contract_api.template_inputs import template_sample_values
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
GOLDEN_ROOT = (REPO_ROOT / "yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api"
|
|
/ "golden_assets/v1_phase13_legal_knowledge_template_library/qa-samples/generation")
|
|
VARIANTS = ("normal", "missing-key-fact", "opposite-adversarial")
|
|
CORE_TEMPLATE_IDS = {
|
|
"TPL-COMMISSION-SERVICE-001", "TPL-LABOR-CONTRACT-001", "TPL-LEASE-HOUSE-001",
|
|
"TPL-NDA-COMMERCIAL-001", "TPL-PURCHASE-GOODS-001", "TPL-SERVICE-GENERAL-001",
|
|
"TPL-SOFTWARE-SAAS-001", "TPL-WORKS-001",
|
|
}
|
|
BASE_OPERATIONS = ["draft", "semantic_review"]
|
|
REPAIR_OPERATIONS = [*BASE_OPERATIONS, "repair_draft", "repair_review"]
|
|
SCENE_OPERATIONS = ["draft", "specialist_review"]
|
|
|
|
|
|
# Six legacy assets have no Phase-13 generation record. Values are explicit,
|
|
# synthetic transaction facts rather than template_sample_values placeholders.
|
|
_EXPLICIT_LEGACY_VALUES: dict[str, dict[str, str]] = {
|
|
"TPL-DATA-PROCESSING-001": {
|
|
"个人信息处理者名称": "合成甲方科技有限公司", "受托人名称": "合成乙方云服务有限公司",
|
|
"主服务合同名称": "企业工单系统服务合同", "主服务合同编号": "TEST-DPA-2026-001",
|
|
"委托处理目的": "为甲方提供工单系统托管及故障处理", "委托处理期限": "2026-10-01至2027-09-30",
|
|
"委托处理方式": "按甲方书面指令在境内加密存储和处理", "个人信息种类": "员工姓名、工作邮箱及工单内容",
|
|
"数据主体类型": "甲方员工和经授权联系人", "安全措施": "最小权限、传输加密、访问日志和季度权限复核",
|
|
"安全事件通知时限": "发现后24小时内", "终止后处置方式": "30日内返还后删除并出具处理记录",
|
|
"争议解决方式": "甲方所在地人民法院管辖", "签署日期": "2026-09-10",
|
|
"处理指令记录方式": "双方工单及书面变更单", "数据保护联系人": "合成数据保护联系人及TEST-DPA邮箱",
|
|
"监督审计频次": "每年一次并在重大事件后专项核查", "再委托安排": "未经甲方书面同意不得再委托",
|
|
"敏感信息范围": "本样本不处理敏感个人信息", "数据出境安排": "本样本不涉及数据出境",
|
|
"删除/返还证明方式": "提供删除范围、完成时间和日志摘要", "电子签署平台": "双方确认的合成电子签署平台",
|
|
},
|
|
"TPL-FINANCIAL-LEASE-001": {
|
|
"甲方名称": "合成融资租赁有限公司", "乙方名称": "合成制造有限公司", "出卖人名称": "合成设备有限公司",
|
|
"租赁物名称": "普通数控加工设备", "租赁物规格": "TEST-CNC-100型", "租赁物价款": "人民币1200000元",
|
|
"交付地点": "合成制造有限公司测试厂区", "交付期限": "2026-11-30前", "租赁期限": "36个月",
|
|
"租金总额": "人民币1320000元", "租金构成": "本金及约定租赁收益", "租金支付方式": "每月等额支付",
|
|
"租赁物归属": "租赁期内归甲方,期满按约定留购", "保险安排": "乙方投保财产险并将甲方列为受益人",
|
|
"争议解决方式": "甲方所在地人民法院管辖", "签署日期": "2026-09-10", "买卖合同编号": "TEST-SALE-2026-001",
|
|
"租赁物清单/编号": "设备一台,序列号TEST-CNC-001", "验收责任与签收文件": "乙方验收并签署设备验收单",
|
|
"逾期违约金率": "按逾期金额每日万分之二", "保证金": "人民币120000元", "留购价款": "人民币1000元",
|
|
"登记机关": "依法适用的统一登记系统", "登记/公示凭证": "登记完成后提供合成登记回执编号",
|
|
"取回与处置方式": "依约通知并依法取回处置", "售后回租权属证明": "本样本非售后回租",
|
|
"电子签署平台": "双方确认的合成电子签署平台",
|
|
},
|
|
"TPL-GUARANTEE-001": {
|
|
"债权人名称": "合成甲方商业有限公司", "保证人名称": "合成保证有限公司", "债务人名称": "合成乙方制造有限公司",
|
|
"主合同名称": "设备采购合同", "主合同编号": "TEST-GUARANTEE-2026-001", "主债权种类": "采购价款债权",
|
|
"主债权数额": "人民币500000元", "主债务履行期限": "2026-12-31前", "保证方式": "连带责任保证",
|
|
"保证范围": "主债权、利息、违约金、损害赔偿金及实现债权的合理费用", "争议解决方式": "债权人所在地人民法院管辖",
|
|
"签署日期": "2026-09-10", "保证期间": "主债务履行期限届满之日起两年", "最高额担保限额/期间": "本样本不适用最高额担保",
|
|
"主合同变更通知规则": "涉及金额或期限变更应书面通知保证人", "反担保方式": "本样本未设置反担保",
|
|
"抵押质押财产": "本样本未设置物的担保", "担保物价值评估": "不适用", "担保登记机关": "不适用",
|
|
"公司决议文件": "合成保证有限公司董事会决议TEST-BOARD-001", "共同担保/追偿安排": "本样本无共同保证人",
|
|
"实现担保费用承担": "由责任方承担经证明的合理费用", "电子签署平台": "双方确认的合成电子签署平台",
|
|
},
|
|
"TPL-INTERMEDIARY-001": {
|
|
"委托人名称": "合成甲方科技有限公司", "居间人名称": "合成乙方咨询有限公司", "居间事项": "寻找普通办公设备供应商",
|
|
"服务类型": "提供交易机会及如实报告订约信息", "服务期限": "2026-10-01至2026-12-31",
|
|
"报告义务范围": "供应商主体、报价、交付期及已知利益关系", "报酬标准": "成交含税金额的1%",
|
|
"报酬支付条件": "委托人与乙方介绍的供应商签约并支付首款后十个工作日内", "成交认定标准": "双方签署采购合同且首款到账",
|
|
"必要费用承担": "事先书面同意的差旅费用由委托人据实承担", "防跳单约定": "服务期内及届满后六个月限于已书面报告的供应商",
|
|
"保密义务": "仅为本次居间事项使用交易资料", "违约责任": "违约方赔偿经证明的实际损失",
|
|
"争议解决方式": "委托人所在地人民法院管辖", "签署日期": "2026-09-10",
|
|
"委托人配合义务": "及时提供需求、预算及反馈并核验最终交易文件", "电子签署平台": "双方确认的合成电子签署平台",
|
|
},
|
|
"TPL-IP-LICENSE-001": {
|
|
"许可方名称": "合成甲方软件有限公司", "被许可方名称": "合成乙方制造有限公司", "知识产权类型": "计算机软件著作权",
|
|
"权利名称": "合成设备管理软件V1.0", "注册登记号": "TEST-SOFT-2026-001", "权利有效期": "依法有效期间",
|
|
"许可方式": "普通许可", "许可权利范围": "在乙方内部部署、运行及备份", "许可地域": "中国大陆境内",
|
|
"许可期限": "2026-10-01至2027-09-30", "许可使用费": "人民币100000元", "付费方式": "签署后十日内支付50%,部署验收后支付50%",
|
|
"争议解决方式": "许可方所在地人民法院管辖", "签署日期": "2026-09-10", "权属证明文件": "合成软件著作权登记材料TEST-SOFT-001",
|
|
"许可产品/使用场景": "乙方内部设备维护管理", "质量标准": "按附件功能清单和验收用例验收", "标识要求": "保留软件内现有权利标识",
|
|
"再许可安排": "未经许可方书面同意不得再许可", "改进成果归属": "各自独立完成的改进归完成方,合作改进另行书面约定",
|
|
"权利续展/维持义务": "许可方依法维持许可权利有效", "侵权处理费用承担": "按责任归属承担经证明的合理费用",
|
|
"跨境许可/外汇税务说明": "本样本不涉及跨境许可", "备案登记机关": "本样本无另行备案安排",
|
|
"电子签署平台": "双方确认的合成电子签署平台",
|
|
},
|
|
"TPL-PARTNERSHIP-001": {
|
|
"合伙企业名称": "合成创新咨询合伙企业", "合伙类型": "有限合伙", "经营范围": "企业管理咨询和软件技术服务",
|
|
"主要经营场所": "合成市测试大道100号", "合伙人名单": "普通合伙人甲、有限合伙人乙",
|
|
"出资方式": "均以货币出资", "出资数额": "甲方人民币100000元,乙方人民币400000元", "出资期限": "2026-12-31前",
|
|
"事务执行人": "普通合伙人甲", "损益分配比例": "甲方20%,乙方80%", "表决机制": "一般事项按实缴出资比例过半通过,重大事项一致同意",
|
|
"退伙安排": "依法并按协议提前三十日书面通知", "份额转让规则": "对外转让须经其他合伙人一致同意",
|
|
"解散事由": "期限届满、全体合伙人决定或法律规定的其他事由", "清算安排": "依法成立清算组并完成债权债务清理",
|
|
"争议解决方式": "主要经营场所所在地人民法院管辖", "签署日期": "2026-09-10", "评估方式": "非货币出资不适用",
|
|
"出资验资/交付证明": "银行回单及合伙企业收款确认", "重大事项清单": "对外担保、重大借款、核心资产处分及新增合伙人",
|
|
"合伙人会议召集规则": "事务执行人提前五个工作日书面通知", "财务账册/信息披露": "每季度提供财务报表并允许依法查阅账册",
|
|
"税务承担说明": "各方依法承担其应负税费", "竞业限制范围": "限于合伙存续期间直接竞争的同类咨询业务",
|
|
"入伙条件": "经全体合伙人一致同意并签署入伙协议", "登记机关": "合成市市场监督管理部门",
|
|
"电子签署平台": "双方确认的合成电子签署平台",
|
|
},
|
|
}
|
|
|
|
# Current template-required fields added or renamed after the Phase-13 records.
|
|
# Keeping this explicit makes fixture drift visible instead of manufacturing a
|
|
# generic value from a field name.
|
|
_GOLDEN_CURRENT_PATCHES: dict[str, dict[str, str]] = {
|
|
"TPL-AGENCY-DISTRIBUTION-001": {"合作类型": "区域经销", "是否独家": "非独家"},
|
|
"TPL-COOPERATION-FRAMEWORK-001": {"后续协议安排": "具体项目另行签署书面项目协议",
|
|
"约束力范围": "保密、争议解决及明确标注为有约束力的条款具有约束力"},
|
|
"TPL-DEMAND-LETTER-001": {"主债权金额或履行内容": "支付已到期货款人民币100000元",
|
|
"宽限期届满日": "2026-10-15"},
|
|
"TPL-FRANCHISE-001": {"冷静期天数": "30日"},
|
|
"TPL-SALES-GOODS-001": {"交付时间": "2026-11-30前", "收款账户": "合成银行测试账户TEST-SALE-001",
|
|
"检验异议期": "到货后十个工作日", "规格型号": "TEST-PRODUCT-100型", "计量单位": "台",
|
|
"质量标准": "按附件规格书及验收用例", "风险转移节点": "买方签署到货验收单时"},
|
|
"TPL-SHAREHOLDER-RESOLUTION-001": {"授权办理人": "合成经办人甲"},
|
|
"TPL-TECH-DEVELOPMENT-001": {"变更控制流程": "双方书面变更单确认范围、费用和工期后生效",
|
|
"签署日期": "2026-09-10", "缺陷修复期限": "验收发现缺陷后十个工作日",
|
|
"质保维护期": "验收通过后十二个月", "里程碑与付款节点": "需求确认30%、测试交付40%、验收30%"},
|
|
"TPL-CONTRACT-TERMINATION-NOTICE-001": {"异议期": "收到通知后五个工作日"},
|
|
"TPL-VALUATION-ADJUSTMENT-001": {"义务主体清单": "目标公司及合成原股东甲"},
|
|
}
|
|
|
|
_GOLDEN_RENAMES = {
|
|
"TPL-AGENCY-DISTRIBUTION-001": {"合作模式": None},
|
|
"TPL-LEGAL-OPINION-001": {"manual_review_reason": None},
|
|
"TPL-POWER-OF-ATTORNEY-001": {"授权相对方/办理机构": None},
|
|
"TPL-SHAREHOLDER-RESOLUTION-001": {"授权代表/经办人": None},
|
|
"TPL-VALUATION-ADJUSTMENT-001": {"补偿上限/下限": None},
|
|
}
|
|
|
|
|
|
def _golden_values(template_id: str) -> dict[str, Any] | None:
|
|
path = GOLDEN_ROOT / f"GEN-SAMPLE-{template_id}-variables.json"
|
|
if not path.is_file():
|
|
return None
|
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
if payload.get("asset_under_test") != template_id:
|
|
raise ValueError(f"golden asset identity mismatch: {template_id}")
|
|
values = {key: value for key, value in payload.items()
|
|
if key not in {"asset_under_test", "contract_type", "source_ids", "review_required"}}
|
|
for old_name in _GOLDEN_RENAMES.get(template_id, {}):
|
|
values.pop(old_name, None)
|
|
values.update(_GOLDEN_CURRENT_PATCHES.get(template_id, {}))
|
|
return values
|
|
|
|
|
|
def _base_values(template) -> dict[str, Any]:
|
|
if template.input_schema is not None:
|
|
return template_sample_values(template.input_schema,
|
|
[item.name for item in template.required_variables],
|
|
[item.name for item in template.optional_variables], with_optional=True)
|
|
values = _golden_values(template.id) or _EXPLICIT_LEGACY_VALUES.get(template.id)
|
|
if values is None:
|
|
raise ValueError(f"legacy template has no explicit transaction fixture: {template.id}")
|
|
required = {item.name for item in template.required_variables}
|
|
missing = sorted(name for name in required if not values.get(name))
|
|
unknown = sorted(set(values) - {item.name for item in [*template.required_variables, *template.optional_variables]})
|
|
if missing or unknown:
|
|
raise ValueError(f"legacy fixture mismatch {template.id}: missing={missing}, unknown={unknown}")
|
|
if any("校验字段" in str(value) for value in values.values()):
|
|
raise ValueError(f"legacy fixture contains generated placeholder: {template.id}")
|
|
return values
|
|
|
|
|
|
def _scene_case(template, variant: str) -> dict[str, Any]:
|
|
schema = template.input_schema
|
|
if schema is None or schema.scene is None:
|
|
raise ValueError(f"scene template lacks typed scene schema: {template.id}")
|
|
matches = [item for item in scene_model_cases(template.id) if item["variant"] == variant]
|
|
if len(matches) != 1:
|
|
raise ValueError(f"server-owned scene model case missing: {template.id}/{variant}")
|
|
return matches[0]
|
|
|
|
|
|
def _case(template, variant: str, values: dict[str, Any], *, base: bool,
|
|
expected_status: str = "candidate_ready", expected_missing: list[str] | None = None,
|
|
source_case: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
repair = base and variant == "normal" and template.id in CORE_TEMPLATE_IDS
|
|
required = [item.name for item in template.required_variables]
|
|
role_fields = [name for name in required if any(token in name for token in
|
|
("甲方", "乙方", "许可方", "被许可方", "委托人", "受托人", "保证人", "债务人", "债权人", "合伙人"))]
|
|
scene = template.input_schema.scene if template.input_schema else None
|
|
special_checks = list(scene.get("review_scope_checklist", [])) if scene else list(template.validation_rules)
|
|
result = {
|
|
"case_id": f"{'base' if base else 'scene'}--{template.id}--{variant}",
|
|
"source_case_id": source_case["id"] if source_case else f"{template.id}--normal",
|
|
"kind": "drafting", "mode": "contract" if base else "scene_evaluation",
|
|
"template_id": template.id,
|
|
"document_kind": (scene or {}).get("document_kind") or template_document_kind(template.contract_type),
|
|
"title": f"{template.name} / {variant} / 合成隔离评测",
|
|
"values": values,
|
|
"instructions": source_case["instructions"] if source_case else (
|
|
"仅依据已提供的合成事实起草;保留事实、角色和选择,不把未核验事项写成已经核验。"),
|
|
"expectation_profile": {
|
|
"schema_version": "multiscene-expectation.v1", "variant": variant,
|
|
"required_fact_fields": required, "role_fact_fields": role_fields,
|
|
"scene_control": values.get("适用场景"), "position_control": values.get("起草立场"),
|
|
"opaque_fact_exact": values.get("补充约定") if variant == "opposite-adversarial" else None,
|
|
"special_checks": special_checks,
|
|
"expected_status": expected_status, "expected_missing_fields": expected_missing or [],
|
|
"requires_full_fact_preservation": True,
|
|
"required_review_operation": "semantic_review" if base else "specialist_review",
|
|
"requires_independent_semantic_review": base,
|
|
"deterministic_checks_are_not_legal_correctness": True,
|
|
},
|
|
"operations": REPAIR_OPERATIONS if repair else (BASE_OPERATIONS if base else SCENE_OPERATIONS),
|
|
"provider_call_budget": 4 if repair else 2,
|
|
"auto_repair": repair,
|
|
}
|
|
if source_case:
|
|
result["scene_request_context"] = {key: source_case[key] for key in (
|
|
"industry", "party_position", "jurisdiction", "transaction_stage")}
|
|
return result
|
|
|
|
|
|
def build_cases() -> list[dict[str, Any]]:
|
|
templates = list_contract_templates().items
|
|
base = [item for item in templates if not (item.input_schema and item.input_schema.scene)]
|
|
scenes = [item for item in templates if item.input_schema and item.input_schema.scene]
|
|
if len(base) != 30 or len(scenes) != 30:
|
|
raise ValueError(f"expected 30 base and 30 scene assets, got {len(base)} and {len(scenes)}")
|
|
cases = [_case(template, "normal", _base_values(template), base=True) for template in base]
|
|
cases.extend(_case(template, variant, model_case["values"], base=False,
|
|
expected_status=model_case["expected_status"],
|
|
expected_missing=model_case["expected_missing"], source_case=model_case)
|
|
for template in scenes for variant in VARIANTS
|
|
for model_case in [_scene_case(template, variant)])
|
|
identifiers = [case["case_id"] for case in cases]
|
|
if len(cases) != 120 or len(set(identifiers)) != 120:
|
|
raise ValueError("multiscene case inventory must contain 120 unique cases")
|
|
if sum(case["provider_call_budget"] for case in cases) != 256:
|
|
raise ValueError("drafting and semantic-review budget must equal 256 calls")
|
|
return cases
|