from __future__ import annotations import hashlib from pathlib import Path from typing import Any import yaml from django.conf import settings DEFAULT_CONFIG_PATH = ( Path(settings.BASE_DIR) / "review_agent" / "application_form_fill" / "templates" / "application_form_templates_v1.yaml" ) SUPPORTED_TARGET_TYPES = {"table_row", "placeholder"} SUPPORTED_FILE_FORMATS = {"doc", "docx"} def load_template_config(path: str | Path | None = None) -> dict[str, Any]: config_path = Path(path) if path else DEFAULT_CONFIG_PATH with config_path.open("r", encoding="utf-8") as handle: payload = yaml.safe_load(handle) or {} return payload def compute_config_hash(path: str | Path | None = None) -> str: config_path = Path(path) if path else DEFAULT_CONFIG_PATH digest = hashlib.sha256() with config_path.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def validate_template_config(config: dict[str, Any], *, base_dir: str | Path | None = None) -> list[str]: errors: list[str] = [] root = Path(base_dir) if base_dir else Path(settings.BASE_DIR) version = config.get("version") if not version: errors.append("模板配置缺少 version。") source_dir_value = config.get("source_dir") source_dir = root / source_dir_value if source_dir_value else None if not source_dir_value: errors.append("模板配置缺少 source_dir。") elif not source_dir.exists(): errors.append(f"模板 source_dir 不存在:{source_dir_value}") templates = config.get("templates") if not isinstance(templates, list) or not templates: errors.append("模板配置必须包含非空 templates 列表。") return errors seen_codes: set[str] = set() for index, template in enumerate(templates, start=1): if not isinstance(template, dict): errors.append(f"第 {index} 个模板配置必须是对象。") continue code = str(template.get("code") or "").strip() if not code: errors.append(f"第 {index} 个模板缺少 code。") elif code in seen_codes: errors.append(f"模板 code 重复:{code}") seen_codes.add(code) file_format = str(template.get("file_format") or "").strip().lower() if file_format not in SUPPORTED_FILE_FORMATS: errors.append(f"模板 {code or index} 的 file_format 不支持:{file_format or '空'}") source_file = str(template.get("source_file") or "").strip() if not source_file: errors.append(f"模板 {code or index} 缺少 source_file。") elif source_dir and source_dir.exists() and not (source_dir / source_file).exists(): errors.append(f"模板 {code or index} 的 source_file 不存在:{source_file}") fields = template.get("fields") or [] if not isinstance(fields, list): errors.append(f"模板 {code or index} 的 fields 必须是列表。") continue for field_index, field in enumerate(fields, start=1): target = field.get("target") if isinstance(field, dict) else None target_type = str((target or {}).get("type") or "").strip() if target_type not in SUPPORTED_TARGET_TYPES: errors.append( f"模板 {code or index} 第 {field_index} 个字段 target.type 不支持:{target_type or '空'}" ) return errors def template_specs(config: dict[str, Any]) -> list[dict[str, Any]]: return list(config.get("templates") or [])