from __future__ import annotations import json from pathlib import Path from typing import Any from .models import SkillStep, StandardAnswer, StandardKind def load_standard_from_file(path: Path) -> StandardAnswer | None: try: raw = json.loads(path.read_text(encoding="utf-8-sig")) except Exception: return None steps = normalize_steps(raw) if not steps: return None schema_version = raw.get("schemaVersion", "") if isinstance(raw, dict) else "" kind = StandardKind.assembly if "assembly" in str(schema_version).lower() else StandardKind.part title = path.stem modeling_plan = raw.get("modelingPlan") if isinstance(raw, dict) and isinstance(raw.get("modelingPlan"), dict) else {} approved_text = raw.get("approvedPlanText") if isinstance(raw, dict) else "" summary = str(modeling_plan.get("summary") or approved_text or "") return StandardAnswer( standard_id=make_standard_id(path.stem), title=title, kind=kind, source_path=str(path), source_type="local_json", tags=tags_for_text(f"{title} {summary}"), summary=summary, steps=steps, raw=raw if isinstance(raw, dict) else {"steps": raw}, ) def normalize_steps(payload: Any) -> list[SkillStep]: raw_steps: list[Any] = [] if isinstance(payload, list): raw_steps = payload elif isinstance(payload, dict): if isinstance(payload.get("steps"), list): raw_steps = payload["steps"] elif isinstance(payload.get("modelingPlan"), dict) and isinstance(payload["modelingPlan"].get("steps"), list): raw_steps = payload["modelingPlan"]["steps"] elif isinstance(payload.get("modeling_plan"), dict) and isinstance(payload["modeling_plan"].get("steps"), list): raw_steps = payload["modeling_plan"]["steps"] result: list[SkillStep] = [] for index, item in enumerate(raw_steps, 1): if not isinstance(item, dict): continue skill = str(item.get("skill") or item.get("skillName") or item.get("skill_name") or "") if not skill: continue args = item.get("args") or item.get("arguments") or {} if not isinstance(args, dict): args = {} result.append( SkillStep( id=str(item.get("id") or f"step-{index:03d}"), order=int(item.get("step") or index), skill=skill, name=str(item.get("name") or item.get("source_feature") or skill), description=str(item.get("description") or item.get("note") or ""), arguments=args, source_feature=str(item.get("source_feature") or item.get("sourceFeature") or item.get("name") or ""), note=str(item.get("note") or item.get("description") or ""), ) ) return result def make_standard_id(text: str) -> str: normalized = [] for ch in text.lower(): if ch.isalnum() or "\u4e00" <= ch <= "\u9fff": normalized.append(ch) else: normalized.append("-") return "-".join(part for part in "".join(normalized).split("-") if part)[:120] or "standard" def tags_for_text(text: str) -> list[str]: tags: list[str] = [] mapping = { "齿轮": "gear", "榻": "gear", "轴": "shaft", "箱": "housing", "端盖": "cover", "减速器": "reducer", "装配": "assembly", "assembly": "assembly", "shell": "shell", "抽壳": "shell", "loft": "loft", "放样": "loft", } lowered = text.lower() for key, tag in mapping.items(): if key.lower() in lowered and tag not in tags: tags.append(tag) return tags