| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- """生成 模板-源数据-DMS 三方字段关联 CSV。
- 输出:
- output/模板_源数据_DMS字段关联.csv
- """
- from __future__ import annotations
- import ast
- import csv
- import importlib.util
- import json
- import re
- import sys
- from pathlib import Path
- sys.stdout.reconfigure(encoding="utf-8")
- ROOT = Path(r"E:\CODE\knowledge_agent")
- OUT = ROOT / "output" / "模板_源数据_DMS字段关联.csv"
- DATA_FILES = [
- p.name for p in (ROOT / "data").rglob("*")
- if p.is_file() and p.suffix.lower() in {".xlsx", ".xls", ".csv", ".docx", ".doc", ".pdf"}
- ]
- DMS_ALIASES: dict[str, str] = {}
- try:
- raw_dms = json.loads((ROOT / "output" / "dms_scoped_models_fields.json").read_text(encoding="utf-8-sig"))
- DMS_ALIASES = {
- k: (v.get("modelAlias") or v.get("path") or k)
- for k, v in raw_dms.items()
- if isinstance(v, dict)
- }
- except Exception: # noqa: BLE001
- pass
- def load_module(name: str, path: Path):
- spec = importlib.util.spec_from_file_location(name, path)
- mod = importlib.util.module_from_spec(spec)
- assert spec and spec.loader
- spec.loader.exec_module(mod)
- return mod
- dms_mod = load_module("dms_supplement", ROOT / "scripts" / "generate_dms_supplement.py")
- tpl_mod = load_module("template_gen", ROOT / "scripts" / "generate_templates.py")
- def norm_header(h: str) -> str:
- h = str(h or "").strip()
- h = re.sub(r"\*$", "", h)
- h = re.sub(r"([^)]*)$", "", h)
- return re.sub(r"\s+", "", h)
- def parse_field_sources() -> dict[str, list[tuple[str, str]]]:
- tree = ast.parse(Path(tpl_mod.__file__).read_text(encoding="utf-8"))
- namespace = {k: v for k, v in tpl_mod.__dict__.items() if not k.startswith("__")}
- result: dict[str, list[tuple[str, str]]] = {}
- for node in ast.walk(tree):
- if not isinstance(node, ast.Call):
- continue
- if not isinstance(node.func, ast.Name) or node.func.id != "_make_file":
- continue
- if len(node.args) < 3:
- continue
- filename_node = node.args[0]
- headers_node = node.args[2]
- try:
- filename = ast.literal_eval(filename_node)
- headers = ast.literal_eval(headers_node)
- except Exception: # noqa: BLE001
- continue
- if not isinstance(filename, str) or not isinstance(headers, list):
- continue
- field_sources = []
- for kw in node.keywords:
- if kw.arg != "field_sources":
- continue
- try:
- value = eval(compile(ast.Expression(body=kw.value), "<tpl>", "eval"),
- namespace, namespace)
- except Exception: # noqa: BLE001
- value = []
- if isinstance(value, list):
- field_sources = [
- (str(a), str(b))
- for a, b in value
- if isinstance(a, str) and isinstance(b, str)
- ]
- stem = Path(filename).stem
- result[stem] = field_sources
- return result
- def _split_source(desc: str) -> tuple[str, str]:
- desc = desc.strip()
- m = re.match(r"(.+?)\s*→\s*(.+)", desc, re.S)
- if not m:
- return desc, desc
- return m.group(1).strip(), m.group(2).strip()
- def _file_names(src: str) -> list[str]:
- names: list[str] = []
- for part in re.split(r"[;;]", src):
- part = part.strip()
- if not part:
- continue
- if "新增(融合)" in part or "新增(分类)" in part:
- continue
- if "关联「" in part or part in {"—", "-"}:
- continue
- name = re.split(r"[\\/]", part)[-1].strip()
- name = re.sub(r"[((][^))]*[))]$", "", name).strip()
- if re.search(r"\.(xlsx|xls|csv|docx|doc|pdf)$", name, re.I):
- names.append(name)
- continue
- # 字段来源里可能是“登记表名称”而非完整文件名,尝试在工作区数据文件中匹配
- hit = next((f for f in DATA_FILES if name and name in f), None)
- if hit:
- names.append(hit)
- return names
- def parse_source(desc: str) -> tuple[str, str]:
- src, field = _split_source(desc)
- names = _file_names(src)
- src_out = ";".join(names)
- if "新增(融合)" in desc or "新增(分类)" in desc:
- return "", field
- if not names:
- return "", field
- quoted = re.findall(r"「([^」]+)」", field)
- if quoted:
- field = " / ".join(quoted)
- return src_out, field
- def split_fields(label: str) -> list[str]:
- return [re.sub(r"\s+", "", x) for x in re.split(r"[/、]", label) if re.sub(r"\s+", "", x)]
- def main() -> None:
- sources = parse_field_sources()
- rows: list[dict] = []
- last_file: dict[str, str] = {}
- for tpl, fields in dms_mod.TEMPLATES.items():
- tpl_sources = sources.get(tpl, [])
- for field in fields:
- dms_source = dms_mod.DMS_SOURCES.get((tpl, field), "")
- dms_model = dms_field = ""
- if dms_source:
- m = re.match(r"^([^.]+)\.(.*)$", dms_source)
- if m:
- dms_model, dms_field = m.group(1), m.group(2)
- dms_model_display = dms_model
- if dms_model and dms_model in DMS_ALIASES:
- dms_model_display = f"{dms_model}({DMS_ALIASES[dms_model]})"
- meta_file = meta_field = ""
- matched_desc = ""
- for label, desc in tpl_sources:
- for lf in split_fields(label):
- if norm_header(field) == norm_header(lf) or (
- len(norm_header(lf)) >= 2 and norm_header(lf) in norm_header(field)
- ):
- matched_desc = desc
- break
- if matched_desc:
- break
- if matched_desc:
- raw_src, _ = _split_source(matched_desc)
- if raw_src.strip() in {"同一登记表", "同上", "同文件", "同一文件"}:
- meta_file = last_file.get(tpl, "")
- meta_field = parse_source(matched_desc)[1]
- else:
- meta_file, meta_field = parse_source(matched_desc)
- if meta_file:
- last_file[tpl] = meta_file
- if not meta_file:
- meta_field = ""
- rows.append({
- "模板文件名称": f"{tpl}.csv",
- "模板字段": field,
- "元数据文件": meta_file,
- "元数据字段名称": meta_field,
- "dms模型": dms_model_display,
- "dms字段名称": dms_field,
- "是否需要人工增加": "否" if dms_source else "是",
- })
- OUT.parent.mkdir(parents=True, exist_ok=True)
- with OUT.open("w", newline="", encoding="utf-8-sig") as f:
- w = csv.writer(f)
- w.writerow(["模板文件名称", "模板字段", "元数据文件", "元数据字段名称",
- "dms模型", "dms字段名称", "是否需要人工增加"])
- for r in rows:
- w.writerow([r["模板文件名称"], r["模板字段"], r["元数据文件"],
- r["元数据字段名称"], r["dms模型"], r["dms字段名称"],
- r["是否需要人工增加"]])
- print("输出:", OUT)
- print("字段行数:", len(rows))
- if __name__ == "__main__":
- main()
|