fix(M01): 시중물가 줄 저장 때 안쪽 묶음 띄어쓰기 안 바뀌게 · 줄 목록에 하위 거름(sub)과 참조 이름(refs)
- 저장기: 바깥 묶음과 안쪽 묶음의 띄어쓰기를 따로 옛 글에서 읽어 그대로 씀 - /rows: sub(조사·세부분류) 거름 · 참조 칸 키의 이름 refs Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
@@ -69,8 +69,10 @@ def get_files(group: str) -> dict:
|
||||
|
||||
|
||||
@router.get("/rows")
|
||||
def get_rows(file: str, page: int = 1, size: int = 50, q: str = "", unlinked: bool = False) -> dict:
|
||||
return _call(store.rows, file, page, size, q, unlinked)
|
||||
def get_rows(
|
||||
file: str, page: int = 1, size: int = 50, q: str = "", unlinked: bool = False, sub: str = ""
|
||||
) -> dict:
|
||||
return _call(store.rows, file, page, size, q, unlinked, sub)
|
||||
|
||||
|
||||
@router.get("/tables")
|
||||
|
||||
@@ -98,19 +98,57 @@ def files_of(group: str) -> list[dict]:
|
||||
return sorted(out, key=lambda f: (f["order"], f["file"]))
|
||||
|
||||
|
||||
def rows(file: str, page: int, size: int, q: str, unlinked: bool = False) -> dict:
|
||||
_REF_KEY = re.compile(r"^[A-Z]{2}\d{6}$")
|
||||
_REF_FILE = {
|
||||
"LB": "인력.json",
|
||||
"MT": "재료_시중물가.json",
|
||||
"MN": "재료_나라장터자재.json",
|
||||
"MP": "재료_품셈재료.json",
|
||||
"EQ": "기계.json",
|
||||
}
|
||||
|
||||
|
||||
def _ref_keys(row: dict) -> list[str]:
|
||||
"""줄이 가리키는 키 — 준용 · 연결 · 조달 출처(「나라장터:<키>」)."""
|
||||
put = (
|
||||
row.get("준용"),
|
||||
row.get("연결"),
|
||||
(row.get("값") or {}).get("조달", {}).get("출처")
|
||||
if isinstance(row.get("값"), dict)
|
||||
else None,
|
||||
)
|
||||
out = [v.removeprefix("나라장터:") for v in put if isinstance(v, str)]
|
||||
return [k for k in out if _REF_KEY.match(k)]
|
||||
|
||||
|
||||
def refs_of(rows_: list[dict]) -> dict[str, str]:
|
||||
"""참조 칸에 키 옆에 보일 이름 — {키: 「이름 규격」}."""
|
||||
want = {k for r in rows_ for k in _ref_keys(r)}
|
||||
out: dict[str, str] = {}
|
||||
for file in {_REF_FILE[k[:2]] for k in want if k[:2] in _REF_FILE}:
|
||||
for r in read(file)[0].get("줄") or []:
|
||||
if r.get("키") in want:
|
||||
out[r["키"]] = f"{r.get('이름') or ''} {r.get('규격') or ''}".strip()
|
||||
return out
|
||||
|
||||
|
||||
def rows(file: str, page: int, size: int, q: str, unlinked: bool = False, sub: str = "") -> dict:
|
||||
data, version = read(file)
|
||||
hits = [r for r in data.get(items_key(data)) or [] if _hit(r, q)]
|
||||
if sub: # 인력 조사 · 기계 세부분류
|
||||
hits = [r for r in hits if sub in (r.get("조사"), r.get("세부분류"))]
|
||||
if unlinked: # 품셈재료 — 아직 못 이은 줄
|
||||
hits = [r for r in hits if not r.get("연결")]
|
||||
page, size = max(page, 1), min(max(size, 1), 500)
|
||||
shown = hits[(page - 1) * size : page * size]
|
||||
return {
|
||||
"file": file,
|
||||
"version": version,
|
||||
"total": len(hits),
|
||||
"page": page,
|
||||
"size": size,
|
||||
"rows": hits[(page - 1) * size : page * size],
|
||||
"rows": shown,
|
||||
"refs": refs_of(shown),
|
||||
}
|
||||
|
||||
|
||||
@@ -393,14 +431,16 @@ def _atom(v) -> str:
|
||||
return json.dumps(v, ensure_ascii=False)
|
||||
|
||||
|
||||
def _flat(v, tight: bool = False) -> str:
|
||||
def _flat(v, tight: bool = False, inner: bool | None = None) -> str:
|
||||
"""한 줄 글 — tight = 바깥 묶음을 붙여 씀 · inner = 안쪽 묶음을 붙여 씀(None 이면 바깥과 같음)."""
|
||||
inner = tight if inner is None else inner
|
||||
if isinstance(v, dict):
|
||||
if not v:
|
||||
return "{}"
|
||||
body = ", ".join(f"{_atom(k)}: {_flat(x, tight)}" for k, x in v.items())
|
||||
body = ", ".join(f"{_atom(k)}: {_flat(x, inner)}" for k, x in v.items())
|
||||
return "{" + body + "}" if tight else "{ " + body + " }"
|
||||
if isinstance(v, list):
|
||||
return "[" + ", ".join(_flat(x, tight) for x in v) + "]"
|
||||
return "[" + ", ".join(_flat(x, inner) for x in v) + "]"
|
||||
return _atom(v)
|
||||
|
||||
|
||||
@@ -443,7 +483,8 @@ def keep_shape(old_text: str, before: dict, after: dict) -> str | None:
|
||||
if was == row:
|
||||
parts.append(text)
|
||||
elif "\n" not in shape:
|
||||
parts.append(_flat(row, tight=shape.startswith('{"')))
|
||||
tight = shape.startswith('{"')
|
||||
parts.append(_flat(row, tight, tight or ': {"' in shape))
|
||||
else:
|
||||
parts.append(dump(row, indent, expand=True).replace("\n", nl))
|
||||
out = old_text[: m.end()] + lead + ("," + lead).join(parts) + old_text[spans[-1][1] :]
|
||||
|
||||
@@ -304,3 +304,31 @@ def test_품셈재료_단가는_연결의_낮은_값과_출처(client: TestClien
|
||||
one = _get(client, "/api/m01/logic", key=key)
|
||||
power = one["prices"][key_of("MP:전력")]
|
||||
assert power["값"] > 0 and power["출처"].startswith(key_of("MT:M0005250064") + ".")
|
||||
|
||||
|
||||
def test_하위_거름과_참조_이름(client: TestClient) -> None:
|
||||
every = _get(client, "/api/m01/rows", file=LABOR, size=500)
|
||||
survey = _get(client, "/api/m01/rows", file=LABOR, size=500, sub="측량")
|
||||
assert 0 < survey["total"] < every["total"] and {r["조사"] for r in survey["rows"]} == {"측량"}
|
||||
machine = _get(client, "/api/m01/rows", file="기계.json", size=500, sub="산림품셈")
|
||||
assert 0 < machine["total"] < 100 and {r["세부분류"] for r in machine["rows"]} == {"산림품셈"}
|
||||
linked = _get(client, "/api/m01/rows", file="재료_품셈재료.json", size=500)
|
||||
keyed = [r for r in linked["rows"] if isinstance(r["연결"], str)]
|
||||
assert keyed and all(linked["refs"][r["연결"]] for r in keyed)
|
||||
|
||||
|
||||
def test_시중물가_저장은_띄어쓰기를_안_바꿈(client: TestClient) -> None:
|
||||
market = "재료_시중물가.json"
|
||||
old = (store.FOLDER / market).read_text(encoding="utf-8").split("\n")
|
||||
rows = _get(client, "/api/m01/rows", file=market, size=1)
|
||||
row = rows["rows"][0]
|
||||
row["값"]["시중"]["가격정보"] = 1
|
||||
change = {"op": "edit", "key": row["키"], "row": row}
|
||||
body = {"files": [{"file": market, "version": rows["version"], "changes": [change]}]}
|
||||
assert client.post("/api/m01/save", json=body).status_code == 200
|
||||
new = (store.FOLDER / market).read_text(encoding="utf-8").split("\n")
|
||||
diff = [(a, b) for a, b in zip(old, new) if a != b]
|
||||
assert len(new) == len(old) and len(diff) == 1
|
||||
assert diff[0][1].replace('"가격정보": 1,', '"가격정보": X,').count("{ ") == diff[0][0].count(
|
||||
"{ "
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user