Download apps/documents/services/generation/context_builder.py from jun2024/fc: direct link, hf CLI and curl.
- Browser
- Download file 10.2 kB
-
https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/documents/services/generation/context_builder.py
- Command line
-
hf download hf://spaces/jun2024/fc/apps/documents/services/generation/context_builder.py
-
curl -L -o context_builder.py https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/documents/services/generation/context_builder.py
10.2 kB
| """Business logic services.""" | |
| from __future__ import annotations | |
| import logging | |
| from datetime import date | |
| from decimal import Decimal | |
| from typing import TYPE_CHECKING, Any, cast | |
| from apps.documents.utils.formatters import format_currency, format_date, format_percentage, get_choice_display | |
| # 当事人角色常量(对应 apps.contracts.models.PartyRole) | |
| _ROLE_PRINCIPAL = "PRINCIPAL" | |
| _ROLE_BENEFICIARY = "BENEFICIARY" | |
| _ROLE_OPPOSING = "OPPOSING" | |
| if TYPE_CHECKING: | |
| from apps.core.interfaces import IContractService | |
| logger = logging.getLogger(__name__) | |
| class ContextBuilder: | |
| """ | |
| 上下文构建器 | |
| 从数据库提取数据并构建替换词上下文字典. | |
| 优先使用 EnhancedContextBuilder 进行上下文构建, | |
| 同时保留直接构建的能力以支持向后兼容. | |
| """ | |
| DEFAULT_DATE_FORMAT = "%Y年%m月%d日" | |
| def __init__( | |
| self, | |
| date_format: str | None = None, | |
| use_enhanced: bool = False, | |
| contract_service: IContractService | None = None, | |
| ) -> None: | |
| """ | |
| 初始化上下文构建器 | |
| Args: | |
| date_format: 日期格式字符串 | |
| use_enhanced: 是否使用 EnhancedContextBuilder(默认 True) | |
| contract_service: 合同服务(可选,用于依赖注入) | |
| """ | |
| self.date_format = date_format or self.DEFAULT_DATE_FORMAT | |
| self._use_enhanced = use_enhanced | |
| self._enhanced_builder = None | |
| self._contract_service = contract_service | |
| def contract_service(self) -> IContractService: | |
| """延迟加载合同服务""" | |
| if self._contract_service is None: | |
| from apps.documents.services.infrastructure.wiring import get_contract_service | |
| self._contract_service = get_contract_service() | |
| return self._contract_service | |
| def enhanced_builder(self) -> Any: | |
| """延迟加载 EnhancedContextBuilder""" | |
| if self._enhanced_builder is None: | |
| from apps.documents.services.placeholders.context_builder import EnhancedContextBuilder | |
| self._enhanced_builder = EnhancedContextBuilder() # type: ignore[no-untyped-call] | |
| return self._enhanced_builder | |
| def build_contract_context(self, contract_id: int) -> dict[str, Any]: | |
| """ | |
| 构建合同相关的完整上下文 | |
| 优先使用 EnhancedContextBuilder 通过占位符服务构建上下文, | |
| 如果失败则回退到直接构建模式. | |
| Args: | |
| contract_id: 合同 ID | |
| Returns: | |
| 包含所有替换词的字典 | |
| """ | |
| if self._use_enhanced: | |
| try: | |
| context = self.enhanced_builder.build_contract_context(contract_id) | |
| if context: | |
| logger.debug("使用 EnhancedContextBuilder 构建合同上下文成功,合同ID: %s", contract_id) | |
| return cast(dict[str, Any], context) | |
| except Exception as e: | |
| logger.warning( | |
| "EnhancedContextBuilder 构建失败,回退到直接构建模式: %s", | |
| e, | |
| extra={"contract_id": contract_id}, | |
| ) | |
| return self._build_contract_context_directly(contract_id) | |
| def _build_contract_context_directly(self, contract_id: int) -> dict[str, Any]: | |
| """ | |
| 直接从数据库构建合同上下文(向后兼容) | |
| 通过 ServiceLocator 获取合同数据,避免跨模块直接导入 Model. | |
| Args: | |
| contract_id: 合同 ID | |
| Returns: | |
| 包含所有替换词的字典 | |
| """ | |
| # 使用 ServiceLocator 获取合同服务,避免跨模块直接导入 Model | |
| # Requirements: 1.3 | |
| contract_dto = self.contract_service.get_contract_with_details_internal(contract_id) | |
| if contract_dto is None: | |
| logger.warning("合同不存在: %s", contract_id) | |
| return {} | |
| # 从 DTO 构建上下文 | |
| context: dict[str, Any] = { | |
| "contract_name": contract_dto.get("name") or "", | |
| "contract_type": contract_dto.get("case_type_display") or "", | |
| "contract_type_code": contract_dto.get("case_type") or "", | |
| "contract_status": contract_dto.get("status_display") or "", | |
| "contract_date": self._format_date(contract_dto.get("specified_date")), | |
| "contract_start_date": self._format_date(contract_dto.get("start_date")), | |
| "contract_end_date": self._format_date(contract_dto.get("end_date")), | |
| "fee_mode": contract_dto.get("fee_mode_display") or "", | |
| "fee_mode_code": contract_dto.get("fee_mode") or "", | |
| "fixed_amount": self._format_currency(contract_dto.get("fixed_amount")), | |
| "fixed_amount_raw": contract_dto.get("fixed_amount") or Decimal("0"), | |
| "risk_rate": self._format_percentage(contract_dto.get("risk_rate")), | |
| "risk_rate_raw": contract_dto.get("risk_rate") or Decimal("0"), | |
| "custom_terms": contract_dto.get("custom_terms") or "", | |
| "representation_stages": ", ".join(contract_dto.get("representation_stages") or []), | |
| } | |
| # 处理当事人信息 | |
| parties = contract_dto.get("contract_parties") or [] | |
| principals = [p for p in parties if p.get("role") == _ROLE_PRINCIPAL] | |
| beneficiaries = [p for p in parties if p.get("role") == _ROLE_BENEFICIARY] | |
| opposing = [p for p in parties if p.get("role") == _ROLE_OPPOSING] | |
| def _get_client_field(party: dict[str, Any], field: str) -> str: | |
| """兼容嵌套 client 字典和扁平结构两种 DTO 格式""" | |
| nested = party.get("client") | |
| if isinstance(nested, dict): | |
| return nested.get(field) or "" | |
| # 扁平结构:client_name / id_number / phone / address 直接在 party 里 | |
| if field == "name": | |
| return party.get("client_name") or "" | |
| return party.get(field) or "" | |
| if principals: | |
| context.update( | |
| { | |
| "principal_name": _get_client_field(principals[0], "name"), | |
| "principal_id_number": _get_client_field(principals[0], "id_number"), | |
| "principal_phone": _get_client_field(principals[0], "phone"), | |
| "principal_address": _get_client_field(principals[0], "address"), | |
| "all_principals": [ | |
| { | |
| "name": _get_client_field(p, "name"), | |
| "id_number": _get_client_field(p, "id_number"), | |
| } | |
| for p in principals | |
| ], | |
| } | |
| ) | |
| else: | |
| context.update( | |
| { | |
| "principal_name": "", | |
| "principal_id_number": "", | |
| "principal_phone": "", | |
| "principal_address": "", | |
| "all_principals": [], | |
| } | |
| ) | |
| if beneficiaries: | |
| context.update( | |
| { | |
| "beneficiary_name": _get_client_field(beneficiaries[0], "name"), | |
| "beneficiary_id_number": _get_client_field(beneficiaries[0], "id_number"), | |
| } | |
| ) | |
| else: | |
| context.update({"beneficiary_name": "", "beneficiary_id_number": ""}) | |
| if opposing: | |
| context.update( | |
| { | |
| "opposing_party_name": _get_client_field(opposing[0], "name"), | |
| "all_opposing_parties": [_get_client_field(p, "name") for p in opposing], | |
| } | |
| ) | |
| else: | |
| context.update({"opposing_party_name": "", "all_opposing_parties": []}) | |
| # 处理律师信息 | |
| assignments = contract_dto.get("assignments") or [] | |
| def _get_lawyer_field(assignment: dict[str, Any], field: str) -> str: | |
| """兼容嵌套 lawyer 字典和扁平结构两种 DTO 格式""" | |
| nested = assignment.get("lawyer") | |
| if isinstance(nested, dict): | |
| return nested.get(field) or "" | |
| # 扁平结构:lawyer_name / lawyer_phone / lawyer_license_no 直接在 assignment 里 | |
| if field in ("real_name", "username"): | |
| return assignment.get("lawyer_name") or "" | |
| if field == "phone": | |
| return assignment.get("lawyer_phone") or "" | |
| if field == "license_no": | |
| return assignment.get("lawyer_license_no") or "" | |
| return assignment.get(field) or "" | |
| primary_assignment = next((a for a in assignments if a.get("is_primary")), None) | |
| if primary_assignment: | |
| context.update( | |
| { | |
| "primary_lawyer_name": _get_lawyer_field(primary_assignment, "real_name"), | |
| "primary_lawyer_phone": _get_lawyer_field(primary_assignment, "phone"), | |
| "primary_lawyer_license": _get_lawyer_field(primary_assignment, "license_no"), | |
| } | |
| ) | |
| elif assignments: | |
| context.update( | |
| { | |
| "primary_lawyer_name": _get_lawyer_field(assignments[0], "real_name"), | |
| "primary_lawyer_phone": _get_lawyer_field(assignments[0], "phone"), | |
| "primary_lawyer_license": _get_lawyer_field(assignments[0], "license_no"), | |
| } | |
| ) | |
| else: | |
| context.update({"primary_lawyer_name": "", "primary_lawyer_phone": "", "primary_lawyer_license": ""}) | |
| context["all_lawyers"] = [ | |
| { | |
| "name": _get_lawyer_field(a, "real_name"), | |
| "is_primary": bool(a.get("is_primary")), | |
| } | |
| for a in assignments | |
| ] | |
| return context | |
| def _format_date(self, value: date | None) -> str: | |
| return format_date(value, self.date_format) | |
| def _format_currency(self, value: Decimal | None) -> str: | |
| return format_currency(value) | |
| def _format_percentage(self, value: Decimal | None) -> str: | |
| return format_percentage(value) | |