From c34f72b2d7249eb034bbde32a28b55d32c3022ad Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 21 Sep 2026 21:20:36 +0900 Subject: [PATCH] =?UTF-8?q?fix(M01):=20=EB=B3=84=EC=B9=AD=20=EB=B3=B5?= =?UTF-8?q?=EC=82=AC=20gather=20=E2=80=94=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EA=B0=80=20=EB=B3=84=EC=B9=AD=EA=B3=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=B4=20=EA=B2=B9=EC=B9=98=EB=A9=B4=20?= =?UTF-8?q?=EC=A1=B0=EC=9A=A9=ED=9E=88=20=EC=95=88=20=EB=B0=94=EA=BE=B8?= =?UTF-8?q?=EA=B3=A0=20=EC=A0=80=EC=9E=A5=20=EB=A7=89=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 교차 검증 어긋남 1건(사용자가 별칭과 같은 이름의 입력·중간·덧줄을 새로 만들면 되돌릴 때 그 이름이 조용히 찾기() 원문으로 바뀌던 것) — gather 에서 별칭 이름을 그 순간의 입력·중간·덧줄 이름(_taken)과 견줘 겹치면 FormulaError(400) · 자동 이름 바꾸기는 화면 개입 없이는 어느 쪽 식인지 못 가려 안전하지 않아 넣지 않음 - 재현 시험 추가 · 전체 로직 1,354줄 펴고 되돌리기 어긋남 0건 재확인 · resources/master_data/ref/_검증_별칭_복사.md 에 「고친 뒤」 덧붙임 --- resources/master_data/ref/_검증_별칭_복사.md | 21 +++++++++++++++++++ resources/master_data/scripts/master_copy.py | 14 ++++++++++++- resources/tester/test_m01_copy.py | 22 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 resources/master_data/ref/_검증_별칭_복사.md diff --git a/resources/master_data/ref/_검증_별칭_복사.md b/resources/master_data/ref/_검증_별칭_복사.md new file mode 100644 index 00000000..0618fa3f --- /dev/null +++ b/resources/master_data/ref/_검증_별칭_복사.md @@ -0,0 +1,21 @@ +# 별칭 복사(3-4) 검증 — 어긋난 것만 + +읽기 전용 검증 · 사본 폴더에서만 저장(정본·대장·키 대장 안 바뀜 → 시험 GX 지울 것 없음). + +## 어긋남 1건 + +- **사용자가 별칭과 같은 이름의 자기 변수를 만들면 조용히 원문 부르기로 바뀜** (`master_copy.gather` → `_unmask`) + - 재현: `GF000219` 를 펴서 별칭 `메쌓기인력` 을 얻은 뒤, 중간에 `메쌓기인력 = 1` 을 더하고 수량 칸을 `메쌓기인력 * 2` 로 씀 → 되돌리면 수량 칸이 `찾기(QF000421, …) * 2` 로 바뀌고 사용자 변수는 안 쓰인 채 남음. 오류 없이 저장됨. + - 원인: `gather` 가 별칭 이름을 입력·중간·덧줄 이름과 견주지 않음(펴기 쪽 `_taken` 은 견줌). + - 고칠 곳: `gather` 에서 별칭 이름이 입력·중간·덧줄 이름과 같으면 `FormulaError`. + +## 어긋남 없음 (확인한 것) + +- ① 정본 40개(산림 9 · 건설 31 · 찾기 많은 8 · 호표 요소에 로직() 14 · 덧줄 13 포함): 펴고 그대로 되돌려 저장 → 40/40 원본과 글자 같음 · 같은 입력 계 40/40 같음. +- ② 별칭 이름 겹침 0건 · 기존 이름과 겹침 0건. 낱말 일부(`별칭량` · `별칭_2`) · 작은따옴표 안 · 이미 원문인 부르기 안 · 점 뒤(값 칸 이름) 는 안 바뀜. 같은 별칭에 두 원문은 막음. +- ③ 호표 요소 안 `로직()` 14개 별칭으로 보였다가 원문으로 돌아옴. +- ④ 저장한 GX 40개 틀·로직 검사 통과(대장 넣은 검사 · 정본 전체 검사 0건). + +## 고친 뒤 + +- `gather` 에 이름 겹침 검사 더함 — 별칭 이름이 그 순간 입력·중간·덧줄 이름(`_taken`)과 같으면 `FormulaError`(400) · 조용히 안 바꾸고 막음(가림 자동화는 화면 개입 없이는 안전하게 못함). 재현 사례 다시 돌리면 막힘 확인 · 전체 로직 1,354줄(40개 표본의 상위 집합) 펴고 되돌리기 어긋남 0건 그대로 · 시험 `test_m01_copy.py` 추가. diff --git a/resources/master_data/scripts/master_copy.py b/resources/master_data/scripts/master_copy.py index 8631fdb6..f053181d 100644 --- a/resources/master_data/scripts/master_copy.py +++ b/resources/master_data/scripts/master_copy.py @@ -243,8 +243,15 @@ def _line(where: str, kind: str, at: int, one: dict, text) -> dict: def gather(shown: dict, marks: list[dict]) -> dict: - """별칭 꼴 식 묶음 → 저장할 로직 한 줄. 별칭 원문은 부르기 하나여야 함(아무 글이나 못 넣음).""" + """별칭 꼴 식 묶음 → 저장할 로직 한 줄. 별칭 원문은 부르기 하나여야 함(아무 글이나 못 넣음). + + 사용자가 편집 중 별칭과 같은 이름의 입력·중간·덧줄을 새로 만들면(예: `메쌓기인력 = 1`) + 그 뒤로는 식 글 속 그 이름이 사용자 것인지 별칭인지 가릴 수 없음(둘 다 같은 토큰) — + 자동으로 별칭 쪽만 다시 이름 지어 피하려 하면 사용자가 실제로 쓴 자리를 건드릴 위험이 + 있어(화면 개입 없이는 안전하게 못 가림) 조용히 바꾸지 않고 저장을 막음. + """ raw_of: dict[str, str] = {} + taken = _taken(shown) # 지금 입력·중간·덧줄에 실제로 있는 이름(사용자 것이 우선) for one in marks or []: name, raw = str(one.get("이름") or ""), str(one.get("원문") or "") if not name or not raw: @@ -253,6 +260,11 @@ def gather(shown: dict, marks: list[dict]) -> dict: raise mf.FormulaError(f"별칭 「{name}」 의 원문이 찾기·로직 부르기가 아님 — 「{raw}」") if name in raw_of and raw_of[name] != raw: raise mf.FormulaError(f"별칭 「{name}」 이 두 가지 원문을 가리킴") + if name in taken: + raise mf.FormulaError( + f"「{name}」 을 입력·중간·덧줄 이름으로도 쓰고 별칭으로도 씀 — " + "어느 쪽 식이 그 이름을 가리키는지 가릴 수 없음 · 이름을 바꿀 것" + ) raw_of[name] = raw out = copy.deepcopy(shown) for _, _, _, col, box in slots(out): diff --git a/resources/tester/test_m01_copy.py b/resources/tester/test_m01_copy.py index e0eff6bc..edc026c9 100644 --- a/resources/tester/test_m01_copy.py +++ b/resources/tester/test_m01_copy.py @@ -169,6 +169,28 @@ def test_식을_고쳐_저장하면_그_값으로_계산된다(client: TestClien assert Decimal(str(now["재료비"])) == Decimal(str(was["재료비"])) +def test_별칭과_같은_이름의_사용자_변수를_만들면_저장을_막는다(client: TestClient) -> None: + """일감 15 — 교차 검증 어긋남 1건. 가림 못 하는 이름 겹침은 조용히 안 바꾸고 막음.""" + view = _get(client, "/api/m01/logic/formula", key=STACK) + row = view["로직"] + row["중간"].insert(0, {"이름": "메쌓기인력", "식": "1"}) # 별칭과 같은 이름 + for h in row["호표"]: + if h["이름"] == "석공": + h["수량"] = "메쌓기인력 * 2" + got = _post( + client, + "/api/m01/logic/formula/save", + {"로직": row, "별칭": view["별칭"], "본뜬키": STACK}, + want=400, + ) + assert "메쌓기인력" in str(got["detail"]) + # 미리보기 길도 같이 막힘 + got2 = _post( + client, "/api/m01/logic/formula/preview", {"로직": row, "별칭": view["별칭"]}, want=400 + ) + assert "메쌓기인력" in str(got2["detail"]) + + def test_별칭_원문이_부르기가_아니면_막는다(client: TestClient) -> None: view = _get(client, "/api/m01/logic/formula", key=STACK) marks = [{**one} for one in view["별칭"]]