fix(m01): 자리 표시 든 참조를 조각 · 이름 식에서 이름 + 입력값으로 (PLAN 5장 다시 돌림 2)
- MO:휘발유:{지역} 날글 대신 「휘발유 · 전국평균」 · 입력이 비면 「휘발유 · 지역」
- 조각 참조 = 입력으로 풀린 실제 키 · 못 풀면 참조 없음
- 요소조각 · 조각 · 이름 식 같은 규칙 · 값 식 · 되돌리기는 그대로
- 자동값 · 빈 입력 14,490줄: 날글 48줄(로직 13) → 0 · 금액 변화 0
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1MKKZKpUHTPKb513FneU8
This commit is contained in:
@@ -89,7 +89,7 @@ def named(show, real, env: dict, master, depth: int = 0) -> str:
|
||||
if kind == "name":
|
||||
return show[1]
|
||||
if kind == "ref":
|
||||
return _elem_name(master, show[1])
|
||||
return _elem_name(master, show[1], env)
|
||||
if kind == "field":
|
||||
return f"{named(show[1], real[1], env, master, depth)}.{show[2]}"
|
||||
if kind == "neg":
|
||||
@@ -111,17 +111,48 @@ def named(show, real, env: dict, master, depth: int = 0) -> str:
|
||||
return _value(real, env, master, depth) # 별칭이 안 붙은 부르기 — 값으로
|
||||
|
||||
|
||||
def _elem_name(master, ref: str) -> str:
|
||||
def _hole(master, ref: str, env: dict | None) -> tuple[list[str], dict | None]:
|
||||
"""자리 표시(`XX:품목:{입력}` · `XX:{입력}`) 든 참조 → (읽는 글 조각들, 입력으로 풀린 요소 줄).
|
||||
글 = 앞 글 + 지금 입력값(「휘발유 · 전국평균」) · 구멍뿐이면 입력 이름이 앞(「장비 · 굴착기」).
|
||||
입력이 비면 입력 이름만(「휘발유 · 지역」) · 못 풀면 줄은 None."""
|
||||
env = env or {}
|
||||
tail = ref[3:]
|
||||
holes = mf._HOLE.findall(tail)
|
||||
words = [w.strip(": ") for w in mf._HOLE.split(tail)[::2] if w.strip(": ")]
|
||||
try:
|
||||
row = master.get(mf.fill(ref, env))
|
||||
except mf.FormulaError:
|
||||
row = None
|
||||
pure = not words and len(holes) == 1
|
||||
shown = []
|
||||
for name in holes:
|
||||
value = env.get(name)
|
||||
if value is None:
|
||||
shown.append(name)
|
||||
elif pure and row is not None: # 입력 하나가 요소 자리 전부 — 그 요소 이름 + 규격
|
||||
shown.append(f"{row.get('이름') or value} {row.get('규격') or ''}".strip())
|
||||
else:
|
||||
shown.append(fmt(value) if isinstance(value, Decimal) else str(value))
|
||||
head = words or holes[:1]
|
||||
return head + [one for one in shown if one not in head], row
|
||||
|
||||
|
||||
def _elem_name(master, ref: str, env: dict | None = None) -> str:
|
||||
"""식 안 요소 한 줄의 이름 — 「석공노임」 꼴(같은 이름의 중간 값과 헷갈리지 않게)."""
|
||||
if (
|
||||
str(ref)[:2] not in ELEM_KEYS
|
||||
): # 별칭 이름이 표 열쇠 그대로인 것(QC…) — 밑이름을 풀어쓰지 않음
|
||||
return ref
|
||||
tail = ELEM_TAIL.get(str(ref)[:2], "단가")
|
||||
try:
|
||||
name = mc.short(master.get(ref).get("이름"))
|
||||
if "{" in str(ref): # 입력으로 풀린 요소 이름 · 못 풀면 입력 이름
|
||||
parts, row = _hole(master, ref, env)
|
||||
name = mc.short(row.get("이름")) if row is not None else mc.plain("".join(parts))
|
||||
else:
|
||||
name = mc.short(master.get(ref).get("이름"))
|
||||
except mf.FormulaError:
|
||||
name = ""
|
||||
return f"{name}{ELEM_TAIL.get(str(ref)[:2], '단가')}" if name else ref
|
||||
return f"{name}{tail}" if name else ref
|
||||
|
||||
|
||||
def _value(node, env: dict, master, depth: int) -> str:
|
||||
@@ -168,12 +199,17 @@ def _alias_index(marks: list[dict]) -> dict[str, dict]:
|
||||
return {m["이름"]: m for m in marks}
|
||||
|
||||
|
||||
def _elem_piece(master, ref: str) -> dict:
|
||||
"""요소 알약 — 글은 이름(못 찾아도 이름만). 자리 표시(`{…}`) 든 참조 14파일은
|
||||
값(참조 키)을 못 구하므로 이름만 적고 참조 자리는 비움."""
|
||||
piece = {"kind": "요소", "글": _elem_name(master, ref)}
|
||||
def _elem_piece(master, ref: str, env: dict | None = None) -> dict:
|
||||
"""요소 알약 — 글은 이름(못 찾아도 이름만). 자리 표시 든 참조는 글 = 이름 + 지금 입력값 ·
|
||||
참조 = 입력으로 풀린 실제 키(못 풀면 참조 없음)."""
|
||||
if "{" in str(ref):
|
||||
parts, row = _hole(master, ref, env)
|
||||
piece = {"kind": "요소", "글": " · ".join(parts)}
|
||||
if row is not None:
|
||||
key = str(row.get("키") or ref)
|
||||
piece["참조"] = {"테이블": key[:2], "키": key}
|
||||
return piece
|
||||
piece = {"kind": "요소", "글": _elem_name(master, ref)}
|
||||
try:
|
||||
row = master.get(ref)
|
||||
except mf.FormulaError:
|
||||
@@ -298,7 +334,7 @@ def _pieces(show, real, env: dict, master, alias_by_name: dict, ifdepth: int = 0
|
||||
if kind == "call" and show[1] == "만약":
|
||||
return _if_pieces(show, real, env, master, alias_by_name, ifdepth)
|
||||
if kind == "ref":
|
||||
return [_elem_piece(master, show[1])]
|
||||
return [_elem_piece(master, show[1], env)]
|
||||
if kind == "field" and show[1][0] == "name" and show[1][1] in alias_by_name:
|
||||
return [_alias_piece(alias_by_name[show[1][1]], env, master, show[2])]
|
||||
if kind == "name" and show[1] in alias_by_name:
|
||||
@@ -579,7 +615,7 @@ def _item_pieces(item: dict, env: dict, master) -> list[dict]:
|
||||
name = str(item.get("이름") or "")
|
||||
if isinstance(ref, dict): # 재료 고르기 조건 — 품명만
|
||||
return [{"kind": "요소", "글": name}]
|
||||
piece = _elem_piece(master, ref)
|
||||
piece = _elem_piece(master, ref, env)
|
||||
return [{**piece, "글": name or piece["글"]}]
|
||||
try:
|
||||
node = mf.parse(ref)
|
||||
|
||||
@@ -308,6 +308,35 @@ def test_조각_로직_부르는_줄_990개_세_비목에_다_걸치고_계산_
|
||||
assert [g["소계"] for g in out["groups"]] == [None]
|
||||
|
||||
|
||||
def test_자리_표시_든_참조는_이름_입력값으로_읽히고_참조는_풀린_키() -> None:
|
||||
"""GF000081 잡품 — 「MO:휘발유:{지역}」 날글 대신 「휘발유 · 전국평균」 · 참조 = 풀린 MO 키."""
|
||||
files = store.cm.load(folder=REAL)
|
||||
master = store.cm.master(REAL)
|
||||
row = master.logic("GF000081")
|
||||
|
||||
def pills(given: dict) -> tuple[list, str]:
|
||||
got = mt.lines(files, "GF000081", given)
|
||||
one = next(o for o in _all_lines(got) if o["이름"] == "잡품")
|
||||
return [p for p in one["조각"] if p["kind"] == "요소"], one["이름글"]
|
||||
|
||||
filled, name_text = pills(dict(store.cm.시험입력(row)))
|
||||
assert [p["글"] for p in filled] == ["휘발유 · 전국평균"]
|
||||
assert filled[0]["참조"]["키"] == "MO000001" and "MO:" not in name_text
|
||||
blank, name_text = pills({})
|
||||
assert [p["글"] for p in blank] == ["휘발유 · 지역"] and "참조" not in blank[0]
|
||||
assert "{" not in name_text and "MO:" not in name_text
|
||||
|
||||
|
||||
def test_자리_표시_참조가_조각_요소조각_이름식에_날글로_남지_않는다() -> None:
|
||||
files = store.cm.load(folder=REAL)
|
||||
master = store.cm.master(REAL)
|
||||
for key in ("GC000138", "GC000211", "GC000267", "GC000559", "GF000081"):
|
||||
for given in (dict(store.cm.시험입력(master.logic(key))), {}):
|
||||
for one in _all_lines(mt.lines(files, key, given)):
|
||||
shown = [one["이름글"], *(p["글"] for p in one["조각"] + one["요소조각"])]
|
||||
assert not any(re.search(r"[A-Z]{2}:|[{}]", t) for t in shown), (key, shown)
|
||||
|
||||
|
||||
def test_표_조각_참조_줄은_엔진이_찾은_줄의_차례다() -> None:
|
||||
"""GC000010 크레인 — 줄에 없는 조건 칸은 맞음이라 흉내 내면 틀림 · GF000160 합판도."""
|
||||
files = store.cm.load(folder=REAL)
|
||||
|
||||
Reference in New Issue
Block a user