Files
eomsangdonandClaude Opus 5.5 48848bb287 refactor(785-7): 옛 맞춤 층 Python 지움 · 서버 새 길로 정리 · 낡은 시험 정리
- 지움(git rm) 10 파일 3,374 줄: M02_Hopyo_Finger · Ours · Option · Fit · Combine · Verdict · Line · SkLink · SkPatch · Basis
- Match: 흐름 몫(candidates · name_cands · fix_typo · clause · b_order · _logic_specs · _line_mod · units_of · _state · view · guess · picks · chain) 지움 · 이름 도구 몫 · line_key 남김
- Words: conds · cond_score · close · cosine · jacc 지움 / Shares: sections 지움(trade · eq_key 남김)
- 옮김: Finger.kind_of → Fixed.kind_of · SkLink(sk_line · kept · left · LEFT · WHY) → Sangeun · SkPatch 기계 몫(MACH_IN · MACHINE · SOLO · _mach_logics) → Units.mach_logics
- Sangeun: 옛 /sangeun 몸(build · _sure · Maker.basis · judge · plan · make) 지움 · Kids: first · proposal · order 지움 · Build: build() · unlinked() 지움(모두 고정 Builder · build_all · _order · _origins 남김)
- Router: 옛 길 지움(GET hopyo/{키}/{번} · guess · hopyo-picks · hopyo-build · hopyo-unlinked · hopyo/{키}/sangeun) · GET hopyo/{키} = Judge.view(1차 줄 판정 줄상태 · 만든 · 블록 풀이 셈)
- Marks: 칠 = Judge.judged 만 · Forget: 맞춤 캐시 지움 줄 걷음 · Ledger: Ours.version 판 칸 걷음 · Item: Kids.first 걷음
- Row(덧): 단산 줄 = 장부 짝이 있어도 늘 「2차 필요」(장부 = 1차 후보 힌트만)

시험 정리(까닭 = 지운 길 · 모듈만 시험함):
- 지움 11 파일: test_m02_hopyo_finger · fit · golden · option · ours · basis · seam · flow · draft_calc · test_635 · test_628(지문 · 순위 · 옵션 거꾸로 · 맡은 절 · 옛 확인 흐름)
- 함수 지움: /sangeun · /hopyo-build · /hopyo-unlinked · hopyo-picks · 옛 줄 맞춤 보기를 부르던 것(572 · 718_1 · 729 · 739_1 · 777_1 · 778_5 · 780_1 · labor_national · 705_3 · m02_hopyo_match · 746_1)
- test_621 S2(지문 해시) 지움 · S1 남김 · test_m02_hopyo_build = 장부 길 시험만(PUT 으로 심음)
- 새 길로: 779_2(줄상태 = 1차 판정 · 칠 셈 전 None) · grid · stmate(hopyo-judge 줄 판) · 778_1 · 778_2(Units.mach_logics) · 642_6(Marks 원문) · 642_3 장부 짝 단산 줄 2차 지킴

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:15:20 +09:00

160 lines
7.5 KiB
Python

