feat: replace 2 m placeholder with station pattern table from the workbook (Task 5)

Source: RAG 지보패턴현황_20260918.xlsx, sheet 참고_지보패턴현황, 당진방향 table
(4+740..5+500, 42 contiguous rows). Lining pattern (M column) per station now
drives the lining colour and the viewer readout. Daesan table kept as reference
only (WL-4 there, not applied). Drawing patterns E-1, E-2, RP-2, WL-3 are in
neither table and stay [미확정].

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A5Cun3RHQKna9qxE6ybZ4A
This commit is contained in:
Claude committed 2026-10-21 06:56:08 +00:00
1 parent 94a7f8226f
commit f65c8ebabe
9 files changed
+311 -1736

No files matched your search

+10 -8
View File
@@ -80,11 +80,13 @@ def ring_world(R, s):
P = np.column_stack([x - ORIGIN[0] + xs * nx, y - ORIGIN[1] + xs * ny, zr + zs])
return P
PAL = {"NP-4AD-1-D-2": (231, 111, 81, 255), "NP-4BD-1-D-2": (244, 162, 97, 255),
"NP-5AE-1-WL-4": (233, 196, 106, 255), "NP-5BE-1-WL-4": (138, 177, 125, 255),
"NP-6A": (42, 157, 143, 255), "NP-6B": (38, 70, 83, 255)}
ZONES = [(int(r["seg_from"]), int(r["seg_to"]), r["pattern"], r["status"])
for r in csv.DictReader(open(f"{ROOT}/data/pattern_zones_placeholder.csv", encoding="utf-8"))]
PAL = {"A": (42, 157, 143, 255), "B-1": (231, 111, 81, 255), "B-2": (244, 162, 97, 255),
"C-1": (233, 196, 106, 255), "C-2": (138, 177, 125, 255), "D-1": (38, 70, 83, 255),
"D-2": (100, 130, 160, 255), "P-2": (190, 110, 140, 255), "P-3": (150, 90, 60, 255),
"RP-1": (80, 170, 200, 255), "WL-1": (200, 200, 120, 255), "WL-2": (120, 90, 170, 255),
"개착부": (170, 170, 170, 255)}
ZONES = [(int(float(r["sta_from"])), int(float(r["sta_to"])), r["lining_pattern"], "엑셀 지보패턴표(당진방향)")
for r in csv.DictReader(open(f"{ROOT}/data/pattern_stations_dangjin.csv", encoding="utf-8"))]
def pattern_of(sta):
for a, b, pat, _ in ZONES:
@@ -168,7 +170,7 @@ checks["road_z_5480_m"] = round(float(np.interp(S1, dg_s, dg_v)), 3)
L_chord = float(np.sum([math.dist(cn[i, 1:], cn[i + 1, 1:]) for i in range(len(cn) - 1)]))
checks["plan_chord_length_total_m (4+700..5+500)"] = round(L_chord, 3)
checks["station_range_modelled"] = f"{S0} .. {S1} (ring step {DS} m, {len(stations)} rings)"
checks["pattern_zones (2 m, placeholder)"] = len(ZONES)
checks["pattern_zones (지보패턴표 당진방향)"] = len(ZONES)
checks["pattern_coverage_all_stations"] = bool(all(pattern_of(x) in PAL for x in stations))
checks["pattern_counts"] = {k: sum(1 for z in ZONES if z[2] == k) for k in PAL}
checks["glb_bytes"] = int(__import__("os").path.getsize(glb))
@@ -188,9 +190,9 @@ meta = {
"superelevation sign convention assumed (z += S/100 * x, x = left of tangent)"],
"layers": [p.metadata["name"] for p in parts],
"ring_step_m": DS, "ring_points": int(parts[0].metadata["ring_points"]), "first_station": S0,
"pattern_status": "PLACEHOLDER: 2 m 임의 배정. 실제 측점 구간 아님 (사용자 결정 2026-10-21). data/pattern_zones_placeholder.csv 를 수정 후 재빌드",
"pattern_status": "지보패턴: 엑셀 참고_지보패턴현황 당진방향 표(4+740~5+500, 42행, 빈틈 없음) 적용. 출처 data/pattern_stations_dangjin.csv (사용자 결정 2026-10-21)",
"patterns": [{"from": a, "to": b, "pattern": pat} for a, b, pat, _ in ZONES],
"open": ["4+700~4+745 and 5+480~5+500 excluded ([미확정])", "pattern zones (03-003~008) pending Task 5",
"open": ["4+700~4+745 and 5+480~5+500 excluded ([미확정])", "지보패턴 측점표 적용(당진). 도면 E-1~2, RP-2, WL-3~4 는 당진 표에 없어 [미확정]. 대산 표 WL-4(4+758.85~4+768.85)는 참고만 [확인 필요]",
"lining reference recorded (meta.lining_reference, 04-004~009); sheet-to-station mapping [미확정]",
"[확정 2026-10-21] R1 6,982 mm 유지 (사용자 결정). 라이닝 도면 6장의 R1 6.892 m 표기는 확인 대상 오기로 기록", "pattern-group to excavation-pattern mapping: user explanation conflicts with filename (WL-4) - re-confirm [확인 필요]"]
}
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Extract the 당진방향 station pattern table from 참고_지보패턴현황 (read-only).
Rows 55..96 of the sheet: C=start STA, G=end STA, J=length, M=lining pattern
(지보패턴), O=실시설계 excavation class (blank = continues the row above),
Q=굴착공법, T=비고. Also extracts the 대산방향 table (rows 13..49) for reference.
Usage: extract_pattern_table.py <xlsx> <out_dir>"""
import csv, os, sys
import openpyxl
SRC, OUT = sys.argv[1], sys.argv[2]
wb = openpyxl.load_workbook(SRC, read_only=True, data_only=True)
ws = wb["참고_지보패턴현황"]
rows = {r: row for r, row in enumerate(ws.iter_rows(min_row=1, max_row=114, max_col=33, values_only=True), start=1)}
def table(r0, r1, tag):
out, last_o = [], None
for r in range(r0, r1 + 1):
row = rows[r]
try:
a, b = float(row[2]), float(row[6])
except (TypeError, ValueError):
continue
m = (row[12] or "").strip() if isinstance(row[12], str) else row[12]
o_raw = row[14]
o = o_raw if o_raw not in (None, "") else last_o
last_o = o
remark = (row[19] or "") if isinstance(row[19], str) else ""
out.append({"direction": tag, "source_row": r, "sta_from": a, "sta_to": b,
"length_m": row[9], "lining_pattern": m or ("개착부" if "개착" in remark else ""),
"excavation": o or "", "excavation_inherited": o_raw in (None, ""),
"method": (row[16] or "") if isinstance(row[16], str) else "",
"remark": remark})
return out
south = table(55, 96, "당진")
north = table(13, 49, "대산")
os.makedirs(OUT, exist_ok=True)
cols = list(south[0].keys())
for name, data in [("pattern_stations_dangjin.csv", south), ("pattern_stations_daesan_ref.csv", north)]:
with open(os.path.join(OUT, name), "w", encoding="utf-8", newline="") as fh:
w = csv.DictWriter(fh, fieldnames=cols)
w.writeheader()
w.writerows(data)
print("dangjin rows", len(south), "range", south[0]["sta_from"], "->", south[-1]["sta_to"])
print("daesan rows", len(north), "range", north[0]["sta_from"], "->", north[-1]["sta_to"])
print("blank lining pattern rows (dangjin):", [(d["source_row"], d["remark"]) for d in south if not d["lining_pattern"]])
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Render the station-by-station lining pattern report (DOC/03_설계) from the
CSVs written by extract_pattern_table.py. Values are read, not typed.
Usage: make_pattern_doc.py <dangjin.csv> <daesan_ref.csv> <lining_spec.json> <out.md>"""
import csv
import json
import sys
DJ, DS, LS, OUT = sys.argv[1:5]
dj = list(csv.DictReader(open(DJ, encoding="utf-8")))
ds = list(csv.DictReader(open(DS, encoding="utf-8")))
lin = json.load(open(LS, encoding="utf-8"))["sheets"]
drawing_groups = sorted({g["group"] for s in lin for g in s["pattern_details"] if g["group"]})
dj_names = {r["lining_pattern"] for r in dj}
ds_names = {r["lining_pattern"] for r in ds}
# Drawing group labels (read from the sheet notes, 04-004..009) -> member patterns.
# Explicit table: the labels mix letter ranges (A~C-2) and number ranges (WL-1~2).
DRAWING_MEMBERS = {"A~C-2": ["A", "B-1", "B-2", "C-1", "C-2"], "D-1~2": ["D-1", "D-2"],
"E-1~2": ["E-1", "E-2"], "WL-1~2": ["WL-1", "WL-2"], "WL-1~2,RP-1~2": ["WL-1", "WL-2", "RP-1", "RP-2"],
"WL-3~4": ["WL-3", "WL-4"], "P-2~3": ["P-2", "P-3"], "RP-1~2": ["RP-1", "RP-2"]}
members = set()
for g in drawing_groups:
members |= set(DRAWING_MEMBERS.get(g, [g]))
missing_dj = sorted(m for m in members if m not in dj_names)
only_ds = sorted(m for m in missing_dj if m in ds_names)
none_any = sorted(m for m in missing_dj if m not in ds_names)
L = ["# 측점별 콘크리트 라이닝 지보패턴표 (당진방향) (초안)\n",
"| 버전 | 일자 | 변경 내용 |", "|---|---|---|",
"| v0.1.00 | 2026-10-21 | 최초 작성. 엑셀 `참고_지보패턴현황` 당진방향 표를 측점별로 정리 (사용자 결정: 모델 placeholder 대체) |\n",
"## 1. 출처와 범위",
"- 원본: `RAG/03_터널명(대산2터널)/지보패턴현황_20260918.xlsx`, 시트 `참고_지보패턴현황`, 당진방향 표 (원본 행 55~96)",
f"- 행 {len(dj)}개, 측점 {dj[0]['sta_from']} ~ {dj[-1]['sta_to']}, 빈틈 없음 (각 행 끝 = 다음 행 시작)",
"- 모델 적용 범위 4+745 ~ 5+480 은 이 표로 전 구간 덮임 (735 m)",
"- 지보패턴(M열) = 라이닝 패턴, 실시설계(O열) = 굴착 등급. 실시설계 칸이 빈 행은 위 행의 값을 이어 받음 (병합 셀 해석, [추론])\n",
"## 2. 측점별 표",
"| 측점 from | 측점 to | 연장(m) | 지보패턴 | 실시설계 | 굴착공법 | 비고 |",
"|---|---|---|---|---|---|---|"]
def sta(x):
v = int(float(x)); return f"{v // 1000}+{v % 1000:03d}"
for r in dj:
exc = r["excavation"] + (" (이어받음)" if r["excavation_inherited"] == "True" and r["excavation"] else "")
L.append(f"| {sta(r['sta_from'])} | {sta(r['sta_to'])} | {float(r['length_m']):g} | {r['lining_pattern']} | {exc} | {r['method'].replace(chr(10), ' ')} | {r['remark']} |")
L += ["", "## 3. 도면과 표의 대응",
f"- 라이닝 도면(04-004~009)의 패턴 {len(members)}개 중 당진 표에 있는 것: {len(members) - len(missing_dj)}개",
f"- 당진 표에 없고 대산 표에만 있는 것: {', '.join(only_ds) if only_ds else '없음'} (대산 표 값 참고만, 적용 안 함 — 아래 4항)",
f"- 두 표 모두 없는 것: {', '.join(none_any) if none_any else '없음'} → 측점 지정 불가, [미확정]\n",
"## 4. 확인 필요 (HITL)",
"- [확인 필요] 대산방향 표의 WL-4 (4+758.85~4+768.85, NP-5) 를 당진 측점에 적용할지. 두 방향이 별도 튜브라는 점은 [추론]이며 확인되지 않았다. 당진 표는 같은 측점에 WL-2/WL-1 을 보여 준다.",
"- [확인 필요] 말씀하신 `NP-5BE-1-WL-4` 의 풀이 (NP-5B 의 E-1 ~ WL-4). 당진 NP-5 구간은 WL-2·WL-1 뿐이고, E-1 은 두 표 어디에도 없다.",
"- [확인 필요] 엑셀 `개요2`·`1` 시트: 굴착 등급은 NP-4/5/6 (A·B 접미사 포함), 괄호 안은 지보 하위 패턴이라는 표기. 이 해석은 엑셀 표기와 일치하는 것으로 보이나 도면 명칭 대응은 확정하지 않았다.\n",
"## 5. 적용 결과 (골격 모델)",
"- placeholder 2 m 구간은 폐기하고 이 표로 대체 (사용자 결정 2026-10-21)",
"- 뷰어의 라이닝 클릭 값: 측점, 지보패턴(본 표), 라이닝 사양 (C0060304-004~009)",
""]
open(OUT, "w", encoding="utf-8").write("\n".join(L))
print("written", OUT, "rows", len(dj), "drawing groups", len(members), "missing in dangjin", missing_dj)