From 02dc4f5842dc516f90fcdc93d97147d077ea875c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 31 Oct 2026 17:20:08 +0900 Subject: [PATCH 1/5] =?UTF-8?q?knowledge(=EC=9A=94=EA=B5=AC):=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20518=20=EC=9B=90=EB=AC=B8=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=E2=80=94=20=EA=B2=BD=EC=A0=9C=EB=A6=BC=EC=9C=A1=EC=84=B1?= =?UTF-8?q?=EB=8B=A8=EC=A7=80=20=ED=8C=8C=EC=9D=BC=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=C2=B7=20=EC=98=A4=ED=94=88API=20=EC=8A=B9=EC=9D=B8=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8(=ED=82=A4=20=EA=B0=92=20=EB=B9=BC=EA=B3=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9 --- docs/raw/요구_1025/사용자요구_1025.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/raw/요구_1025/사용자요구_1025.md b/docs/raw/요구_1025/사용자요구_1025.md index 92e9ef3fb..10922eb15 100644 --- a/docs/raw/요구_1025/사용자요구_1025.md +++ b/docs/raw/요구_1025/사용자요구_1025.md @@ -1037,3 +1037,7 @@ CPU는 더늘리기는 힘들어보이고 GPU 사용을 위해 부품사용에 ## 요구 517 (10-31 · 사용자) 그럼 제주만 받은 데이터는 삭제하고 다른곳을 찾아보자. + +## 요구 518 (10-31 · 사용자) + +경제림육성단지 다운로드함. 어디에 저장해줘? / (붙여넣음: 공공데이터포털 [개발계정] 산림청_경제림육성단지구역도_GW — REST · 자동승인 · 승인 · 활용기간 2026-10-31 ~ 2028-10-31 · XML · End Point https://apis.data.go.kr/1400000/fsInfoService · 일반 인증키(값 적지 않음) · 상세기능 /ecoFrstyOpenAPI 경제림육성단지 국유림 또는 사유림 단지명을 입력받아 기본정보 제공 · 일일 1000 · 활용목적 웹 사이트 개발 · 활용내용 임도설계자동화프로그램 개발) 이부분 확인해봐. From ebd33de9c97788a0661fbda197a19d5507cf9e2f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 31 Oct 2026 17:20:45 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix(B09=20=ED=86=B5=ED=95=A9=20=EC=A0=90?= =?UTF-8?q?=EA=B2=80=20=C2=B7=20703-2=20=C3=97=20703-3):=20=EC=82=AC?= =?UTF-8?q?=EC=A0=84=EC=A1=B0=ED=95=A9=EC=97=90=EC=84=9C=20=EB=B3=B5?= =?UTF-8?q?=EC=82=AC=ED=95=9C=20=EC=9D=BC=EC=9C=84=EB=8C=80=EA=B0=80=20?= =?UTF-8?q?=E2=80=94=20[=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8]=20=EB=92=A4?= =?UTF-8?q?=20=EA=B8=88=EC=95=A1=200=20=C2=B7=20[=EC=A0=80=EC=9E=A5]=20?= =?UTF-8?q?=EB=95=8C=20=EC=9B=90=EB=B3=B8=20=EC=82=AC=EB=B3=B8=20=EC=A7=80?= =?UTF-8?q?=EC=9B=80=20=EB=A7=89=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 흠: 사전조합 연결 줄을 「복사해 새 일위대가」 → [바꿈] 하면 복사본 원본 열쇠가 `C:참조…` 꼴인데 쓰는 사본 셈(used_folders)이 `L:` 만 봄 → [업데이트] 수요에서 원본 사본 노임 줄이 빠져 책에서 지워짐(금액 40,490 → 0 · 빈 줄 3) · [저장] 정리가 원본 사본 폴더를 지울 수 있음 - 고침: 복사본의 복사본(`K:`)은 맨 처음 원본까지 따라감 · `C:참조` = 그 참조를 담은 사본 폴더도 지킴(프로젝트 자리 줄 때) - 고친 파일: B09_Estimation/B09_Estimation_Bill_Library.py · B09_Estimation/B09_Estimation_Router.py · resources/tester/test_703_b09_daega_copy.py - 확인: 시험 test_703 · 703_1 · 703_3 · 204_2 · 251_1 29 통과 · 검증_sub6 [업데이트] 뒤 측구파기(토사) 40,490 그대로 · [저장] 뒤 서버 161,960 = 화면 · 사본 폴더 남음 Co-Authored-By: Claude Opus 5.5 --- B09_Estimation/B09_Estimation_Bill_Library.py | 43 +++++++++++++------ B09_Estimation/B09_Estimation_Router.py | 2 +- resources/tester/test_703_b09_daega_copy.py | 22 ++++++++++ 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/B09_Estimation/B09_Estimation_Bill_Library.py b/B09_Estimation/B09_Estimation_Bill_Library.py index 538f200ff..a53d71108 100644 --- a/B09_Estimation/B09_Estimation_Bill_Library.py +++ b/B09_Estimation/B09_Estimation_Bill_Library.py @@ -340,24 +340,43 @@ def copies(project_root: str | Path) -> list[tuple[Path, dict[str, Any]]]: return out -def used_folders(user: dict[str, Any]) -> set[str]: +def used_folders(user: dict[str, Any], project_root: str | Path | None = None) -> set[str]: """설계내역서 덮개 줄 · 거리이정표 운반 연결(251-5)이 잇는 서고 사본 폴더 + - 일위대가 복사본(703-2 `@copy`)의 원본 사본.""" - copies = [ - str(lines["@copy"].get("from") or "") - for lines in (user.get("daega_cover") or {}).values() + 일위대가 복사본(703-2 `@copy`)의 원본 사본 — 복사본의 복사본(`K:`)은 맨 처음 원본까지 · + 사전조합 원본(`C:참조`) = 그 참조를 담은 사본 폴더(`project_root` 줄 때).""" + covers = user.get("daega_cover") or {} + + def origin(src: str) -> str: + seen: set[str] = set() + while src.startswith("K:") and src not in seen: + seen.add(src) + src = str(((covers.get(src) or {}).get("@copy") or {}).get("from") or "") + return src + + srcs = { + origin(str(lines["@copy"].get("from") or "")) + for lines in covers.values() if isinstance(lines, dict) and isinstance(lines.get("@copy"), dict) - ] - return { - str(ref["lib"]) - for row in ref_rows(user) - if isinstance(ref := row.get("ref"), dict) and ref.get("tab") == "dg" and ref.get("lib") - } | {src[2:] for src in copies if src.startswith("L:")} + } + pre = {src[2:] for src in srcs if src.startswith("C:")} + return ( + { + str(ref["lib"]) + for row in ref_rows(user) + if isinstance(ref := row.get("ref"), dict) and ref.get("tab") == "dg" and ref.get("lib") + } + | {src[2:] for src in srcs if src.startswith("L:")} + | { + folder.name + for folder, manifest in (copies(project_root) if pre and project_root else []) + if refs.ref_id((manifest.get("원본") or {}).get("참조") or {}) in pre + } + ) def prune(project_root: str | Path, user: dict[str, Any]) -> list[str]: """[저장] 뒤 — 줄이 안 잇는 사본 폴더를 지움(다시 고르면 새로 들여옴) · 지운 이름.""" - keep = used_folders(user) + keep = used_folders(user, project_root) base = library_dir(project_root) gone = [] for child in sorted(base.iterdir()) if base.is_dir() else []: diff --git a/B09_Estimation/B09_Estimation_Router.py b/B09_Estimation/B09_Estimation_Router.py index 2e6424a3a..b414a1727 100644 --- a/B09_Estimation/B09_Estimation_Router.py +++ b/B09_Estimation/B09_Estimation_Router.py @@ -210,7 +210,7 @@ async def _demand(root: Path, draft: dict[str, Any] | None = None) -> list[str]: """단가 수요 키 — 연결된 사본 + 설계내역서 손 줄 로직(166-2) + 거리이정표 운반 연결(251-5) · draft = 화면 캐시(296 · 서고 사본 = 그 줄이 잇는 것만).""" user = await asyncio.to_thread(calc_user, root, draft) # 운반 값 사본 먼저(263-2) - keep = None if draft is None else library.used_folders(user) + keep = None if draft is None else library.used_folders(user, root) keys = await asyncio.to_thread(linked_resource_keys, root, keep) hand = await asyncio.to_thread(sangeun.hand_resource_keys, user) hand = [*hand, *added_keys(user.get("daega_cover"))] # 705-2 붙인 줄 이음 diff --git a/resources/tester/test_703_b09_daega_copy.py b/resources/tester/test_703_b09_daega_copy.py index b1f272386..c4930ef5c 100644 --- a/resources/tester/test_703_b09_daega_copy.py +++ b/resources/tester/test_703_b09_daega_copy.py @@ -96,6 +96,28 @@ def test_서고_사본_원본_지킴(): assert library.used_folders(user) == {"combo@system@-@SS1"} +def test_사전조합_원본_복사본_사본_지킴(tmp_path): + """통합 점검 — 사전조합(`C:참조`) 복사본 · 복사본의 복사본 = 원본 사본 폴더 지킴(업데이트 수요 · 저장 정리).""" + base = library.library_dir(tmp_path) + for name, ref in ( + ("precombo@personal@1-3@SC-000001", {"층": "personal", "주인": "1/3", "키": "SC-000001"}), + ("precombo@personal@1-3@SC-000002", {"층": "personal", "주인": "1/3", "키": "SC-000002"}), + ): + (base / name).mkdir(parents=True) + manifest = {"종류": "precombo", "원본": {"참조": ref}} + (base / name / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8") + src = "C:참조|personal|1/3|SC-000001" + user = { + "daega_cover": { + "K:aa": {"@copy": {"from": src, "name": "복사"}}, + "K:bb": {"@copy": {"from": "K:aa", "name": "복사의 복사"}}, + } + } + assert library.used_folders(user) == set() # 폴더 모름 = 옛 꼴 + assert library.used_folders(user, tmp_path) == {"precombo@personal@1-3@SC-000001"} + assert library.prune(tmp_path, user) == ["precombo@personal@1-3@SC-000002"] + + # ── 내 자료집(개인 층 사전조합 · 임시 storage) ───────────────────────────── From 9a6fd80e750fe572ccba16c4ef1caa51fc5a6551 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 31 Oct 2026 17:24:35 +0900 Subject: [PATCH 3/5] =?UTF-8?q?knowledge(=EC=9A=94=EA=B5=AC):=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20519=20=EC=9B=90=EB=AC=B8=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=E2=80=94=20=EC=82=B0=EC=82=AC=ED=83=9C=EC=9C=84=ED=97=98?= =?UTF-8?q?=EC=A7=80=EB=8F=84=20WMS=20=ED=8F=AC=ED=84=B8=20=ED=99=9C?= =?UTF-8?q?=EC=9A=A9=EC=8B=A0=EC=B2=AD=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9 --- docs/raw/요구_1025/사용자요구_1025.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/raw/요구_1025/사용자요구_1025.md b/docs/raw/요구_1025/사용자요구_1025.md index 10922eb15..e0828c8d3 100644 --- a/docs/raw/요구_1025/사용자요구_1025.md +++ b/docs/raw/요구_1025/사용자요구_1025.md @@ -1041,3 +1041,7 @@ CPU는 더늘리기는 힘들어보이고 GPU 사용을 위해 부품사용에 ## 요구 518 (10-31 · 사용자) 경제림육성단지 다운로드함. 어디에 저장해줘? / (붙여넣음: 공공데이터포털 [개발계정] 산림청_경제림육성단지구역도_GW — REST · 자동승인 · 승인 · 활용기간 2026-10-31 ~ 2028-10-31 · XML · End Point https://apis.data.go.kr/1400000/fsInfoService · 일반 인증키(값 적지 않음) · 상세기능 /ecoFrstyOpenAPI 경제림육성단지 국유림 또는 사유림 단지명을 입력받아 기본정보 제공 · 일일 1000 · 활용목적 웹 사이트 개발 · 활용내용 임도설계자동화프로그램 개발) 이부분 확인해봐. + +## 요구 519 (10-31 · 사용자) + +같은 데이터 인지 확인해보면 될것같은데. / (붙여넣음: 산사태위험지도 WMS — 재난안전 · 제공기관 산림청 · REST · WMS · 활용 신청 258 · 갱신주기 1년 · 수정일 2025-05-30 · 5등급 · 호출 safemap.go.kr/openapi2/IF_0046_WMS · 레이어 A2SM_SANSATAI · serviceKey · srs · bbox · format · width · height · transparent · 범례 lgdInfo?intId=IF_0046) 이거 확인해봐. 그리고 포털 활용신청은 한거야. 이전 채팅에 일반 인증키가 있잖아. From 0959f287177eaa96cd370850a3eb0b28ccf60d42 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 31 Oct 2026 17:29:04 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat(B09=20=EC=84=A4=EA=B3=84=EB=82=B4?= =?UTF-8?q?=EC=97=AD=EC=84=9C):=20=EC=9D=B4=20=EC=A7=9D=20=EC=9E=8A?= =?UTF-8?q?=EA=B8=B0=20=3D=20=EC=9E=8A=EC=9D=8C=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EB=A5=BC=20=EA=B8=B0=EC=96=B5=EC=97=90=20=EB=82=A8=EA=B9=80=20?= =?UTF-8?q?=E2=80=94=20=EB=8A=90=EC=8A=A8=ED=95=9C=20=EA=B8=B0=EC=96=B5(?= =?UTF-8?q?=ED=92=88=EB=AA=85=20=C2=B7=20=EB=8B=A8=EC=9C=84=20=EA=B0=99?= =?UTF-8?q?=EC=9D=8C)=EC=9C=BC=EB=A1=9C=EB=8F=84=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=9C=EC=95=88=20=EC=95=88=20=EB=90=A8=20=C2=B7=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EC=9D=B4=EC=9C=BC=EB=A9=B4=20=ED=92=80=EB=A6=BC=20?= =?UTF-8?q?(PLAN=20707-4=20=EB=8D=A7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- B09_Estimation/B09_Estimation_Bill_Link.py | 11 +++++--- B09_Estimation/B09_Estimation_Router_Link.py | 7 ++--- resources/tester/test_707_4_bill_link.py | 27 +++++++++++++++++--- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/B09_Estimation/B09_Estimation_Bill_Link.py b/B09_Estimation/B09_Estimation_Bill_Link.py index 0732de6f8..456cc062c 100644 --- a/B09_Estimation/B09_Estimation_Bill_Link.py +++ b/B09_Estimation/B09_Estimation_Bill_Link.py @@ -33,6 +33,7 @@ from M02_MasterTemplete.M02_Hopyo_Match import line_key MEMORY = "내역맞춤" TABLE, RECALL = "짝 표", "지난번 이음" +FORGOT = "잊음" # 기억 칸 {잊음: True} = 이 짝 잊기 표시 — 느슨한 기억에서도 안 불러옴 · 다시 이으면 덮임 HEADS = ("earth", "st", "haul") _TAIL = re.compile(r"^(.+?)\((.*)\)$") ROCK = ("절토(연암)", "암발파") @@ -97,11 +98,15 @@ def _loose(key: str) -> str: def recall(memory: dict[str, dict[str, Any]], row: dict[str, Any]) -> dict[str, Any] | None: - """기억 한 칸 — 같은 열쇠 · 없으면 품명 · 단위 같고 짝이 하나인 것.""" + """기억 한 칸 — 같은 열쇠 · 없으면 품명 · 단위 같고 짝이 하나인 것(잊은 칸 = 없음).""" key = key_of(row) if isinstance(memory.get(key), dict): - return memory[key] - loose = [v for k, v in memory.items() if isinstance(v, dict) and _loose(k) == _loose(key)] + return None if memory[key].get(FORGOT) else memory[key] + loose = [ + v + for k, v in memory.items() + if isinstance(v, dict) and not v.get(FORGOT) and _loose(k) == _loose(key) + ] return loose[0] if len({v.get("확정") for v in loose}) == 1 else None diff --git a/B09_Estimation/B09_Estimation_Router_Link.py b/B09_Estimation/B09_Estimation_Router_Link.py index d7f27a368..3f8f05ebf 100644 --- a/B09_Estimation/B09_Estimation_Router_Link.py +++ b/B09_Estimation/B09_Estimation_Router_Link.py @@ -10,7 +10,8 @@ 이은 짝 = 맞춤 기억에 씀(회사 층 = 회사 관리자 · 아니면 개인 층) POST …/link/undo {target, user?} → {user, 알림, payload} — 잇기 풀기(맞춤 기억 그대로) POST …/link/forget {key, user?} → {잊음, 기억층, 남음} — 이 짝 잊기(불러온 줄 · 이은 줄 키 → - 맞춤 열쇠 · 쓸 수 있는 층에서 지움 · 남음 = 못 지운 위 층 기억) + 맞춤 열쇠 · 쓸 수 있는 층에 잊음 표시 `{잊음: True}` — 느슨한 기억도 안 불러옴 · + 개인 층 표시가 회사 기억을 덮음 · 남음 = 그래도 살아 있는 기억) 맞춤 기억 = 층 `실무자료/내역맞춤.json`(실무자료 맞춤 꼴) · 읽기 = 회사 + 개인(개인이 이김). 끼워 넣기 = `B09_Estimation_Router`(같은 머리 · 같은 권한) · 화면 = `B09_Estimation_UI_Link`. """ @@ -175,6 +176,6 @@ async def forget( memo = memo or next((r["memo"] for r in board["이은"] if r["key"] == body.key), None) if not memo: raise HTTPException(status_code=400, detail="불러온 줄 · 이은 줄이 아님") - layer = await asyncio.to_thread(keep, session, {memo: None}) - left = memo in await asyncio.to_thread(recalled, session) + layer = await asyncio.to_thread(keep, session, {memo: {linker.FORGOT: True}}) + left = bool((await asyncio.to_thread(recalled, session)).get(memo, {}).get("확정")) return {"잊음": memo, "기억층": layer, "남음": left} diff --git a/resources/tester/test_707_4_bill_link.py b/resources/tester/test_707_4_bill_link.py index 13b342a39..4be04925b 100644 --- a/resources/tester/test_707_4_bill_link.py +++ b/resources/tester/test_707_4_bill_link.py @@ -274,9 +274,28 @@ def test_줄마다_갈래_판(geo): def test_이_짝_잊기(tmp_path, monkeypatch): monkeypatch.setattr(route.access, "folder", lambda got, create=False: tmp_path / got.layer) user = {"company_id": 7, "user_id": 3, "role": "USER"} + gone = {lk.FORGOT: True} route.keep(user, {"a||": {"확정": "earth:4"}, "b||": {"확정": "earth:5"}}) hopyo_store.remember(tmp_path / "company", {"c||": {"확정": "x"}}, lk.MEMORY) - assert route.keep(user, {"a||": None}) == "personal" - assert set(route.recalled(user)) == {"b||", "c||"} - route.keep(user, {"c||": None}) - assert "c||" in route.recalled(user) # 일반 = 회사 기억 못 지움(남음) + assert route.keep(user, {"a||": gone}) == "personal" + assert route.recalled(user)["a||"] == gone and route.recalled(user)["b||"]["확정"] == "earth:5" + route.keep(user, {"c||": gone}) # 일반 = 회사 기억을 개인 층 표시로 덮음 + assert route.recalled(user)["c||"] == gone + + +def test_잊은_짝_느슨한_기억에도_안_나옴(tmp_path, monkeypatch): + monkeypatch.setattr(route.access, "folder", lambda got, create=False: tmp_path / got.layer) + user = {"company_id": 7, "user_id": 3, "role": "USER"} + row = {"name": "토사절취", "spec": "A", "unit": "m3"} + other = {"name": "토사절취", "spec": "B", "unit": "m3"} + live = {"확정": "earth:4", "이름": "절토(토사)"} + route.keep(user, {lk.key_of(other): live}) + assert ( + lk.recall(route.recalled(user), row) == live + ) # 규격 달라도 품명 · 단위 같음 = 느슨한 기억 + route.keep(user, {lk.key_of(row): {lk.FORGOT: True}}) + memory = route.recalled(user) + assert lk.recall(memory, row) is None # 잊음 = 느슨한 기억도 끊김 + assert lk.recall(memory, other) == live # 다른 줄은 그대로 + route.keep(user, {lk.key_of(row): live}) # 다시 이음 = 풀림 + assert lk.recall(route.recalled(user), row) == live From 74f3fb80092333aa3f8fc3709bccfbcfc64e60e3 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 31 Oct 2026 17:46:00 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat(B09=20=EC=84=A4=EA=B3=84=EB=82=B4?= =?UTF-8?q?=EC=97=AD=EC=84=9C):=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=EB=81=9D=20=3D=20=EC=A7=80=EB=82=9C=EB=B2=88=20=EC=9D=B4?= =?UTF-8?q?=EC=9D=8C=20=EB=B0=94=EB=A1=9C=20=EC=9E=87=EA=B3=A0=20=EB=82=A8?= =?UTF-8?q?=EC=9D=80=20=EC=A0=9C=EC=95=88=EB=A7=8C=20=EC=B0=BD=EC=97=90(?= =?UTF-8?q?=EB=82=A8=EC=9D=80=200=20=3D=20=EC=B0=BD=20=EC=97=86=EC=9D=8C)?= =?UTF-8?q?=20=C2=B7=20=EC=9E=8A=EC=9D=80=20=EC=A7=9D=20=EA=B3=81=20?= =?UTF-8?q?=EB=8A=90=EC=8A=A8=ED=95=9C=20=EA=B8=B0=EC=96=B5=20=EC=95=88=20?= =?UTF-8?q?=EC=A2=81=ED=9E=98=20(PLAN=20707-4=20=EB=8D=A7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- B09_Estimation/B09_Estimation_Bill_Link.py | 11 ++++----- B09_Estimation/B09_Estimation_UI_Import.ts | 2 +- B09_Estimation/B09_Estimation_UI_Link.ts | 27 ++++++++++++++++++++-- resources/tester/test_707_4_bill_link.py | 2 ++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/B09_Estimation/B09_Estimation_Bill_Link.py b/B09_Estimation/B09_Estimation_Bill_Link.py index 456cc062c..97920a1d7 100644 --- a/B09_Estimation/B09_Estimation_Bill_Link.py +++ b/B09_Estimation/B09_Estimation_Bill_Link.py @@ -98,16 +98,13 @@ def _loose(key: str) -> str: def recall(memory: dict[str, dict[str, Any]], row: dict[str, Any]) -> dict[str, Any] | None: - """기억 한 칸 — 같은 열쇠 · 없으면 품명 · 단위 같고 짝이 하나인 것(잊은 칸 = 없음).""" + """기억 한 칸 — 같은 열쇠 · 없으면 품명 · 단위 같고 짝이 하나인 것 · + 잊은 칸은 없음 취급 + 느슨한 기억에서는 다른 짝으로 셈(잊은 짝 곁에서 하나로 좁히지 않음).""" key = key_of(row) if isinstance(memory.get(key), dict): return None if memory[key].get(FORGOT) else memory[key] - loose = [ - v - for k, v in memory.items() - if isinstance(v, dict) and not v.get(FORGOT) and _loose(k) == _loose(key) - ] - return loose[0] if len({v.get("확정") for v in loose}) == 1 else None + loose = [v for k, v in memory.items() if isinstance(v, dict) and _loose(k) == _loose(key)] + return loose[0] if len({v.get("확정") for v in loose}) == 1 and loose[0].get("확정") else None def _targets(rows: list[dict[str, Any]]) -> dict[str, dict[str, Any]]: diff --git a/B09_Estimation/B09_Estimation_UI_Import.ts b/B09_Estimation/B09_Estimation_UI_Import.ts index 889de4691..9d88bcd11 100644 --- a/B09_Estimation/B09_Estimation_UI_Import.ts +++ b/B09_Estimation/B09_Estimation_UI_Import.ts @@ -161,7 +161,7 @@ export function createBillImport( // 창 닫고 다시 그림 — 표 높이(fillViewport)가 열린 창 가림막을 아래 자리로 셈 close(); done(got.payload, made); - void openBillLink(user(), done, mode.value); // 707-4 다음: 우리 공종으로 잇기 + void openBillLink(user(), done, mode.value, true); // 707-4 다음: 지난번 이음 바로 · 남은 것만 창 const n = got.알림 || {}; const head = `새 줄 ${n.실음} · 붙임 ${n.붙임} · 지움 ${n.지움} · 이음 ${n.이음}${tail} — [저장]해야 남음`; const off: Row[] = got.어긋남 || []; diff --git a/B09_Estimation/B09_Estimation_UI_Link.ts b/B09_Estimation/B09_Estimation_UI_Link.ts index bd87dca85..59e495b2c 100644 --- a/B09_Estimation/B09_Estimation_UI_Link.ts +++ b/B09_Estimation/B09_Estimation_UI_Link.ts @@ -110,16 +110,39 @@ function pickOurs(spots: Row[], line: Row, take: (key: string) => void): void { }); } -/** 잇기 창 — user = 지금 캐시 · mode = 불러올 때 고른 갈래(없으면 마지막) */ -export async function openBillLink(user: Row, done: LinkDone, mode?: string): Promise { +/** 잇기 창 — user = 지금 캐시 · mode = 불러올 때 고른 갈래(없으면 마지막) · + * fresh = 불러오기 끝에서 부름 — 지난번 이음은 먼저 바로 잇고 남은 것만 창에(남은 0 = 창 없음) */ +export async function openBillLink( + user: Row, + done: LinkDone, + mode?: string, + fresh = false, +): Promise { if (mode === "take" || mode === "price") lastMode = mode; let cur = user; let seen: Row; try { seen = await post("suggest", { user: cur }); + if (fresh && seen.저절로) { + const got = await post("apply", { + pairs: [], + auto: true, + mode: lastMode, + counts: {}, + user: cur, + }); + done(got.payload, got.user); + cur = got.user; + showToast( + `지난번 이음 ${got.알림.이음 + got.알림.묶음} 줄 바로 이음 — [저장]해야 남음`, + "success", + ); + seen = await post("suggest", { user: cur }); + } } catch (error) { return fail("제안을 못 받음")(error); } + if (fresh && !seen.남은.length) return; // 남은 줄 없음 — 창 안 뜸 if (!seen.남은.length && !seen.이은.length) return; // 불러온 줄 없음 let busy = false; const run = (job: () => Promise): void => { diff --git a/resources/tester/test_707_4_bill_link.py b/resources/tester/test_707_4_bill_link.py index 4be04925b..f6ce3ddf5 100644 --- a/resources/tester/test_707_4_bill_link.py +++ b/resources/tester/test_707_4_bill_link.py @@ -297,5 +297,7 @@ def test_잊은_짝_느슨한_기억에도_안_나옴(tmp_path, monkeypatch): memory = route.recalled(user) assert lk.recall(memory, row) is None # 잊음 = 느슨한 기억도 끊김 assert lk.recall(memory, other) == live # 다른 줄은 그대로 + third = {"name": "토사절취", "spec": "C", "unit": "m3"} + assert lk.recall(memory, third) is None # 잊은 짝 곁에서는 느슨하게 안 좁힘 route.keep(user, {lk.key_of(row): live}) # 다시 이음 = 풀림 assert lk.recall(route.recalled(user), row) == live