feat(application-form-fill): 新增模板配置骨架
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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 [])
|
||||
Reference in New Issue
Block a user