feat(779-5): 블록 안 줄 칠 — Marks 블록 덧칸 줄 [[출처 행, 상태, 차]](D-2 줄 잇기 상태)
- 상태 = M02_Hopyo_Units.line(이어짐 · 고를 것 · 못 이음) · 셈 밖 = 밖 · 불리는 블록만 - 차 = 이어진 노무 줄의 엑셀 노임 ≠ M01 지금 노임(주황 「엑셀과 다름」 = 금액 참고 차만) · 기계 · 자재 참고 차는 D-3 셈 뒤 - SheetMap 상태 이름 「고를 것」 맞춤 · 실물 시험(북부청 임도 231 · 영월 135 줄 칠 = 단위 줄 수) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,9 @@
|
||||
`어긋남`(778-5 D-6 · 요구 623 · 624) = 불리는 블록의 엑셀 셈 어긋남(엑셀 = 과거 값 · 사용자가 보고 판단 ·
|
||||
칠 · 판정에 안 씀) — `{줄합, 금액, 차, 덮음, 줄 [{행, 이름, 묶음, 셈, 금액, 차, 까닭}], 까닭}` · 없으면 None.
|
||||
줄 = 재현 못 한 단위 줄(`UnitLine.redo` — 엑셀 단가 × 매개값 ≠ 엑셀 줄 금액 · 셈 밖 뺌) · 덮음 실패 = 줄 합 ≠ 블록 금액.
|
||||
|
||||
`줄`(779-5) = 불리는 블록 단위 줄 `[[출처 행, 상태, 차]]` — 상태 = 줄 잇기(`M02_Hopyo_Units.line` · D-2
|
||||
이어짐 · 고를 것 · 못 이음) · 셈 밖 = 「밖」 · 차 = 이어졌는데 엑셀 금액과 참고 차(노무 = 엑셀 노임 ≠ M01 지금 노임).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -30,6 +33,7 @@ from M02_MasterTemplete import M02_Hopyo_Fit as fit
|
||||
from M02_MasterTemplete import M02_Hopyo_Sangeun as sangeun
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_Hopyo_UnitLine as unit_line
|
||||
from M02_MasterTemplete import M02_Hopyo_Units as hunits
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
|
||||
|
||||
@@ -87,14 +91,43 @@ def misses(block: dict[str, Any]) -> dict[str, Any] | None:
|
||||
return {"줄합": have, "금액": total, "차": diff, "덮음": cover, "줄": lines, "까닭": why}
|
||||
|
||||
|
||||
def _wage_diff(units: hunits.Units, one: dict[str, Any], got: dict[str, Any]) -> bool:
|
||||
"""노무 줄 엑셀 노임 ≠ M01 지금 노임(참고 차)."""
|
||||
# ponytail: 노무만 · 기계 · 자재 참고 차는 D-3 우리 금액 셈이 생기면 그 값으로
|
||||
key = (got.get("단위") or {}).get("노임") if one["묶음"] == "L" else None
|
||||
row = (
|
||||
next((r for rs in units.lb.values() for r in rs if str(r.get("키")) == key), None)
|
||||
if key
|
||||
else None
|
||||
)
|
||||
ours, xl = (row or {}).get("값"), (one.get("단가") or {}).get("노무비")
|
||||
return bool(ours and xl and not unit_line.near(float(ours), float(xl)))
|
||||
|
||||
|
||||
def lines(block: dict[str, Any], units: hunits.Units) -> list[list[Any]]:
|
||||
"""블록 단위 줄 `[[출처 행, 상태, 차]]`(779-5) — 행 없는 줄 뺌."""
|
||||
out = []
|
||||
for one in block.get("항목") or []:
|
||||
if one.get("행") is None:
|
||||
continue
|
||||
if one.get("밖"):
|
||||
out.append([one["행"], "밖", False])
|
||||
continue
|
||||
got = units.line(one)
|
||||
diff = got["상태"] == hunits.LINKED and _wage_diff(units, one, got)
|
||||
out.append([one["행"], got["상태"], diff])
|
||||
return out
|
||||
|
||||
|
||||
def blocks(folder: Path, key: str, to: Path | None = None) -> list[dict[str, Any]]:
|
||||
"""블록마다 `{번, 판정, SK, 갈래, 못옮김, 어긋남}` — `to` = 만든 SK 를 볼 층(없으면 파일 층)."""
|
||||
"""블록마다 `{번, 판정, SK, 갈래, 못옮김, 어긋남, 줄}` — `to` = 만든 SK 를 볼 층(없으면 파일 층)."""
|
||||
folder, to = Path(folder), Path(to or folder)
|
||||
hp = hopyo_store.read(folder, key)
|
||||
here = refs.where(folder)
|
||||
cache = (fit.result(folder, key) or {}).get("블록") or {}
|
||||
have = sangeun.made(to, key, here)
|
||||
all_, first = sangeun.calls(hp)
|
||||
units = hunits.Units() if first else None
|
||||
out = []
|
||||
for no in sorted(all_):
|
||||
line = first.get(no)
|
||||
@@ -110,6 +143,7 @@ def blocks(folder: Path, key: str, to: Path | None = None) -> list[dict[str, Any
|
||||
"갈래": origin.get("갈래"),
|
||||
"못옮김": origin.get("못옮김") or [],
|
||||
"어긋남": misses(all_[no]) if line else None,
|
||||
"줄": lines(all_[no], units) if line and units else [],
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
@@ -129,7 +129,7 @@ export function blockTone(m: BlockMark | undefined): { tone: Tone; 글: string }
|
||||
}
|
||||
|
||||
/** 블록 안 줄 칠(779-5) — D-2 줄 잇기 상태 · `diff` = 이어졌는데 엑셀 금액과 참고 차 */
|
||||
const UNIT_TONE: Record<string, Tone> = { 이어짐: "success", 후보: "info", "못 이음": "danger", 밖: "muted" }; // prettier-ignore
|
||||
const UNIT_TONE: Record<string, Tone> = { 이어짐: "success", "고를 것": "info", "못 이음": "danger", 밖: "muted" }; // prettier-ignore
|
||||
export const unitTone = (state: string, diff: boolean): Tone => {
|
||||
const tone = UNIT_TONE[state] ?? "info";
|
||||
return tone === "success" && diff ? "warning" : tone;
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
"""779-5 블록 안 줄 칠(`logic/M02_MasterTemplete_Hopyo_SheetMap.ts` · Node) — 블록 `줄 [[행, 상태, 차]]` →
|
||||
출처 행만 칠(이어짐 초록 · 차 있으면 주황 · 후보 파랑 · 못 이음 빨강 · 밖 흐림) · 머리 행 = 가장 나쁜 칠 ·
|
||||
목록 행 = 머리와 같음 · 줄 재료 없으면 옛 블록 칠(범위 통째)."""
|
||||
목록 행 = 머리와 같음 · 줄 재료 없으면 옛 블록 칠(범위 통째) · 실물(북부청 임도 · 영월) 줄 칠 수 = 단위 줄 수 ·
|
||||
상태 = 줄 잇기(D-2) · 참고 차 = STmate 옛 노임만."""
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from M02_MasterTemplete import M02_Hopyo_Excel as hx
|
||||
from M02_MasterTemplete import M02_Hopyo_Grid as hg
|
||||
from M02_MasterTemplete import M02_Hopyo_Marks as mk
|
||||
from M02_MasterTemplete import M02_Hopyo_Sangeun as sg
|
||||
from M02_MasterTemplete import M02_Hopyo_Units as hu
|
||||
from resources.tester.helper_m02_hopyo_golden import FILES
|
||||
from resources.tester.test_779_4_sheet_map import _node
|
||||
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="openpyxl")
|
||||
|
||||
GRID = {
|
||||
"호표": [],
|
||||
"단산": [{"번": 1, "행": [10, 20]}, {"번": 2, "행": [21, 30]}],
|
||||
@@ -19,7 +32,7 @@ def _mark(no, lines=None):
|
||||
|
||||
|
||||
def test_줄_칠_머리_가장_나쁨_옛_칠():
|
||||
lines = [[12, "이어짐", False], [13, "이어짐", True], [15, "후보", False], [16, "밖", False]]
|
||||
lines = [[12, "이어짐", False], [13, "이어짐", True], [15, "고를 것", False], [16, "밖", False]]
|
||||
got = _node({"grid": GRID, "blocks": [_mark(1, lines), _mark(2)]})["paint"]
|
||||
detail, listing = got["s3"], got["s2"]
|
||||
assert {r: detail[str(r)] for r in (12, 13, 15, 16)} == {
|
||||
@@ -35,3 +48,37 @@ def test_줄_칠_머리_가장_나쁨_옛_칠():
|
||||
{"grid": GRID, "blocks": [_mark(1, [[12, "이어짐", False], [14, "못 이음", False]])]}
|
||||
)
|
||||
assert worst["paint"]["s3"]["10"] == "danger" and worst["paint"]["s3"]["14"] == "danger"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def units():
|
||||
return hu.Units()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tag", ["북부청임도", "동부청영월"])
|
||||
def test_실물_줄_칠_수_단위_줄_수(tag, units):
|
||||
path = FILES[tag]
|
||||
data = path.read_bytes()
|
||||
parsed = hx.read_book(path.name, data)
|
||||
grid = hg.read(path.name, data, parsed)
|
||||
grid.pop("책")
|
||||
blocks, first = sg.calls(parsed)
|
||||
marks, want = [], 0
|
||||
for no in sorted(blocks):
|
||||
lines = mk.lines(blocks[no], units) if no in first else []
|
||||
rows = [u["행"] for u in blocks[no]["항목"] if u.get("행") is not None]
|
||||
want += len(rows) if no in first else 0
|
||||
assert [x[0] for x in lines] == (rows if no in first else [])
|
||||
assert all(s in ("이어짐", "고를 것", "못 이음", "밖") for _, s, _ in lines)
|
||||
assert all(not d or s == "이어짐" for _, s, d in lines) # 차 = 이어진 줄만
|
||||
marks.append(
|
||||
{"번": no, "판정": "맞음", "SK": None, "갈래": None, "못옮김": [], "줄": lines}
|
||||
)
|
||||
detail = _node({"grid": grid, "blocks": marks})["paint"][grid["보기"]["단가산출상세"]]
|
||||
heads = {b["행"][0] for b in grid["단산"] if b["번"] in first}
|
||||
unit_rows = {x[0] for m in marks for x in m["줄"]}
|
||||
spans = [b["행"] for b in grid["단산"] if b["번"] in first]
|
||||
painted = {int(r) for r in detail if any(x <= int(r) <= y for x, y in spans)} - heads
|
||||
assert unit_rows - heads == painted and want == sum(len(m["줄"]) for m in marks)
|
||||
diff = sum(d for m in marks for _, _, d in m["줄"])
|
||||
assert (diff > 0) == (tag == "동부청영월") # STmate = 옛 해 노임 · EST = 지금 노임
|
||||
|
||||
Reference in New Issue
Block a user