feat(M01): 50-2 원문 패널 — 마크다운 기호 벗김 · 출처 항 · 주 강조
- 서버 원문 조각에서 역슬래시 · 인용 머리 · 글 안 주석 벗김(원본 자료 손 안 댐) - 출처 항 · 주 번호(3-4-2 1 · 9-7-1 주⑥)를 source() 답에 실음 — 그 조각 hit 표시 - 패널이 그 항을 옅은 바탕 + 막대로 강조 · 보이게 스크롤 · 머리에 「1항」 · 「주⑥」 - 시험 3 추가 · 화면 시나리오 50-2 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
@@ -61,3 +61,46 @@ def test_tables_split_and_br() -> None:
|
||||
tables = [b["table"] for b in one["blocks"] if "table" in b]
|
||||
assert [len(t[0]) for t in tables] == [7, 2]
|
||||
assert tables[0][2][0] == "인력운반\n타설"
|
||||
|
||||
|
||||
def test_markdown_stripped() -> None:
|
||||
"""역슬래시 · 인용 `>` 머리 · 글 안 주석을 벗김(50-2) — 모든 정본 출처 원문에 0."""
|
||||
bad = 0
|
||||
for path in store.FOLDER.glob("로직_*품셈_*.json"):
|
||||
for row in json.loads(path.read_text(encoding="utf-8"))["줄"]:
|
||||
for one in make.source(row.get("출처", ""))["found"]:
|
||||
for b in one["blocks"]:
|
||||
t = b.get("p") or b.get("text") or ""
|
||||
bad += "\\" in t or "<!--" in t or t.startswith(">")
|
||||
assert bad == 0
|
||||
one = make.source("산림품셈 3-4-2 1")["found"][0]
|
||||
assert any(b.get("p") == "Q = 3,600 × q × K × f × E / Cm" for b in one["blocks"])
|
||||
|
||||
|
||||
def test_item_marked() -> None:
|
||||
"""출처 항 · 주 번호 → 그 조각만 hit(절은 통째) · 못 찾으면 item 없음."""
|
||||
|
||||
def hits(src: str) -> list[str]:
|
||||
one = make.source(src)["found"][0]
|
||||
return [(b.get("p") or b.get("text") or "table")[:2] for b in one["blocks"] if b.get("hit")]
|
||||
|
||||
one = make.source("산림품셈 3-4-2 1")["found"][0]
|
||||
assert one["item"] == "1" and one["blocks"][0].get("hit") is None
|
||||
assert not any(b.get("hit") for b in one["blocks"] if b.get("p", "").startswith("2."))
|
||||
assert hits("산림품셈 3-4-2 1")[0] == "1."
|
||||
assert hits("산림품셈 9-7-1 주⑥") == ["⑥ "]
|
||||
assert make.source("산림품셈 13-10-2 2")["found"][0]["item"] == "2"
|
||||
assert "item" not in make.source("산림품셈 3-4-2")["found"][0]
|
||||
|
||||
|
||||
def test_item_all_marked() -> None:
|
||||
"""항 · 주 번호가 달린 정본 출처는 전부 표시가 붙음(전수)."""
|
||||
total = missing = 0
|
||||
for path in store.FOLDER.glob("로직_*품셈_*.json"):
|
||||
for row in json.loads(path.read_text(encoding="utf-8"))["줄"]:
|
||||
src = row.get("출처", "")
|
||||
if not any(i for _n, i in make._NUMBER.findall(src)):
|
||||
continue
|
||||
total += 1
|
||||
missing += not any(f.get("item") for f in make.source(src)["found"])
|
||||
assert total >= 30 and missing == 0, (total, missing)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/* 50-2 원문 패널 — 마크다운 기호 벗김 · 출처 항 · 주 강조 · `orca eval --expression "$(cat 이 파일)"` 한 번에 돎 · JSON 돌려줌.
|
||||
*
|
||||
* 시작 화면: M01 마스터 데이터(#/m01-master-data · 시스템 관리자 로그인) · 아무 화면이나.
|
||||
* 로직 GF000003(산림품셈 3-4-2 1) · GF000094(산림품셈 9-7-1 주⑥)을 엶 · 고치지 않음 · 패널 취향을 처음 값으로 되돌림.
|
||||
* 기대 수치:
|
||||
* item1 = {head:"3-4-2. …", file:"1항", hit:12, first:"1. 단가산출 방법은 …", nextNotHit:true, bad:0(역슬래시 · 「<!--」 · 「>」 머리 글 수), inView:true}
|
||||
* note6 = {file:"주⑥", hit:1, first:"⑥ 굴착기의 작업능력 …", nextNotHit:true, bad:0, inView:true}
|
||||
*/
|
||||
(async () => {
|
||||
const W = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const $ = (s, root = document) => root.querySelector(s);
|
||||
const $$ = (s, root = document) => [...root.querySelectorAll(s)];
|
||||
const until = async (f, ms = 15000) => {
|
||||
for (let t = 0; t < ms; t += 200) {
|
||||
const v = f();
|
||||
if (v) return v;
|
||||
await W(200);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const PREF = "aislo:pref:m01-logic-side";
|
||||
const first = localStorage.getItem(PREF);
|
||||
const out = {};
|
||||
const openLogic = async (key) => {
|
||||
sessionStorage.setItem("m01.logic.pending", key);
|
||||
const tab = $$(".ui-collapsible__title").find((h) => h.innerText.trim() === "단가산출 로직");
|
||||
if (!tab) return false;
|
||||
if (!tab.closest(".ui-collapsible").classList.contains("is-collapsed")) tab.click();
|
||||
tab.click();
|
||||
return until(() => {
|
||||
const t = $(".m02-dfp__title")?.innerText ?? "";
|
||||
return !$(".m01lab-side")?.hidden && $(".m01lab-side .m02-dfp") && t;
|
||||
});
|
||||
};
|
||||
const shown = (n) => !!n && n.getBoundingClientRect().width > 0;
|
||||
const panel = (id) => $(`.m01lab-side .ui-side-panels__panel[data-panel="${id}"]`);
|
||||
const strip = (title) =>
|
||||
$$(".m01lab-side .ui-side-panels__handles > *").find(
|
||||
(s) => s.innerText.replace(/\s/g, "") === title,
|
||||
);
|
||||
const inView = (n) => {
|
||||
const r = n.getBoundingClientRect();
|
||||
return r.top >= 0 && r.bottom <= innerHeight && r.height > 0;
|
||||
};
|
||||
const read = async (key, wantFile) => {
|
||||
if (!(await openLogic(key))) return { error: "로직 못 엶" };
|
||||
await W(600);
|
||||
if (!shown(panel("source"))) strip("원문")?.click();
|
||||
await until(() => $$(".m01lab-src__file").some((f) => f.innerText.includes(wantFile)), 6000);
|
||||
await W(600);
|
||||
const ps = $$(".m01lab-src__body > *");
|
||||
const hits = $$(".m01lab-src__hit");
|
||||
const texts = $$(".m01lab-src__p, .m01lab-src__head").map((n) => n.innerText);
|
||||
return {
|
||||
head: $(".m01lab-src__head")?.innerText,
|
||||
file: $(".m01lab-src__file")?.innerText.split(" · ").pop(),
|
||||
hit: hits.length,
|
||||
first: hits[0]?.innerText.slice(0, 20),
|
||||
nextNotHit: !hits.includes(ps[ps.indexOf(hits[hits.length - 1]) + 1]),
|
||||
bad: texts.filter((t) => /\|<!--|^>/.test(t)).length,
|
||||
inView: hits.length > 0 && inView(hits[0]),
|
||||
};
|
||||
};
|
||||
if (!(await openLogic("GF000003"))) return JSON.stringify({ error: "로직 못 엶" });
|
||||
out.item1 = await read("GF000003", "1항");
|
||||
out.note6 = await read("GF000094", "주⑥");
|
||||
// 되돌림 — 처음 취향
|
||||
if (first === null) localStorage.removeItem(PREF);
|
||||
else localStorage.setItem(PREF, first);
|
||||
return JSON.stringify(out);
|
||||
})();
|
||||
Reference in New Issue
Block a user