"""M02 실무 호표가 다른 호표를 부를 때 = 일위대가 둘(PLAN 777-1 · 요구 621-4 · 요구 581 「SK · SC 서로 독립」).
하위 호표 번(`kids_of`) · 하위 먼저 만들기는 옮기기(`M02_Hopyo_Make`) · 모두 고정(`M02_Hopyo_Build.build_all`) 몫 ·
부모 줄 = 하위 사본(740-1 그대로) · 옛 서버 체크 제안 하위 함께(`first` · `proposal`)는 785-7 에서 지움.
같은 내용 일위대가 재사용 `save` — 같은 본뜬(키 · 원본)이면 그 SC(혼자 쓰면 덮음 · 나눠 쓰면 내용 같을 때만) ·
아니면 이 층 같은 이름 · 같은 내용 SC(`M02_Library_Same.find_same` + 수량까지) 의 `본뜬.또` 에 출처를 더해 씀
(SK 꼴 `M02_Hopyo_Sangeun.Maker.write`) · 없으면 새로 → 다시 불러와도 SC 가 안 늚.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from M02_MasterTemplete import M02_Hopyo_Fixed as hfix
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Library_Flat as lflat
from M02_MasterTemplete import M02_Library_Same as lsame
from M02_MasterTemplete import M02_MasterTemplete_Store as store
ORIGIN = "실무자료"
KIND = "precombo"
# ── 같은 내용 재사용 ─────────────────────────────────────────────────
def _same_mark(a: dict, b: dict) -> bool:
"""같은 출처 — 본뜬 키 같음 · 원본(파일 층)이 둘 다 있으면 같음(SC · SK 함께 · `Sangeun` 이 빌림)."""
if a.get("키") != b.get("키"):
return False
return not a.get("원본") or not b.get("원본") or tuple(a["원본"]) == tuple(b["원본"])
def entries(folder: str | Path):
"""층 사전조합 실무자료 출처 → (키, 본뜬 · 또 한 칸, 판) — 또 = 같은 내용으로 이 SC 를 쓴 뒤 출처."""
for path in sorted((Path(folder) / KIND).glob("SC-*.json")):
try:
raw = path.read_bytes()
doc = json.loads(raw)
except (OSError, ValueError):
continue
got = doc.get("본뜬") if isinstance(doc, dict) else None
if isinstance(got, dict) and got.get("층") == ORIGIN:
for one in [got, *(x for x in got.get("또") or [] if isinstance(x, dict))]:
yield path.stem, one, raw
def same_source(got: dict[str, Any], source: refs.Here | None) -> bool:
"""본뜬 원본 층이 같음 — 원본 없는 옛 것 · `source` None 은 가리지 않음."""
have = got.get("원본")
return source is None or not have or tuple(have) == tuple(source)
def _bare(value: Any) -> Any:
"""사본 블록 `주인`(몸 지문 · 메모까지 셈) 뺌 — 셈 내용은 `몸` 이 다 가짐."""
if isinstance(value, list):
return [_bare(x) for x in value]
if not isinstance(value, dict):
return value
return {k: _bare(x) for k, x in value.items() if not (k == "주인" and "몸" in value)}
def _rows(doc: dict[str, Any], part: str) -> list[str]:
return sorted(json.dumps(lsame.norm(_bare(x)), ensure_ascii=False, sort_keys=True) for x in doc.get(part) or []) # fmt: skip
def body(doc: dict[str, Any]) -> list:
"""셈 내용 그대로(수량까지 · 하위 = 펼친 사본 몸) — `find_same` 지문은 SC 수량을 안 봄."""
flat = lflat.flat_doc(KIND, doc)
head = [lsame.unit(flat.get("단위")), lsame.norm(flat.get("규격"))]
return [*head, _rows(flat, "담은로직"), _rows(flat, "자재")]
def _entry(doc: dict[str, Any]) -> dict[str, Any]:
return {**doc["본뜬"], "이름": doc["이름"]}
def _split(old: dict[str, Any], mine: dict[str, Any]) -> tuple[dict, list]:
got = old.get("본뜬") or {}
own = {k: v for k, v in got.items() if k != "또"}
rest = [x for x in got.get("또") or [] if isinstance(x, dict) and not _same_mark(x, mine)]
return own, rest
def _put(key: str, raw: bytes, old: dict, own: dict, also: list, doc: dict | None = None) -> None:
new = {**(doc or old), "본뜬": {**own, "또": also} if also else own}
if new != old:
store.write(KIND, key, store.version_of(raw), new)
def _empty(root: Path, doc: dict[str, Any]) -> list[str]:
"""빈 호표(줄 · 자재 없음 = 지문 없음) — 이름 · 단위 같은 실무자료 SC."""
if doc.get("담은로직") or doc.get("자재"):
return []
out = []
for path in sorted((root / KIND).glob("SC-*.json")):
try:
got = json.loads(path.read_bytes())
except (OSError, ValueError):
continue
same = (got.get("이름"), lsame.unit(got.get("단위"))) == (doc["이름"], lsame.unit(doc.get("단위"))) # fmt: skip
if same and (got.get("본뜬") or {}).get("층") == ORIGIN:
out.append(path.stem)
return out
def save(doc: dict[str, Any], same: lsame.Same | None = None) -> tuple[dict[str, Any], bool, bool]:
"""(참조, 다시씀, 재사용) — 엑셀 수량 = 저장(742-1) · `same` = 한 요청 안 지문 메모."""
hands = {
"줄": {str(i): x["수량"]["손"] for i, x in enumerate(doc["담은로직"]) if x.get("수량")},
"자재": {m["코드"]: m["수량"]["손"] for m in doc.get("자재") or [] if m.get("수량")},
}
doc = {**doc, "본뜬": {**doc["본뜬"], "실무수량": hands}}
same, root, mine = same or lsame.Same(), store.root(), doc["본뜬"]
here = refs.here()
ref = lambda key: {"층": here[0], "주인": here[1], "키": key} # noqa: E731
have = next(
(
(key, raw)
for key, got, raw in entries(root)
if got.get("키") == mine["키"] and same_source(got, tuple(mine["원본"]))
),
None,
)
if have:
key, raw = have
old = json.loads(raw)
own, rest = _split(old, mine)
owner, alike = _same_mark(own, mine), body(old) == body(doc)
if owner and (not rest or alike): # 혼자 · 내용 같음 = 제자리 덮음(나눈 출처 그대로)
_put(key, raw, old, mine, rest, doc)
same.add(root, KIND, key, doc)
return ref(key), True, False
if alike: # 남의 SC 를 같은 내용으로 씀 — 제 출처 칸만
_put(key, raw, old, own, [*rest, _entry(doc)])
return ref(key), True, False
head = {k: v for k, v in rest[0].items() if k != "이름"} if owner else own
_put(key, raw, old, head, rest[1:] if owner else rest) # 내용 달라짐 = 떼어 냄
for key in same.find_same(root, KIND, doc) or _empty(root, doc):
raw = (root / KIND / f"{key}.json").read_bytes()
old = json.loads(raw)
if old.get("이름") == doc["이름"] and body(old) == body(doc): # 이름까지 = 같은 호표
own, rest = _split(old, mine)
_put(key, raw, old, own, [*rest, _entry(doc)])
return ref(key), False, True
key = refs.create(KIND, doc)["이름"]
same.add(root, KIND, key, doc)
return ref(key), False, False
# ── 하위 함께 만들기 ─────────────────────────────────────────────────
def kids_of(hopyo: dict[str, Any]) -> list[int]:
"""호표 줄이 부르는 호표 번(줄 차례)."""
out = []
for line in hopyo.get("줄") or []:
ref = line.get("참조") or {}
if hfix.kind_of(line) == "호표" or ref.get("종류") == "일위":
out.append(int(ref.get("번") or 0))
return out