chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
964a841de1
3978 files changed
+58013356
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,331 @@
|
||||
# 교각 도면 모델링 앱 요구사항 검토서 — 운곡교 P1 실작업 기반
|
||||
|
||||
| 버전 | 수정일 | 수정자 | 주요 변경 내용 | 비고 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| v0.1.00 | 2026-10-08 | 심영표 | 신규 작성 | 초안 — 기술 기반 선택(8장)은 의사결정 대기 |
|
||||
| v0.1.01 | 2026-10-08 | 심영표 | 담당자 검토 확정 | 확정 (8.2 권고안·9.3 결정 사항은 별도 의사결정 대기) |
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
교각 도면(DWG)을 입력하면 3D 모델(콘크리트·받침부·철근)과 도면 검토보고서를 만드는 앱을 개발하기 전에, 필요한 기능·입력·검토 기준·예외·기술 기반을 정리합니다.
|
||||
|
||||
### 1.2 근거 — 운곡교 P1 실작업
|
||||
본 검토서는 2026-10-07~08에 운곡교 교각(완도방향 P1)을 수작업 + 스크립트로 모델링한 경험을 요구사항으로 옮긴 것입니다.
|
||||
|
||||
| 항목 | 실작업 규모 | 근거 |
|
||||
| :--- | :--- | :--- |
|
||||
| 입력 | DWG 3매 (C0050205-025/026/027) | `RAG/` |
|
||||
| 모델 | 3,190객체 — 콘크리트 34, 받침 대좌·패드 20, 교량받침·솔플레이트 20, 철근 1,513본(관 + 중심선) | `Code/교각모델링/출력/실행_운곡교P1교각모델_2026-10-08_v0.1.09.3dm` |
|
||||
| 자동 검사 | 약 590항목 (콘크리트 172, 기둥 철근 43, 기초 74, 코핑 117, 받침부 184) | 검증 보고서 v0.1.05 |
|
||||
| 사람 결정 | 32건 (5장) | 작업 로그 `prompt/심영표/2026-10-07~08/` |
|
||||
| 도면 지적 | 22건 (오류·불일치 12, 누락 10) | 도면 검토보고서 v0.1.01 |
|
||||
| 스크립트 | 25종 (부록 B) | `Code/교각모델링/` |
|
||||
| 참고 앱 | 통로박스 모델링 웹앱(Drawing Model Studio) | 분석서 `DOC/02_분석/분석_통로박스모델링과정_2026-10-07_v0.1.00.md` |
|
||||
|
||||
### 1.3 범위
|
||||
- 입력: 교각 도면(DWG/DXF) + 사용자 결정 입력(5장)
|
||||
- 출력: 3D 모델(.3dm 또는 웹 뷰어 모델), 단계별 검토 결과, 도면 검토보고서(.md·.pdf)
|
||||
- 공정: 형상모델링 → 검토확인 → 기둥 철근 → 검토확인 → 확대기초 철근 → 검토확인 → 코핑 철근 → 검토확인 → 솔플레이트·받침 → 검토보고서
|
||||
- 근거가 교각 1세트(원형 단주, T형 코핑, 말뚝 기초)이므로 다른 형식(벽식, 다주식, 라멘 등)으로 넓히는 판단은 `[추론]`으로 표기합니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 전체 구성
|
||||
|
||||
### 2.1 공정 흐름
|
||||
공통 전처리 2단계와 지정 공정 10단계로 구성합니다. 각 "검토확인"은 **게이트**입니다. 통과하거나 사용자가 예외를 승인해야 다음 단계로 갑니다.
|
||||
|
||||
| # | 단계 | 주요 출력 | 게이트 |
|
||||
| :-- | :--- | :--- | :--- |
|
||||
| 0-1 | 도면 입력·변환 | DXF, 원본 해시 | 변환 엔티티 수, 원본 무결성 |
|
||||
| 0-2 | 도면 판독 | 뷰 분류, 치수·문자·점 집계, 재료표·상세표 추출 | 판독 결과 사용자 확인(추가 입력 5장) |
|
||||
| 1 | 형상모델링 | 기초·기둥·코핑·버림·말뚝, 받침 대좌·패드 | **G1** (4.2) |
|
||||
| 2 | 기둥 철근 | 주철근·띠철근·크로스타이 | **G2** |
|
||||
| 3 | 확대기초 철근 | 하·상면 망, 둘레근, 수직 결속근 | **G3** |
|
||||
| 4 | 코핑 철근 | 상·하면 주철근, 스터럽, 폐합근, 보조근, 교좌면 보강 | **G4** |
|
||||
| 5 | 솔플레이트·받침 | 교량받침, 솔플레이트 | **G5** |
|
||||
| 6 | 검토보고서 | 도면 오류·개선·추가·누락·재료표·상세 | 보고서 검사(장 구성·출처·버전) |
|
||||
|
||||
### 2.2 데이터 모델
|
||||
실작업에서 쓴 구조를 앱의 기본 데이터로 정합니다.
|
||||
|
||||
| 데이터 | 내용 | 실작업 대응 |
|
||||
| :--- | :--- | :--- |
|
||||
| 프로젝트 | 도면 묶음(파일명·SHA-256), 좌표계, 단위(mm, Z = EL×1000) | `conversion_audit.json` |
|
||||
| 판독 장부 | 뷰·엔티티 핸들·치수 사슬·표 행, 출처 | `판독/*.json` |
|
||||
| 파라미터(정본) | 부재·철근 그룹별 값과 **값마다 출처(src)**, 결정 기록 | `params.json` (lean_concrete … sole_plates) |
|
||||
| 객체 | ID, 역할, 호스트·정착 호스트, 마크·직경·형상, occurrence, 원길이, 출처 | 객체 속성(UserText) |
|
||||
| 결정 기록 | 결정 지점, 채택안, 기각안, 근거, 일시, 결정자 | 작업 로그 DECISIONS |
|
||||
| 검사 결과 | 항목·기대값·실측·허용오차·판정·파일 | `검증/*.json` |
|
||||
| 산출물 버전 | 모델·문서·파라미터의 판(vMAJOR.MINOR.PATCH)과 구판 보관 | `출력/backup/`, `DOC/*/backup/` |
|
||||
|
||||
### 2.3 사람 개입 지점
|
||||
- **판독 확인**: 표에서 읽은 값과 해석(상동 기호, 절반 작도 대칭 등)을 사용자가 확인
|
||||
- **결정 입력**: 도면만으로 정할 수 없는 항목(5장)을 입력. 입력하지 않으면 해당 단계를 진행하지 않음
|
||||
- **게이트 판정**: FAIL 항목을 예외(6장)로 승인하거나 수정을 요청
|
||||
- **보고서 확정**: '오류'·'불일치' 판정은 사람이 확정
|
||||
|
||||
---
|
||||
|
||||
## 3. 단계별 요구사항
|
||||
|
||||
### 3.1 도면 입력·판독
|
||||
| 구분 | 요구사항 | 실작업 근거·어려운 점 |
|
||||
| :--- | :--- | :--- |
|
||||
| 변환 | DWG→DXF 변환기 내장(ODA File Converter 또는 LibreDWG). 원본은 읽기 전용, 해시 기록 | 박스 앱은 LibreDWG만 허용했으나 실작업 PC에는 ODA만 있었음 |
|
||||
| 시트 영역 | 도면틀 안만 판독, 틀 밖 잔여물 제외 | D1 틀 밖에 교량 선형·사본 뷰가 흩어져 있었음 |
|
||||
| 뷰 분류 | 제목 문자(정면도·단면 A-A 등)와 축척으로 뷰 영역 자동 분류, 사용자 확인 | — |
|
||||
| 치수 | DIMENSION 엔티티와 **분해된 치수(선 + 문자)** 모두 지원. 치수 사슬(`100`, `200`, `70@125=8.750` …)을 위치 목록으로 변환. **양끝 눈금도 철근 위치로 포함**하는 규칙 | 실작업 도면은 DIMENSION 0건, 모든 치수가 분해됨. 양끝 포함 시 본수 일치(73 = 38+35) |
|
||||
| 축약 작도 | 파단선으로 줄인 부재(기둥)는 작도 길이 대신 치수 문자 사용. 파단선 위·아래 좌표계 분리 | 기둥 12,003이 6,100으로 작도됨 |
|
||||
| 절반 작도 | 정면도·평면도 절반 작도를 감지하고 대칭 환산. 비대칭 부위(선단 높이·소단)는 경고 | 코핑 좌측·거더선 1·2 미작도 |
|
||||
| 철근 점·선 | 단면의 철근 점(원 r 27)을 높이별로 전수 집계해 층·본수 판독 | 기초 6층 구성이 이 방법으로 확정됨 |
|
||||
| 재료표 | 마크·형상·직경·본수·a~e·각도·R·L·총연장·그룹 소계 추출. **상동(") 기호를 그룹 순서로 해석**, 빈칸 경고(B9 수량) | — |
|
||||
| 가공도 | 형상 번호(I~XVII)별 길이식 라이브러리 | 7.1 |
|
||||
| 문자 인코딩 | CP949·UTF-8 자동 판별 | — |
|
||||
|
||||
### 3.2 형상모델링
|
||||
| 요구사항 | 실작업 근거 |
|
||||
| :--- | :--- |
|
||||
| 객체 장부를 먼저 만든 뒤(역할·발생 수·사용 뷰·접합 객체) 형상 생성 | 박스 앱 원칙 |
|
||||
| 3뷰(평면·정면·단면)가 동시에 만족될 때만 폐합 솔리드 생성 | — |
|
||||
| 형상 유형: 직육면체, 원기둥, 정면 프로파일 압출(곡선 하면·경사 선단·소단 상면), 회전 사변형(대좌), 사각뿔대(패드), 쐐기(솔플레이트) | 코핑 하면 R 12,000 원호, 선단 200 경사, 5단 소단 |
|
||||
| 원호 중심은 끝점과 R로 계산(도면 정점 미사용) 후 도면 정점과 대조 | 계산값과 DXF가 0.1 mm 이내로 일치 |
|
||||
| 접합면 공유·겹침 0 검사, 의도된 겹침(말뚝 근입 100) 예외 등록 | — |
|
||||
| 받침 대좌 높이 = 코핑 상면 + h1, 패드 상단 = EL.'X' (= 코핑 상면 + h1 + h2) | E 열(h1~h4 합)로 해석하면 211 mm 틀림 — 표 정의 해석이 핵심 |
|
||||
|
||||
### 3.3 기둥 철근
|
||||
| 요구사항 | 실작업 근거 |
|
||||
| :--- | :--- |
|
||||
| 주철근: 원주 n등분(Div.46), 반경방향 다발(2본), 시작각 0°, 마크별 반지름 | P1 870.5 / P2 825.5 |
|
||||
| 주철근 상·하단: 재료표 길이에서 역산(갈고리 수평부 높이 + R + a), 작도선과 비교 | 작도 P2가 기둥 상단에서 끝나 2.65 m 차이 |
|
||||
| 띠철근: 치수 사슬 구간별 간격·본수, 구간별 마크(P4/P5/P3), 반지름 R | @100 표기 vs @120 사슬 충돌 |
|
||||
| 형상: 반원 2 + 갈고리(VI), 반원 + 커플러(V), 링 + 커플러(IX), 이음 위치 엇갈림 | 커플러 길이가 재료표 L에 포함 |
|
||||
| 크로스타이: 단마다 방향 수, 띠철근 위 얹힘 | 4본 교차 — 간섭 예외 |
|
||||
| 관 솔리드 생성은 형상별 단건 시험 후 일괄 | `fitRail=True`에서 Rhino 크래시 |
|
||||
|
||||
### 3.4 확대기초 철근
|
||||
| 요구사항 | 실작업 근거 |
|
||||
| :--- | :--- |
|
||||
| 층 구성 자동 판독(하면 4층 Y·X·Y·X, 상면 2층, 중앙, 둘레, 수직 결속) | 단면 점 높이 집계 |
|
||||
| 교대 배치(U자 + 직선, 양끝 U자) 규칙 | B2/B4 38+35, B6/B8 32+29 |
|
||||
| 체크무늬 등 2차원 배치 패턴 | B9 34×29 격자의 절반 = 493 |
|
||||
| 층 높이 기준 선택(작도 점 / 치수선 + 직경 적층) — 입력 사항 | 작도 점 채택 시 상면 피복 58.5 |
|
||||
| 수량 빈칸 시 총연장 ÷ L 역산과 경고 | B9 |
|
||||
|
||||
### 3.5 코핑 철근
|
||||
| 요구사항 | 실작업 근거 |
|
||||
| :--- | :--- |
|
||||
| 변단면 대응: 하면 곡선(오프셋 동심원)·선단 경사·소단 상면에 따른 철근 길이·높이 | 스터럽 높이, 폐합근 길이가 위치마다 다름 |
|
||||
| 재료표 변길이 `~` 해석: 범위 양끝 대조 + L = (최대 + 최소)/2 관례 | 평균으로 해석하면 350~400 mm 불일치 |
|
||||
| 재료표 치수와 작도 중심선의 오프셋 자동 추정(실작업 54.3 mm) | C9~C13 범위 양끝 모두 일치 |
|
||||
| 꺾인 다리(VIII, 98°·96°) = 선단면 평행, 하면근(VII) = 원호 + 다리 | C1 다리 끝과 C7 원호 끝이 만남 |
|
||||
| 다중 폐합 스터럽(1개 위치에 3개, 겹침 배치), 스터럽 안쪽 수평 폐합근 | 단면 A-A 다리 위치·27 mm 안쪽 점 |
|
||||
| 받침 대좌 회전 좌표계의 교좌면 보강(S1·S2) | 대좌 4.405° |
|
||||
| 갈고리 방향 추정 시 콘크리트 내부 검사로 검증 | C14 아래 갈고리가 하면 관통 |
|
||||
|
||||
### 3.6 솔플레이트·받침
|
||||
| 요구사항 | 실작업 근거 |
|
||||
| :--- | :--- |
|
||||
| 받침 표(ELEVATION TABLE)·솔플레이트 표(S, L, W, T, T1, T2) 연동 | L = A+100, W = B+100 |
|
||||
| 교축방향 = h2 패드 방향(긴 변이 아님) | NO.3는 W > L |
|
||||
| 경사 방향 입력, 표의 T1·T2와 S의 반올림 차 기록 | 37.6/22.4 vs 38/22 |
|
||||
|
||||
### 3.7 검토보고서
|
||||
| 요구사항 | 실작업 근거 |
|
||||
| :--- | :--- |
|
||||
| 단계 진행 중 발견 사항을 자동 수집(충돌·누락·추정·예외) → 보고서 초안 | 미해결 목록 16건 → 지적 22건 |
|
||||
| 장 구성: 개요, 오류·불일치, 개선방안, 추가 사항, 누락, 철근재료표(원본 vs 모델), 철근상세 | 보고서 v0.1.01 |
|
||||
| 재료표 집계: 그룹 소계 검산, 단위중량 × 총연장, 모델 본수 대조 | `compile_rebar_schedule.py` |
|
||||
| 판정 어휘 고정(오류·불일치·누락·확인 필요), 지적마다 도면·뷰·값 출처 | `check_report.py` |
|
||||
| .md 정본 + .pdf 변환(한글 글꼴, 표 열 폭, 특수기호 글리프 점검) | 맑은 고딕에 U+2212 글리프 없음 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 검토확인(게이트) 요구사항
|
||||
|
||||
### 4.1 공통 원칙
|
||||
- 기대값은 **생성 입력(파라미터)이 아니라 도면 치수·표 값에서 따로** 가져온다(독립 기대값)
|
||||
- 판정마다 기대값·실측·허용오차·모집단을 기록하고, 결과 파일을 보관한다
|
||||
- **음성 대조**: 검사기 자체를 확인하기 위해 객체를 일부러 옮겨(수평 위치·절대 높이 각각) FAIL이 나오는지 매 단계 확인한다
|
||||
- **재로드 검증**: 저장 파일을 새로 열어 같은 검사를 반복한다
|
||||
- 숨김·잠금 객체를 포함해 센다
|
||||
- 앞 단계 객체의 지문(바운딩·체적 또는 면 수·길이)이 바뀌지 않았는지 확인한다
|
||||
|
||||
### 4.2 단계별 검사표
|
||||
| 게이트 | 주요 검사 | 허용오차 | 실작업 항목 수 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| G1 형상 | 치수·표고, 폐합, 접합면·겹침, 단면 재투영(원호 R), 체적 산식, 대좌·패드 높이·크기·회전 | ±1 mm, 체적 0.1 % | 172 |
|
||||
| G2 기둥 철근 | 마크별 본수, 원길이 = L(커플러 포함), 반지름·각도(±0.01°), 띠철근 단 높이, 콘크리트 내부·피복 | ±1 mm | 43 |
|
||||
| G3 기초 철근 | 본수, 원길이, 층 높이, 치수 사슬 위치, 교대 패턴, 체크무늬, 내부·피복 | ±1 mm | 74 |
|
||||
| G4 코핑 철근 | 본수, 원길이, 변길이 범위 양끝, L 관례 재현, 단·교대·스터럽 위치, 내부·피복 | ±1 mm | 117 |
|
||||
| G5 받침부 | 높이(EL.'X'·h3), 크기, 경사(±0.01 %), 회전(±0.01°), 평면 중심, 겹침, 색 | ±1 mm | 184 |
|
||||
| 공통 | 철근 색(직경별), 기존 객체 지문, 원본 DWG 해시 | 0 | — |
|
||||
|
||||
### 4.3 실작업에서 발견된 검사기 결함 (앱 검사 설계 시 반영)
|
||||
| 결함 | 원인 | 대책 |
|
||||
| :--- | :--- | :--- |
|
||||
| 수평 이동을 못 잡음 | 높이·크기만 검사 | 수평 위치·절대 높이 검사를 모두 포함하고, 음성 대조로 확인 |
|
||||
| 피복이 음수로 나옴 | 부재 간 내부 접합면까지 거리를 잼 | 콘크리트 합집합 외곽면 기준 |
|
||||
| 객체를 '없음'으로 판정 | 기본 객체 목록이 숨긴 객체를 제외 | 숨김·잠금 포함 열거 |
|
||||
| 비대칭 철근 위치 오판 | 곡선 매개변수 중간점 사용 | 바운딩·형상 특징점 사용 |
|
||||
| 갈고리 끝을 높이로 잡음 | 바운딩 최고점 | 수평부 중간점 |
|
||||
| 교축방향 오판 | 긴 변 = 교축 가정 | 방향(축과의 각도)으로 판별 |
|
||||
| 길이 FAIL 오판 | 재료표 L에 커플러·겹침 포함 여부 불일치 | 형상별 L 정의를 라이브러리화 |
|
||||
|
||||
### 4.4 자기검증 한계
|
||||
생성과 검증을 같은 주체가 하면 독립 검증이 아닙니다. 앱에서는 ① 기대값 출처 분리 ② 음성 대조 ③ 재로드에 더해, 최종 단계에서 **사람의 육안 확인(뷰어 체크리스트)**을 게이트에 넣어야 합니다. 실작업에서는 헤드리스 3D 캡처가 실패해 육안 확인이 끝까지 미완이었습니다(미해결 #3).
|
||||
|
||||
---
|
||||
|
||||
## 5. 추가 입력 사항
|
||||
|
||||
### 5.1 사용자 결정 입력 (실작업 32건을 유형화)
|
||||
| 유형 | 입력 항목 | 실작업 사례 | 기본값 제안 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 범위 | 모델링 범위(콘크리트만/철근 포함), 말뚝·대좌·패드·조립용 철근(UA1)·교좌면 보강 포함 여부 | 말뚝 포함, 대좌 처음엔 제외 후 추가, UA1 제외, S1·S2 포함 | 도면에 형상 근거가 있는 것은 포함 |
|
||||
| 표현 | 철근 표현(중심선/관), 커플러·겹침 이음 표현, 색상 규칙 | 관 + 중심선, 커플러는 틈, 직경별 5색 | 관 + 중심선, 이음은 길이만 반영 |
|
||||
| 충돌 해소 | 도면 값 A/B 중 채택(표기 vs 치수 사슬, 작도 vs 재료표, 치수 vs 작도 점) | 띠철근 @120, 주철근 상단 재료표, 반지름 작도, B12 작도 28, 스터럽 폭 작도, 대좌 높이 표 | 치수 사슬 > 작도. 재료표 수량은 작도로 위치 확인 |
|
||||
| 추정 확인 | 미작도 부위 대칭·평행 이동, 갈고리 방향, 층 순서, 교대 시작, 경사 방향 | 거더선 1·2, C14 갈고리, S1 위층, ①CYCLE부터, 솔플레이트 −Y | 추정값 제시 + `[추론]` 표시 |
|
||||
| 기준 | 피복 판정 방식, 층 높이 기준, 높이 기준면(EL.'X') | 작도 위치 ±1 mm, 작도 점 높이 | 작도 위치 기준 |
|
||||
| 보고 | 보고서 독자, 산출 형식(PDF 위치) | 설계사·발주처, PDF는 출력 폴더 | — |
|
||||
| 표시 상태 | 콘크리트 숨김 등 저장 시 표시 상태 | P1-FT·P1-CP 숨김 유지 | 모두 표시 |
|
||||
|
||||
### 5.2 외부 자료 입력
|
||||
| 항목 | 필요 이유 |
|
||||
| :--- | :--- |
|
||||
| 교량 선형·측점, 교각 중심 측량좌표 | 도면에 없음 → 모델이 로컬 원점(0, 0)에 머묾 (미해결 #4) |
|
||||
| 설계기준(피복·정착·굽힘 반지름) | 도면 미표기 부위 기본값. 실작업은 박스 앱 기준(D16 이하 2d, D19 이상 3.5d) 사용 |
|
||||
| 표준 상세(받침·솔플레이트·커플러) | 커플러 형상 미표현 |
|
||||
| 디자인 지침(색·서체) | 조직 지침 없음 → 수용 기준으로 대체 |
|
||||
|
||||
### 5.3 입력 UI 요구사항
|
||||
- 결정 항목마다 도면 근거(값 A·B와 위치 이미지)와 기본값·영향을 함께 보여 주고 선택하게 한다
|
||||
- 결정은 파라미터 파일에 기록하고(결정자·일시·근거), 이후 단계와 보고서가 자동으로 참조한다
|
||||
- 결정을 바꾸면 영향받는 단계만 다시 생성·검사한다
|
||||
|
||||
---
|
||||
|
||||
## 6. 검토 예외 사항
|
||||
|
||||
### 6.1 예정된 FAIL (사용자 결정에 따른 불일치)
|
||||
| 예외 | 실작업 사례 | 처리 |
|
||||
| :--- | :--- | :--- |
|
||||
| 재료표 본수와 작도 본수 불일치를 작도로 채택 | B12 28/34 | FAIL로 기록, 보고서 '불일치' 자동 등재, 게이트는 예외 승인으로 통과 |
|
||||
| 재료표 치수 대신 작도 폭 채택 | C9~C13 원길이 | 위와 같음, 변하는 치수(범위)는 별도 PASS 검사 |
|
||||
|
||||
### 6.2 기록 전용 항목 (판정하지 않고 값만 남김)
|
||||
| 항목 | 실작업 사례 |
|
||||
| :--- | :--- |
|
||||
| 표 반올림 차 | 솔플레이트 T1/T2 ±0.4, 받침 패드 상단 −1 mm |
|
||||
| 굽힘 반영 곡선길이와 원길이 차 | P3 −493 mm 등 |
|
||||
| 피복 실측값(기준 미달 포함) | 기초 B5 58.5, S1 40.5(중심 기준 표기) |
|
||||
| 굽힘 반지름 최소값 미달 | B9 상단 갈고리 R 20.4 < 24 |
|
||||
|
||||
### 6.3 검사 대상 외 (현 단계 범위 밖)
|
||||
- 철근 간 간섭 정량 검사(교차 위치 목록만 기록) — 크로스타이 교차, 다리 겹침, 스터럽과 하면근 동일 높이 등
|
||||
- 시공성(조립 순서, 이음 위치 적정성)
|
||||
- 구조 계산 적합성
|
||||
|
||||
### 6.4 예외 승인 절차
|
||||
1. 게이트가 FAIL 목록과 함께 예외 후보(6.1~6.3 유형)를 표시
|
||||
2. 사용자가 항목별로 예외 승인 또는 수정 요청
|
||||
3. 승인 내역(사유·결정자·일시)을 결정 기록과 보고서에 자동 반영
|
||||
4. 예외 없이 남은 FAIL이 있으면 다음 단계 진행 불가
|
||||
|
||||
---
|
||||
|
||||
## 7. 비기능 요구사항
|
||||
|
||||
| 구분 | 요구사항 | 실작업 근거 |
|
||||
| :--- | :--- | :--- |
|
||||
| 변환 | ODA/LibreDWG 선택, 변환 실패 시 DXF 직접 입력 | — |
|
||||
| 3D 커널 | B-rep 불리언(차집합·합집합·교집합), 관(pipe) 생성, 체적 계산 | 버림-말뚝 차집합, 피복 판정용 합집합 |
|
||||
| 안정성 | 대량 생성 전 형상별 단건 시험, 작업 단위 분할, 장애 시 이어서 실행 | `fitRail=True` 크래시, 세션 종료 2회, 호출 300초 제한 |
|
||||
| 성능 | 철근 1,500본 규모 생성·검사가 단계당 수 분 이내 | 철근 관 1,000개 체적 계산 300초 초과 |
|
||||
| 파일 | 모델 50~70 MB(철근 포함) 처리, 경량 뷰어용 메시 별도 | v0.1.07 72 MB |
|
||||
| 재현성 | 파라미터 → 모델 재생성이 결정적(같은 입력 = 같은 결과), 스크립트·버전 보관 | 지문 비교 0.000 |
|
||||
| 버전·감사 | 판 올리기(구판 보관), 변경 이력, 결정·검사 로그 | 모델 v0.1.00~v0.1.09 |
|
||||
| 문서 오염 방지 | 파일 열기 직후 객체 수 확인, 실패 시 새 세션 | 숨김 객체가 남아 3,172객체로 오염 |
|
||||
| 보안 | 원본 도면 외부 전송 시 승인, 인증 정보 비저장 | 박스 앱 정책 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 기술 기반 비교·권고
|
||||
|
||||
### 8.1 후보 비교
|
||||
| 기준 | ① 박스 웹앱 확장 | ② Rhino 플러그인·스크립트 | ③ 신규 웹앱 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 재사용 | 판독(DXF 파서·치수 사슬·시트 분할), 객체 장부, 캐시·JSON, 보고서·PDF, 뷰어 | 이번 실작업 스크립트 25종 그대로 | 낮음 |
|
||||
| 3D 품질 | JS 메시 커널 — 관·불리언·피복 판정 정밀도 제한 `[추론]` | B-rep 정밀, 불리언·관 생성 검증됨 | 커널 선택에 따름 |
|
||||
| 산출물 | 웹 뷰어 + OBJ·JSON | .3dm(BIM 연계 용이) | 선택 |
|
||||
| 배포 | 서버 1곳, 브라우저 접속 | 사용자별 Rhino 라이선스 필요, 헤드리스 불안정 | 서버 |
|
||||
| 검증 자동화 | 기존 회귀검사 체계 있음 | 실작업 검사 스크립트 있음 | 신규 |
|
||||
| 개발 비용 | 중 | 낮음(초기) / 운영 부담 | 높음 |
|
||||
|
||||
### 8.2 권고안 — 혼합 구조 `[추론, 의사결정 필요]`
|
||||
- **판독·규칙·검사 엔진**은 3D 커널과 무관한 Python 라이브러리로 분리합니다. 실작업 스크립트의 extract·verify 계열을 정리하고, 박스 앱 판독 모듈과 통합합니다.
|
||||
- **형상 생성**은 B-rep 커널에서 수행합니다. 서버형은 Rhino.Compute, 데스크톱은 Rhino 플러그인입니다. 웹 뷰어는 박스 앱 뷰어를 재사용합니다.
|
||||
- **사용자 입력(5장)·게이트(4장)·예외 승인(6장)**은 웹 UI에서 처리합니다.
|
||||
- 이유는 두 가지입니다. 실작업에서 정밀 B-rep 연산(불리언·관·체적)이 검사의 핵심이었고, 박스 앱의 판독·UI 자산은 교각에도 재사용 가치가 큽니다.
|
||||
|
||||
### 8.3 단계적 로드맵 `[추론]`
|
||||
| 단계 | 내용 | 완료 기준 |
|
||||
| :--- | :--- | :--- |
|
||||
| P0 | 실작업 스크립트 25종을 라이브러리로 재구성, 운곡교 P1 회귀 테스트 고정 | 같은 입력으로 v0.1.09와 지문 일치 |
|
||||
| P1 | CLI 파이프라인(단계·게이트·결정 파일), 보고서 자동 생성 | 5개 게이트 + 보고서 무인 실행 |
|
||||
| P2 | 웹 UI(판독 확인, 결정 입력, 예외 승인, 뷰어 육안 확인) | 사용자 1명이 처음부터 끝까지 수행 |
|
||||
| P3 | 다른 교각 도면 2~3세트로 범용화(형식별 규칙 추가) | 신규 도면에서 규칙 수정 없이 게이트 통과율 측정 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 리스크·미해결
|
||||
|
||||
### 9.1 기술 리스크
|
||||
| 리스크 | 영향 | 대응 |
|
||||
| :--- | :--- | :--- |
|
||||
| 근거 도면이 1세트 | 규칙의 과적합(예: 오프셋 54.3, 층 구성) | P3에서 복수 도면 검증. 고정 상수 금지, 도면에서 매번 추정 |
|
||||
| 3D 커널 불안정 | 대량 생성 중 크래시 | 단건 시험·분할 실행·재시작 |
|
||||
| 시각 확인 자동화 실패 | 형상 오류 누락 | 뷰어 육안 체크리스트를 게이트에 포함 |
|
||||
|
||||
### 9.2 도면 품질 리스크
|
||||
실작업 도면에서 지적 22건이 나왔습니다(도면 검토보고서 v0.1.01). 범용 앱은 다음과 같은 도면 편차를 **정상 입력**으로 다뤄야 합니다.
|
||||
- 분해된 치수
|
||||
- 절반 작도
|
||||
- 표기와 치수 사슬의 충돌
|
||||
- 재료표 빈칸·상동 기호
|
||||
- 작도와 재료표의 수량·폭 불일치
|
||||
- 표 정의(EL.'X')의 생략
|
||||
|
||||
### 9.3 결정 필요 사항
|
||||
1. 기술 기반(8.2 권고안 채택 여부)
|
||||
2. 1차 대상 교각 형식 범위(원형 단주 T형 코핑만 / 확장)
|
||||
3. 산출 모델 형식(.3dm, IFC 연계 여부)
|
||||
4. 측량좌표·선형 자료의 입력 방식
|
||||
|
||||
---
|
||||
|
||||
## 부록
|
||||
|
||||
### A. 근거 파일
|
||||
| 구분 | 경로 |
|
||||
| :--- | :--- |
|
||||
| 박스 앱 분석 | `DOC/02_분석/분석_통로박스모델링과정_2026-10-07_v0.1.00.md` |
|
||||
| 판독서 | `DOC/02_분석/분석_운곡교P1교각도면판독_2026-10-07_v0.1.01.md`, `DOC/02_분석/분석_운곡교P1코핑철근판독_2026-10-07_v0.1.02.md` |
|
||||
| 설계서 | `DOC/03_설계/설계_운곡교P1교각모델링_2026-10-08_v0.1.10.md` |
|
||||
| 검증 보고서 | `DOC/05_검증/검증_운곡교P1교각모델_2026-10-08_v0.1.05.md` |
|
||||
| 도면 검토보고서 | `DOC/06_보고서/보고서_운곡교P1교각도면검토_2026-10-08_v0.1.01.md` |
|
||||
| 미해결 목록·기술노트 | `context-migration/OPEN_미해결목록.md`, `context-migration/기술노트_DWG판독_Rhino모델링환경.md` |
|
||||
| 작업 로그 | `prompt/심영표/2026-10-07/`, `prompt/심영표/2026-10-08/` |
|
||||
|
||||
### B. 실작업 스크립트 → 앱 모듈 대응
|
||||
| 앱 모듈 | 실작업 스크립트 (`Code/교각모델링/`) |
|
||||
| :--- | :--- |
|
||||
| 입력·변환 | `convert_dwg.py` |
|
||||
| 판독 | `survey_dxf.py`, `render_region.py`, `extract_outline.py`, `calc_bearing_seat.py`, `extract_bearing_seats.py`, `extract_column_rebar.py`, `extract_footing_rebar.py`, `extract_coping_rebar.py`, `extract_sole_plates.py` |
|
||||
| 생성 | `build_rhino.py`, `build_rebar_rhino.py`, `build_footing_rebar_rhino.py`, `build_coping_rebar_rhino.py`, `build_bearing_plates_rhino.py`, `apply_rebar_colors.py` |
|
||||
| 검사(게이트) | `verify_rhino.py`, `verify_rebar.py`, `verify_footing_rebar.py`, `verify_coping_rebar.py`, `verify_bearing_plates.py`, `check_outputs.py` |
|
||||
| 보고서 | `compile_rebar_schedule.py`, `check_report.py`, `md_to_pdf.py` |
|
||||
| 데이터 | `params.json`, `판독/*.json`, `검증/*.json` |
|
||||
@@ -0,0 +1,280 @@
|
||||
# 교각 모델링 앱 개선 기획서 — 「2. 입력 묶음 확인·확정」 개편
|
||||
|
||||
| 버전 | 수정일 | 수정자 | 주요 변경 내용 | 비고 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| v0.1.00 | 2026-10-14 | 심영표 | 신규 작성 | 초안 `[미확정]` — D1·D2는 사용자 결정 반영, D3~D6은 결정 대기(7장) |
|
||||
|
||||
> 상태: **초안 `[미확정]`**. 코드는 고치지 않았고, 이 문서는 계획만 담습니다. 수치는 2026-10-14 현재 앱(`Code/교각모델링앱/`)과 보호 템플릿 4종을 직접 센 값입니다. `[추론]`은 확인이 필요한 판단입니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
앱 교각 화면의 「2. 입력 묶음 확인·확정」을 다음 네 가지가 되도록 고칩니다.
|
||||
|
||||
1. 도면 **표 밖 치수**(표 3종에 없는 치수·EL·위치)를 앱이 찾아 후보로 제시합니다.
|
||||
2. 묶음별 **확인**(도면 대조)을 앱이 수행하고 근거를 보여 줍니다. **확정**은 사람이 합니다(결정 D1).
|
||||
3. 같은 템플릿 안에서 **바꿔도 되는 값과 바꾸면 안 되는 값**을 구분해 화면에 표시합니다.
|
||||
4. 지금의 문제점과 해결방법을 정리해 개발 순서를 정합니다.
|
||||
|
||||
### 1.2 범위
|
||||
|
||||
| 포함 | 제외 |
|
||||
| :--- | :--- |
|
||||
| 교각 화면 2절(묶음 표·일괄 확정)과 [표 후보값]·[도면 문자 검색] 화면 | 3절 게이트 실행·승인 흐름(그대로) |
|
||||
| 값 확인 엔진(`engine/`)에 검사·탐색 추가, 확정 기록(`confirm.json`) 확장 | 새 교각 형식의 생성기·검사기(형식이 다르면 별도 Task) |
|
||||
| 보호 템플릿의 묶음 분류 메타 | 구조계산·간섭 검사·측량좌표(기존 한계 그대로) |
|
||||
|
||||
### 1.3 용어
|
||||
|
||||
| 용어 | 뜻 |
|
||||
| :--- | :--- |
|
||||
| 묶음 | `params.json`·`expected.json`의 최상위 항목 하나(예: `footing`, `G4`) |
|
||||
| 확인 | 묶음 값을 도면 원자료와 대조하는 일. 이 기획에서는 **앱이 수행** |
|
||||
| 확정 | "내가 이 값을 도면과 대조했다"는 **사람의 검토 기록**(해시로 값 변경 시 자동 무효) |
|
||||
| 표 밖 치수 | 받침 ELEVATION TABLE·솔플레이트 TABLE·철근재료표 3종에 없는 값. 정면·측면·평면·단면의 치수 문자와 작도선·철근 점에서 읽음 |
|
||||
| 앵커 | 원본 교각 판독에서 "이 값을 도면의 어디서(뷰·레이어·객체·상대 위치·문자 패턴) 읽었는가"를 적은 기록 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 현황
|
||||
|
||||
### 2.1 현재 화면과 동작
|
||||
|
||||
- 화면: 교각 화면 **「2. 입력 묶음 확인·확정」** = 묶음 표(파일·묶음·상태·확정자·확정 시각) + **[필요한 묶음 일괄 확정]** 버튼 (`web/templates/pier.html` 18~35행).
|
||||
- 동작: 사용자가 묶음을 열어 값을 도면과 **눈으로** 대조·수정하고 확정합니다. 일괄 확정은 미확정 필수 묶음에 확정자 이름·시각·해시를 **그대로 찍습니다**(`web/app.py` `confirm_all`: 값 검사 없음).
|
||||
- 값을 고치면 해시가 달라져 '변경됨 · 재확정 필요'가 되고 승인된 게이트도 '재실행 필요'가 됩니다(이 부분은 잘 동작).
|
||||
- 표 밖 치수: [도면 문자 검색](글자·정규식)과 [핸들 조회](객체 핸들의 좌표·길이·반지름)로 사람이 찾습니다. EL 문자는 목록만 보여 줍니다(`reader.el_list`). 값 자동 반영은 없습니다.
|
||||
- 표 3종은 자동 인식해 [표 후보값]에서 현재 값과 비교합니다(`reader.proposals`).
|
||||
|
||||
### 2.2 규모 (보호 템플릿 4종, 직접 집계)
|
||||
|
||||
| 템플릿 | 전체 묶음 | 참고(확정 불필요) | **확정 필요** | 확정 필요 묶음 합계 용량 | 철근 중심선 목록(손으로 못 고침)의 비중 |
|
||||
| :--- | --: | --: | --: | --: | --: |
|
||||
| 운곡교P1형 | 30 | 7 | 23 | 32 KB | 0 % (철근이 params 규칙 값) |
|
||||
| 산외교울산P1형 | 31 | 5 | 26 | 117 KB | 84 % (`coping_rebar` 98 KB) |
|
||||
| Ramp-A교P1형 | 30 | 5 | 25 | 135 KB | 89 % (`coping_rebar` 121 KB) |
|
||||
| 옥성교P1형 | 32 | 5 | 27 | 463 KB | 95 % (`coping_rebar` 301 KB + `footing_rebar` 142 KB) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 문제점
|
||||
|
||||
| # | 문제 | 근거 | 영향 |
|
||||
| :-- | :--- | :--- | :--- |
|
||||
| P1 | **일괄 확정이 값을 검사하지 않음.** 확정 = 도장 | `confirm_all`은 `p.confirm()`만 호출. 화면 문구는 "값을 도면·판독서와 대조하셨습니까?" 한 줄 | 확정 기록의 신뢰가 사람의 성실에만 의존. 일괄 버튼이 쉬워 검토가 생략되기 쉬움 |
|
||||
| P2 | **템플릿 원본 값이 그대로 확정될 수 있음.** 복사 직후 값은 원본 교각의 값 | 사용설명서 5.1 주의문. 묶음 표에 '원본 값 그대로'라는 표시 없음 | 새 도면 대조 없이 확정·승인하면 **원본 교각 모델이 PASS**로 나옴(가장 위험한 실수) |
|
||||
| P3 | **근거가 안 보임.** 묶음마다 "이 값이 도면 어디서 왔는지"가 없음 | 묶음 표에 출처 열 없음. `_src` 메모는 '확정 불필요(참고)'로 숨음 | 사람이 매번 판독서·도면을 따로 열어 대조 |
|
||||
| P4 | **표 밖 치수는 전부 수작업.** 판독 스크립트를 교각마다 새로 씀 | 새 형식 1개당 판독·등록 스크립트 약 10개(`Code/옥성P1판독/` 등). 앱은 문자 검색·핸들 조회까지 | 확인 단계 병목. 오타·누락·잘못된 뷰 기준 위험 |
|
||||
| P5 | **손으로 대조할 수 없는 묶음.** 철근 중심선 목록이 용량의 84~95 % | 2.2절 | 사람이 '열어서 눈으로 확인'할 수 없으므로 확정이 형식적 |
|
||||
| P6 | **같은 템플릿에서 바꿔도 되는 값과 안 되는 값이 구분되지 않음.** 모두 같은 JSON 편집 화면 | 묶음 편집은 '형식(객체·목록·숫자) 유지'만 강제(`set_section`) | 형식을 정의하는 값(`form`, `builders`, 마크 구성)을 실수로 고쳐 생성기가 깨지거나, 값이 아닌 형식이 달라진 도면을 같은 템플릿으로 밀어 넣음 |
|
||||
| P7 | **params와 expected가 같은 후보에서 함께 채워짐.** | 사용설명서 5.3 주의문(두 줄 함께 반영 시 검사 무의미) | 검사의 독립성(독립 기대값 원칙)이 사용 습관에 의존 |
|
||||
| P8 | **확인 상태의 층이 하나.** '미확정 / 확정 / 변경됨' 3값뿐 | `store.section_state` | "앱이 확인했다", "도면에 근거 없음", "불일치 의심"을 표현할 방법이 없음 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 해결 방향
|
||||
|
||||
### 4.1 화면 개편 — 「2. 입력 묶음 확인·확정」
|
||||
|
||||
**원칙**: 앱이 확인하고 근거를 보이며, 사람은 근거를 보고 확정합니다(결정 D1). 앱은 **확정하지 않습니다**.
|
||||
|
||||
현행 표(파일·묶음·상태·확정자·확정 시각)를 아래 열로 바꿉니다.
|
||||
|
||||
| 열 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 묶음 | 파일·이름. 클릭하면 편집 화면 |
|
||||
| 변수 등급 | V1 / V2 / V3 / F (4.4절). 색과 글자 병기 |
|
||||
| 값 출처 | 표 / 표 밖 치수(도면 위치) / 판독 스크립트 / **템플릿 원본 그대로** / 사람 입력 |
|
||||
| **앱 확인** | 일치 / 주의 / 불일치 / 근거 없음 / 해당 없음(참고) + 근거 보기 |
|
||||
| 사람 확정 | 확정·미확정·변경됨, 확정자·시각 |
|
||||
|
||||
- 표 위에 한 줄 요약: "필요 27개 중 앱 확인 일치 19 · 주의 4 · 불일치 1 · 근거 없음 3 · 사람 확정 0".
|
||||
- **[일치 묶음 일괄 확정]**: 앱 확인이 '일치'인 묶음만 대상. 누르기 전 근거표를 한 번 열어야 활성화(D4 결정 대기). '주의·불일치·근거 없음'은 묶음마다 사유를 적어 개별 확정합니다.
|
||||
- '템플릿 원본 그대로'인 묶음(P2)은 앱 확인이 **'근거 없음(원본값)'**이며 일괄 확정에서 제외됩니다. 새 도면과 대조(표 후보값 반영·표 밖 치수 반영·사람 입력)해 값이 바뀌거나 근거가 붙어야 풀립니다. 새 도면 값이 우연히 원본과 같은 묶음은 '일치(원본과 동일, 근거 있음)'로 구분합니다.
|
||||
|
||||
### 4.2 도면 표 밖 치수 찾기 (결정 D2: 규칙 + 템플릿 앵커)
|
||||
|
||||
**탐색 규칙 6종** (4개 교각 도면에서 판독 스크립트가 쓴 방법을 앱 규칙으로 일반화합니다)
|
||||
|
||||
| 규칙 | 찾는 것 | 방법 | 이미 있는 것 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| R1 레벨 | 기초·기둥·코핑 EL | `EL.` 문자 + 인접 레벨선·치수선 연결 | `reader.el_list`(목록만) |
|
||||
| R2 치수 사슬 | `22@120=2.640`, `100 \| 2@100 \| 72@125=9.000` | 문자 패턴 → 치수 사슬로 파싱, 합계가 전체 치수와 맞는지 검산 | `store.chain`(입력용 파서만) |
|
||||
| R3 철근 점·선 | 철근 위치 목록 | 철근 레이어의 점 심볼·선의 좌표(뷰 기준) → 위치 목록, R2와 대조 | 없음(스크립트) |
|
||||
| R4 콘크리트 외곽 | 코핑 정면 외곽 점 목록 | 콘크리트 레이어 선 → 중심선(y = 0) 단면 점열 | 없음(스크립트) |
|
||||
| R5 뷰 기준 | 정면·측면·평면 각각의 기준점·좌표 변환(예: z = 도면 y − 309,179.8) | 뷰 제목 문자('정면도' 등)·축척·EL 문자로 기준 후보 | 없음(스크립트에 상수) |
|
||||
| R6 표 수량 대조 | 재료표 N과 작도 점·선 개수 | R3 결과 개수 = 재료표 수량이면 근거 강화(옥성교: 스터럽 106 = 39 + 27 + 40) | `reader.proposals` 일부 |
|
||||
|
||||
**앵커(템플릿 원본의 판독 위치 기억)**
|
||||
|
||||
- 앵커 한 줄 = `{값 경로, 뷰, 도면 레이어, 객체 종류, 기준 대비 상대 위치 또는 문자 패턴, 변환 규칙}`. 원본 교각의 판독 스크립트가 값을 읽을 때 같이 남기는 형식으로 정합니다(소급 추출 범위는 D6).
|
||||
- 새 도면에서 같은 레이어·뷰·상대 위치·문자 패턴을 따라가 **후보**를 만들고, 규칙 R1~R6의 검산 결과로 **신뢰 등급**을 붙입니다.
|
||||
|
||||
| 신뢰 등급 | 조건 | 화면 |
|
||||
| :--- | :--- | :--- |
|
||||
| A | 앵커 일치 + 검산(사슬 합계·재료표 수량) 일치 | 후보 + 근거, 반영 가능 |
|
||||
| B | 앵커 일치, 검산 없음 또는 일부 불일치 | 후보 + 주의 |
|
||||
| C | 규칙만 일치, 앵커 없음(형식이 다른 도면 등) | 후보 목록만, 반영 시 사유 필요 |
|
||||
| 없음 | 어떤 규칙도 일치하지 않음 | **'근거 없음'으로 표시, 억지 후보를 만들지 않음** |
|
||||
|
||||
- 출력 = [표 후보값]과 같은 비교 표에 '표 밖 치수' 구역을 추가: 현재 값 / 후보 / 도면 위치(D1·좌표·핸들) / 신뢰 등급 / [도면 보기](`dxftools.render`와 같은 확대 이미지). 선택해 반영하는 것은 사람입니다(자동 반영 없음).
|
||||
- **한계**: 규칙과 앵커는 4개 교각의 도면 틀(운곡교 P1·P2, 산외교 울산 P1, Ramp-A교 P1, 옥성교 P1)에서만 검증됩니다. 틀이 다르면 등급 C 또는 '없음'이 나오는 것이 **정상 동작**입니다.
|
||||
|
||||
### 4.3 입력 묶음 확인·확정을 앱이 수행하기 (결정 D1: 앱 확인 + 사람 확정)
|
||||
|
||||
**앱 확인 8종** (묶음마다 해당하는 것만 수행, 결과는 일치·주의·불일치·근거 없음·해당 없음)
|
||||
|
||||
| # | 확인 | 방법 | 막는 문제 |
|
||||
| :-- | :--- | :--- | :--- |
|
||||
| C1 | 출처 있음 | 값마다 표·표 밖 치수·스크립트·사람 입력 중 어디서 왔는지 기록 여부 | P3 |
|
||||
| C2 | 도면 재대조 | 표 후보값·표 밖 치수 후보와 현재 값 비교 | P4 |
|
||||
| C3 | 템플릿 원본 잔존 | 현재 값이 템플릿 원본과 같은데 새 도면 근거가 없는 항목 수 | **P2** |
|
||||
| C4 | 내부 정합 | 치수 사슬 합 = 전체 치수, 본수 = 재료표 N, 직경 = 재료표, 위치 목록 길이 = 사슬 개수 | P5 |
|
||||
| C5 | params ↔ expected 독립 | expected 값이 params에서 복사됐는지(두 값이 같고 근거도 같은 한 후보) | **P7** |
|
||||
| C6 | 범위·단위 | EL(m×1000)·mm 단위 상식 범위, 음수·0·순서 역전 | 오타 |
|
||||
| C7 | 변수 등급 위반 | F(고정) 항목이 템플릿 원본과 달라졌는지, V3(생성 변수)가 생성 스크립트 없이 손으로 고쳐졌는지 | **P6** |
|
||||
| C8 | 재생성 일치(V3) | 판독 스크립트로 재생성한 목록의 해시와 현재 해시 | P5 |
|
||||
|
||||
**상태와 기록 (기존 `confirm.json` 호환)**
|
||||
|
||||
```
|
||||
미확인 → 앱 확인(일치|주의|불일치|근거 없음) → 사람 확정(확정자·시각·근거 해시)
|
||||
↑ 값이 바뀌면 둘 다 자동 무효(기존 해시 방식 유지)
|
||||
```
|
||||
|
||||
- `confirm.json`의 확정 기록은 그대로 두고, 앱 확인은 `autocheck.json`(묶음 해시·결과·근거 목록·검사 버전·시각)에 별도로 둡니다. **앱 확인은 확정 기록을 만들지 않습니다.**
|
||||
- 게이트 실행 조건은 지금처럼 '필요 묶음이 사람 확정됨'입니다(변경 없음). 앱 확인은 조건이 아니라 **판단 자료**입니다.
|
||||
- 보고서 초안 8장(검증 방법)에 "앱 확인 결과 요약"을 추가해 사람 확정 근거가 남게 합니다.
|
||||
|
||||
**앱이 스스로 확정하지 않는 이유 (결정 D1 근거)**
|
||||
|
||||
1. 이 앱의 검증 구조는 **생성과 검사의 독립**에 기대고 있습니다. 앱이 값을 만들고(후보 반영) 같은 앱이 확정하면 검토자가 없어집니다.
|
||||
2. 앱 확인 '일치'는 "후보를 만든 규칙과 값이 같다"이지 "도면이 맞다"가 아닙니다(규칙이 틀린 도면에서 거짓 일치 가능).
|
||||
3. 확정은 책임 기록입니다. 앱이 근거를 모아 주면 사람의 확정이 **빨라지되 의미는 유지**됩니다.
|
||||
|
||||
### 4.4 같은 템플릿에서 변수 가능과 불가능 구분
|
||||
|
||||
**4단계 분류** (묶음마다, 필요하면 필드마다 표시)
|
||||
|
||||
| 등급 | 뜻 | 예 | 앱에서의 처리 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **V1 값 변수** | 같은 형식에서 도면마다 달라지는 숫자·문자 값 | 기초·버림 크기, 기둥 지름·높이, 코핑 치수·EL, 말뚝 개수·간격·길이, 받침 표 값, 직경, fck | 화면 편집 가능, 앱 확인 대상 |
|
||||
| **V2 목록 변수** | 같은 형식에서 도면마다 달라지는 위치·점 목록 | 철근 위치 목록, 치수 사슬, 코핑 정면 외곽 점 | 치수 사슬 입력 가능, 앱 확인 대상(C4) |
|
||||
| **V3 생성 변수** | 값이 도면마다 다르지만 **판독 스크립트가 만든** 큰 목록 | `coping_rebar.bars`(98~301 KB), `footing_rebar.bars`(옥성) | 화면 직접 편집 **차단**, 판독 스크립트 재실행 안내, 재생성 일치(C8) |
|
||||
| **F 고정** | 형식을 정의하는 값. 바꾸면 같은 템플릿이 아님 | `scope`, `builders`, `checkers`, 각 묶음의 `form`·`shape`·`kind`, 철근 마크 체계, 게이트 검사기 이름 | 편집 시 **경고(D3: 차단 또는 경고)**, 바꾸면 '형식 변경 — 새 템플릿 필요' 표시 |
|
||||
|
||||
**같은 템플릿에서 바꿔도 되는가** (예시, `[추론]` 항목은 시험 후 확정)
|
||||
|
||||
| 변경 | 판정 | 이유 |
|
||||
| :--- | :--- | :--- |
|
||||
| 기초 크기·두께, 기둥 지름·높이, 말뚝 개수·간격·길이 | 가능(V1) | 생성기가 값으로 받음 |
|
||||
| 받침 표 값(EL.'X', h1~h3, A·B, S, L·T) | 가능(V1) | 표 후보값으로 이미 지원 |
|
||||
| 철근 간격·개수·직경(재료표) | 가능(V1·V2) | 치수 사슬 입력 |
|
||||
| 코핑 상면 단 수·받침 개수 변경 | 조건부 `[추론]` | 검사기 기대값·음성 시험 대상이 개수에 묶여 있음(OPEN #19 A7: 받침 10개 전제) |
|
||||
| 코핑 철근 중심선 목록 | 스크립트 필요(V3) | 사람이 좌표 수만 개를 고칠 수 없음 |
|
||||
| 하면 곡선 ↔ 직선 경사, 평면 사각 유무, 기초판 유무, 기둥 단면 형식 | **불가(F)** | 생성기·검사기가 다름 → 다른 템플릿 |
|
||||
| 철근 형상 종류(U형 → 폐합 등), 마크 체계 | **불가(F)** | 생성기·expected 스키마가 정함 |
|
||||
| 게이트 범위(`scope`)·생성기·검사기 이름 | **불가(F)** | 형식 정의 |
|
||||
|
||||
**새 도면이 이 템플릿으로 가능한지 판별하는 체크리스트** (교각 만들기 화면에 질문으로 제공, 예/아니오)
|
||||
1. 기초 형식(확대기초+말뚝 / 직접기초 / 기초판 없는 단일 말뚝)이 같은가
|
||||
2. 기둥 단면 형식(원형 / 기타)이 같은가
|
||||
3. 코핑 하면 형상(곡선 / 직선 경사 / 나팔형)과 평면 형식(직각 / 사각 / 볼록 끝면)이 같은가
|
||||
4. 받침 배치 구조(받침마다 대좌 / 앞뒤 공유 대좌)가 같은가
|
||||
5. 철근 형상 종류가 재료표 형식 열과 같은가
|
||||
6. 철근 생성 방식(규칙 값 / 철근별 중심선)이 같은가
|
||||
|
||||
하나라도 '아니오'면 "같은 템플릿으로 만들 수 없음 — 가장 가까운 템플릿 + 생성기·검사기 추가 필요"로 안내하고, 억지로 진행하면 묶음 표에 '형식 불일치 경고'를 상시 표시합니다.
|
||||
|
||||
**구현**: 보호 템플릿에 `variability.json`(묶음·필드 경로 → 등급)을 추가합니다. 편집 화면과 앱 확인 C7이 이 파일을 읽습니다. 4개 템플릿 분류는 부록 A.
|
||||
|
||||
---
|
||||
|
||||
## 5. 문제점과 해결방법
|
||||
|
||||
| 문제 | 해결방법 | 기대 효과 | 난이도 | 우선순위 |
|
||||
| :--- | :--- | :--- | :--: | :--: |
|
||||
| P1 일괄 확정이 검사 안 함 | 앱 확인(C1~C8) + '일치 묶음만 일괄 확정' + 근거표 열람 조건(D4) | 일괄 확정이 도장이 아닌 근거 기반 확정이 됨 | 중 | 1 |
|
||||
| P2 템플릿 원본값 확정 위험 | C3 '템플릿 원본 잔존' + 일괄 확정 제외 + 묶음 표에 '원본값' 표시 | 원본 모델이 PASS로 나오는 사고 방지 | **하** | **1** |
|
||||
| P3 근거가 안 보임 | 값 출처 열 + 근거 보기(도면 위치·표 행·스크립트·사슬 검산) | 판독서를 따로 열 필요 감소 | 중 | 2 |
|
||||
| P4 표 밖 치수 수작업 | 규칙 R1~R6 + 앵커 + 신뢰 등급 후보 표(결정 D2) | 새 도면 확인 시간 단축, 오타·누락 감소 | **상** | 3 |
|
||||
| P5 손으로 대조 불가 | V3 분류 + C4·C8(내부 정합·재생성 일치) + 편집 차단 | 확정이 '요약된 검산 결과'에 근거 | 중 | 2 |
|
||||
| P6 변수·고정 미구분 | 4단계 분류 + `variability.json` + 판별 체크리스트 + 형식 불일치 경고 | 형식이 다른 도면을 같은 템플릿에 밀어 넣는 실수 방지 | 중 | 2 |
|
||||
| P7 params·expected 동시 채움 | C5(독립성) + 후보 반영 화면에서 params 줄 별도 선택 안내 강화 | 검사 독립성 유지 | 하 | 2 |
|
||||
| P8 확인 상태 1층 | `autocheck.json` 별도 층(앱 확인), 확정 기록은 불변 | "앱이 확인", "근거 없음", "불일치 의심" 표현 가능 | 하 | 1 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 구현 계획
|
||||
|
||||
| 단계 | 내용 | 주요 변경 | 시험·판정 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **A 분류·표시** | `variability.json` 작성(4템플릿), 묶음 표에 변수 등급·값 출처 열, '템플릿 원본 그대로' 표시(C3) | `web/templates/pier.html`, `web/store.py`, `data/_템플릿/*/variability.json` | 4템플릿에서 분류 100 %, 새 교각(템플릿 복사 직후) 전 묶음이 '원본값' 표시 |
|
||||
| **B 앱 확인 엔진** | C1~C8 구현, `autocheck.json`, 근거표 화면, '일치 묶음 일괄 확정'(D4), 보고서 8장 요약 | `engine/verify.py`(신설), `web/app.py`, 템플릿, `engine/report.py` | 5개 교각 자기 데이터 전 묶음 '일치'(거짓 불일치 0), 값을 일부러 바꾸면 '불일치'(거짓 일치 0, 음성 시험), 기존 회귀 9종 FAIL 0 |
|
||||
| **C 표 밖 치수 탐색** | 규칙 R1~R6, 앵커 형식·추출 도구, 후보 표 구역, 도면 보기 | `engine/reader.py`, `engine/anchors.py`(신설), 판독 스크립트 앵커 기록 | 4개 도면에서 원본 params 값 재현율(등급 A·B 비율)·거짓 후보 0건 집계, 틀이 다른 도면에서 '없음' 정상 표시 |
|
||||
| **D 통합·문서** | 일괄 확정 문구·흐름, 사용설명서 개정(5.3·5.4·9장), 회귀 전체 | 사용설명서 v0.2.08, 시험 `tools/test_confirm_check.py` | 웹 E2E 포함 전체 회귀 PASS |
|
||||
|
||||
- 각 단계는 **정지·승인**(사용자) 후 진행합니다(HITL).
|
||||
- 확정·게이트 승인은 어느 단계에서도 앱이 대신하지 않습니다. 시험은 별도 시험 폴더에서만 확정 흐름을 실행합니다(실제 교각 데이터 보호: 웹 일괄 실행이 Ramp-A·울산·옥성 게이트 보고서를 '재로드 안 함' 상태로 덮어쓴 일이 있음, 원인(시험 또는 앱 사용) 미확인, OPEN #25).
|
||||
- 수용 기준(착수 전 확정): ① 모든 필수 묶음에 앱 확인 상태가 있다 ② '근거 없음'을 숨기지 않는다 ③ 거짓 일치 0·거짓 불일치 0(4개 도면 기준) ④ 앱이 확정 기록을 만들지 않는다 ⑤ 기존 시험 FAIL 0.
|
||||
|
||||
---
|
||||
|
||||
## 7. 리스크·결정 사항
|
||||
|
||||
### 7.1 리스크
|
||||
|
||||
| # | 리스크 | 대응 |
|
||||
| :-- | :--- | :--- |
|
||||
| R1 | 앱 확인 '일치'를 "도면이 맞다"로 오해 | 근거 보기에서 '무엇과 무엇이 같은가'를 문장으로 표시, 신뢰 등급 병기 |
|
||||
| R2 | 규칙·앵커가 4개 도면에 과적합 | 등급 C·'없음'을 정상으로 표시, 새 형식 도면마다 앵커 추가 |
|
||||
| R3 | 앵커 소급 추출 비용(원본 4교각) | D6 결정(신규부터 vs 소급) |
|
||||
| R4 | 앱 확인 규칙이 생성 스크립트와 같은 논리여서 순환 | 도면 원자료(DXF·문자 색인)를 직접 읽는 경로만 사용, 스크립트 재생성 일치(C8)는 '독립성 낮음'으로 표시 |
|
||||
| R5 | 일괄 확정이 다시 도장화 | 일치만 대상, 근거표 열람 조건, 주의·불일치는 사유 필수 |
|
||||
| R6 | 변수 등급 오분류 | `variability.json`은 템플릿 원본 교각 담당자가 확정, 분류 변경은 별도 Task |
|
||||
|
||||
### 7.2 결정 사항
|
||||
|
||||
| # | 내용 | 상태 | 권고 |
|
||||
| :-- | :--- | :--- | :--- |
|
||||
| D1 | 확정 주체 | **결정 2026-10-14 / 심영표: 앱 확인 + 사람 확정** | — |
|
||||
| D2 | 표 밖 치수 탐색 방식 | **결정 2026-10-14 / 심영표: 규칙 + 템플릿 앵커** | — |
|
||||
| D3 | F(고정) 항목 편집 시 처리 | **결정 대기** | 경고 + 사유 입력(차단은 생성기 개발자 모드에서만) |
|
||||
| D4 | 일괄 확정 조건 | **결정 대기** | '일치' 묶음만, 근거표를 한 번 열어야 활성화 |
|
||||
| D5 | 착수 범위·순서 | **결정 대기** | A → B 먼저(위험 큰 P1·P2 해소), C는 B 검증 뒤 |
|
||||
| D6 | 앵커 소급 추출 | **결정 대기** | 옥성교·Ramp-A(최근, 판독 스크립트 최신)부터, 나머지는 필요 시 |
|
||||
|
||||
---
|
||||
|
||||
## 부록 A. 보호 템플릿 묶음 분류 (등급 초안, 담당자 확정 전)
|
||||
|
||||
| 묶음 | 운곡교P1형 | 울산형 | Ramp-A형 | 옥성교형 |
|
||||
| :--- | :-: | :-: | :-: | :-: |
|
||||
| `pier_id` | F | F | F | F |
|
||||
| `scope`·`builders`·`checkers`·`negative` (실행 설정) | F (없음=기본) | F | F | F |
|
||||
| `lean_concrete`·`footing`·`column`·`materials` | V1 | V1 | V1(기초 없음) | V1 |
|
||||
| `piles` / `pile` | V1 | — | V1(`pile` 구간) | V1 |
|
||||
| `coping` | V1 + 점 목록 V2 (`form` F) | V1 + V2 (`form` F) | V1 + V2 (`form` F) | V1 + V2 (`form`·`skew_deg` 구조는 F) |
|
||||
| `bearing_seats`·`sole_plates` | V1 + 위치 V2 (대좌 구조 F) | V1 + V2 | V1 + V2 | V1 + V2 (대좌 공유 구조 F) |
|
||||
| `rebar_marks` | 직경 V1, 마크 체계 F | 동일 | 동일 | 동일 |
|
||||
| `column_rebar` | V1·V2 (`form` F) | V1·V2 | V1·V2 | V1·V2 |
|
||||
| `footing_rebar` | V1·V2 | V1·V2 | — (`shaft_rebar` V1·V2) | **V3** (중심선 목록) |
|
||||
| `coping_rebar` | V1·V2 (규칙 값) | **V3** | **V3** | **V3** |
|
||||
| `expected.G1~G5` | V1·V2 (**params와 독립 입력**) | 동일 | 동일 | 동일 |
|
||||
| `expected.planned_fail`·`rebar_palette` | 사람 결정 기록(교각별) | 동일 | 동일 | 동일 |
|
||||
| `display`·`_schema`·`_meta`·`*_src`·`hitl_decisions*` | F / 참고 | 동일 | 동일 | 동일 |
|
||||
|
||||
> 이 표는 묶음 수준의 초안입니다. 필드 수준 분류(`variability.json`)는 A단계에서 템플릿마다 작성하고 담당자가 확정합니다.
|
||||
|
||||
## 부록 B. 근거 파일
|
||||
|
||||
| 구분 | 경로 |
|
||||
| :--- | :--- |
|
||||
| 화면·동작 | `Code/교각모델링앱/web/templates/pier.html`, `web/app.py`(`confirm_all`, `unconfirmed`), `web/store.py`(`GATE_SECTIONS`, `section_state`, `confirm`, `set_section`) |
|
||||
| 표 판독·후보 | `engine/reader.py`(`table_candidates`, `field_candidates`, `proposals`, `el_list`, `entity_info`) |
|
||||
| 사용 방법 | `DOC/04_실행/실행_T형교각모델링앱사용설명서_2026-10-14_v0.2.07.md` §4~5, §9 |
|
||||
| 일반화 이력 | `context-migration/OPEN_미해결목록.md` #19·#23·#24·#25, `DOC/02_분석/분석_운곡교P2교각도면판독_2026-10-12_v0.1.00.md` §7 |
|
||||
| 표 밖 치수 판독 사례 | `Code/옥성P1판독/extract_g*.py`, `DOC/02_분석/분석_옥성교P1교각도면판독_2026-10-14_v0.1.07.md` |
|
||||
@@ -0,0 +1,680 @@
|
||||
# 교각 모델링 앱 개선 기획서 — 「2. 입력 묶음 확인·확정」 개편
|
||||
|
||||
| 버전 | 수정일 | 수정자 | 주요 변경 내용 | 비고 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| v0.1.00 | 2026-10-14 | 심영표 | 신규 작성 | 초안 `[미확정]` — D1~D6 결정 반영(7.2절) |
|
||||
| v0.1.01 | 2026-10-15 | 심영표 | 7.2절 D3~D6을 권고안대로 결정 반영 | 사용자 지시 "권고안대로" |
|
||||
| v0.1.02 | 2026-10-15 | 심영표 | 4.4절 템플릿 구분 기준 재정의(각도=값, 기초=말뚝 유무, 도면 먼저 추천) | 사용자 결정 3건(OPEN #30 ①②④) |
|
||||
| v0.1.03 | 2026-10-16 | 심영표 | A단계(분류·표시) 구현 결과 6.1절 추가 | 사용자 "개편 구현 진행" 지시; B단계는 사용자 승인 후 |
|
||||
| v0.1.04 | 2026-10-16 | 심영표 | B단계(앱 확인 엔진·근거표·일치 묶음 일괄 확정) 구현 결과 6.2절 추가, Prism 적용 시점 결정 반영 | 사용자 "B단계 착수" + "Prism은 B단계 새 화면부터, 기존 화면 교체는 D단계" |
|
||||
| v0.1.05 | 2026-10-19 | 심영표 | 이전 방식 일괄 확정 제거 반영, C단계 1차(앵커 학습·따라가기, R1·R2·R5) 구현 결과 6.3절 추가 | 사용자 "이전방식 일괄 확정은 지금 제거하고, C단계 착수" |
|
||||
| v0.1.06 | 2026-10-19 | 심영표 | C단계 2차 결과 6.4절 추가(표 값 교차 확인, 운곡교 P3·P4 실사용 확인, R3·R4 기하 앵커 시험 결과 = 미채택) | 사용자 "C단계 2차 착수, 운곡교 P3·P4에 앵커를 불러와 실제 후보 확인" |
|
||||
| v0.1.07 | 2026-10-19 | 심영표 | D단계(전 화면 Prism 교체, 사용설명서 v0.2.12, 전체 회귀, 운곡교 P3·P4 A등급 반영) 결과 6.5절 추가 | 사용자 "D단계 착수, 운곡교 P3·P4 후보 반영" |
|
||||
| v0.1.08 | 2026-10-20 | 심영표 | 운곡교 P3·P4 실사용 결과와 앱 개선점 6.6절 추가(OPEN #34·#35) | 사용자 "앱의 개선점을 알려줘" → "진행해줘" |
|
||||
| v0.1.09 | 2026-10-20 | 심영표 | 개선 ②③④⑥ 구현 결과 6.7절 추가 | 사용자 "#34의 2·3·4·6번 순으로 진행" |
|
||||
| v0.1.10 | 2026-10-20 | 심영표 | 개선 ①⑤ 구현 결과 6.8절 추가 | 사용자 "①(읽기 도구 통합), ⑤(일괄 실행 중단 표시)를 진행해줘" |
|
||||
| v0.1.11 | 2026-10-20 | 심영표 | 검토 요약 확정(한 화면 검토 + 한 번 확정) 6.9절 추가 | 사용자 "확정 판정이 오래 걸리고 확인하기 어렵다 → 앱이 일괄 처리, 사용자는 일괄 검토" 제안 → 권고안 진행 |
|
||||
| v0.1.12 | 2026-10-20 | 심영표 | 6.9절에 후속 변경 추가(도면에서 읽기에 표 값 통합, 검토 요약 모두 체크·주의 표시) | 사용자 "템플릿 원본 그대로도 모두 도면에서 읽기로 바꾸고, 미확정도 일괄 확정" |
|
||||
| v0.1.13 | 2026-10-20 | 심영표 | 운곡교 P4 복구 결과와 앱 수정 계획 6.10절 추가 | 사용자 "앱을 수정계획을 정리해줘" (P4 결정 소실 사고 기반) |
|
||||
|
||||
| v0.1.14 | 2026-10-20 | 심영표 | W4(원본 잔존 검사 확대) 상세 계획 6.11절 추가, 실측 근거 스크립트 보관 | 사용자 "「원본 잔존 검사 확대」에 대한 계획을 세워줘" |
|
||||
|
||||
> 상태: **초안 `[미확정]`**. 코드는 고치지 않았고, 이 문서는 계획만 담습니다. 수치는 2026-10-14 현재 앱(`Code/교각모델링앱/`)과 보호 템플릿 4종을 직접 센 값입니다. `[추론]`은 확인이 필요한 판단입니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
앱 교각 화면의 「2. 입력 묶음 확인·확정」을 다음 네 가지가 되도록 고칩니다.
|
||||
|
||||
1. 도면 **표 밖 치수**(표 3종에 없는 치수·EL·위치)를 앱이 찾아 후보로 제시합니다.
|
||||
2. 묶음별 **확인**(도면 대조)을 앱이 수행하고 근거를 보여 줍니다. **확정**은 사람이 합니다(결정 D1).
|
||||
3. 같은 템플릿 안에서 **바꿔도 되는 값과 바꾸면 안 되는 값**을 구분해 화면에 표시합니다.
|
||||
4. 지금의 문제점과 해결방법을 정리해 개발 순서를 정합니다.
|
||||
|
||||
### 1.2 범위
|
||||
|
||||
| 포함 | 제외 |
|
||||
| :--- | :--- |
|
||||
| 교각 화면 2절(묶음 표·일괄 확정)과 [표 후보값]·[도면 문자 검색] 화면 | 3절 게이트 실행·승인 흐름(그대로) |
|
||||
| 값 확인 엔진(`engine/`)에 검사·탐색 추가, 확정 기록(`confirm.json`) 확장 | 새 교각 형식의 생성기·검사기(형식이 다르면 별도 Task) |
|
||||
| 보호 템플릿의 묶음 분류 메타 | 구조계산·간섭 검사·측량좌표(기존 한계 그대로) |
|
||||
|
||||
### 1.3 용어
|
||||
|
||||
| 용어 | 뜻 |
|
||||
| :--- | :--- |
|
||||
| 묶음 | `params.json`·`expected.json`의 최상위 항목 하나(예: `footing`, `G4`) |
|
||||
| 확인 | 묶음 값을 도면 원자료와 대조하는 일. 이 기획에서는 **앱이 수행** |
|
||||
| 확정 | "내가 이 값을 도면과 대조했다"는 **사람의 검토 기록**(해시로 값 변경 시 자동 무효) |
|
||||
| 표 밖 치수 | 받침 ELEVATION TABLE·솔플레이트 TABLE·철근재료표 3종에 없는 값. 정면·측면·평면·단면의 치수 문자와 작도선·철근 점에서 읽음 |
|
||||
| 앵커 | 원본 교각 판독에서 "이 값을 도면의 어디서(뷰·레이어·객체·상대 위치·문자 패턴) 읽었는가"를 적은 기록 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 현황
|
||||
|
||||
### 2.1 현재 화면과 동작
|
||||
|
||||
- 화면: 교각 화면 **「2. 입력 묶음 확인·확정」** = 묶음 표(파일·묶음·상태·확정자·확정 시각) + **[필요한 묶음 일괄 확정]** 버튼 (`web/templates/pier.html` 18~35행).
|
||||
- 동작: 사용자가 묶음을 열어 값을 도면과 **눈으로** 대조·수정하고 확정합니다. 일괄 확정은 미확정 필수 묶음에 확정자 이름·시각·해시를 **그대로 찍습니다**(`web/app.py` `confirm_all`: 값 검사 없음).
|
||||
- 값을 고치면 해시가 달라져 '변경됨 · 재확정 필요'가 되고 승인된 게이트도 '재실행 필요'가 됩니다(이 부분은 잘 동작).
|
||||
- 표 밖 치수: [도면 문자 검색](글자·정규식)과 [핸들 조회](객체 핸들의 좌표·길이·반지름)로 사람이 찾습니다. EL 문자는 목록만 보여 줍니다(`reader.el_list`). 값 자동 반영은 없습니다.
|
||||
- 표 3종은 자동 인식해 [표 후보값]에서 현재 값과 비교합니다(`reader.proposals`).
|
||||
|
||||
### 2.2 규모 (보호 템플릿 4종, 직접 집계)
|
||||
|
||||
| 템플릿 | 전체 묶음 | 참고(확정 불필요) | **확정 필요** | 확정 필요 묶음 합계 용량 | 철근 중심선 목록(손으로 못 고침)의 비중 |
|
||||
| :--- | --: | --: | --: | --: | --: |
|
||||
| 운곡교P1형 | 30 | 7 | 23 | 32 KB | 0 % (철근이 params 규칙 값) |
|
||||
| 산외교울산P1형 | 31 | 5 | 26 | 117 KB | 84 % (`coping_rebar` 98 KB) |
|
||||
| Ramp-A교P1형 | 30 | 5 | 25 | 135 KB | 89 % (`coping_rebar` 121 KB) |
|
||||
| 옥성교P1형 | 32 | 5 | 27 | 463 KB | 95 % (`coping_rebar` 301 KB + `footing_rebar` 142 KB) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 문제점
|
||||
|
||||
| # | 문제 | 근거 | 영향 |
|
||||
| :-- | :--- | :--- | :--- |
|
||||
| P1 | **일괄 확정이 값을 검사하지 않음.** 확정 = 도장 | `confirm_all`은 `p.confirm()`만 호출. 화면 문구는 "값을 도면·판독서와 대조하셨습니까?" 한 줄 | 확정 기록의 신뢰가 사람의 성실에만 의존. 일괄 버튼이 쉬워 검토가 생략되기 쉬움 |
|
||||
| P2 | **템플릿 원본 값이 그대로 확정될 수 있음.** 복사 직후 값은 원본 교각의 값 | 사용설명서 5.1 주의문. 묶음 표에 '원본 값 그대로'라는 표시 없음 | 새 도면 대조 없이 확정·승인하면 **원본 교각 모델이 PASS**로 나옴(가장 위험한 실수) |
|
||||
| P3 | **근거가 안 보임.** 묶음마다 "이 값이 도면 어디서 왔는지"가 없음 | 묶음 표에 출처 열 없음. `_src` 메모는 '확정 불필요(참고)'로 숨음 | 사람이 매번 판독서·도면을 따로 열어 대조 |
|
||||
| P4 | **표 밖 치수는 전부 수작업.** 판독 스크립트를 교각마다 새로 씀 | 새 형식 1개당 판독·등록 스크립트 약 10개(`Code/옥성P1판독/` 등). 앱은 문자 검색·핸들 조회까지 | 확인 단계 병목. 오타·누락·잘못된 뷰 기준 위험 |
|
||||
| P5 | **손으로 대조할 수 없는 묶음.** 철근 중심선 목록이 용량의 84~95 % | 2.2절 | 사람이 '열어서 눈으로 확인'할 수 없으므로 확정이 형식적 |
|
||||
| P6 | **같은 템플릿에서 바꿔도 되는 값과 안 되는 값이 구분되지 않음.** 모두 같은 JSON 편집 화면 | 묶음 편집은 '형식(객체·목록·숫자) 유지'만 강제(`set_section`) | 형식을 정의하는 값(`form`, `builders`, 마크 구성)을 실수로 고쳐 생성기가 깨지거나, 값이 아닌 형식이 달라진 도면을 같은 템플릿으로 밀어 넣음 |
|
||||
| P7 | **params와 expected가 같은 후보에서 함께 채워짐.** | 사용설명서 5.3 주의문(두 줄 함께 반영 시 검사 무의미) | 검사의 독립성(독립 기대값 원칙)이 사용 습관에 의존 |
|
||||
| P8 | **확인 상태의 층이 하나.** '미확정 / 확정 / 변경됨' 3값뿐 | `store.section_state` | "앱이 확인했다", "도면에 근거 없음", "불일치 의심"을 표현할 방법이 없음 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 해결 방향
|
||||
|
||||
### 4.1 화면 개편 — 「2. 입력 묶음 확인·확정」
|
||||
|
||||
**원칙**: 앱이 확인하고 근거를 보이며, 사람은 근거를 보고 확정합니다(결정 D1). 앱은 **확정하지 않습니다**.
|
||||
|
||||
현행 표(파일·묶음·상태·확정자·확정 시각)를 아래 열로 바꿉니다.
|
||||
|
||||
| 열 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 묶음 | 파일·이름. 클릭하면 편집 화면 |
|
||||
| 변수 등급 | V1 / V2 / V3 / F (4.4절). 색과 글자 병기 |
|
||||
| 값 출처 | 표 / 표 밖 치수(도면 위치) / 판독 스크립트 / **템플릿 원본 그대로** / 사람 입력 |
|
||||
| **앱 확인** | 일치 / 주의 / 불일치 / 근거 없음 / 해당 없음(참고) + 근거 보기 |
|
||||
| 사람 확정 | 확정·미확정·변경됨, 확정자·시각 |
|
||||
|
||||
- 표 위에 한 줄 요약: "필요 27개 중 앱 확인 일치 19 · 주의 4 · 불일치 1 · 근거 없음 3 · 사람 확정 0".
|
||||
- **[일치 묶음 일괄 확정]**: 앱 확인이 '일치'인 묶음만 대상. 누르기 전 근거표를 한 번 열어야 활성화(D4 결정 대기). '주의·불일치·근거 없음'은 묶음마다 사유를 적어 개별 확정합니다.
|
||||
- '템플릿 원본 그대로'인 묶음(P2)은 앱 확인이 **'근거 없음(원본값)'**이며 일괄 확정에서 제외됩니다. 새 도면과 대조(표 후보값 반영·표 밖 치수 반영·사람 입력)해 값이 바뀌거나 근거가 붙어야 풀립니다. 새 도면 값이 우연히 원본과 같은 묶음은 '일치(원본과 동일, 근거 있음)'로 구분합니다.
|
||||
|
||||
### 4.2 도면 표 밖 치수 찾기 (결정 D2: 규칙 + 템플릿 앵커)
|
||||
|
||||
**탐색 규칙 6종** (4개 교각 도면에서 판독 스크립트가 쓴 방법을 앱 규칙으로 일반화합니다)
|
||||
|
||||
| 규칙 | 찾는 것 | 방법 | 이미 있는 것 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| R1 레벨 | 기초·기둥·코핑 EL | `EL.` 문자 + 인접 레벨선·치수선 연결 | `reader.el_list`(목록만) |
|
||||
| R2 치수 사슬 | `22@120=2.640`, `100 \| 2@100 \| 72@125=9.000` | 문자 패턴 → 치수 사슬로 파싱, 합계가 전체 치수와 맞는지 검산 | `store.chain`(입력용 파서만) |
|
||||
| R3 철근 점·선 | 철근 위치 목록 | 철근 레이어의 점 심볼·선의 좌표(뷰 기준) → 위치 목록, R2와 대조 | 없음(스크립트) |
|
||||
| R4 콘크리트 외곽 | 코핑 정면 외곽 점 목록 | 콘크리트 레이어 선 → 중심선(y = 0) 단면 점열 | 없음(스크립트) |
|
||||
| R5 뷰 기준 | 정면·측면·평면 각각의 기준점·좌표 변환(예: z = 도면 y − 309,179.8) | 뷰 제목 문자('정면도' 등)·축척·EL 문자로 기준 후보 | 없음(스크립트에 상수) |
|
||||
| R6 표 수량 대조 | 재료표 N과 작도 점·선 개수 | R3 결과 개수 = 재료표 수량이면 근거 강화(옥성교: 스터럽 106 = 39 + 27 + 40) | `reader.proposals` 일부 |
|
||||
|
||||
**앵커(템플릿 원본의 판독 위치 기억)**
|
||||
|
||||
- 앵커 한 줄 = `{값 경로, 뷰, 도면 레이어, 객체 종류, 기준 대비 상대 위치 또는 문자 패턴, 변환 규칙}`. 원본 교각의 판독 스크립트가 값을 읽을 때 같이 남기는 형식으로 정합니다(소급 추출 범위는 D6).
|
||||
- 새 도면에서 같은 레이어·뷰·상대 위치·문자 패턴을 따라가 **후보**를 만들고, 규칙 R1~R6의 검산 결과로 **신뢰 등급**을 붙입니다.
|
||||
|
||||
| 신뢰 등급 | 조건 | 화면 |
|
||||
| :--- | :--- | :--- |
|
||||
| A | 앵커 일치 + 검산(사슬 합계·재료표 수량) 일치 | 후보 + 근거, 반영 가능 |
|
||||
| B | 앵커 일치, 검산 없음 또는 일부 불일치 | 후보 + 주의 |
|
||||
| C | 규칙만 일치, 앵커 없음(형식이 다른 도면 등) | 후보 목록만, 반영 시 사유 필요 |
|
||||
| 없음 | 어떤 규칙도 일치하지 않음 | **'근거 없음'으로 표시, 억지 후보를 만들지 않음** |
|
||||
|
||||
- 출력 = [표 후보값]과 같은 비교 표에 '표 밖 치수' 구역을 추가: 현재 값 / 후보 / 도면 위치(D1·좌표·핸들) / 신뢰 등급 / [도면 보기](`dxftools.render`와 같은 확대 이미지). 선택해 반영하는 것은 사람입니다(자동 반영 없음).
|
||||
- **한계**: 규칙과 앵커는 4개 교각의 도면 틀(운곡교 P1·P2, 산외교 울산 P1, Ramp-A교 P1, 옥성교 P1)에서만 검증됩니다. 틀이 다르면 등급 C 또는 '없음'이 나오는 것이 **정상 동작**입니다.
|
||||
|
||||
### 4.3 입력 묶음 확인·확정을 앱이 수행하기 (결정 D1: 앱 확인 + 사람 확정)
|
||||
|
||||
**앱 확인 8종** (묶음마다 해당하는 것만 수행, 결과는 일치·주의·불일치·근거 없음·해당 없음)
|
||||
|
||||
| # | 확인 | 방법 | 막는 문제 |
|
||||
| :-- | :--- | :--- | :--- |
|
||||
| C1 | 출처 있음 | 값마다 표·표 밖 치수·스크립트·사람 입력 중 어디서 왔는지 기록 여부 | P3 |
|
||||
| C2 | 도면 재대조 | 표 후보값·표 밖 치수 후보와 현재 값 비교 | P4 |
|
||||
| C3 | 템플릿 원본 잔존 | 현재 값이 템플릿 원본과 같은데 새 도면 근거가 없는 항목 수 | **P2** |
|
||||
| C4 | 내부 정합 | 치수 사슬 합 = 전체 치수, 본수 = 재료표 N, 직경 = 재료표, 위치 목록 길이 = 사슬 개수 | P5 |
|
||||
| C5 | params ↔ expected 독립 | expected 값이 params에서 복사됐는지(두 값이 같고 근거도 같은 한 후보) | **P7** |
|
||||
| C6 | 범위·단위 | EL(m×1000)·mm 단위 상식 범위, 음수·0·순서 역전 | 오타 |
|
||||
| C7 | 변수 등급 위반 | F(고정) 항목이 템플릿 원본과 달라졌는지, V3(생성 변수)가 생성 스크립트 없이 손으로 고쳐졌는지 | **P6** |
|
||||
| C8 | 재생성 일치(V3) | 판독 스크립트로 재생성한 목록의 해시와 현재 해시 | P5 |
|
||||
|
||||
**상태와 기록 (기존 `confirm.json` 호환)**
|
||||
|
||||
```
|
||||
미확인 → 앱 확인(일치|주의|불일치|근거 없음) → 사람 확정(확정자·시각·근거 해시)
|
||||
↑ 값이 바뀌면 둘 다 자동 무효(기존 해시 방식 유지)
|
||||
```
|
||||
|
||||
- `confirm.json`의 확정 기록은 그대로 두고, 앱 확인은 `autocheck.json`(묶음 해시·결과·근거 목록·검사 버전·시각)에 별도로 둡니다. **앱 확인은 확정 기록을 만들지 않습니다.**
|
||||
- 게이트 실행 조건은 지금처럼 '필요 묶음이 사람 확정됨'입니다(변경 없음). 앱 확인은 조건이 아니라 **판단 자료**입니다.
|
||||
- 보고서 초안 8장(검증 방법)에 "앱 확인 결과 요약"을 추가해 사람 확정 근거가 남게 합니다.
|
||||
|
||||
**앱이 스스로 확정하지 않는 이유 (결정 D1 근거)**
|
||||
|
||||
1. 이 앱의 검증 구조는 **생성과 검사의 독립**에 기대고 있습니다. 앱이 값을 만들고(후보 반영) 같은 앱이 확정하면 검토자가 없어집니다.
|
||||
2. 앱 확인 '일치'는 "후보를 만든 규칙과 값이 같다"이지 "도면이 맞다"가 아닙니다(규칙이 틀린 도면에서 거짓 일치 가능).
|
||||
3. 확정은 책임 기록입니다. 앱이 근거를 모아 주면 사람의 확정이 **빨라지되 의미는 유지**됩니다.
|
||||
|
||||
### 4.4 같은 템플릿에서 변수 가능과 불가능 구분
|
||||
|
||||
**4단계 분류** (묶음마다, 필요하면 필드마다 표시)
|
||||
|
||||
| 등급 | 뜻 | 예 | 앱에서의 처리 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **V1 값 변수** | 같은 형식에서 도면마다 달라지는 숫자·문자 값 | 기초·버림 크기, 기둥 지름·높이, 코핑 치수·EL, 말뚝 개수·간격·길이, 받침 표 값, 직경, fck | 화면 편집 가능, 앱 확인 대상 |
|
||||
| **V2 목록 변수** | 같은 형식에서 도면마다 달라지는 위치·점 목록 | 철근 위치 목록, 치수 사슬, 코핑 정면 외곽 점 | 치수 사슬 입력 가능, 앱 확인 대상(C4) |
|
||||
| **V3 생성 변수** | 값이 도면마다 다르지만 **판독 스크립트가 만든** 큰 목록 | `coping_rebar.bars`(98~301 KB), `footing_rebar.bars`(옥성) | 화면 직접 편집 **차단**, 판독 스크립트 재실행 안내, 재생성 일치(C8) |
|
||||
| **F 고정** | 형식을 정의하는 값. 바꾸면 같은 템플릿이 아님 | `scope`, `builders`, `checkers`, 각 묶음의 `form`·`shape`·`kind`, 철근 마크 체계, 게이트 검사기 이름 | 편집 시 **경고(D3: 차단 또는 경고)**, 바꾸면 '형식 변경 — 새 템플릿 필요' 표시 |
|
||||
|
||||
**같은 템플릿에서 바꿔도 되는가** (예시, `[추론]` 항목은 시험 후 확정)
|
||||
|
||||
| 변경 | 판정 | 이유 |
|
||||
| :--- | :--- | :--- |
|
||||
| 기초 크기·두께, 기둥 지름·높이, 말뚝 개수·간격·길이 | 가능(V1) | 생성기가 값으로 받음 |
|
||||
| 받침 표 값(EL.'X', h1~h3, A·B, S, L·T) | 가능(V1) | 표 후보값으로 이미 지원 |
|
||||
| 철근 간격·개수·직경(재료표) | 가능(V1·V2) | 치수 사슬 입력 |
|
||||
| 코핑 상면 단 수·받침 개수 변경 | 조건부 `[추론]` | 검사기 기대값·음성 시험 대상이 개수에 묶여 있음(OPEN #19 A7: 받침 10개 전제) |
|
||||
| 코핑 철근 중심선 목록 | 스크립트 필요(V3) | 사람이 좌표 수만 개를 고칠 수 없음 |
|
||||
| 평면 skew(사각)·받침 회전·기초 회전 등 **각도** | 가능(V1) `[2026-10-15 결정]` | 각도는 템플릿이 아닌 값. 옥성교(skew 18°)·새재1교(0°) 검사기는 하나로 통합(선행: 두 교각 시험 재실행) |
|
||||
| 말뚝 개수·간격·배치 | 가능(V1·V2) | 말뚝 **유무**만 형식 |
|
||||
| 하면 곡선 ↔ 직선 경사, 기초의 말뚝 유무, 기둥 단면 형식, 대좌 공유 여부 | **불가(F)** | 생성기·검사기가 다름 → 다른 템플릿 |
|
||||
| 철근 형상 종류(U형 → 폐합 등), 마크 체계 | **불가(F)** | 생성기·expected 스키마가 정함 |
|
||||
| 게이트 범위(`scope`)·생성기·검사기 이름 | **불가(F)** | 형식 정의 |
|
||||
|
||||
**새 도면이 이 템플릿으로 가능한지 판별하는 체크리스트** (교각 만들기 화면에 질문으로 제공, 예/아니오)
|
||||
1. 기초 형식(말뚝 유무: 기초판+말뚝 / 기초판만 / 기초판 없는 단일 말뚝)이 같은가
|
||||
2. 기둥 단면 형식(원형 / 기타)이 같은가
|
||||
3. 코핑 하면 형상(곡선 / 직선 경사 / 나팔형)이 같은가. 평면의 사각·직각과 각도는 **문항이 아니라 값 확인**(skew·회전 각도 입력)으로 옮긴다 `[2026-10-15 결정]`
|
||||
4. 받침 배치 구조(받침마다 대좌 / 앞뒤 공유 대좌)가 같은가
|
||||
5. 철근 형상 종류가 재료표 형식 열과 같은가
|
||||
6. 철근 생성 방식(규칙 값 / 철근별 중심선)이 같은가
|
||||
|
||||
하나라도 '아니오'면 "같은 템플릿으로 만들 수 없음 — 가장 가까운 템플릿 + 생성기·검사기 추가 필요"로 안내하고, 억지로 진행하면 묶음 표에 '형식 불일치 경고'를 상시 표시합니다.
|
||||
|
||||
**도면 먼저 추천 (2026-10-15 결정)**: 교각 만들기 화면의 순서를 "도면 올리기 → 추천 순위(근거) → 템플릿 선택 → 6문항 확인"으로 바꾼다. 도면의 단서(말뚝·PILE 문자, Ø, 재료표 형식 열, 받침 표 행 수, 정면도 R 문자)로 문항 값을 추정해 미리 채우고 템플릿을 일치 문항 수로 순위 매긴다. 사람이 바꿀 수 있고, 맞는 템플릿이 없으면 "판단 불가"로 표시한다. R·각도 문자는 정면도·평면도 영역으로 한정해 철근 굽힘 반경 r 등과 구분한다. 논의 근거: `context-migration/템플릿구분기준과도면기반추천_논의.md`.
|
||||
|
||||
**구현**: 보호 템플릿에 `variability.json`(묶음·필드 경로 → 등급)을 추가합니다. 편집 화면과 앱 확인 C7이 이 파일을 읽습니다. 4개 템플릿 분류는 부록 A.
|
||||
|
||||
---
|
||||
|
||||
## 5. 문제점과 해결방법
|
||||
|
||||
| 문제 | 해결방법 | 기대 효과 | 난이도 | 우선순위 |
|
||||
| :--- | :--- | :--- | :--: | :--: |
|
||||
| P1 일괄 확정이 검사 안 함 | 앱 확인(C1~C8) + '일치 묶음만 일괄 확정' + 근거표 열람 조건(D4) | 일괄 확정이 도장이 아닌 근거 기반 확정이 됨 | 중 | 1 |
|
||||
| P2 템플릿 원본값 확정 위험 | C3 '템플릿 원본 잔존' + 일괄 확정 제외 + 묶음 표에 '원본값' 표시 | 원본 모델이 PASS로 나오는 사고 방지 | **하** | **1** |
|
||||
| P3 근거가 안 보임 | 값 출처 열 + 근거 보기(도면 위치·표 행·스크립트·사슬 검산) | 판독서를 따로 열 필요 감소 | 중 | 2 |
|
||||
| P4 표 밖 치수 수작업 | 규칙 R1~R6 + 앵커 + 신뢰 등급 후보 표(결정 D2) | 새 도면 확인 시간 단축, 오타·누락 감소 | **상** | 3 |
|
||||
| P5 손으로 대조 불가 | V3 분류 + C4·C8(내부 정합·재생성 일치) + 편집 차단 | 확정이 '요약된 검산 결과'에 근거 | 중 | 2 |
|
||||
| P6 변수·고정 미구분 | 4단계 분류 + `variability.json` + 판별 체크리스트 + 형식 불일치 경고 | 형식이 다른 도면을 같은 템플릿에 밀어 넣는 실수 방지 | 중 | 2 |
|
||||
| P7 params·expected 동시 채움 | C5(독립성) + 후보 반영 화면에서 params 줄 별도 선택 안내 강화 | 검사 독립성 유지 | 하 | 2 |
|
||||
| P8 확인 상태 1층 | `autocheck.json` 별도 층(앱 확인), 확정 기록은 불변 | "앱이 확인", "근거 없음", "불일치 의심" 표현 가능 | 하 | 1 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 구현 계획
|
||||
|
||||
| 단계 | 내용 | 주요 변경 | 시험·판정 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **A 분류·표시** | `variability.json` 작성(4템플릿), 묶음 표에 변수 등급·값 출처 열, '템플릿 원본 그대로' 표시(C3) | `web/templates/pier.html`, `web/store.py`, `data/_템플릿/*/variability.json` | 4템플릿에서 분류 100 %, 새 교각(템플릿 복사 직후) 전 묶음이 '원본값' 표시 |
|
||||
| **B 앱 확인 엔진** | C1~C8 구현, `autocheck.json`, 근거표 화면, '일치 묶음 일괄 확정'(D4), 보고서 8장 요약 | `engine/verify.py`(신설), `web/app.py`, 템플릿, `engine/report.py` | 5개 교각 자기 데이터 전 묶음 '일치'(거짓 불일치 0), 값을 일부러 바꾸면 '불일치'(거짓 일치 0, 음성 시험), 기존 회귀 9종 FAIL 0 |
|
||||
| **C 표 밖 치수 탐색** | 규칙 R1~R6, 앵커 형식·추출 도구, 후보 표 구역, 도면 보기 | `engine/reader.py`, `engine/anchors.py`(신설), 판독 스크립트 앵커 기록 | 4개 도면에서 원본 params 값 재현율(등급 A·B 비율)·거짓 후보 0건 집계, 틀이 다른 도면에서 '없음' 정상 표시 |
|
||||
| **D 통합·문서** | 일괄 확정 문구·흐름, 사용설명서 개정(5.3·5.4·9장), 회귀 전체 | 사용설명서 v0.2.08, 시험 `tools/test_confirm_check.py` | 웹 E2E 포함 전체 회귀 PASS |
|
||||
|
||||
- 각 단계는 **정지·승인**(사용자) 후 진행합니다(HITL).
|
||||
|
||||
### 6.1 구현 현황 (2026-10-16)
|
||||
|
||||
**A단계 분류·표시 — 구현 완료, 사용자 승인 대기**
|
||||
|
||||
| 항목 | 구현 |
|
||||
| :--- | :--- |
|
||||
| 변수 등급 분류 | `engine/variability.py`(규칙 표 + 묶음 내용으로 V3 판정: 철근 `bars` 목록 100개 이상), 보호 템플릿 6종에 `variability.json`(초안) 작성 — `tools/make_variability.py`. 템플릿 저장·교각 생성 때 자동 작성·복사 |
|
||||
| 묶음 표 | 「변수 등급」「값 출처」 열 추가. 값 출처는 등급 파일의 일반 출처(표 / 표 밖 치수 / 판독 스크립트 / 사람 입력 / 템플릿 고정) |
|
||||
| 템플릿 원본 그대로(C3) | 교각 생성 시 묶음별 해시를 `pier.json`의 `origin`에 기록 → 값이 그대로면 표에 '▲ 템플릿 원본 그대로'. 필수 묶음 중 원본인 것은 표 위에 목록 경고 |
|
||||
| F 항목 편집(D3) | F 묶음 또는 묶음 안의 고정 필드(`column.shape`, `coping.form` 등)를 바꾸면 **사유 입력 필수**, `edit_log.json`에 기록. 차단하지 않음 |
|
||||
| V3 편집 | 화면에서 저장 거부, 판독 스크립트 재실행 안내 |
|
||||
| 시험 | `tools/test_variability.py` 전부 PASS(분류 100 %, 부록 A 일치, 신규 교각 26/26 원본 표시·수정 시 해제, F 사유·V3 차단, 기록 없는 기존 교각은 주장하지 않음) |
|
||||
|
||||
**기획과 다르게 한 점·한계**
|
||||
|
||||
1. 「값 출처」는 **일반 출처**입니다. 묶음 값이 실제로 도면의 어디서 왔는지(C1·C2 근거)는 B·C단계 몫입니다.
|
||||
2. **기존 교각**(2026-10-16 이전 생성)은 템플릿 복사 기록이 없어 '원본 그대로' 여부를 판단하지 않습니다(표시도 안 함). 새로 만드는 교각부터 적용됩니다.
|
||||
3. `variability.json`은 묶음 수준 초안입니다. 필드 수준 분류와 담당자 확정(R6)은 남아 있습니다.
|
||||
4. 시안의 Prism 시각 디자인(UX1~UX3)은 아직 앱에 적용하지 않았습니다(기존 화면 스타일 유지). 적용 시점은 사용자 결정 대기.
|
||||
5. 「앱 확인」 열·요약 띠·일괄 확정 근거표는 B단계입니다.
|
||||
|
||||
### 6.2 B단계 구현 현황 (2026-10-16)
|
||||
|
||||
**앱 확인 엔진 `engine/verify.py` — 구현 완료, 사용자 승인 대기**
|
||||
|
||||
| 항목 | 구현 |
|
||||
| :--- | :--- |
|
||||
| 앱 확인 C1~C8 | C1 출처 기록, C2 도면 재대조(ELEVATION·솔플레이트·재료표 후보 vs 현재 값), C3 템플릿 원본 잔존, C4 내부 정합(철근 수량 vs 재료표 수량; 계획 FAIL은 '주의'), C5 params/expected 독립, C6 범위·단위, C7 변수 등급 위반(F 항목), C8 재생성(연결된 스크립트 없음 → 해당 없음) |
|
||||
| 상태 판정 | 불일치(검사 하나라도 실패) > 근거 없음(템플릿 원본 그대로, 독립 근거 없음) > 일치(표 대조 일치) / 주의(수량만 확인, 계획된 예외 등) / 해당 없음(참고·사람 결정 기록). F 묶음은 템플릿 원본(기록 없는 기존 교각은 보호 템플릿들)과 같으면 일치 |
|
||||
| 저장 | `autocheck.json`(묶음 해시·결과·근거 목록·검사 버전) — `confirm.json`과 별도, **앱은 확정 기록을 만들지 않음**. 값·도면·검사 버전이 바뀌면 자동 재실행(약 1초) |
|
||||
| 화면(Prism) | 묶음 표 위 **요약 띠**(필요 N개 중 일치·주의·불일치·근거 없음·해당 없음·사람 확정), 「앱 확인」 열 + 근거 한 줄, **근거 보기**(8종 검사별 결과·비교 내용·도면 위치), **일치 묶음 일괄 확정**(근거표를 끝까지 내려야 활성, 대상 목록 해시 표식, 이름 필수), 앱 확인이 일치가 아닌 묶음은 개별 확정 시 **사유 필수**(확정 기록에 사유·당시 앱 확인 상태 저장) |
|
||||
| 보고서 | 8장에 「8.1 입력 묶음 앱 확인 결과 요약」(묶음별 앱 확인·근거·사람 확정·사유) |
|
||||
| Prism | `web/static/prism.css`(시안 pier.css에서 `.prism` 범위로 생성, `tools/make_prism_css.py`)를 새 화면(요약 띠·표·근거 보기·일괄 확정)에만 적용. 기존 화면 교체는 D단계 |
|
||||
|
||||
**실제 교각 6종 결과** (`tools/report_autocheck.py`, 필요 묶음 기준)
|
||||
|
||||
| 교각 | 일치 | 주의 | 불일치 | 근거 없음 | 해당 없음 |
|
||||
| :--- | --: | --: | --: | --: | --: |
|
||||
| Ramp-A교P1 | 11 | 2 | 0 | 9 | 3 |
|
||||
| 산외교울산P1 | 12 | 4 | 0 | 7 | 3 |
|
||||
| 산외교울산P2 | 12 | 4 | 0 | 8 | 3 |
|
||||
| 새재1교P1 | 9 | 4 | 0 | 10 | 3 |
|
||||
| 옥성교P1 | 9 | 4 | 0 | 11 | 3 |
|
||||
| 운곡교P1 | 9 | 0 | 0 | 11 | 3 |
|
||||
| (참고) 운곡교P3·P4 | 0~1 | 0~1 | **8** | 11 | 3 |
|
||||
|
||||
- **거짓 불일치 0**: 모델링된 교각의 자기 데이터에서 불일치 0건. 운곡교 P3·P4(P1 값을 복사만 한 교각)는 표 대조에서 **불일치 8건**으로 정확히 잡힙니다.
|
||||
- **거짓 일치 0**(시험): 받침 h1 +5 → 불일치, expected 값 +1 → 불일치, 코핑 철근 1본 삭제 → 불일치, 두께 −5 → 불일치. 수량만 확인한 철근 목록은 '일치'가 아니라 '주의'.
|
||||
- 근거 없음이 많은 이유: 기초·기둥·코핑 치수, 철근 위치·좌표, EL은 **표 밖 치수**라 앱이 아직 대조하지 못합니다(C단계). 숨기지 않고 '근거 없음'으로 표시합니다.
|
||||
|
||||
**기획과 다르게 한 점·한계 (B단계)**
|
||||
|
||||
1. 기획의 수용 기준 "5개 교각 자기 데이터 전 묶음 '일치'"는 **달성하지 못함** — 표 밖 치수 묶음은 C단계 전에는 일치가 될 수 없고 '근거 없음'입니다(거짓 일치를 만들지 않기 위해). 거짓 불일치 0·거짓 일치 0은 달성.
|
||||
2. C8(재생성 일치)은 앱에 생성 스크립트가 연결되어 있지 않아 '해당 없음'입니다.
|
||||
3. ~~이전 방식 일괄 확정은 접힌 항목으로 유지~~ → **2026-10-19 제거**(사용자 결정). `confirm_all` 경로와 화면 항목을 없앴고, 묶음은 [일치 묶음 일괄 확정] 또는 근거 보기에서 하나씩(일치가 아니면 사유 필수) 확정합니다. 이 경로를 쓰던 시험 9종은 `tools/testlib.confirm_required`(묶음마다 사유를 붙여 개별 확정)로 바꿨습니다. P1 '확정 = 도장' 문제 해소.
|
||||
4. 개별 확정(`section/.../confirm`)은 앱 확인이 일치가 아니면 사유가 서버에서 강제됩니다. 이에 맞춰 시험 4곳이 사유를 넣도록 바뀌었습니다.
|
||||
5. 기존 교각의 C3(템플릿 원본)은 판단하지 않고, F 묶음만 보호 템플릿과 비교합니다.
|
||||
6. 육안 확인: 임시 서버+헤드리스 Edge로 요약 띠·표·근거 보기·일괄 확정 화면을 캡처해 확인했습니다(`검증/ui_check_shots/`).
|
||||
|
||||
### 6.3 C단계 1차 구현 현황 (2026-10-19)
|
||||
|
||||
**표 밖 치수 탐색: 앵커 학습·따라가기 — 구현 완료, 사용자 승인 대기** (R1 레벨·R2 치수 사슬·R5 뷰 기준 구현, R3·R4 미구현)
|
||||
|
||||
| 항목 | 구현 |
|
||||
| :--- | :--- |
|
||||
| 앵커 학습 `engine/anchors.py` | 모델링된 교각의 숫자 필드(V1·V2 묶음, 1 m 이상)마다, 값과 같은 도면 치수·EL 문자(레이어·도면·좌표)를 기록. 같은 값이 1~6곳에서 나올 때만 앵커. 같은 파일 안에서 다른 종류 필드가 같은 값을 가지면(예: 두께와 지름 2500) '공유'로 표시해 A등급에서 뺌 |
|
||||
| 따라가기(R5) | 새 도면과 앵커 도면에 공통인 문자(제목·라벨)로 뷰별 이동량을 구하고(가까운 5개 중앙값), 예측 위치 반경 1,200 mm 안에서 같은 레이어·같은 종류의 문자를 후보로 취함. **도면 틀 일치율**(공통 문자 비율)이 70 % 미만이면 모든 후보를 C로 낮춤 |
|
||||
| 신뢰 등급 | A = 2곳 이상 따라가 모두 같고 이동 편차 작음 / B = 2곳 이상 일치 또는 1곳 / C = 서로 다름·틀이 다른 도면. 후보가 없으면 '없음'(억지 후보를 만들지 않음) |
|
||||
| R1 레벨 | 'EL. 38.916' 형식 문자를 앵커 종류 EL로 처리(z 계열 필드) |
|
||||
| R2 치수 사슬 | `n@간격=합계` 문자를 파싱해 개수 × 간격 = 합계 검산. 맞지 않으면 묶음 표 위 경고와 후보 화면 목록(도면 보기 링크) |
|
||||
| 저장 | 보호 템플릿 6종에 `anchors.json`(`tools/make_anchors.py`, 앵커 도면은 각 템플릿의 원본 교각). 교각 생성 시 복사, 앵커 없는 교각은 후보 화면에서 템플릿을 골라 불러옴. 앵커가 자기 도면에서 나왔으면(`sources` 해시 일치) 근거로 쓰지 않음 |
|
||||
| 화면(Prism) | [표 후보값]에 「표 밖 치수 후보」 구역: 요약 띠(후보·다름·A·B·C 수, 틀 일치율), 표(반영·대상·현재·후보·등급·근거 n곳 중 m곳·도면 위치·**도면 보기**), 반영 사유(C 필수). 도면 보기 = 해당 위치 확대 PNG(`engine/render.py`) |
|
||||
| 반영 규칙 | 같은 도면 문자에서 나온 후보를 params와 expected에 함께(한 번에 또는 따로) 반영 불가(P7), C등급은 사유 필수, 이미 같은 값은 반영 안 함, 반영·이전 값·등급·위치를 `anchor_log.json`에 기록, 바뀐 묶음은 재확정. 앱이 스스로 반영하지 않음 |
|
||||
| 앱 확인 연동 | 새 검사 **C2b 표 밖 치수 재대조**: A 후보가 현재 값과 다르면 불일치, B가 다르면 주의, A가 같고 앵커 필드의 60 % 이상 대조되면 일치(강한 근거), 일부만이면 주의. 자기 도면 앵커는 제외 |
|
||||
|
||||
**검증 결과** (`tools/test_anchors.py`, 실제 교각 9종 도면)
|
||||
|
||||
| 항목 | 결과 |
|
||||
| :--- | :--- |
|
||||
| 같은 도면 틀(울산 P1↔P2, 운곡교 P1↔P2) A등급 | **45건 중 틀린 후보 0건**, 값이 바뀐 필드를 A로 정확히 복원한 것 13건 |
|
||||
| 같은 도면 틀 B등급 | 451건 중 388건 일치(86 %), 틀린 후보 약 14 % — 경고로 표시 |
|
||||
| 같은 도면 틀 C등급 | 48건 중 12건 일치 — 목록만, 반영 시 사유 필요 |
|
||||
| 다른 도면 틀(36쌍) | **A·B 후보 0건**(틀 일치율 < 70 % → 전부 C) |
|
||||
| R2 사슬 검산 | 9개 도면 중 **운곡교 P4 도면의 사슬 2건이 불일치**(`62@125=7.250`은 계산 7,750, `29@250=7.750`은 계산 7,250 — 합계가 서로 바뀐 것으로 보임). 나머지 8개 도면은 0건 |
|
||||
| 새 교각 시험 | 템플릿 운곡교P1형 + 운곡교 P2 도면: 후보 146건, A 11건(다른 값 7건 — 모두 운곡교 P2 모델링 값과 일치), 후보가 다른 묶음 6개가 앱 확인에서 불일치 |
|
||||
| 시험 중 사고 | 처음 시험이 `store`를 환경변수 설정 전에 불러와 실제 `data/`에 시험 교각 2개를 만들었음 → 즉시 삭제, 시험은 `PIERAPP_DATA` 설정 뒤에 불러오도록 수정하고 `data/`를 건드리지 않는지 단언 추가 |
|
||||
|
||||
**기획과 다르게 한 점·한계 (C단계 1차)**
|
||||
|
||||
1. **R3(철근 점·선)·R4(콘크리트 외곽 점열)는 미구현.** 철근 위치 목록·코핑 외곽 점열은 앵커로 읽을 수 없어 여전히 '근거 없음'입니다. 스칼라 치수·EL 문자로 읽히는 필드만 후보가 됩니다.
|
||||
2. 수용 기준 "거짓 후보 0건"은 **A등급에서만 달성**(같은 틀 45건 중 0건). B등급은 약 14 %가 틀립니다(단, B는 경고 표시이고 앱 확인에서도 주의까지만 반영).
|
||||
3. 앵커는 단일 숫자 문자와 값이 같은 필드만 학습합니다. 계산해서 얻는 값(예: 사슬 합, EL 차)은 앵커가 없습니다. 1 m 미만 값은 대상이 아닙니다.
|
||||
4. D6(소급 추출: 옥성교·Ramp-A교부터)에 따라 만들었으나, 학습이 자동이라 **보호 템플릿 6종 모두**에 앵커를 만들었습니다.
|
||||
5. 같은 도면 틀이어야 작동합니다. 틀이 다른 도면(다른 설계 회사 도면 등)은 후보가 C등급 이하로 떨어지는 것이 정상 동작입니다.
|
||||
6. 기존 교각 9종은 앵커가 없어 후보 화면에서 템플릿을 골라 불러와야 합니다(자동 선택 안 함).
|
||||
7. 육안 확인: 임시 서버+헤드리스 Edge로 후보 화면을 캡처해 확인(`검증/ui_check_shots/candidates.png`).
|
||||
|
||||
### 6.4 C단계 2차 결과 (2026-10-19)
|
||||
|
||||
**① 운곡교 P3·P4에 운곡교P1형 앵커를 불러와 실제 후보 확인** (`tools/report_anchor_pier.py`, 실제 `data/운곡교P3·P4`에 `anchors.json` 복사 — 값은 하나도 반영하지 않음)
|
||||
|
||||
- 두 교각의 현재 값은 `운곡교p2`를 복사한 것이고(`pier.json`의 template 항목), 도면은 P3·P4 자신의 것입니다. 도면 틀 일치율 0.77(≥ 0.70, 같은 틀로 인정).
|
||||
- 후보 146건 중 현재 값과 다른 것 91건(P3) · 102건(P4).
|
||||
|
||||
| 단계 | P3 | P4 |
|
||||
| :--- | :--- | :--- |
|
||||
| 표 교차 확인 전 | A 11 · B 116 · C 19 | A 9 · B 118 · C 19 |
|
||||
| **표 교차 확인 후** | **A 56**(표 값과도 일치 47) · B 65 · C 25(표 값과 달라 낮춘 것 18) | A 54(47) · B 67 · C 25(18) |
|
||||
|
||||
- 표 값이 있는 필드만 비교하면 B등급은 34건 중 23건(68 %)만 맞았습니다. 틀린 것은 대부분 **같은 값을 공유하는 마크의 재료표 길이**(P3·P4·P5의 L이 서로 같아 후보가 서로 바뀜)였고, 표 교차 확인으로 모두 C로 내려갑니다.
|
||||
- 화면 확인: 후보 구역 최상단에 표 값과도 일치하는 A등급이 나열되고(받침 EL, 철근 길이·수량, 기초 EL, 기둥 높이), 앱 확인에서 불일치가 P3 10개·P4 11개로 표시됩니다(표 후보 8개 묶음 + 앵커 A 후보 묶음)(`검증/ui_check_shots/candidates_P3.png`, `pier_P3.png`).
|
||||
- 이 후보는 **아직 반영하지 않았습니다.** 반영 여부는 사용자가 [표 후보값] 화면에서 결정합니다(P3·P4는 모델링 전이고, 반영 후에도 앵커가 못 읽는 철근 위치 목록·코핑 외곽 등은 직접 판독해야 합니다).
|
||||
|
||||
**② 표 값 교차 확인 구현** (`engine/anchors.cross_check`, 화면에 "표 값과도 일치" / "표 값 X와 다름 — 표를 따르세요")
|
||||
|
||||
- 표(ELEVATION·솔플레이트·재료표) 값이 있는 필드: 앵커 후보가 표와 같으면 **A**, 다르면 표 값을 병기하고 **C**. 표 값이 없는 필드는 그대로.
|
||||
- 같은 도면 틀 4쌍 시험: A등급 45건 → **165건, 전부 맞음**(128건이 표로 확인, 47건은 표와 달라 C로 하향).
|
||||
|
||||
**③ R3·R4(기하 앵커) 시험 결과 — 채택하지 않음**
|
||||
|
||||
- 시도: 앵커가 없는 숫자 필드(≥ 1 m)에 대해 콘크리트·철근·중심선 레이어의 선분 길이가 같은 값인지 찾아, 새 도면에서 같은 위치·방향의 선분 길이를 후보로 삼음(울산 P1↔P2, 운곡교 P1↔P2).
|
||||
- 결과: A등급 15~18건/쌍이 **값이 바뀐 필드를 하나도 복원하지 못했고**(바뀐 값 복원 0건), 같은 값이 우연히 맞은 것과 철근 위치 목록(좌표인데 길이로 잘못 대응)의 오답 A가 2~7건 섞임. 위치 목록·외곽 점열은 길이가 아니라 **뷰 기준점 대비 좌표**이므로 뷰 기준점(R5)을 도면 틀마다 확정해야 하는데, 이는 현재 구조(문자 기반 이동량)로는 신뢰할 수 있게 구할 수 없었습니다.
|
||||
- 결론: 철근 점·선(R3)과 콘크리트 외곽 점열(R4)은 **여전히 '근거 없음'**으로 두고 판독 스크립트(사람)로 읽습니다. 억지 후보를 만들지 않는 방침 유지. 시도 코드는 보관하지 않고 결과만 기록(프로토타입 삭제).
|
||||
|
||||
**④ 화면 편의**: A등급 중 현재 값과 다른 후보를 expected만 / params만 한 번에 선택(두 파일 동시 선택은 P7 때문에 제공하지 않음).
|
||||
|
||||
**한계 (C단계 2차)**
|
||||
|
||||
1. 표 교차 확인은 표가 있는 필드만 개선합니다. 표 밖 필드의 B등급 정확도(약 86 %)는 그대로입니다.
|
||||
2. R3·R4 미구현 상태 유지 — 철근 위치·코핑 외곽 묶음은 앱 확인에서 계속 '근거 없음'이 됩니다.
|
||||
3. P3·P4의 후보를 반영하지 않은 상태라 두 교각의 앱 확인은 불일치 10~11개 그대로입니다(반영 후 다시 확인).
|
||||
|
||||
|
||||
|
||||
- 확정·게이트 승인은 어느 단계에서도 앱이 대신하지 않습니다. 시험은 별도 시험 폴더에서만 확정 흐름을 실행합니다(실제 교각 데이터 보호: 웹 일괄 실행이 Ramp-A·울산·옥성 게이트 보고서를 '재로드 안 함' 상태로 덮어쓴 일이 있음, 원인(시험 또는 앱 사용) 미확인, OPEN #25).
|
||||
- 수용 기준(착수 전 확정): ① 모든 필수 묶음에 앱 확인 상태가 있다 ② '근거 없음'을 숨기지 않는다 ③ 거짓 일치 0·거짓 불일치 0(4개 도면 기준) ④ 앱이 확정 기록을 만들지 않는다 ⑤ 기존 시험 FAIL 0.
|
||||
|
||||
---
|
||||
|
||||
### 6.5 D단계 결과 (2026-10-19)
|
||||
|
||||
**① 전 화면 Prism 교체**: `web/templates/base.html`을 Prism 틀(위쪽 띠·본문)로 바꾸고 `web/static/prism-app.css`를 추가해, 옛 화면의 표·버튼·입력칸·안내 문구가 Prism 토큰(색·간격·Pretendard)을 따르게 했습니다(템플릿별 개별 수정 없이 공통 틀로 교체). 상태 표시는 색과 함께 글자·기호를 씁니다. 형식 불일치 경고는 계속 닫을 수 없습니다. 어두운 테마는 시험하지 않았습니다.
|
||||
|
||||
**② 사용설명서 v0.2.12**: 앱 확인·근거 보기·일치 묶음 일괄 확정·변수 등급·표 밖 치수 후보·도면 먼저 추천·6번째 템플릿을 반영하고, 이전 방식 일괄 확정 설명을 삭제했습니다.
|
||||
|
||||
**③ 운곡교 P3·P4 후보 반영(A등급, 파일 하나씩)**: `tools/apply_anchor_a.py`로 앱의 후보 반영 경로를 거쳐 expected 쪽 A등급 후보를 반영했습니다(P3 29필드, P4 34필드, 반영 전 params·expected·confirm.json은 `backup/before_anchor_A_*`에 보관, 기록은 `anchor_log.json`). params 쪽 A등급 후보는 같은 도면 문자를 expected에 이미 썼기 때문에 P7 규칙으로 막혀 반영하지 못했습니다. 그 결과 묶음 단위 앱 확인 불일치는 P3 10·P4 11로 그대로이고, 필드 단위 expected 차이 줄은 69→52(P3), 75→63(P4)로 줄었습니다. 남은 불일치는 A등급 후보가 아닌 값(표 후보·params 독립 판독이 필요한 값)입니다.
|
||||
|
||||
**④ 회귀**: fast 15/15 PASS, Rhino 그룹 결과는 OPEN #26에 기록합니다.
|
||||
|
||||
**남은 결정**: 표 후보(expected 쪽) 검토 후 반영 여부, params 독립 판독, 운곡교 P4 도면 판(OPEN #33).
|
||||
|
||||
---
|
||||
|
||||
### 6.6 운곡교 P3·P4 실사용 결과와 앱 개선점 (2026-10-20)
|
||||
|
||||
**결과 요약**
|
||||
|
||||
| 교각 | 입력 | 게이트 결과 | 검사 | 계획된 FAIL | 계획 외 FAIL |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 운곡교 P3 | 도면에서 읽은 값으로 params·expected 갱신(77곳 + 표 후보 105건 + 2차 48곳) | G1~G5 모두 PASS·승인 | 578/585 | 7 (B12, C9~C13 원래 길이 5건, C13 규칙) | 0 |
|
||||
| 운곡교 P4 | `apply_pier_read.py` 로 144곳 + 표 후보 118건 | G1~G5 모두 PASS·승인 | 577/585 | 8 (B11, B12, C9~C13 원래 길이 5건, C13 규칙) | 0 |
|
||||
|
||||
- 첫 실행 → 최종 실행의 계획 외 FAIL: P3 G1 34·G2 7·G3 5·G4 28 → 0, P4 G1 18·G2 10·G3 22·G5 10(허위 G4 PASS) → 0.
|
||||
- 새 도구: `tools/verify_pier_geometry.py`(도면 값 55항목을 params 와 비교; P1·P3 55/55 로 재현 확인), `tools/apply_pier_read.py`(읽은 값을 params·expected 에 반영; P1·P3 `--dry` 로 재현 확인), `tools/infer_footing_rebar.py`(기초 철근 층 규칙; P1 재현), `tools/extract_coping_outline.py`.
|
||||
- 읽을 수 있는 값: 표 3종(받침·솔플레이트·재료표), 치수 글자, 코핑 외곽·철근 단·C15·C16, 받침 위치·회전, 기초·말뚝·기둥, 기초 철근 층(규칙 + 재료표 개수 일치), 기둥 철근(후프·결속). 읽을 수 없거나 가정: 스터럽 좌측 윗면, c14 좌측 규칙, 결속철근 끝 높이, 설계 의도.
|
||||
|
||||
**앱 개선점 (OPEN #34, 효과 큰 순)**
|
||||
|
||||
| # | 개선 | 근거(실제 사례) |
|
||||
| :--- | :--- | :--- |
|
||||
| 1 | 도면 읽기 도구를 후보 화면에 통합, 템플릿 재현 검증 포함 | P3 수작업 77곳, P4 144곳 |
|
||||
| 2 | params↔expected 일관성 검사(Rhino 실행 전) | 게이트 1회 약 150초, P3 실패의 대부분이 옛 값 혼재 |
|
||||
| 3 | 필드 단위 '템플릿 원본 그대로 남음' 검사 | P3: 앱 확인 불일치 0인데 게이트 40여 건 실패 |
|
||||
| 4 | 표 칸에서 학습한 앵커의 거짓 후보 제거 | P3·P4: 재료표 S1 길이 칸 '3.800' 을 G3 사슬 끝으로 오인 |
|
||||
| 5 | 일괄 실행 중단 시 중간 결과·이전 승인 요약 구분 표시 | P4: `display.py` 오류 후 승인 요약이 이전 실행 것으로 남음 |
|
||||
| 6 | 점 좌표를 키로 쓰는 목록 정합 검사 | P3: `hooks_up` 옛 좌표로 G4 코핑 밖 점 56개 |
|
||||
| 7 | 확정 사유 최소 품질 | 사유 'asf', 'afs', '12' |
|
||||
| 8 | 임시 등록 '설계자 확인 대기' 상태 관리, 해당 없음 항목 자동 탐지 | P2 의 G3 B9 551/493 항목이 P3·P4 에 남음 |
|
||||
| 9 | 보고서에 독립 근거 구분 표시 | PASS = 내부 일관성(params·expected 가 같은 읽기에서 나온 값 다수) |
|
||||
| 10 | P1 규칙 가정 단계(스터럽 좌측 윗면, c14 좌측 규칙)의 도면 읽기 | P4: 규칙이 틀려 재료표 역산값으로 교체 |
|
||||
| 11 | 도면 파일 해시·개정 번호 기록·비교 | P4 D2 사슬 오류: 사용자 CAD 판과 올린 파일 불일치 의심 |
|
||||
| 12 | 다른 도면 틀·형식(울산·새재1교)에서의 일반성 확인 | 교각 2개·템플릿 1개로만 확인 |
|
||||
|
||||
**설계자 확인 대상**(OPEN #35): P3 C13, P4 B11·B12·C9·C13, D2 사슬(#33). 모두 임시 등록이며 보고서에 '설계자 확인 전'으로 표시해야 한다.
|
||||
|
||||
**이번 작업에서 생긴 오류(재발 방지)**: `apply_pier_read.py` 가 pier_id 를 바꾸면서 `display.hidden_ids` 를 바꾸지 않아 P4 일괄 실행이 마지막 단계에서 중단됨 → 스크립트 수정(개선 3·6 과 같은 종류: 파생 값의 동시 갱신 누락).
|
||||
|
||||
---
|
||||
|
||||
### 6.7 개선 ②③④⑥ 구현 결과 (2026-10-20)
|
||||
|
||||
| 개선 | 구현 | 시험 결과 |
|
||||
| :--- | :--- | :--- |
|
||||
| ② params↔expected 일관성(C9) | `engine/consistency.py`: 같은 양을 나타내는 두 파일 값의 규칙 103~106건(기초·말뚝·기둥, 코핑 윗면 단·하면, 받침 피치·열 위치, 코핑 철근 단·C15·C16·루프·스터럽 하단, 후프·결속 사슬, 기초 철근 층·B9·B10, 재료표 사본). 다르면 해당 묶음이 불일치(b). 규칙의 경로가 없는 교각(새재1교 등)은 건너뜀 | 완성된 P1·P3·P4: 불일치 0. P3 최초 백업 상태(`before_stage2`): 54건 모두 검출, expected G1~G4 전부 불일치. P4 `before_table_apply`: 16건 검출. 다른 형식 4종: 거짓 불일치 0 |
|
||||
| ③ 필드 단위 원본 잔존(C3b) | 교각 생성 시 `origin_values.json` 기록(이전 교각은 `tools/make_origin_values.py`로 초기 백업에서 재구성). 바뀐 묶음 안에서 높이로 보이는 값(10~60 m, z·단 높이·점 좌표의 z)이 기록값 그대로이면 주의(w) | P3 `before_hooks_up` 상태에서 `hooks_up` 좌표 4개를 정확히 지목. 완성된 상태: 경고 0(처음에는 길이 12400·12000 등을 높이로 오인해 경고가 많았으나 높이 키로 제한) |
|
||||
| ④ 거짓 앵커 | `engine/anchors.py` `TABLE_RULE="single"`: 후보를 따라간 근거가 재료표 칸 **하나**뿐이면 C등급(`table_only`). 두 방식(`none`: 표 칸을 아예 제외)과 비교 | P3·P4 확인값 기준 B등급 오답: 끔 10/65 · single 2/39 · none 3/29. A등급 오답 0 유지. 부작용: P3에서 expected:G2(일치→주의)·params:column_rebar(일치→근거 없음) 2개 묶음의 근거가 줄어듦(표 칸 하나로 맞던 B등급 후보) |
|
||||
| ⑥ 점 좌표 목록 | C9 규칙: `c14.hooks_up`의 각 점이 `c14.points`에 있어야 함 | 최초 오류(P3 `before_hooks_up`)의 4점 모두 검출, 점 하나를 옮기면 해당 항목만 깨짐 |
|
||||
|
||||
**검사 환경 정리**: 실제 사용 중 `운곡교p2`(휴지통으로 이동)에 의존하던 시험 5종(`test_open19`·`test_rampa`·`test_saejae`·`test_okseong`·`test_ulsan_p2`)과 `test_anchors`를 `tools/testlib.pier_dir`(휴지통 최신 복사본, 대소문자 구분)로 바꿈. `test_autocheck`는 P3가 완성 값을 갖게 되어 최초 백업(다른 교각 값) 상태로 시험하도록 바꿈. 회귀 fast 16/16 PASS(`test_consistency` 추가).
|
||||
|
||||
**남은 개선**: ① 읽기 도구 통합, ⑤ 일괄 실행 중단 표시, ⑦ 확정 사유 품질, ⑧ 임시 등록 관리, ⑨ 독립 근거 구분 표시, ⑩~⑫.
|
||||
|
||||
---
|
||||
|
||||
### 6.8 개선 ①⑤ 구현 결과 (2026-10-20)
|
||||
|
||||
| 개선 | 구현 | 시험 결과 |
|
||||
| :--- | :--- | :--- |
|
||||
| ① 읽기 도구 통합 | `engine/geomread.py`: P3·P4 모델링 때 따로 만든 읽기 도구(코핑 외곽·철근 선·원, 기초·말뚝·기둥 글자, 받침 폴리라인, 재료표, 기초 철근 규칙)를 하나로 합친 순수 함수 `read()`(부작용 없음, 사본과 변경 목록 반환)와 `apply_selected()`. `engine/footing_rule.py`(기초 철근 규칙). 화면 「도면에서 읽기」(`/pier/<이름>/geomread`): 묶음별 현재/읽은 값/근거 표, 체크한 줄만 반영, 반영 전 `backup/before_geomread_*`, `read_log.json`, 추론 줄 기본 미선택, 도면 틀 일치율 70 % 미만 또는 필요한 선·글자가 없으면 이유를 알리고 읽지 않음. `tools/apply_pier_read.py` 는 같은 읽기의 명령줄 판 | `tools/test_geomread.py` PASS: 운곡교 P1·P3 에서 읽은 값 = 현재 값(변경 0), P4 는 추론 줄 1건만, 초기 P4 백업(P1 복사)에서 144건 제안이 검증된 P4 값과 모두 같음(표시 선택 `hidden_ids` 제외), 다른 도면 틀 2종 거절, 화면에서 고른 줄만 기록·백업·로그·확정 무효화 |
|
||||
| ⑤ 일괄 실행 중단 표시 | `batch.json` 에 `started`·`reason`(error/fail/blocked, 서버 중단은 killed)·`error`·`gate`, 게이트 결과에 `at`. 게이트 화면: 중단 이유 안내, 결과 시각과 '이전 실행 결과' 표시, 중단되기 전에 계산된 검사기 원본(`<게이트>.json` 이 기록(`_gate.json`)보다 새로울 때)을 '중단된 실행의 임시 결과'로 표시(승인·보고서에 쓰이지 않음), 입력이 바뀐 뒤의 승인은 '승인 기록은 이전 입력 것(무효)' | `tools/test_batch_notice.py` PASS: 오류·FAIL·조건 미충족·서버 중단 안내, 이전 실행 결과 표시, 임시 결과, 무효 승인 표시, 완료된 실행에는 안내 없음, 실제 작업자(`_batch`)에 가짜 Rhino 를 연결해 세 중단 경로의 기록 확인 |
|
||||
|
||||
**선택과 한계**
|
||||
- 읽기 결과를 params 와 expected 에 함께 채우는 것은 두 파일의 독립성을 떨어뜨림 → 화면 문구와 앱 확인 C9 설명으로 알림, 표(재료표) 값은 표 후보값 화면에서 따로 대조.
|
||||
- 표시 선택 `display.hidden_ids` 는 교각 이름이 바뀌면 템플릿의 숨김을 새 이름으로 옮겨 제안(P4 는 사용자가 비움으로 정함).
|
||||
- 읽기 규칙의 범위: 운곡교 도면 틀 1종, 교각 4개. 좌측 스터럽 윗면(추론)·c14 좌측 규칙·결속철근 끝 높이는 P1 규칙 가정.
|
||||
- 환경: 일괄 실행 오류 시 중간 결과가 `<게이트>.json` 으로 남는 것을 이용(별도 저장 없음).
|
||||
|
||||
**남은 개선**: ⑦ 확정 사유 품질, ⑧ 임시 등록 관리, ⑨ 독립 근거 구분 표시, ⑩ P1 규칙 가정 단계 읽기, ⑪ 도면 해시·개정 번호, ⑫ 다른 도면 틀 일반성.
|
||||
|
||||
---
|
||||
|
||||
### 6.9 검토 요약 확정 (2026-10-20)
|
||||
|
||||
**문제**: 묶음 20~23개를 하나씩 확정하는 데 시간이 오래 걸리고, 사유가 비거나('12', 'asf') 무엇이 바뀌었는지 보기 어려웠다. 사용자 제안: 앱이 일괄 확정하고 사용자는 일괄 검토만 한다.
|
||||
|
||||
**결정(권고안)**: "앱이 확정"은 확정의 의미(사람이 도면과 대조했다는 기록)를 없애므로 채택하지 않고, **앱이 검토를 준비하고 사람이 한 화면에서 검토·체크해 한 번에 확정**한다(D1 유지). 근거 없음 묶음은 **항목별 체크**(a)를 요구, 기존 개별 확정 화면은 예외 처리용으로 유지.
|
||||
|
||||
| 요소 | 구현 |
|
||||
| :--- | :--- |
|
||||
| 화면 | `/pier/<이름>/review` (교각 화면 [검토 요약으로 확정]). 확정이 필요한 묶음을 네 구역으로: 불일치(확정 불가), 주의·근거 없음(항목별 체크), 사람 결정 기록(체크), 일치(근거 훑어보기 + 구역 체크 1회) |
|
||||
| 앱이 준비하는 것 | `engine/review.py`: 바뀐 것(교각 생성 시점 값 기록 `origin_values.json` 대비 변경 값 수와 예), 앱이 확인하지 못한 것(근거 없음·주의 검사 항목), 값의 출처(`read_log.json`·`anchor_log.json` 이력), 사유 자동 작성(`auto_reason`) |
|
||||
| 사람이 하는 것 | 도면과 대조한 묶음에 체크, 목록 끝까지 읽기(표식 `review_view.json` = 그때 목록의 해시), 확정자 이름, 공통 의견(선택) |
|
||||
| 기록 | 묶음별 확정 기록에 사유(앱 확인·변경·출처·의견 포함), 확정 당시 앱 확인 상태, 이름. 앱은 체크가 없으면 아무것도 확정하지 않음 |
|
||||
| 안전장치 | 이름·끝까지 읽기·체크 없으면 거절, 값이 바뀌면 표식 무효, 불일치는 요청에 id 가 들어와도 확정하지 않음 |
|
||||
|
||||
**시험** `tools/test_review.py`(PASS): 화면 조회만으로는 확정 0, 표식·이름 없으면 거절, 체크 없으면 0, 일치 구역은 구역 체크로만, B·D 는 체크한 것만, 불일치는 거절, 기록에 사유·앱 확인 상태·이름, 값이 바뀌면 표식 무효, 불일치를 고치면 모든 필수 묶음이 이 화면으로 확정되어 게이트가 열림. 회귀 fast 19/19.
|
||||
|
||||
**후속 변경 (같은 날)**: ① 「도면에서 읽기」가 표 값(받침·솔플레이트·재료표 후보)을 같은 화면에 "표 값" 구역으로 포함(`geomread.table_changes`), 반영은 `reader.apply_proposals` 와 같은 규칙, 받침마다 다른 솔플레이트 길이는 `{번호: 값}` 으로 정규화 — 새 교각에서 템플릿 원본 값이 한 화면으로 모두 도면 값이 됨(시험: 초기 P4 백업 + 읽기 + 표 행 = 검증된 P4 의 표 관련 필드, 알려진 예외는 앵커 값·`tie_offset` 상수·낡은 직경 기록). ② 「검토 요약 확정」에 [모두 체크]·[모두 해제] 버튼, 주의 표시(템플릿 원본 그대로·설계자 확인 전 임시 등록 포함)를 칩과 확정 사유에 기록, 주의 표시가 있으면 모두 체크 시 확인창. 불일치 묶음은 여전히 확정 불가, 이름·끝까지 읽기·체크 없이는 확정되지 않음. 앞서 제안한 '낮은 위험/높은 위험 두 단계' 대신 사용자 요청에 따라 일괄 체크 + 주의 표시로 구현(위험도별 분류는 미구현).
|
||||
|
||||
**한계**: 체크는 사용자의 선언이라 실제 대조 여부는 앱이 확인할 수 없다. 사유의 "무엇을 어떻게 대조했는지"는 공통 의견에 적어야 남는다. 앞으로 앱 확인 'C10 도구 읽기 이력'을 근거(출처, 독립 검증 아님)로 연결하면 근거 없음 항목이 줄 수 있다(미구현).
|
||||
|
||||
### 6.10 운곡교 P4 복구 결과와 앱 수정 계획 (2026-10-20)
|
||||
|
||||
> 상태: **계획 `[미확정]`** — 코드는 고치지 않았다. 아래 W1~W8은 사용자 승인 후 PDCA 순서로 구현한다.
|
||||
|
||||
#### 6.10.1 사고 경위와 복구
|
||||
|
||||
| 시각 | 사실 |
|
||||
| :--- | :--- |
|
||||
| 09:08 | 첫 P4 G1~G5 통과·승인 → 11:20 사용자가 삭제(휴지통 `운곡교P4_20261020_112015`) |
|
||||
| 11:22~12:01 | 템플릿에서 다시 만든 판에 읽기·결정 반영 → 12:01 삭제(휴지통 `운곡교P4_20261020_121116`) |
|
||||
| 12:11 | 템플릿(운곡교P1형)에서 또 새로 만들고 읽기 279건 반영 → 12:12 검토 요약으로 5개 게이트 묶음 일괄 확정 |
|
||||
| 12:15 | 일괄 실행: G5만 통과, G1~G4 **계획 외 FAIL 11건**으로 G1에서 중단 |
|
||||
| 복구 | 휴지통 판의 `expected.json`·`params.json` 복원(현재 판 백업 `data/운곡교P4/backup/before_restore_decisions_20261020/`). 임시 폴더 시험: G1 171/171·G2 42/42·G3 71/73(계획 2)·G4 110/116(계획 6)·G5 183/183, 계획 외 0 |
|
||||
|
||||
12:11 판에서 템플릿 값으로 되돌아가 있던 곳(결정 소실 6곳): ① 앵커 `rear_first_from_tip` 1198 → 1232 ② `tie_offset` 22.5 → 17.5 ③ 좌측 스터럽 윗면 40447([추론]) ④ 코핑 상단 `a` 11856/11881/11905 → 11855/11880/11904 ⑤ c14 높이 .5 → .1 ⑥ 계획된 FAIL B11·C13 누락, B12 사유가 P1 값(28/34)으로 복귀. 1232는 기준 템플릿 운곡교P1형의 값이며 교각마다 다르다(P1 1232·P2 1223·P3 1212·P4 1198).
|
||||
|
||||
#### 6.10.2 원인 (반복될 수 있는 실패)
|
||||
|
||||
| # | 원인 | 근거 |
|
||||
| :-- | :--- | :--- |
|
||||
| C1 | 사람의 결정(앵커 채택, 계획된 FAIL, 역산값)이 교각 데이터 안에만 있어 교각을 지우면 같이 사라지고, 새 교각에 다시 적용할 수단이 없다 | 6곳 소실 |
|
||||
| C2 | 앱이 알고 있는 후보(B등급 1198)가 읽기에 자동 반영되지 않고, 경고만 남은 채 확정됨 | G1 확정 사유 `현재 1232 ≠ 도면 1198` |
|
||||
| C3 | 원본 잔존 검사(C3b)가 높이로 보이는 값만 비교해 `tie_offset`·`a` 등을 놓침 | 12:15 FAIL |
|
||||
| C4 | params와 expected가 서로 다른 값을 가리켜도 실행 전에 막지 못함(모델은 1198, 기준값은 1232) | G1 후열 패드 중심 X 5건 |
|
||||
| C5 | 계획된 FAIL이 템플릿(P1) 승인 상태로 상속되고, 새 교각에 필요한 항목은 사람이 추가해야 함 | B11·C13 누락 |
|
||||
| C6 | 게이트 실패가 어느 입력 필드 때문인지 앱이 알려 주지 않아 휴지통 판과 수동 비교가 필요했음 | 이번 복구 |
|
||||
|
||||
#### 6.10.3 가능과 불가능의 경계
|
||||
|
||||
| 값의 종류 | 앱이 채울 수 있나 | 방식 |
|
||||
| :--- | :--- | :--- |
|
||||
| 표·글자·치수(재료표, ELEVATION TABLE, 사슬) | 가능 | 도면에서 읽기(같은 틀 A등급 165건 전부 정답) |
|
||||
| 식이 정해진 계산값 | 가능(식 확인 후) | 규칙으로 다시 계산, 출처 「계산」 표시 |
|
||||
| 도면에 없는 역산·추론값 | 제안만 가능 | 사람이 채택, 결정 기록에 남겨 재적용 |
|
||||
| 선·점으로 그린 기하(R3·R4) | 불가능 | 시험에서 미채택(오답 A 2~7건/쌍) |
|
||||
| 도면끼리 충돌하는 값 | 불가능 | 사람이 선택, 계획된 FAIL·설계자 확인 대기 |
|
||||
| 다른 도면 틀 | 불가능 | 앵커 새로 학습 |
|
||||
|
||||
모든 묶음이 읽기로 채워지지는 않는다(P4: `tol_mm`·`rebar_palette`·`planned_fail`·`materials`는 읽기 0건). 게이트 시작에는 자기 묶음 확정과 이전 게이트 승인이 필요하나, 확정은 사람의 선언일 뿐 값의 정확성을 보장하지 않는다(P4 12:15).
|
||||
|
||||
#### 6.10.4 수정 항목
|
||||
|
||||
설계 원칙: 앱이 준비하고 사람이 한 화면에서 체크해 한 번에 확정한다는 기존 결정(D1, 6.9절)을 유지한다. 확정 부담을 늘리지 않고, **앱이 더 많이 채우고 사실로 확인되는 모순만 차단**한다.
|
||||
|
||||
| # | 수정 | 해결하는 원인 | 내용 |
|
||||
| :-- | :--- | :--- | :--- |
|
||||
| W1 | 실행 전 관문 | C4·C2 | 일괄 실행 전에 params↔expected 불일치(사실)와 후보≠현재 값을 모아 보여 주고, 불일치는 실행을 막고 후보 차이는 확인을 받는다. 기존 C9(`engine/consistency.py`)에 앵커 대상 필드를 추가하고 검사 항목 → 깨질 게이트 항목을 예측한다 |
|
||||
| W2 | 읽기에 후보 빠짐없이 | C2 | 앱이 아는 B등급 이상 후보 중 현재 값과 다른 것을 「도면에서 읽기」 화면에 기본 선택으로 올린다(추론 줄은 지금처럼 기본 미선택) |
|
||||
| W3 | 결정 기록 분리 | C1·C5 | `decisions.json`에 사람 결정(앵커 채택, 계획된 FAIL, 역산·추론값과 사유)을 읽기 이력과 따로 보관, 교각 삭제 시 휴지통에 함께 이동. 「이전 결정 다시 적용」 화면에서 같은 도면 틀일 때만 기본 선택, 고른 것만 반영 |
|
||||
| W4 | 원본 잔존 검사 확대 | C3 | C3b를 변수 등급 V1·V2 숫자 필드 전체로 넓혀 템플릿 값 그대로인 필드를 「확인 필요」로 모은다. 파생값(`a`, c14 높이)이 어떤 입력에서 나오는지 식을 먼저 조사 |
|
||||
| W5 | 실패 → 입력 필드 연결 | C6 | 계획 외 FAIL마다 원인 후보 필드를 표시(예: `rear pad centre X` → `G1.seats.rear_first_from_tip`), 「계획된 FAIL로 등록」 버튼(사유 필수) |
|
||||
| W6 | 계획된 FAIL 상속 상태 | C5 | 템플릿에서 상속된 항목은 「미승인(상속)」으로 표시하고 게이트 결과로 해당 여부를 대조 (OPEN #34 ⑧과 통합) |
|
||||
| W7 | 재현 시험 | 전체 | 검증된 첫 P4를 정답으로 「템플릿 + 읽기 + 재적용한 결정」 결과가 같은지 비교하는 `tools/test_pier_rebuild.py`. 이번 6곳 소실을 잡아야 통과 |
|
||||
| W8 | 템플릿 승격 | C1 | 같은 도면 틀에서 결정이 반복되면(C13 L 28.966, B12 32/38) 「운곡교P3·P4형」 템플릿 후보로 올린다. 사용자 승인 필수, 설계자 확인 대기 항목은 상속 상태 유지 |
|
||||
|
||||
시험 방식: 각 항목마다 P4 12:11 판(결정 소실 상태)을 입력으로 쓰는 음성 시험과, 완성된 P1·P3·P4에서 거짓 차단 0건을 확인하는 양성 시험을 둔다.
|
||||
|
||||
#### 6.10.5 순서와 결정 필요 사항
|
||||
|
||||
| 단계 | 항목 | 이유 |
|
||||
| :--- | :--- | :--- |
|
||||
| 1 | W1·W2 | 변경이 작고 이번 사고의 직접 원인을 막는다(같은 화면) |
|
||||
| 2 | W3·W7 | 근본 대책과 그 검증 |
|
||||
| 3 | W5·W6 | 실패 해석과 계획된 FAIL 정리 |
|
||||
| 4 | W4·W8 | 파생식 조사·템플릿 승인이 필요해 뒤로 미룸 |
|
||||
|
||||
| # | 결정 필요 | 권고 |
|
||||
| :-- | :--- | :--- |
|
||||
| D7 | 이전 결정의 자동 재적용 범위 | 같은 도면 틀일 때만 기본 선택, 나머지는 목록에서 고름 |
|
||||
| D8 | 기존 교각에 결정 기록을 소급할지 | 새 교각부터만 적용 |
|
||||
| D9 | 미확정 상태의 시험 실행(승인 없이 임시 폴더에만 결과)을 앱에 둘지 | 둔다. 승인 기록은 남기지 않음 |
|
||||
| D10 | W4의 파생식 조사를 1~3단계 뒤로 미룰지 | 미룬다 |
|
||||
|
||||
**미해소(P4 승인)**: 복구한 값은 아직 확정되지 않았다. 미확정 5개 묶음(`expected:planned_fail`·G1·G2·G4, `params:coping_rebar`)을 검토 요약에서 재확정하고, 일괄 실행·G4·G5 승인 후 보고서 초안을 쓴다(OPEN #37).
|
||||
|
||||
### 6.11 W4 원본 잔존 검사 확대 계획 (2026-10-20)
|
||||
|
||||
> 상태: **계획 `[미확정]`** — 코드는 고치지 않았다. 실측 스크립트: `Code/교각모델링앱/tools/analysis_w4_residual_sizing.py`.
|
||||
|
||||
#### 6.11.1 목적
|
||||
|
||||
새 교각을 템플릿에서 만든 뒤 **도면 값으로 바뀌어야 하는데 템플릿 값 그대로 남은 필드**를 150초짜리 게이트 실행 전에 찾아 사람에게 보여 준다.
|
||||
|
||||
#### 6.11.2 현행 C3b와 한계
|
||||
|
||||
`engine/verify.py` `stale_fields_check`: ① 키 이름이 높이처럼 생긴 필드(`height_like`)만 ② 값이 10~60 m ③ 같은 묶음에서 그런 값이 3개 이상 바뀐 경우에만 ④ 생성 시점 값(`origin_values.json`)과 같으면 경고(w, 실패 아님). 그래서 높이가 아닌 값(`tie_offset`, 앵커 `rear_first_from_tip`, 코핑 상단 `a`)은 비교 대상이 아니다.
|
||||
|
||||
#### 6.11.3 실측 (P4 12:11 판 = 결정 소실 상태, 정답 = 복원한 P4)
|
||||
|
||||
| 항목 | 값 |
|
||||
| :--- | :--- |
|
||||
| 템플릿 숫자 필드 / P1~P3에서 교각마다 달라진 필드 | 1,676 / 958 |
|
||||
| 12:11 판에서 템플릿 값 그대로인 숫자 필드 | 767 |
|
||||
| 현행 C3b가 잡은 후보 | **0건** |
|
||||
| 「템플릿 값 그대로 + 교각마다 달라지는 필드」(단순 확대) | 88건 중 실제 틀림 5건 → **정밀도 6 %** (거짓 경보 83건) |
|
||||
| 위 기준 + 3교각 모두 서로 다른 값 | 2건 중 1건(`rear_first_from_tip`) → 정밀도 50 %, 놓침 많음 |
|
||||
|
||||
해석: **통계만으로 확대하면 경보가 94 % 거짓이라 쓸 수 없다.** 거짓 경보 대부분은 P4가 P1과 같은 구조라 맞게 같은 값인 필드(받침 이동 검사, 기초 철근 층, 솔플레이트 표 등)다. 실제로 게이트를 깬 필드는 소수(`rear_first_from_tip`, `tie_offset`, 코핑 상단 `a` 3개)다. 한편 c14 높이(.1 대 .5)와 계획된 FAIL 차이는 템플릿 잔존이 아니다(c14는 0.4 mm 차이로 게이트는 통과, 계획된 FAIL은 목록 항목 누락) → W3·W5·W6 몫이다.
|
||||
|
||||
필드별 이력(검증된 교각): `tie_offset` P1·템플릿 17.5, P2·P3·P4 22.5 / `a` P1·P2 11855·11880·11904, P3 11856·11880·11905, P4 11856·11881·11905 / `rear_first_from_tip` 1232·1223·1212·1198(전부 다름). 즉 교각마다 달라지는 폭이 작은(1~5 mm) 값은 사람이 눈으로 놓치기 쉽다.
|
||||
|
||||
#### 6.11.4 설계 — 통계 경보 대신 단계별 확인
|
||||
|
||||
| # | 내용 | 효과·비고 |
|
||||
| :-- | :--- | :--- |
|
||||
| W4a | **교각별 확인 목록(watch-list)**: `variability.json`에 필드 단위로 「교각마다 달라지며 템플릿 값이면 확인 필요」를 지정. 초기 목록은 이력으로 실증된 필드(위 3개와 `G2.schedule_L` 등)로 시작. 템플릿 값 그대로면 앱 확인 「주의」(C3b 확장), W1 실행 전 관문에 연결 | 정밀도가 높은 소수 목록. 새 교각이 검증될 때 도구가 후보 추가를 제안하고 담당자가 승인 |
|
||||
| W4b | **통계 후보는 경보가 아니라 묶음 요약**: 「교각마다 달라지는 필드 N개가 템플릿 값 그대로」를 묶음 단위로 접어 보여 주고 필드 목록은 펼쳐 볼 때만 표시 | 거짓 94 %를 경보로 쓰지 않으면서 훑어볼 수 있게 함 |
|
||||
| W4c | **읽기 후보 연동**(W2): 목록·통계 후보 중 도면에서 후보(앵커·표)가 있는 필드는 「도면에서 읽기」 화면에 기본 선택으로 올림 | `rear_first_from_tip`은 B등급 후보가 있었다 |
|
||||
| W4d | **파생값 조사**: `a`, `tie_offset` 등이 어떤 입력·도면 글자에서 나오는지 P1~P4 값과 도면 문자로 조사. 식이 확인되면 계산값으로 제안(출처 「계산」), 못 찾으면 W4a 목록에 남김 | 현재 식을 모름. 조사 결과에 따라 W4a 목록이 줄어듦 |
|
||||
| W4e | **교각 그룹별 기본값 승격**(W8 연계): `tie_offset` 22.5처럼 P2 이후 전부 같은 값은 템플릿 P1 값(17.5)이 오히려 예외이므로, 같은 도면 틀 그룹 템플릿에서 기본값을 바꾸는 후보로 제안 | 승인 필수 |
|
||||
|
||||
#### 6.11.5 판정 규칙 (C3b 확장안)
|
||||
|
||||
| 조건 | 판정 |
|
||||
| :--- | :--- |
|
||||
| W4a 목록 필드가 생성 시점 값 그대로 | **주의(w)**, 검토 요약의 해당 묶음에 필드명·템플릿 값·교각별 이력 표시 |
|
||||
| 위 값이 읽기 후보(A·B등급)와 다름 | 주의 + 읽기 후보로 제시 |
|
||||
| 통계 후보(W4b) | 정보(건수만), 판정에 영향 없음 |
|
||||
| 묶음 전체가 그대로 | 기존 C3 |
|
||||
|
||||
실패(불일치)로 판정하지 않는다: 같은 값이 맞을 수도 있고(P2~P4 `a` 11880 등), 근거 없이 막으면 6.9절의 일괄 검토 흐름을 해치기 때문이다. 막는 것은 W1(params↔expected 사실 불일치)이 맡는다.
|
||||
|
||||
#### 6.11.6 시험
|
||||
|
||||
| 시험 | 기준 |
|
||||
| :--- | :--- |
|
||||
| 음성(잡아야 함) | P4 12:11 판에서 `rear_first_from_tip`·`tie_offset`·`a` 3개 모두 주의로 표시 |
|
||||
| 양성(거짓 경보 억제) | 완성된 P1·P2·P3·P4에서 W4a 목록 경보 0건 (값이 도면 값이면) |
|
||||
| 통계 후보 | 12:11 판 기준 건수만 표시, 판정 불변 |
|
||||
| 회귀 | `tools/test_autocheck.py`·`test_consistency.py`·`test_review.py` 통과, 기존 C3b 동작 유지 |
|
||||
| 신규 | `tools/test_stale_watchlist.py` |
|
||||
|
||||
#### 6.11.7 순서와 결정 필요
|
||||
|
||||
1. W4d 파생값 조사(코드 변경 없음, 결과를 보고) → 2. W4a·W4b 구현 → 3. W4c(W2 구현 시 함께) → 4. W4e(W8 승인 시).
|
||||
|
||||
| # | 결정 필요 | 권고 |
|
||||
| :-- | :--- | :--- |
|
||||
| D11 | 초기 watch-list 필드 범위 | 게이트를 실제로 깬 3개 + `G2.schedule_L`로 시작해 사고 때마다 추가 |
|
||||
| D12 | 통계 후보(W4b)를 화면에 둘지 | 묶음 요약만 두고 경보 아님 |
|
||||
| D13 | 템플릿 값 그대로를 실패가 아닌 주의로 둘지 | 주의 유지(차단은 W1 사실 불일치만) |
|
||||
|
||||
---
|
||||
|
||||
## 7. 리스크·결정 사항
|
||||
|
||||
### 7.1 리스크
|
||||
|
||||
| # | 리스크 | 대응 |
|
||||
| :-- | :--- | :--- |
|
||||
| R1 | 앱 확인 '일치'를 "도면이 맞다"로 오해 | 근거 보기에서 '무엇과 무엇이 같은가'를 문장으로 표시, 신뢰 등급 병기 |
|
||||
| R2 | 규칙·앵커가 4개 도면에 과적합 | 등급 C·'없음'을 정상으로 표시, 새 형식 도면마다 앵커 추가 |
|
||||
| R3 | 앵커 소급 추출 비용(원본 4교각) | D6 결정(신규부터 vs 소급) |
|
||||
| R4 | 앱 확인 규칙이 생성 스크립트와 같은 논리여서 순환 | 도면 원자료(DXF·문자 색인)를 직접 읽는 경로만 사용, 스크립트 재생성 일치(C8)는 '독립성 낮음'으로 표시 |
|
||||
| R5 | 일괄 확정이 다시 도장화 | 일치만 대상, 근거표 열람 조건, 주의·불일치는 사유 필수 |
|
||||
| R6 | 변수 등급 오분류 | `variability.json`은 템플릿 원본 교각 담당자가 확정, 분류 변경은 별도 Task |
|
||||
|
||||
### 7.2 결정 사항
|
||||
|
||||
| # | 내용 | 상태 | 권고 |
|
||||
| :-- | :--- | :--- | :--- |
|
||||
| D1 | 확정 주체 | **결정 2026-10-14 / 심영표: 앱 확인 + 사람 확정** | — |
|
||||
| D2 | 표 밖 치수 탐색 방식 | **결정 2026-10-14 / 심영표: 규칙 + 템플릿 앵커** | — |
|
||||
| D3 | F(고정) 항목 편집 시 처리 | **결정 2026-10-15 / 심영표: 권고안** | 경고 + 사유 입력(차단은 생성기 개발자 모드에서만) |
|
||||
| D4 | 일괄 확정 조건 | **결정 2026-10-15 / 심영표: 권고안** | '일치' 묶음만, 근거표를 한 번 열어야 활성화 |
|
||||
| D5 | 착수 범위·순서 | **결정 2026-10-15 / 심영표: 권고안** | A → B 먼저(위험 큰 P1·P2 해소), C는 B 검증 뒤 |
|
||||
| D6 | 앵커 소급 추출 | **결정 2026-10-15 / 심영표: 권고안** | 옥성교·Ramp-A(최근, 판독 스크립트 최신)부터, 나머지는 필요 시 |
|
||||
|
||||
---
|
||||
|
||||
## 부록 A. 보호 템플릿 묶음 분류 (등급 초안, 담당자 확정 전)
|
||||
|
||||
| 묶음 | 운곡교P1형 | 울산형 | Ramp-A형 | 옥성교형 |
|
||||
| :--- | :-: | :-: | :-: | :-: |
|
||||
| `pier_id` | F | F | F | F |
|
||||
| `scope`·`builders`·`checkers`·`negative` (실행 설정) | F (없음=기본) | F | F | F |
|
||||
| `lean_concrete`·`footing`·`column`·`materials` | V1 | V1 | V1(기초 없음) | V1 |
|
||||
| `piles` / `pile` | V1 | — | V1(`pile` 구간) | V1 |
|
||||
| `coping` | V1 + 점 목록 V2 (`form` F) | V1 + V2 (`form` F) | V1 + V2 (`form` F) | V1 + V2 (`form`·`skew_deg` 구조는 F) |
|
||||
| `bearing_seats`·`sole_plates` | V1 + 위치 V2 (대좌 구조 F) | V1 + V2 | V1 + V2 | V1 + V2 (대좌 공유 구조 F) |
|
||||
| `rebar_marks` | 직경 V1, 마크 체계 F | 동일 | 동일 | 동일 |
|
||||
| `column_rebar` | V1·V2 (`form` F) | V1·V2 | V1·V2 | V1·V2 |
|
||||
| `footing_rebar` | V1·V2 | V1·V2 | — (`shaft_rebar` V1·V2) | **V3** (중심선 목록) |
|
||||
| `coping_rebar` | V1·V2 (규칙 값) | **V3** | **V3** | **V3** |
|
||||
| `expected.G1~G5` | V1·V2 (**params와 독립 입력**) | 동일 | 동일 | 동일 |
|
||||
| `expected.planned_fail`·`rebar_palette` | 사람 결정 기록(교각별) | 동일 | 동일 | 동일 |
|
||||
| `display`·`_schema`·`_meta`·`*_src`·`hitl_decisions*` | F / 참고 | 동일 | 동일 | 동일 |
|
||||
|
||||
> 이 표는 묶음 수준의 초안입니다. 필드 수준 분류(`variability.json`)는 A단계에서 템플릿마다 작성하고 담당자가 확정합니다.
|
||||
|
||||
## 부록 B. 근거 파일
|
||||
|
||||
| 구분 | 경로 |
|
||||
| :--- | :--- |
|
||||
| 화면·동작 | `Code/교각모델링앱/web/templates/pier.html`, `web/app.py`(`confirm_all`, `unconfirmed`), `web/store.py`(`GATE_SECTIONS`, `section_state`, `confirm`, `set_section`) |
|
||||
| 표 판독·후보 | `engine/reader.py`(`table_candidates`, `field_candidates`, `proposals`, `el_list`, `entity_info`) |
|
||||
| 사용 방법 | `DOC/04_실행/실행_T형교각모델링앱사용설명서_2026-10-14_v0.2.07.md` §4~5, §9 |
|
||||
| 일반화 이력 | `context-migration/OPEN_미해결목록.md` #19·#23·#24·#25, `DOC/02_분석/분석_운곡교P2교각도면판독_2026-10-12_v0.1.00.md` §7 |
|
||||
| 표 밖 치수 판독 사례 | `Code/옥성P1판독/extract_g*.py`, `DOC/02_분석/분석_옥성교P1교각도면판독_2026-10-14_v0.1.07.md` |
|
||||
@@ -0,0 +1,569 @@
|
||||
# T형 교각 모델링 앱 화면 PRD — 「입력 묶음 확인·확정」 개편 중심
|
||||
|
||||
| 버전 | 수정일 | 수정자 | 주요 변경 내용 | 비고 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| v0.1.00 | 2026-10-15 | 심영표 | 신규 작성 — 기획서 v0.1.00의 기능을 구현하는 화면 13종의 요구사항 | 초안 `[미확정]`. 코드는 고치지 않음. 결정 대기 항목은 11장 |
|
||||
| v0.1.01 | 2026-10-15 | 심영표 | 11.1절 D3~D6을 권고안대로 결정 반영 | 사용자 지시 "권고안대로" |
|
||||
| v0.1.02 | 2026-10-15 | 심영표 | 11.2절 UX1~UX6 결정 반영 | 사용자 지시(UX1 대체, UX2 변경, UX3 내장, UX4 승인, UX5 제외, UX6 제작) |
|
||||
|
||||
> 상태: **초안 `[미확정]`**. 근거는 `DOC/01_기획/기획_입력묶음확인확정개편_2026-10-14_v0.1.00.md`(이하 기획서)와 현행 앱(`Code/교각모델링앱/web/`)이다. 화면 디자인 규칙은 같은 폴더의 `design.md`가 정하고, 그 근거는 `RAG/design system_v0.1.1`(Prism)이다. `[추론]`은 확인이 필요한 판단이다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 문서 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
기획서가 제안한 네 가지 기능을 사용자가 실제로 쓸 수 있는 **화면 단위의 요구사항**으로 바꾼다.
|
||||
|
||||
| 기획서 기능 | 이 문서의 화면 |
|
||||
| :--- | :--- |
|
||||
| 「2. 입력 묶음 확인·확정」 개편 (4.1) | S03, S04, S10 |
|
||||
| 앱이 수행하는 확인과 근거 제시 (4.3) | S04, S05, S12 |
|
||||
| 도면 표 밖 치수 찾기 (4.2) | S07, S08 |
|
||||
| 같은 템플릿에서 변수 가능과 불가능 구분 (4.4) | S02, S06, S13 |
|
||||
|
||||
### 1.2 범위
|
||||
|
||||
| 포함 | 제외 |
|
||||
| :--- | :--- |
|
||||
| 교각 작업 화면의 1~4단계(도면·묶음·게이트·보고서) 중 변경이 필요한 화면 | 게이트 실행·승인의 동작 자체(그대로) |
|
||||
| 신규 화면 5종(S02, S05, S08, S10, S13)과 S04의 요약 띠 | 새 교각 형식의 생성기·검사기 |
|
||||
| 화면 공통 어휘(상태·등급·출처)와 문안 | 모바일·태블릿 화면(로컬 데스크톱 전용) |
|
||||
| 수용 기준과 시험 방법 | 서버·엔진의 내부 구현(필요한 자료 계약만 6장에 기술) |
|
||||
|
||||
### 1.3 사용자와 전제
|
||||
|
||||
| 구분 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 주 사용자 | 담당자(교각 도면을 모델링하는 구조엔지니어). 한 번에 한 사람이 사용한다 |
|
||||
| 보조 사용자 | 검토자(보고서와 확정 기록을 읽는 사람). 읽기 전용 |
|
||||
| 환경 | 사용자 PC의 로컬 Flask 앱. 화면 폭 1280px 이상 데스크톱. 인터넷 없이 동작한다 |
|
||||
| 전제 | 확정과 게이트 승인은 **사람의 검토 기록**이다. 앱은 어느 화면에서도 대신하지 않는다 |
|
||||
| 언어 | 화면 문구는 한국어, 코드·API·키 이름은 영어 |
|
||||
|
||||
### 1.4 용어
|
||||
|
||||
기획서 1.3절의 용어(묶음·확인·확정·표 밖 치수·앵커)를 그대로 쓴다. 이 문서에서 추가하는 용어는 다음과 같다.
|
||||
|
||||
| 용어 | 뜻 |
|
||||
| :--- | :--- |
|
||||
| 근거 | 앱 확인 한 건이 어떤 두 값을 비교해 어떤 결과가 나왔는지 적은 한 문장과 그 출처 |
|
||||
| 원본값 | 템플릿 원본 교각에서 복사한 뒤 새 도면 근거 없이 그대로인 값 |
|
||||
| 변수 등급 | V1 값 변수 · V2 목록 변수 · V3 생성 변수 · F 고정 (기획서 4.4) |
|
||||
| 작업대 | 교각 한 개의 작업 화면 전체(S03). 1~4단계를 한곳에서 진행한다 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 제품 원칙
|
||||
|
||||
| ID | 원칙 | 화면에 미치는 영향 |
|
||||
| :--- | :--- | :--- |
|
||||
| **PP-1** | **앱은 확인하고, 사람은 확정한다.** 앱 확인은 확정 기록을 만들지 않는다 | 확정 버튼은 사람의 이름 입력과 클릭으로만 동작한다. 어느 화면에도 자동 확정이 없다 |
|
||||
| **PP-2** | **근거 없음을 숨기지 않는다.** 억지 후보를 만들지 않는다 | '근거 없음'은 일치와 같은 비중으로 표시하고 요약 띠에 개수를 낸다 |
|
||||
| **PP-3** | **원본 값 그대로 확정되는 사고를 막는다.** (기획서 P2) | '원본값' 표시를 묶음 표에 상시 노출하고 일괄 확정에서 제외한다 |
|
||||
| **PP-4** | **색만으로 구분하지 않는다.** | 모든 상태는 글자와 형태를 함께 쓴다(Prism BD-1) |
|
||||
| **PP-5** | **판정 위에 근거를 둔다.** 결론을 먼저, 경위를 뒤에 | 각 화면의 첫 영역이 결론(요약 띠·판정)이고 근거는 이어서 둔다 |
|
||||
| **PP-6** | **값을 바꾸면 이전 판단이 무효가 된다.** (현행 해시 방식 유지) | 앱 확인·사람 확정·게이트 승인 모두 '재확인 필요'를 같은 어휘로 표시한다 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 화면 목록
|
||||
|
||||
### 3.1 화면 지도
|
||||
|
||||
```
|
||||
S01 교각 목록 ──┬─ S02 새 교각 적합성 판별 ── (만들기)
|
||||
│
|
||||
└─ S03 교각 작업대 (1 도면 · 2 입력 묶음 · 3 게이트 · 4 보고서)
|
||||
├─ 1 도면 ── S09 도면 문자 검색·핸들 조회
|
||||
├─ 2 묶음 ── S04 입력 묶음 확인·확정
|
||||
│ ├─ S05 근거 보기 ── S06 값 편집
|
||||
│ ├─ S07 표 후보값·표 밖 치수 ── S08 도면 보기
|
||||
│ └─ S10 일괄 확정 확인
|
||||
├─ 3 게이트 ── S11 게이트
|
||||
└─ 4 보고서 ── S12 검토보고서 초안
|
||||
S13 변수 등급 안내 (템플릿별, S01·S02·S06에서 진입)
|
||||
```
|
||||
|
||||
### 3.2 화면 일람
|
||||
|
||||
| ID | 화면 | 구분 | 패턴 | 해결하는 문제(기획서) | 단계 |
|
||||
| :--- | :--- | :--- | :--- | :--- | :-: |
|
||||
| S01 | 교각 목록 | 변경 | W5 | P6 (템플릿 형식 설명 노출) | A |
|
||||
| S02 | 새 교각 적합성 판별 | **신규** | W3 | P6 | A |
|
||||
| S03 | 교각 작업대 | 변경 | W1 + W2 | P1~P3, P8 (진행 요약) | A·B |
|
||||
| S04 | 입력 묶음 확인·확정 | **변경(핵심)** | W5 | P1, P2, P3, P5, P6, P8 | A·B |
|
||||
| S05 | 근거 보기 | **신규** | W5 | P3, P5, P7 | B |
|
||||
| S06 | 값 편집 | 변경 | W3 | P6, P7 | A |
|
||||
| S07 | 표 후보값·표 밖 치수 | 변경 | W5 | P4, P7 | C |
|
||||
| S08 | 도면 보기 | **신규** | 모달 | P3, P4 | C |
|
||||
| S09 | 도면 문자 검색·핸들 조회 | 유지(토큰 적용) | W3 + W5 | — | — |
|
||||
| S10 | 일괄 확정 확인 | **신규** | 모달 | P1 | B |
|
||||
| S11 | 게이트 | 유지 + 소변경 | W2 | 앱 확인 요약 링크만 추가 | D |
|
||||
| S12 | 검토보고서 초안 | 변경 | W4 | 8장에 앱 확인 요약 | B·D |
|
||||
| S13 | 변수 등급 안내 | **신규** | W4 | P6 | A |
|
||||
|
||||
단계는 기획서 6장의 A 분류·표시, B 앱 확인 엔진, C 표 밖 치수 탐색, D 통합·문서이다. 화면은 해당 단계가 끝나야 켜진다. 아직 구현되지 않은 단계의 열은 화면에 나타나지 않는다(빈 열을 두지 않는다).
|
||||
|
||||
---
|
||||
|
||||
## 4. 공통 요구사항
|
||||
|
||||
### 4.1 화면 골격
|
||||
|
||||
| 항목 | 요구 |
|
||||
| :--- | :--- |
|
||||
| 헤더 | 서비스명, 화면 ID, 교각 이름, 마지막 저장 시각, 실행 환경(로컬)을 한 줄에 둔다 |
|
||||
| 좌측 레일 | 1280px 이상에서 교각 안의 단계 내비게이션(1 도면 · 2 입력 묶음 · 3 게이트 · 4 보고서)과 단계별 완료 표시를 둔다 |
|
||||
| 본문 폭 | 연속 문장은 38em 이내. 표는 가로 스크롤을 허용한다 |
|
||||
| 언어 | 안내·근거·오류 문장은 규정문체(평서 종결 `~한다`)를 쓴다. 버튼 레이블은 동사로 시작하고 8자 이내로 쓴다 |
|
||||
| 폭 | 1280px에서 표 외 가로 스크롤이 없다 |
|
||||
|
||||
### 4.2 상태 어휘 (전 화면 공통)
|
||||
|
||||
모든 상태는 **글자 + 형태 + 색**을 함께 쓴다. 형태와 색의 대응은 `design.md` 6장이 정한다.
|
||||
|
||||
| 어휘 | 값 | 표시 위치 |
|
||||
| :--- | :--- | :--- |
|
||||
| **앱 확인** | 일치 · 주의 · 불일치 · 근거 없음 · —(해당 없음) · 확인 안 함 | S03, S04, S05, S10, S12 |
|
||||
| **사람 확정** | 확정 · 미확정 · 변경됨 | S03, S04, S05, S10, S12 |
|
||||
| **변수 등급** | V1 · V2 · V3 · F | S04, S05, S06, S13 |
|
||||
| **값 출처** | 표 · 표 밖 치수 · 판독 스크립트 · 원본값 · 사람 입력 | S04, S05 |
|
||||
| **신뢰 등급** | A · B · C · 없음 | S07, S08 |
|
||||
| **게이트 판정** | PASS · FAIL · 계획 FAIL · 재실행 필요 | S03, S11, S12 |
|
||||
|
||||
규칙은 다음과 같다.
|
||||
|
||||
- 상태 글자는 6자 이내다. 설명이 필요하면 상태 옆이 아니라 근거 문장(S05)에 쓴다.
|
||||
- 값이 바뀌면 앱 확인과 사람 확정이 함께 '변경됨 · 재확인 필요'가 된다. 표시 어휘를 두 개로 나누지 않는다.
|
||||
- '해당 없음'은 배지가 아니라 `—`로 쓴다. '확인 안 함'은 배지가 아니라 [확인 실행] 버튼으로 대신한다.
|
||||
|
||||
### 4.3 오류·빈 상태·진행 표시
|
||||
|
||||
| 상황 | 요구 |
|
||||
| :--- | :--- |
|
||||
| 오류 | 무엇이·왜·어떻게 3요소와 기호를 함께 쓴다. 예: "도면 색인이 없다. DWG 변환이 끝나지 않았다. 1단계에서 도면을 다시 올린다" |
|
||||
| 빈 상태 | 원인 + 다음 행동 한 문장(60자 이내). 예: "올린 도면이 없다. DWG 파일을 올린다" |
|
||||
| 진행 | 2초를 넘는 작업(앱 확인, 게이트)은 진행 표시를 보이고 `aria-live`로 낭독한다. 중요 결과를 토스트로만 알리지 않는다 |
|
||||
| 파괴적 동작 | 삭제·승인 반려·형식 변경 확정은 확인 절차를 거친다 |
|
||||
|
||||
### 4.4 접근성·시험
|
||||
|
||||
| 항목 | 기준 |
|
||||
| :--- | :--- |
|
||||
| 표준 | WCAG 2.2 AA를 목표로 한다 |
|
||||
| 키보드 | 전 기능을 키보드만으로 조작한다. 탭 순서가 시각 순서와 같다 |
|
||||
| 히트 영역 | 44px 이상. 인접 간격 8px 이상 |
|
||||
| 표 | `caption`, `th scope`, 고정 헤더. 상태 셀은 글자를 포함한다 |
|
||||
| 색 | 그레이스케일로 인쇄해도 모든 상태를 구분할 수 있다 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 화면별 요구사항
|
||||
|
||||
각 화면은 **목적 → 진입 → 구성 → 행동 → 상태 → 수용 기준** 순서로 쓴다. 문안은 예시이며 최종 문구는 디자인 시안에서 확정한다.
|
||||
|
||||
### S01 교각 목록 (변경)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 교각을 고르고 새 교각을 만든다. 현행 화면의 기능을 유지한다 |
|
||||
| 구성 | ① 교각 표(교각·형식·만든 날·템플릿·진행 요약) ② 새 교각 영역 ③ 보호 템플릿 표 |
|
||||
| 변경 | 교각 표에 **진행 요약** 열(예: 묶음 27 중 사람 확정 12, 게이트 3/5 승인)을 추가한다. 템플릿 표의 '형식 설명' 열에 [변수 등급 보기](S13) 링크를 둔다 |
|
||||
| 행동 | [새 교각 만들기] → S02, 교각 이름 → S03, 삭제(확인 절차) |
|
||||
| 수용 기준 | 진행 요약이 실제 `confirm.json`·게이트 승인 기록과 100% 일치한다 |
|
||||
|
||||
### S02 새 교각 적합성 판별 (신규)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 새 도면이 선택한 템플릿으로 모델링 가능한 형식인지 교각을 만들기 전에 판별한다 (기획서 4.4 체크리스트 6문항) |
|
||||
| 진입 | S01의 [새 교각 만들기] |
|
||||
| 구성 | ① **도면 올리기** ② **추천 순위(근거 표시, 맞는 것이 없으면 "판단 불가")** ③ 이름·템플릿 선택(추천 1위 기본) ④ 6문항(추정 답 미리 채움, 사람이 변경) ⑤ 각도 값 확인(skew·받침 회전·기초 회전) ⑥ 결과 판정 영역 ⑦ [교각 만들기] |
|
||||
| 6문항 | 기초 형식(말뚝 유무) · 기둥 단면 형식 · 코핑 하면 형상 · 받침 배치 구조 · 철근 형상 종류 · 철근 생성 방식. 평면 사각·직각과 각도는 문항이 아니라 ⑤의 값으로 확인한다 |
|
||||
| 추천 | 도면 단서로 문항 값을 추정하고 5개 템플릿과 일치 문항 수로 순위를 낸다. 단서와 도면 위치를 근거로 보인다. 추정은 확정이 아니며 사람이 바꾼다. 교각 폴더는 [교각 만들기] 시점에 생성한다 |
|
||||
| 추정 범위 `[구현 2026-10-15]` | 6문항 중 **3문항만 추정**한다: 기초 형식(도면의 '말뚝' 문자 유무) · 코핑 하면 형상(정면도 R 문자: 10 m 이상 곡선, 1~10 m 나팔형(약함), 없으면 직선 경사, 작은 R은 평면 모서리로 제외) · 받침 개수(ELEVATION TABLE 행 수). 기둥 단면·철근 형상·철근 생성 방식은 도면 문자로 판별할 근거가 없어 '미확인'으로 두고 사람이 답한다. 근거마다 신뢰(보통·약함)를 표시한다. 규칙은 9개 도면 세트로만 확인했다 |
|
||||
| 행동 | 모든 문항에 답하면 판정이 나온다. 전부 '예'이면 "이 템플릿으로 만들 수 있다". 하나라도 '아니오'이면 "같은 템플릿으로 만들 수 없다. 가장 가까운 템플릿에 생성기·검사기를 추가해야 한다"를 보이고, 문항별로 어떤 부분이 다른지 적는다 |
|
||||
| 상태 | 미응답 문항은 오류로 표시하고 어느 문항인지 이름으로 알린다 |
|
||||
| 진행 허용 | '아니오'가 있어도 [그래도 만들기]로 진행할 수 있다. 이 경우 교각 이름 옆과 S03·S04 상단에 **형식 불일치 경고**가 상시 표시된다(경고는 닫을 수 없다) |
|
||||
| 수용 기준 | 6문항의 답이 교각 메타에 저장된다. 경고 교각의 S03·S04에서 경고가 100% 보인다 |
|
||||
| 구현 현황 | **전부 구현됨(2026-10-15)**: 추천 순위·근거, 6문항 답과 각도 값 3종(skew·받침 회전·기초 회전)을 `pier.json`의 `fit_answers`에 저장, 결과 판정 영역(답을 바꾸면 즉시 갱신, 불일치 시 버튼이 [그래도 만들기]로 바뀜), 불일치 항목을 `fit_mismatch`에 저장하고 교각의 모든 화면 상단에 닫을 수 없는 경고 표시(S03·S04 포함). 화면 시안: `시안/index.html`의 `#/new` |
|
||||
|
||||
### S03 교각 작업대 (변경)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 교각 한 개의 진행 상태를 한눈에 보고 다음 단계로 간다 |
|
||||
| 구성 | ① 결론 띠(다음에 할 일 한 가지) ② 1~4단계 카드 ③ 단계별 상세 진입 |
|
||||
| 결론 띠 | 현재 상태에서 가장 앞선 미완 단계를 말한다. 예: "필요 묶음 27개 중 앱 확인 일치 19 · 주의 4 · 불일치 1 · 근거 없음 3, 사람 확정 0. 2단계에서 근거를 확인한다" |
|
||||
| 1 도면 | DWG 목록(DXF 변환 상태, 해시 앞 16자), 색인 문자 수, 도면 올리기, S09·S07 링크 |
|
||||
| 2 입력 묶음 | S04의 요약(4.2절 어휘별 개수)과 [입력 묶음 열기] |
|
||||
| 3 게이트 | G1~G5 판정·승인 요약과 [게이트 열기] |
|
||||
| 4 보고서 | 초안 생성 여부와 [보고서 열기] |
|
||||
| 행동 | 단계 카드의 주 버튼은 화면에 **하나**(결론 띠가 가리키는 단계의 버튼)만 강조한다 |
|
||||
| 상태 | 도면 없음 → 1단계만 활성 안내. 형식 불일치 경고가 있으면 결론 띠 위에 경고 |
|
||||
| 수용 기준 | 결론 띠의 숫자가 S04 표의 집계와 100% 일치한다. 주 버튼이 화면에 1개다 |
|
||||
|
||||
### S04 입력 묶음 확인·확정 (변경, 핵심)
|
||||
|
||||
기획서 4.1절의 화면이다.
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 모든 입력 묶음의 앱 확인 결과와 사람 확정 상태를 보고, 근거를 확인한 뒤 확정한다 |
|
||||
| 진입 | S03의 2단계, 레일 |
|
||||
| 구성 | ① **요약 띠** ② 도구 줄(필터·확인 실행·일괄 확정) ③ **묶음 표** ④ 형식 불일치 경고(해당 시) |
|
||||
|
||||
**① 요약 띠**
|
||||
|
||||
- 한 줄: "필요 27개 중 앱 확인 일치 19 · 주의 4 · 불일치 1 · 근거 없음 3 · 사람 확정 0".
|
||||
- 각 숫자는 필터 버튼이다(누르면 표가 해당 상태만 보인다).
|
||||
- 마지막 앱 확인 시각과 검사 버전을 함께 보인다. 값이 바뀌어 오래된 결과가 있으면 "재확인 필요 N개"를 별도 표시한다.
|
||||
|
||||
**③ 묶음 표 (열 정의)**
|
||||
|
||||
| 열 | 내용 | 비고 |
|
||||
| :--- | :--- | :--- |
|
||||
| 묶음 | 파일·이름. 누르면 S05 | 이름은 영문 키와 한글 설명 병기 |
|
||||
| 변수 등급 | V1 · V2 · V3 · F | S13 링크 |
|
||||
| 값 출처 | 표 · 표 밖 치수 · 판독 스크립트 · 원본값 · 사람 입력 | '원본값'은 강조(PP-3) |
|
||||
| 앱 확인 | 일치 · 주의 · 불일치 · 근거 없음 · — | 누르면 S05의 해당 근거로 |
|
||||
| 사람 확정 | 확정 · 미확정 · 변경됨, 확정자·시각 | |
|
||||
| 확인 | 개별 [확인 실행] | 앱 확인 결과가 오래되었거나 없을 때 |
|
||||
|
||||
- 정렬 기본값: 앱 확인이 '불일치 → 근거 없음 → 주의 → 일치' 순, 같은 상태는 파일 순. 사용자가 열 머리를 눌러 바꿀 수 있다.
|
||||
- '확정 불필요(참고)' 묶음은 표의 맨 끝 구분 영역으로 내리고 기본으로 접는다. 접힌 개수를 표시한다.
|
||||
- 숫자 열은 우측 정렬이며 등폭 숫자를 쓴다.
|
||||
|
||||
**② 도구 줄**
|
||||
|
||||
| 요소 | 동작 |
|
||||
| :--- | :--- |
|
||||
| 필터 | 앱 확인 상태·사람 확정 상태·변수 등급·출처로 좁힌다. 현재 적용 필터를 글자로 보인다 |
|
||||
| [전체 확인] | 필요 묶음 전체의 앱 확인 C1~C8을 실행한다. 진행 표시 후 요약 띠를 갱신한다 |
|
||||
| [일치분 확정] | 앱 확인이 '일치'이고 원본값이 아닌 묶음만 대상으로 S10을 연다 (기획서 D4) |
|
||||
|
||||
**상태**
|
||||
|
||||
| 상태 | 동작 |
|
||||
| :--- | :--- |
|
||||
| 앱 확인 전 | 앱 확인 열이 모두 [확인 실행]. 요약 띠는 "앱 확인 전 — [전체 확인]을 실행한다" |
|
||||
| 확인 실행 중 | 행마다 진행 표시, 결과가 나오는 대로 갱신. 실행 중에도 표 읽기는 가능 |
|
||||
| 원본값 묶음 | 앱 확인은 '근거 없음(원본값)'. 일괄 확정 대상에서 제외하고 행에 제외 사유를 쓴다 |
|
||||
| 형식 불일치 경고 | 표 상단에 닫을 수 없는 경고 띠 |
|
||||
|
||||
**수용 기준**
|
||||
|
||||
| # | 기준 |
|
||||
| :-: | :--- |
|
||||
| 1 | 필수 묶음 100%에 앱 확인 상태(또는 [확인 실행])가 있다 |
|
||||
| 2 | '근거 없음'과 '원본값'이 숨겨지지 않는다 |
|
||||
| 3 | 새로 복사한 교각(템플릿 직후)에서 값이 바뀌지 않은 묶음은 전부 '원본값'이다 |
|
||||
| 4 | 표와 요약 띠의 개수가 일치한다 |
|
||||
| 5 | 1280px 폭에서 표 외 가로 스크롤이 없다 |
|
||||
| 6 | 키보드만으로 필터·행 열기·확인 실행·일괄 확정 진입이 가능하다 |
|
||||
|
||||
### S05 근거 보기 (신규)
|
||||
|
||||
기획서 4.3절의 C1~C8 결과를 사람이 읽는 화면이다.
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 한 묶음의 앱 확인 근거를 문장으로 읽고 확정 여부를 판단한다 |
|
||||
| 진입 | S04의 묶음 이름 또는 앱 확인 셀 |
|
||||
| 패턴 | W5 목록-상세. 좌측에 묶음 이동 목록(상태 포함), 우측에 상세 |
|
||||
|
||||
**상세 구성**
|
||||
|
||||
1. **결론**: 묶음 이름, 변수 등급, 앱 확인 결과 한 줄, 사람 확정 상태.
|
||||
2. **근거 표**: C1~C8 각각의 `검사 · 결과 · 근거 문장 · 도면 위치`. 해당 없는 검사는 `—`.
|
||||
3. **값 비교**: 현재 값과 후보 값 또는 템플릿 원본 값을 나란히 보인다(차이가 있는 항목만).
|
||||
4. **확정 영역**: 확정자 이름, 사유(주의·불일치·근거 없음이면 필수), [확정].
|
||||
|
||||
**근거 문장 규칙** (R1: "일치"를 "도면이 맞다"로 오해하지 않게)
|
||||
|
||||
- 모든 근거는 '무엇과 무엇이 같은가'를 쓴다. 예: "기둥 지름 2,200은 도면 단면도(D1, 좌표 …)의 치수 문자 '2.200'과 같다."
|
||||
- 규칙으로 만든 후보와의 비교임을 밝힌다. 예: "이 결과는 후보를 만든 규칙과 값이 같다는 뜻이다. 도면이 맞다는 판정이 아니다."
|
||||
- 검사에 쓴 방법의 독립성을 표기한다(예: C8 재생성 일치는 '독립성 낮음').
|
||||
|
||||
**행동**
|
||||
|
||||
| 행동 | 조건·동작 |
|
||||
| :--- | :--- |
|
||||
| [도면 보기] | 근거의 도면 위치마다 S08을 연다 |
|
||||
| [값 편집] | S06으로 이동 |
|
||||
| [확정] | 확정자 이름이 있고, 주의·불일치·근거 없음이면 사유가 있어야 한다. 조건이 모자라면 버튼은 활성인 채 오류 메시지를 보인다 |
|
||||
| 이전·다음 묶음 | 목록의 순서로 이동. 확정하면 다음 미확정 묶음으로 이동하는 선택지를 준다 |
|
||||
|
||||
**상태**
|
||||
|
||||
| 상태 | 동작 |
|
||||
| :--- | :--- |
|
||||
| 앱 확인 전 | 근거 표 대신 [확인 실행]과 "이 묶음은 아직 확인하지 않았다" |
|
||||
| 값 변경됨 | 결론에 "값이 바뀌었다. 이전 확인과 확정이 무효가 되었다" 표시, [다시 확인] |
|
||||
| V3 묶음 | 값 편집 대신 "판독 스크립트로 다시 만든다" 안내와 C8 결과 |
|
||||
| 원본값 | 값 비교에 "템플릿 원본과 같다. 새 도면 근거가 없다" |
|
||||
|
||||
**수용 기준**
|
||||
|
||||
| # | 기준 |
|
||||
| :-: | :--- |
|
||||
| 1 | 검사 8종 각각의 결과·문장·도면 위치가 보인다 |
|
||||
| 2 | '일치' 근거마다 비교한 두 값과 출처가 문장에 있다 |
|
||||
| 3 | 사유 없는 '주의·불일치·근거 없음' 확정이 불가능하다(오류 메시지 표시) |
|
||||
| 4 | 확정 기록에는 사람이 입력한 이름만 들어가고, 앱 확인 서버 경로는 확정 기록을 쓰지 않는다 |
|
||||
|
||||
### S06 값 편집 (변경)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 묶음의 값을 수정하고 저장한다. 현행 `section` 화면의 기능 유지 |
|
||||
| 변경 | ① 상단에 **변수 등급 안내**(이 묶음에서 바꿔도 되는 값과 안 되는 값) ② F 항목 편집 시 경고 ③ V3 묶음은 편집 차단 ④ 저장 뒤 앱 확인 결과가 무효가 됨을 알림 ⑤ [저장된 값으로 확정] 버튼 제거(확정은 S05·S10에서만) |
|
||||
| F 항목 | 필드 수준 등급이 F이면 편집 영역에 "형식을 정의하는 값이다. 바꾸면 같은 템플릿이 아니다" 경고. 저장하려면 사유를 입력해야 한다(기획서 D3 권고). 저장하면 묶음에 '형식 변경' 표시가 붙고 S03에 새 템플릿 필요 안내가 뜬다 |
|
||||
| V3 묶음 | 편집창 대신 읽기 전용 요약(개수·크기·해시)과 "판독 스크립트로 다시 만든다" 안내, 스크립트 경로 |
|
||||
| 입력 보조 | 치수 사슬 입력(`22@120=2.640`)을 지원하는 필드는 합계 검산 결과를 즉시 보인다 |
|
||||
| 수용 기준 | V3 묶음에서 편집 영역이 없다. F 편집은 사유 없이 저장되지 않는다. 저장 직후 S04 상태가 '변경됨'으로 바뀐다 |
|
||||
|
||||
### S07 표 후보값·표 밖 치수 (변경)
|
||||
|
||||
기획서 4.2절의 화면이다. 현행 `candidates` 화면을 확장한다.
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 도면 표와 표 밖 치수에서 찾은 후보를 현재 값과 비교하고, 사람이 골라 반영한다 |
|
||||
| 구성 | ① 요약 띠(표 후보 N · 표 밖 치수 후보 N(A·B·C 개수) · 다름 N) ② 후보 비교 표 ③ 인식한 표 ④ EL 표기 목록 |
|
||||
| 후보 비교 표 열 | 반영(선택) · 대상 · 현재 값 · 후보 값 · 구분(표/표 밖 치수) · 상태(같음/다름) · 신뢰 등급 · 도면 위치 · 근거 |
|
||||
| 표 밖 치수 구역 | 규칙 R1~R6 중 어느 규칙이 찾았는지 '규칙' 열에 쓴다. 신뢰 등급 A·B·C·없음을 병기한다 |
|
||||
| 행동 | 행 선택 → [선택한 후보값 반영]. 등급 C는 반영 시 사유 필수. 등급 '없음'은 후보 행이 아니라 "근거 없음" 목록에 따로 나열하고 반영 버튼이 없다 |
|
||||
| params·expected 분리 | 한 후보가 params와 expected 양쪽에 반영될 수 있는 경우, 반영 대상 줄을 **둘 중 하나씩** 고르게 하고 둘을 함께 고르면 경고한다(기획서 P7) |
|
||||
| 상태 | 후보 0건 → "후보가 없다. 도면의 틀이 앱의 규칙과 다르면 정상이다. 문자 검색(S09)으로 직접 찾는다" |
|
||||
| 수용 기준 | 자동 반영이 없다. 모든 반영은 사람이 선택한 행에만 적용된다. 등급 '없음'이 후보로 둔갑하지 않는다 |
|
||||
|
||||
### S08 도면 보기 (신규)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 근거·후보의 도면 위치를 확대 이미지로 확인한다 (`dxftools.render`와 같은 이미지) |
|
||||
| 형태 | 모달. 대상 좌표 주변 확대 이미지에 대상 위치를 표시한다 |
|
||||
| 구성 | 이미지, 캡션(도면 이름·뷰·좌표·핸들), 확대·축소·이동, 인접 후보로 이동 |
|
||||
| 행동 | Esc로 닫는다. 닫으면 호출한 행으로 포커스가 돌아간다 |
|
||||
| 접근성 | 이미지에 대체 텍스트로 위치·객체 설명을 쓴다. 이동·확대는 버튼과 방향키 둘 다 지원한다 |
|
||||
| 수용 기준 | 모든 이미지에 캡션이 있다. 모달 안에 모달이 열리지 않는다 |
|
||||
|
||||
### S09 도면 문자 검색·핸들 조회 (유지)
|
||||
|
||||
현행 기능을 유지한다. 디자인 토큰만 새 규칙으로 바꾼다. 검색 결과 행에 [후보로 보내기]는 두지 않는다(자동 반영 금지, PP-1).
|
||||
|
||||
### S10 일괄 확정 확인 (신규)
|
||||
|
||||
기획서 4.1절 D4의 화면이다.
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 앱 확인이 '일치'인 묶음을, 근거를 확인한 뒤 한꺼번에 확정한다 |
|
||||
| 형태 | 모달 |
|
||||
| 구성 | ① 결론("일치 묶음 19개를 확정한다. 주의·불일치·근거 없음·원본값 8개는 대상이 아니다") ② **근거표**(묶음·근거 한 줄·출처) ③ 확정자 이름 ④ [확정] ⑤ [취소] |
|
||||
| 근거표 열람 조건 | 근거표를 끝까지 보지 않으면 [확정]이 오류를 낸다. 오류는 "근거표를 먼저 확인한다. 일치 묶음의 근거를 읽지 않으면 확정할 수 없다. 근거표를 끝까지 내린다"로 알린다 (비활성화하지 않고 오류 메시지를 낸다) |
|
||||
| 제외 목록 | 대상에서 빠진 묶음과 사유(주의·불일치·근거 없음·원본값)를 같은 모달에서 보여 준다. 누르면 S05로 이동 |
|
||||
| 완료 | 확정 기록 개수와 확정자·시각을 결과로 보인다. 토스트만으로 알리지 않고 S04 요약 띠에도 반영한다 |
|
||||
| 수용 기준 | 원본값·주의·불일치·근거 없음 묶음이 대상에 0건이다. 근거표를 읽지 않은 상태에서는 확정되지 않는다. 확정자 이름이 비어 있으면 확정되지 않는다 |
|
||||
|
||||
### S11 게이트 (유지 + 소변경)
|
||||
|
||||
현행 기능을 유지한다. 변경은 두 가지이다.
|
||||
|
||||
- 게이트 실행 조건 문구에 "필요 묶음이 사람 확정됨" 외에 앱 확인 요약 링크(S04)를 추가한다. 앱 확인은 조건이 아니라 판단 자료임을 문구로 밝힌다.
|
||||
- 판정 표시를 4.2절 게이트 어휘로 통일한다.
|
||||
|
||||
### S12 검토보고서 초안 (변경)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 변경 | 보고서 8장(검증 방법)에 **앱 확인 결과 요약**을 넣는다 |
|
||||
| 요약 내용 | 묶음별 앱 확인 상태·근거 한 줄·신뢰 등급, 사람 확정자·시각, 확정 시점의 앱 확인 검사 버전, 근거 없음·원본값 목록 |
|
||||
| 화면 | 생성한 초안을 화면에서 열람하고 MD·PDF를 받는다. 앱 확인 요약이 어느 시점 결과인지 표기한다 |
|
||||
| 수용 기준 | 보고서의 요약 숫자가 S04 요약 띠와 같다. 값이 바뀐 뒤 만든 초안과 그 이전 초안을 구분한다 |
|
||||
|
||||
### S13 변수 등급 안내 (신규)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 목적 | 한 템플릿에서 바꿔도 되는 값과 바꾸면 안 되는 값을 문서처럼 열람한다 (기획서 4.4, 부록 A) |
|
||||
| 패턴 | W4 문서. 좌측 목차(묶음), 우측 본문 |
|
||||
| 구성 | ① 등급 4종 정의 ② 묶음별 등급 표 ③ "바꿔도 되는가" 예시 표 ④ 새 도면 판별 체크리스트 요약(S02 링크) |
|
||||
| 진입 | S01 템플릿 표, S02 템플릿 선택, S04·S06의 등급 배지 |
|
||||
| 수용 기준 | 4개 보호 템플릿 모두에 등급 표가 있다. 등급 배지에서 해당 묶음 위치로 이동한다 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 자료 계약 (화면이 읽고 쓰는 파일)
|
||||
|
||||
화면 구현에 필요한 자료만 적는다. 내부 구현은 기획서 4.3~4.4절과 엔진 설계를 따른다.
|
||||
|
||||
| 파일 | 위치 | 내용 | 쓰는 주체 | 읽는 화면 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `confirm.json` | 교각 폴더 | 사람 확정 기록(확정자·시각·해시) | **사람 조작만** | S03~S05, S10, S12 |
|
||||
| `autocheck.json` | 교각 폴더 | 묶음 해시·앱 확인 결과·근거 목록·검사 버전·시각 | 앱 확인 엔진 | S03~S05, S10, S12 |
|
||||
| `variability.json` | 템플릿 폴더 | 묶음·필드 경로 → 변수 등급 | 템플릿 담당자 | S04~S06, S13 |
|
||||
| 앵커 | 템플릿 폴더 | 값이 도면의 어디서 읽혔는지 기록 | 판독 스크립트 | S07, S08 |
|
||||
| 교각 메타 | 교각 폴더 | 적합성 6문항 답, 형식 불일치 여부 | S02 | S03, S04 |
|
||||
|
||||
`autocheck.json`의 근거 항목은 다음 필드를 가진다: `check`(C1~C8), `result`(match / caution / mismatch / no_basis / n/a), `sentence`(근거 문장), `refs`(도면 이름·뷰·좌표·핸들 또는 표 행), `confidence`(A / B / C / none), `independence`(high / low).
|
||||
|
||||
---
|
||||
|
||||
## 7. 비기능 요구
|
||||
|
||||
| 항목 | 요구 |
|
||||
| :--- | :--- |
|
||||
| 성능 | 필요 묶음 전체의 앱 확인(C1~C8, 도면 재대조 제외)을 10초 이내에 마친다 `[추론]`. 도면 재대조(C2)가 포함되면 진행 표시로 대신한다 |
|
||||
| 신뢰 | 앱 확인 서버 경로는 `confirm.json`을 쓰지 않는다. 자동화 시험이 이를 확인한다 |
|
||||
| 보호 | 시험은 실제 교각 데이터가 아닌 시험 폴더에서만 확정 흐름을 실행한다 (OPEN #25 사례) |
|
||||
| 이력 | 앱 확인 결과는 검사 버전과 시각을 남긴다. 규칙이 바뀌면 이전 결과에 '이전 버전' 표시를 붙인다 |
|
||||
| 호환 | 기존 `confirm.json`·`params.json`·`expected.json`을 바꾸지 않는다. 새 파일을 추가하는 방식이다 |
|
||||
| 테마 | 라이트를 기본으로 하고 다크 토큰을 같이 정의한다. 다크 전수 시험은 사용자 요청 시 수행한다 `[추론]` |
|
||||
|
||||
---
|
||||
|
||||
## 8. 수용 기준 요약 (기획서 6장 승계)
|
||||
|
||||
| # | 기준 | 시험 방법 |
|
||||
| :-: | :--- | :--- |
|
||||
| 1 | 모든 필수 묶음에 앱 확인 상태가 있다 | 5개 교각 데이터에서 S04 행 전수 확인 |
|
||||
| 2 | '근거 없음'을 숨기지 않는다 | 근거 없음 묶음이 표·요약 띠·S10 제외 목록에 모두 나온다 |
|
||||
| 3 | 거짓 일치 0, 거짓 불일치 0 | 4개 도면 기준. 값을 일부러 바꾼 음성 시험에서 '불일치'가 나온다 |
|
||||
| 4 | 앱이 확정 기록을 만들지 않는다 | 앱 확인 실행 전후 `confirm.json` 해시 불변 |
|
||||
| 5 | 기존 시험 FAIL 0 | 기존 회귀 시험 일괄 |
|
||||
| 6 | 화면 접근성 | 모든 상태 셀에 글자 100%, 1280px 가로 스크롤 0, 키보드 전 기능, axe 위반 0 |
|
||||
| 7 | 디자인 시스템 적합 | `design.md` 11장 점검표 전항 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 단계와 화면의 대응
|
||||
|
||||
| 단계 | 켜지는 화면·기능 | 선행 |
|
||||
| :--- | :--- | :--- |
|
||||
| A 분류·표시 | S01 진행 요약, S02, S03 결론 띠(확정 기준), S04의 변수 등급·값 출처·원본값, S06 등급 안내·V3 차단, S13 | 변수 등급 파일 작성 |
|
||||
| B 앱 확인 엔진 | S04 앱 확인 열·요약 띠·전체 확인, S05, S10, S12 | A |
|
||||
| C 표 밖 치수 탐색 | S07 표 밖 치수 구역, S08, 앵커 | B 검증 후 |
|
||||
| D 통합·문서 | S11 문구, 사용설명서 개정, 회귀 | A~C |
|
||||
|
||||
---
|
||||
|
||||
## 10. 리스크
|
||||
|
||||
| # | 리스크 | 대응 |
|
||||
| :-: | :--- | :--- |
|
||||
| R1 | 사용자가 '일치'를 '도면이 맞다'로 읽는다 | S05 근거 문장에 비교 대상을 쓰고 한계 문장을 고정으로 넣는다 |
|
||||
| R2 | 요약 띠·표 숫자가 서로 어긋난다 | 같은 집계 함수를 쓰고 수용 기준 4로 시험한다 |
|
||||
| R3 | 일괄 확정이 다시 도장이 된다 | S10 근거표 열람 조건과 제외 목록, 사유 필수 |
|
||||
| R4 | 상태 어휘가 늘어나 판독이 어려워진다 | 4.2절에서 어휘를 고정하고 배지는 열마다 4종 이내 |
|
||||
| R5 | 디자인 시스템에 없는 요소가 필요하다 | `design.md` 10장의 공백 목록으로 제안하고 승인 전에는 조합 컴포넌트로 대신한다 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 결정 사항
|
||||
|
||||
### 11.1 기획서에서 승계한 결정 (기획서 7.2절)
|
||||
|
||||
| # | 내용 | 상태 |
|
||||
| :-- | :--- | :--- |
|
||||
| D1 | 확정 주체: 앱 확인 + 사람 확정 | **결정 2026-10-14** |
|
||||
| D2 | 표 밖 치수 탐색: 규칙 + 템플릿 앵커 | **결정 2026-10-14** |
|
||||
| D3 | F(고정) 항목 편집 처리 | **결정 2026-10-15 / 심영표: 권고안** — 경고 + 사유 입력, 차단은 생성기 개발자 모드에서만 |
|
||||
| D4 | 일괄 확정 조건 | **결정 2026-10-15 / 심영표: 권고안** — '일치' 묶음만, 근거표 열람 뒤 확정 |
|
||||
| D5 | 착수 범위·순서 | **결정 2026-10-15 / 심영표: 권고안** — A → B 먼저, C는 B 검증 뒤 |
|
||||
| D6 | 앵커 소급 추출 | **결정 2026-10-15 / 심영표: 권고안** — 옥성교·Ramp-A교부터, 나머지는 필요 시. S07·S08에만 영향 |
|
||||
|
||||
### 11.2 화면 설계에서 새로 생긴 결정
|
||||
|
||||
| # | 내용 | 상태 |
|
||||
| :-- | :--- | :--- |
|
||||
| UX1 | 디자인 시스템 적용: 기존 R8 기준(맑은 고딕 14px·흰 배경·철근 팔레트 판정색)을 Prism 앱 프로파일(`design.md`)로 대체한다 | **결정 2026-10-15 / 심영표: 대체.** `디자인지침_경로.md` 갱신 완료 |
|
||||
| UX2 | 판정 색: Prism 상태색을 쓴다. 철근 팔레트는 Rhino 모델과 범례에만 둔다 | **결정 2026-10-15 / 심영표: 변경** |
|
||||
| UX3 | 서체: Pretendard를 앱에 내장한다 | **결정 2026-10-15 / 심영표: 내장.** 파일을 `web/static/fonts/PretendardVariable.woff2`에 배치(OFL). 적용은 A단계 구현 시 |
|
||||
| UX4 | 상태 마커(●▲■◆◇) 도형 사용 | **결정 2026-10-15 / 심영표: 승인** |
|
||||
| UX5 | 다크 테마 시험 | **결정 2026-10-15 / 심영표: 제외.** 토큰은 정의하되 시험하지 않는다 |
|
||||
| UX6 | 핵심 화면 HTML 시안 | **결정 2026-10-15 / 심영표: 제작.** `시안/index.html` (S04·S05·S07·S10 동작 시안) |
|
||||
|
||||
## 부록 A. 핵심 화면 와이어프레임 (정보 구조, 외형 아님)
|
||||
|
||||
### S04 입력 묶음 확인·확정 (1280px)
|
||||
|
||||
```
|
||||
┌ 헤더 ──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 서비스명 · S04 · 옥성교P1 · 저장 14:32 · 로컬 │
|
||||
├ 레일 ┬─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 1 도면 ✓│ 2. 입력 묶음 확인·확정 │
|
||||
│ 2 묶음 ●│ ┌ 요약 띠 ─────────────────────────────────────────────────────────────┐ │
|
||||
│ 3 게이트│ │ 필요 27개 중 일치 19 · 주의 4 · 불일치 1 · 근거 없음 3 · 사람 확정 0 │ │
|
||||
│ 4 보고서│ │ 마지막 앱 확인 14:30 · 검사 v0.1 · 재확인 필요 0 │ │
|
||||
│ │ └──────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │ 필터: [전체 ▾] [전체 확인] [일치분 확정] (주 버튼 1개) │
|
||||
│ │ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ │묶음 │등급│출처 │앱 확인 │사람 확정 │확인 │ │
|
||||
│ │ │G4.stirrups │V2 │표 밖치수│● 일치 │미확정 │ │ │
|
||||
│ │ │footing │V1 │원본값 │◆ 근거 없음 │미확정 │[확인 실행] │ │
|
||||
│ │ │coping_rebar │V3 │스크립트│▲ 주의 │미확정 │ │ │
|
||||
│ │ │… │ │
|
||||
│ │ └────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │ ▸ 확정 불필요(참고) 5개 (접힘) │
|
||||
└─────────┴─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### S05 근거 보기 (1280px)
|
||||
|
||||
```
|
||||
├ 레일 ┬───────────────┬────────────────────────────────────────────────────────────┤
|
||||
│ │ 묶음 이동 │ G4.stirrups V2 목록 변수 앱 확인: ● 일치 사람: 미확정 │
|
||||
│ │ ● G4.stirrups │ ┌ 근거 표 ──────────────────────────────────────────────┐ │
|
||||
│ │ ▲ coping_rebar │ │ C1 출처 ● 일치 표 밖 치수(D1 정면도) … [도면 보기] │ │
|
||||
│ │ ◆ footing │ │ C4 내부 ● 일치 사슬 합 9,000 = 전체 치수 9,000 │ │
|
||||
│ │ … │ │ C5 독립 ● 일치 params와 expected의 근거가 다르다 │ │
|
||||
│ │ │ └────────────────────────────────────────────────────────┘ │
|
||||
│ │ │ 이 결과는 후보를 만든 규칙과 값이 같다는 뜻이다. 도면이 맞다는 판정이 아니다. │
|
||||
│ │ │ ┌ 값 비교 ┐ ┌ 확정 ─────────────────────────────────────┐ │
|
||||
│ │ │ │ … │ │ 확정자 [ ] 사유 [ ] [확정] │ │
|
||||
│ │ │ └─────────┘ └────────────────────────────────────────────┘ │
|
||||
└──────┴───────────────┴────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### S10 일괄 확정 확인 (모달)
|
||||
|
||||
```
|
||||
┌ 일치 묶음 확정 ─────────────────────────────────────────────┐
|
||||
│ 일치 19개를 확정한다. 주의 4 · 불일치 1 · 근거 없음 3 · 원본값 1은 대상이 아니다. │
|
||||
│ ┌ 근거표 (끝까지 내려 확인) ─────────────────────────────┐ │
|
||||
│ │ G4.stirrups 사슬 합 9,000 = 전체 치수 9,000 │ │
|
||||
│ │ … │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ ▸ 제외된 묶음 9개 (누르면 근거 보기) │
|
||||
│ 확정자 [ ] │
|
||||
│ [취소] [확정] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### S07 표 후보값·표 밖 치수
|
||||
|
||||
```
|
||||
요약: 표 후보 14 · 표 밖 치수 후보 31 (A 18 · B 9 · C 4) · 다름 12 · 근거 없음 3
|
||||
┌────────────────────────────────────────────────────────────────────────────────┐
|
||||
│반영│대상 │현재 │후보 │구분 │상태│등급│도면 위치 │근거 │
|
||||
│ ☐ │footing.height │2,500 │2,500 │표 │같음│ — │D1 표 행 3 │보기 │
|
||||
│ ☑ │coping.z_tip │31,232 │31,231.5│표 밖 치수│다름│ B │D1 정면 (225817,…) │보기 │
|
||||
└────────────────────────────────────────────────────────────────────────────────┘
|
||||
[선택한 후보값 반영] 근거 없음 3개 ▸ (후보 없음, 문자 검색으로 직접 찾는다)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 부록 B. 근거 파일
|
||||
|
||||
| 구분 | 경로 |
|
||||
| :--- | :--- |
|
||||
| 기획서 | `DOC/01_기획/기획_입력묶음확인확정개편_2026-10-14_v0.1.00.md` |
|
||||
| 디자인 시스템 | `RAG/design system_v0.1.1/` (`Design.md`, `design.tokens.json`, `design.components.md`, `webapp/module.webapp.md`) |
|
||||
| 현행 화면 | `Code/교각모델링앱/web/templates/` (`index`, `pier`, `section`, `candidates`, `search`, `gates`) |
|
||||
| 화면 설계 규칙 | `DOC/01_기획/웹앱디자인시안/design.md` |
|
||||
| 디자인 지침 기록 | `context-migration/디자인지침_경로.md` (R8) |
|
||||
@@ -0,0 +1,740 @@
|
||||
# T형 교각 모델링 앱 디자인 시스템 — Prism 앱 프로파일
|
||||
|
||||
| 버전 | 수정일 | 수정자 | 주요 변경 내용 | 비고 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| v0.1.00 | 2026-10-15 | 심영표 | 신규 작성 — Prism Design System v0.1.1을 교각 모델링 앱에 적용하는 프로파일(토큰·상태 체계·조합 컴포넌트·문안·점검표) | 초안 `[미확정]`. PRD v0.1.00의 화면 13종이 소비한다 |
|
||||
| v0.1.01 | 2026-10-15 | 심영표 | PRD UX1~UX6 결정 반영: R8 대체 확정, Pretendard 내장 파일 지정, 마커 도형 승인, 다크 시험 제외 | 사용자 결정 |
|
||||
|
||||
| 메타 | 값 |
|
||||
| :--- | :--- |
|
||||
| 문서 | 앱 프로파일 (Prism 4계층 중 **Layer 3 모듈의 앱 한정 보강**) |
|
||||
| 상위 | `RAG/design system_v0.1.1/` — `Design.md` 0.1.1 · `design.tokens.json` 0.1.1 · `design.foundations.md` · `design.medium.screen.md` · `design.components.md` · `webapp/module.webapp.md` 0.1.0 |
|
||||
| 브랜드 | `saman` (활성). 앱 전용 브랜드를 새로 만들지 않는다 |
|
||||
| 매체 | `screen.web` (로컬 데스크톱, 1280px 이상) |
|
||||
| 독자 | 화면을 구현하는 에이전트와 개발자(1차), 디자이너(2차) |
|
||||
| 짝 문서 | `PRD.md` (같은 폴더) — 이 문서의 규칙이 어느 화면에서 쓰이는지는 PRD 5장이 정한다 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 위치와 우선순위
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
PRD의 13개 화면을 **같은 부품과 같은 규칙으로** 만들도록 디자인을 시스템으로 묶는다. 이 문서는 Prism을 대체하지 않고, Prism이 정하지 않은 앱 고유의 부분(상태 어휘의 형태 부호, 조합 컴포넌트, 문안)만 더한다.
|
||||
|
||||
### 1.2 값 충돌 우선순위
|
||||
|
||||
Prism `Design.md` 1.4절을 그대로 따른다.
|
||||
|
||||
| 순위 | 출처 |
|
||||
| :-: | :--- |
|
||||
| 1 | 사용자의 명시적 지시(해당 화면 한정) |
|
||||
| 2 | `design.tokens.json` 정식값 |
|
||||
| 3 | Prism 각 `.md`의 표·수치 |
|
||||
| 4 | **이 문서** — Prism과 충돌하면 Prism이 이긴다 |
|
||||
|
||||
> 이 문서의 어느 규칙이 Prism과 충돌하면 이 문서를 고친다. 충돌을 해소하려고 Prism 값을 바꾸지 않는다.
|
||||
|
||||
### 1.3 기존 기준(R8)과의 관계
|
||||
|
||||
`context-migration/디자인지침_경로.md`의 R8(2026-10-12)은 조직 디자인 지침이 없던 때의 대체 수용 기준이다(맑은 고딕 14px, 흰 배경, 철근 팔레트 판정색). R8은 2026-10-15에 **대체되었다**(PRD 11.2절 UX1, `디자인지침_경로.md` 갱신). 두 기준이 같이 적용되는 기간을 두지 않는다.
|
||||
|
||||
R8에서 **유지하는** 조건은 세 가지이다.
|
||||
|
||||
| R8 조건 | 이 문서에서 |
|
||||
| :--- | :--- |
|
||||
| 색과 글자를 함께 쓴다(색만으로 구분 금지) | 6장으로 강화(형태 부호 추가) |
|
||||
| 1280px 폭에서 표 외 가로 스크롤 없음 | 4.2절 |
|
||||
| 화면 문구 한국어 | 9장 |
|
||||
|
||||
철근 팔레트(D32 적·D29 주황 등)는 **Rhino 모델 철근과 범례 전용**이다. 화면의 판정·상태 표시에는 쓰지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 모듈 선언
|
||||
|
||||
```yaml
|
||||
module:
|
||||
id: pier-app
|
||||
name: T형 교각 모델링 앱
|
||||
base_module: webapp # RAG/design system_v0.1.1/webapp/module.webapp.md
|
||||
brand: saman
|
||||
medium: screen
|
||||
medium_variant: screen.web
|
||||
dual_medium: false
|
||||
format: desktop-first # lg(1240~1599) 기준 설계, md 이하는 지원하되 시험 제외
|
||||
min_width_px: 1280
|
||||
columns: 12
|
||||
type_set: compact
|
||||
patterns: [w1-shell, w2-dashboard, w3-form, w4-document, w5-list-detail]
|
||||
color_theme: [light, dark] # light 기본, dark는 토큰만 정의
|
||||
accessibility: WCAG 2.2 AA
|
||||
depends_on: { design_system: 0.1.1, webapp_module: 0.1.0 }
|
||||
```
|
||||
|
||||
화면별 패턴 배정(Prism webapp 10절 1단계)은 PRD 3.2절 일람의 '패턴' 열을 따른다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 토큰
|
||||
|
||||
### 3.1 원칙
|
||||
|
||||
| ID | 규칙 |
|
||||
| :--- | :--- |
|
||||
| **TK-1** | 산출물 CSS는 **변수 정의부 밖에 HEX·px 리터럴을 두지 않는다** (Prism W1·W2). 보더·헤어라인도 stroke 토큰을 쓴다 |
|
||||
| **TK-2** | 3.2절 `prism.sys` 블록은 Prism `webapp` 3절의 정본을 **그대로** 옮긴 것이다. 값을 고치지 않는다 |
|
||||
| **TK-3** | 3.3절 `comp` 토큰은 `sys`·`ref`만 참조한다. 리터럴을 두지 않는다 |
|
||||
| **TK-4** | `ref` 토큰(원색)은 컴포넌트에서 직접 참조하지 않는다 |
|
||||
| **TK-5** | 철근 팔레트는 UI 토큰이 아니다. Rhino 범례 이미지로만 쓴다 |
|
||||
| **TK-6** | 새 토큰이 필요하면 `comp` 계층에 추가하고 이 문서의 버전을 올린다. `sys` 이하는 건드리지 않는다 |
|
||||
|
||||
### 3.2 sys 토큰 (Prism 정본, 수정 금지)
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* color (light) */
|
||||
--prism-bg-canvas: #F4F3F0;
|
||||
--prism-bg-card: #FFFFFF;
|
||||
--prism-text-body: #474235;
|
||||
--prism-text-heading: #1F524B;
|
||||
--prism-text-subtle: #474235;
|
||||
--prism-text-accent: #D05C26; /* 대형 전용(24px 이상 또는 19px 이상 Bold) */
|
||||
--prism-border-default: #474235;
|
||||
--prism-border-strong: #1F524B;
|
||||
--prism-status-do: #005A4E; --prism-status-do-bg: #DCEAE6;
|
||||
--prism-status-dont: #8F2A11; --prism-status-dont-bg: #F6DFDA;
|
||||
--prism-status-caution: #6B4A00; --prism-status-caution-bg: #F5E7CC;
|
||||
--prism-status-tip: #0A4E86; --prism-status-tip-bg: #DDE8F3;
|
||||
|
||||
/* space (기본 8px = 0.5rem) */
|
||||
--prism-space-025: 0.125rem; --prism-space-050: 0.25rem; --prism-space-100: 0.5rem;
|
||||
--prism-space-150: 0.75rem; --prism-space-200: 1rem; --prism-space-300: 1.5rem;
|
||||
--prism-space-400: 2rem; --prism-space-600: 3rem; --prism-space-800: 4rem;
|
||||
|
||||
/* type */
|
||||
--prism-type-caption: 0.8125rem; --prism-lh-caption: 1.55;
|
||||
--prism-type-body-sm: 0.875rem; --prism-lh-body: 1.70;
|
||||
--prism-type-body: 1rem; --prism-type-body-lg: 1.125rem;
|
||||
--prism-type-title-sm: 1.25rem; --prism-lh-title: 1.50;
|
||||
--prism-type-title: 1.625rem; --prism-type-title-lg: 2rem;
|
||||
|
||||
/* radius · stroke · z · motion · focus · measure */
|
||||
--prism-radius-sm: 4px; --prism-radius-md: 8px; --prism-radius-lg: 12px; --prism-radius-pill: 9999px;
|
||||
--prism-stroke-hairline: 1px; --prism-stroke-regular: 2px; --prism-stroke-bold: 3px;
|
||||
--prism-z-raised: 100; --prism-z-sticky: 200; --prism-z-dropdown: 300; --prism-z-scrim: 400;
|
||||
--prism-z-modal: 500; --prism-z-popover: 600; --prism-z-toast: 700; --prism-z-tooltip: 800;
|
||||
--prism-scrim: rgba(0, 0, 0, 0.32);
|
||||
--prism-motion-fast: 100ms; --prism-motion-moderate: 200ms; --prism-motion-slow: 300ms;
|
||||
--prism-ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--prism-focus-ring: 3px; --prism-focus-offset: 2px;
|
||||
--prism-measure-max: 38em;
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
--prism-bg-canvas: #111111; --prism-bg-card: #111111;
|
||||
--prism-text-body: #F4F3F0; --prism-text-heading: #F4F3F0; /* 다크에서 그린 텍스트 금지 */
|
||||
--prism-text-subtle: #D3D3D3; --prism-text-accent: #D05C26;
|
||||
--prism-border-default: #D3D3D3; --prism-border-strong: #F4F3F0;
|
||||
--prism-status-do: #DCEAE6; --prism-status-do-bg: #005A4E;
|
||||
--prism-status-dont: #F6DFDA; --prism-status-dont-bg: #8F2A11;
|
||||
--prism-status-caution: #F5E7CC; --prism-status-caution-bg: #6B4A00;
|
||||
--prism-status-tip: #DDE8F3; --prism-status-tip-bg: #0A4E86;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) { /* data-theme dark 블록과 같은 값 세트를 적용 */ }
|
||||
}
|
||||
```
|
||||
|
||||
Prism 토큰 파일에 있는 `--prism-dv-*`(데이터 시각화 6색), `--prism-type-display*`, `--prism-ease-enter/exit`는 이 앱에서 쓰지 않아 옮기지 않는다. 쓰게 되면 정본에서 그대로 옮긴다.
|
||||
|
||||
### 3.3 comp 토큰 (앱 전용, 이 문서가 정의)
|
||||
|
||||
모두 `sys`를 참조한다. 라이트·다크 전환은 `sys` 값이 바뀌므로 `comp`는 두 번 정의하지 않는다.
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 표면 */
|
||||
--prism-comp-surface-page: var(--prism-bg-canvas);
|
||||
--prism-comp-surface-card: var(--prism-bg-card);
|
||||
--prism-comp-surface-sunken: var(--prism-bg-canvas);
|
||||
--prism-comp-line: var(--prism-border-default);
|
||||
--prism-comp-line-strong: var(--prism-border-strong);
|
||||
|
||||
/* 상태색 4쌍의 앱 의미 */
|
||||
--prism-comp-state-good-fg: var(--prism-status-do); --prism-comp-state-good-bg: var(--prism-status-do-bg);
|
||||
--prism-comp-state-bad-fg: var(--prism-status-dont); --prism-comp-state-bad-bg: var(--prism-status-dont-bg);
|
||||
--prism-comp-state-warn-fg: var(--prism-status-caution); --prism-comp-state-warn-bg: var(--prism-status-caution-bg);
|
||||
--prism-comp-state-info-fg: var(--prism-status-tip); --prism-comp-state-info-bg: var(--prism-status-tip-bg);
|
||||
|
||||
/* 배지 */
|
||||
--prism-comp-badge-pad-y: var(--prism-space-025);
|
||||
--prism-comp-badge-pad-x: var(--prism-space-100);
|
||||
--prism-comp-badge-radius: var(--prism-radius-sm);
|
||||
--prism-comp-badge-font: var(--prism-type-caption);
|
||||
--prism-comp-badge-line-solid: var(--prism-stroke-regular);
|
||||
--prism-comp-badge-line-bold: var(--prism-stroke-bold);
|
||||
|
||||
/* 표 */
|
||||
--prism-comp-table-cell-pad-y: var(--prism-space-100);
|
||||
--prism-comp-table-cell-pad-x: var(--prism-space-150);
|
||||
--prism-comp-table-font: var(--prism-type-body-sm);
|
||||
--prism-comp-table-head-bg: var(--prism-bg-canvas);
|
||||
--prism-comp-table-rule: var(--prism-stroke-hairline);
|
||||
|
||||
/* 레이아웃 */
|
||||
--prism-comp-rail-width: 15rem; /* 240px */
|
||||
--prism-comp-header-height: 3.5rem; /* 56px */
|
||||
--prism-comp-hit-min: 2.75rem; /* 44px */
|
||||
|
||||
/* 도면 이미지 */
|
||||
--prism-comp-drawing-frame-line: var(--prism-border-default);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 상태 시프트 토큰 (공백 — 산출 필요)
|
||||
|
||||
Prism은 hover ±4·active ±8·selected ±12의 **GRADE 시프트**를 정하지만(screen 6.2절), 시프트된 HEX 표는 토큰 파일에 없다. 이 문서는 값을 지어내지 않는다.
|
||||
|
||||
| 처리 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 지금 | hover는 보강일 뿐이므로(HV-3) 색 변화 없이도 모든 기능이 성립한다. 시안 단계에서는 hover에 밑줄 또는 보더 굵기 변화만 쓴다 |
|
||||
| 구현 전 | `design.foundations.md` 2장의 GRADE 산출식으로 green·brown·white 면의 ±4·±8·±12 HEX를 산출해 `comp` 토큰(`--prism-comp-*-hover/-active/-selected`)으로 추가한다 |
|
||||
| 책임 | 산출은 Prism 담당자 승인 사항(공백 목록 10장 G7) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 레이아웃
|
||||
|
||||
### 4.1 브레이크포인트
|
||||
|
||||
Prism webapp 2절을 따른다. 임의 브레이크포인트를 두지 않는다(WA-1).
|
||||
|
||||
| BP | 범위(px) | 컬럼 | 마진 | 거터 | 이 앱에서 |
|
||||
| :--- | :--- | :-: | ---: | ---: | :--- |
|
||||
| `lg` | 1240–1599 | 12 | 32 | 24 | **설계 기준.** 1280px에서 모든 화면이 성립해야 한다 |
|
||||
| `xl` | 1600– | 12 | 24 | 24 | 컨테이너 1440 고정 |
|
||||
| `md` | 904–1239 | 8 | 32 | 24 | 지원하되 시험 제외. 레일은 상단으로 접힌다 |
|
||||
| `sm` `xs` | 0–903 | 8 · 4 | 24 · 16 | 16 | 비대상. 가로 스크롤 없이 단일 열로만 표시 |
|
||||
|
||||
### 4.2 1280px 규칙
|
||||
|
||||
| ID | 규칙 |
|
||||
| :--- | :--- |
|
||||
| **LY-1** | 1280px 폭에서 **표 외** 가로 스크롤이 없다 |
|
||||
| **LY-2** | 표는 가로 스크롤을 허용한다(MS-3 예외). 스크롤 시 첫 열(묶음 이름)과 헤더를 고정한다 |
|
||||
| **LY-3** | 연속 문장 블록은 `max-width: var(--prism-measure-max)`. 표·도면 이미지·근거 표는 제외 |
|
||||
| **LY-4** | 최상위 영역만 그리드에 맞춘다. 영역 내부는 간격 토큰으로 배치한다 (WA-4) |
|
||||
|
||||
### 4.3 앱 셸 (W1)
|
||||
|
||||
```
|
||||
┌ header (sticky, comp-header-height) ──────────────────────────────────────────┐
|
||||
│ 서비스명 │ 화면 ID · 교각 │ │ 저장 시각 · 환경(로컬) │ 테마 │
|
||||
├ rail (comp-rail-width) ┬ main ────────────────────────────────────────────────┤
|
||||
│ 교각 목록 │ 화면 제목 (h1) │
|
||||
│ ─ 교각 이름 │ 결론 영역 (요약 띠 · 판정) │
|
||||
│ 1 도면 │ 본문 영역 (표 · 비교 · 폼) │
|
||||
│ 2 입력 묶음 │ │
|
||||
│ 3 게이트 │ │
|
||||
│ 4 보고서 │ │
|
||||
└─────────────────────────┴──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| 요소 | 규칙 |
|
||||
| :--- | :--- |
|
||||
| header | 메타 4슬롯(화면 ID·권한·갱신 시각·환경)을 쓴다. 권한 슬롯은 로컬 앱에서 "담당자"로 고정 표시한다 |
|
||||
| rail | `stepper` 컴포넌트(단계 3~7개 규칙, SP-2)로 구현한다. 완료·현재·미완을 형태로 구분한다(SP-3) |
|
||||
| main | 첫 영역이 결론이다(PD-1). 결론 영역은 viewport당 1개 |
|
||||
| skip link | 모든 화면 첫 요소가 "본문 바로가기"다 |
|
||||
|
||||
### 4.4 패턴 적용
|
||||
|
||||
| 패턴 | 화면 | 이 앱에서의 구체화 |
|
||||
| :--- | :--- | :--- |
|
||||
| W1 앱 셸 | 전 화면 | 4.3절 |
|
||||
| W2 대시보드 | S03, S11 | 카드 그리드. 존당 stat 3개 이하. 1280px에서 3열 |
|
||||
| W3 폼 | S02, S06, S09 검색 | 1열 수직, 다열 폼 금지, 본문 폭 38em, 주 버튼 1개. 단 `S06`의 값 편집창(JSON)은 38em 예외를 둔다(코드 영역) |
|
||||
| W4 문서 | S12, S13 | 좌측 목차 4col + 본문 8col, 본문 38em |
|
||||
| W5 목록-상세 | S01, S04, S05, S07 | 목록이 7열 이상의 표이면 분할 뷰를 쓰지 않고 **전폭 표 + 별도 상세 화면**으로 나눈다. S05만 `4:8` 분할 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 타이포그래피
|
||||
|
||||
### 5.1 서체
|
||||
|
||||
| 슬롯 | 값 |
|
||||
| :--- | :--- |
|
||||
| 패밀리 | Pretendard. 폴백 `-apple-system, BlinkMacSystemFont, "Noto Sans KR", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif` |
|
||||
| 웨이트 | 400 · 500 · 700 |
|
||||
| 숫자 | `font-variant-numeric: tabular-nums` 전 화면 적용 (표의 수치 열은 필수) |
|
||||
| 내장 | `web/static/fonts/PretendardVariable.woff2`(OFL, 가변 폰트)를 `@font-face`로 불러온다 (PRD UX3 결정). 파일이 없으면 폴백 맑은 고딕으로 렌더되며, 이 경우에도 5.2절 크기는 유지한다 |
|
||||
|
||||
`html { font-size: ... }` 선언을 금지한다(CSS-1).
|
||||
|
||||
### 5.2 크기 사용표
|
||||
|
||||
| 용도 | 토큰 | 웨이트 | 비고 |
|
||||
| :--- | :--- | :-: | :--- |
|
||||
| 화면 제목(h1) | `type-title` | 700 | 한 화면에 1개. 20자 이내 |
|
||||
| 영역 제목(h2) | `type-title-sm` | 500 | 규정문체 명사형 |
|
||||
| 소영역 제목(h3) | `type-body-lg` | 700 | |
|
||||
| 본문 | `type-body` | 400 | 38em 이내 |
|
||||
| **표 셀** | `type-body-sm` | 400 | 표 최소 13px (TB 규정) |
|
||||
| 배지·캡션·출처 | `type-caption` | 500 | |
|
||||
| 큰 숫자(요약 띠) | `type-title` | 700 | 숫자만 키운다 |
|
||||
|
||||
### 5.3 기술 표기 (해시·핸들·좌표·키)
|
||||
|
||||
해시 앞 16자, DXF 핸들, 좌표, JSON 키는 **본문과 구분**해야 한다. Prism에는 등폭 서체가 없다(공백 G3). 임시 규칙은 다음과 같다.
|
||||
|
||||
| 규칙 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 형태 | 보더 1px(hairline)이 있는 인라인 칩. 폰트는 본문과 같고 `tabular-nums` |
|
||||
| 복사 | 칩을 누르면 복사한다. 키보드로 접근 가능하다 |
|
||||
| 줄바꿈 | 칩 내부에서 줄바꿈하지 않는다. 길면 앞 16자만 보이고 전체는 `title` 대신 도움말로 연다 |
|
||||
| 금지 | 등폭 서체 임의 지정 금지(토큰 외 서체). 승인 후 `sys` 토큰으로 편입 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 상태 표시 체계
|
||||
|
||||
PRD 4.2절의 어휘를 **글자 + 형태 + 색** 세 신호로 부호화한다. 한 열에서 같은 신호를 두 상태가 공유하지 않는다.
|
||||
|
||||
### 6.1 형태 신호
|
||||
|
||||
| 신호 | 의미 |
|
||||
| :--- | :--- |
|
||||
| 선행 마커 `●` | 정상·완료·충족 |
|
||||
| 선행 마커 `▲` | 주의·확인 필요 |
|
||||
| 선행 마커 `■` | 실패·불일치 |
|
||||
| 선행 마커 `◆` | 변경됨·재실행 필요 |
|
||||
| 선행 마커 `◇` | 근거 없음(빈 마름모) |
|
||||
| 보더 실선 2px | 확정된 상태 |
|
||||
| 보더 점선 2px | 근거·확정이 없는 상태 |
|
||||
| 보더 굵게 3px | 실패 |
|
||||
|
||||
마커는 CSS로 그리는 도형이며 아이콘이 아니다(PRD UX4: 2026-10-15 승인). 이모지와 아이콘 폰트를 쓰지 않는다 (IC-3).
|
||||
|
||||
### 6.2 앱 확인
|
||||
|
||||
| 값 | 글자 | 마커 | 보더 | 전경·배경 |
|
||||
| :--- | :--- | :-: | :--- | :--- |
|
||||
| 일치 | 일치 | ● | 실선 2px | good-fg / good-bg |
|
||||
| 주의 | 주의 | ▲ | 실선 2px | warn-fg / warn-bg |
|
||||
| 불일치 | 불일치 | ■ | 굵게 3px | bad-fg / bad-bg |
|
||||
| 근거 없음 | 근거 없음 | ◇ | 점선 2px | text-body / 투명 |
|
||||
| 해당 없음 | `—` | — | — | text-body (배지 아님) |
|
||||
| 확인 안 함 | [확인 실행] 버튼 | — | — | (배지 아님) |
|
||||
|
||||
### 6.3 사람 확정
|
||||
|
||||
| 값 | 글자 | 마커 | 보더 | 전경·배경 |
|
||||
| :--- | :--- | :-: | :--- | :--- |
|
||||
| 확정 | 확정 | ● | 실선 2px | `surface-emphasis`: green 면 + white 글자 |
|
||||
| 미확정 | 미확정 | — | 점선 2px | text-body / 투명 |
|
||||
| 변경됨 | 변경됨 | ◆ | 실선 2px | warn-fg / warn-bg |
|
||||
|
||||
앱 확인(6.2)이 상태색 면을 쓰고 사람 확정(6.3)이 green 면을 쓰므로, 한 행 안에서 두 배지가 **색·형태 모두 다르다**.
|
||||
|
||||
### 6.4 변수 등급
|
||||
|
||||
| 값 | 글자 | 보더 | 면 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| V1 | V1 값 | 실선 2px | 없음 |
|
||||
| V2 | V2 목록 | 실선 2px | 없음 |
|
||||
| V3 | V3 생성 | 점선 2px | 없음 |
|
||||
| F | F 고정 | 실선 2px | green 면 + white 글자 |
|
||||
|
||||
편집 가능(V1·V2) → 스크립트 필요(V3, 점선) → 편집 불가(F, 면 채움)의 순서로 시각 무게가 커진다.
|
||||
|
||||
### 6.5 신뢰 등급
|
||||
|
||||
| 값 | 글자 | 보더 |
|
||||
| :--- | :--- | :--- |
|
||||
| A | 등급 A | 실선 2px |
|
||||
| B | 등급 B | 실선 2px + 마커 ▲ |
|
||||
| C | 등급 C | 점선 2px |
|
||||
| 없음 | 후보 구역에 행으로 나오지 않는다. '근거 없음' 목록에만 나온다 | — |
|
||||
|
||||
### 6.6 게이트 판정
|
||||
|
||||
| 값 | 글자 | 마커 | 보더 | 전경·배경 |
|
||||
| :--- | :--- | :-: | :--- | :--- |
|
||||
| PASS | PASS | ● | 실선 2px | good-fg / good-bg |
|
||||
| FAIL | FAIL | ■ | 굵게 3px | bad-fg / bad-bg |
|
||||
| 계획 FAIL | 계획 FAIL | ▲ | 실선 2px | warn-fg / warn-bg |
|
||||
| 재실행 필요 | 재실행 | ◆ | 실선 2px | info-fg / info-bg |
|
||||
|
||||
### 6.7 규칙
|
||||
|
||||
| ID | 규칙 |
|
||||
| :--- | :--- |
|
||||
| **ST-1** | 배지 텍스트는 6자 이내(BD-3). 문장을 넣지 않는다 |
|
||||
| **ST-2** | 한 열의 배지 종류는 4종 이내(BD-2). 5종 이상이 필요하면 열을 나눈다 |
|
||||
| **ST-3** | 색을 지우고(그레이스케일) 읽어도 모든 상태가 구분되어야 한다 |
|
||||
| **ST-4** | 배지 색은 악센트 경제에 세지 않는다(BD-4). 그러나 **orange는 상태에 쓰지 않는다** |
|
||||
| **ST-5** | 다크 테마에서 상태색 쌍은 `sys` 정의대로 전경·배경이 반전된다. `comp`가 따로 정의하지 않는다 |
|
||||
| **ST-6** | 배지 안에 `aria-label`로 "앱 확인: 일치"처럼 열 이름을 포함해 낭독하게 한다 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 컴포넌트
|
||||
|
||||
### 7.1 Prism 컴포넌트 사용표
|
||||
|
||||
Prism이 정한 20종 중 이 앱이 쓰는 것과 쓰는 곳이다. 카탈로그에 있는 변형만 쓴다.
|
||||
|
||||
| 컴포넌트 | 변형 | 쓰는 곳 | 비고 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `header` | 1 | 전 화면 셸 | 메타 4슬롯 |
|
||||
| `eyebrow` | 2 | 화면 제목 위 단계 표기("2단계") | |
|
||||
| `rule` | 1 | 영역 구분 | |
|
||||
| `card` | 4 | S03 단계 카드, S05 값 비교 | |
|
||||
| `table` | `base` `compact` | S01, S04, S05, S07, S11 | S04는 `compact` + sticky |
|
||||
| `stat` | 1 | S03·S04 요약 띠의 숫자 | 존당 3개 이하 |
|
||||
| `specsheet` | 1 | S05 결론(묶음 메타), S03 도면 목록 | |
|
||||
| `directive` | `caution` `tip` 등 3 | 형식 불일치 경고, 원본값 경고, 한계 문장 | |
|
||||
| `badge` | 4 | 6장의 모든 상태 | |
|
||||
| `scope` | 1 | 각 화면 최상단 "적용 조건/제외 조건" | 해당 없음 즉시 이탈 |
|
||||
| `toc` | 1 | S13 좌측 목차 | |
|
||||
| `stepper` | 1 | 레일의 1~4단계 | |
|
||||
| `xref` | 1 | 화면 간 링크(S05 → S06 등) | 방향어 금지 |
|
||||
| `mediaframe` | 1 | S08 도면 이미지 | 캡션 필수 |
|
||||
| `button` | `primary` `secondary` `quiet` `danger` | 전 화면 | 화면당 primary 1개 |
|
||||
| `input` | 1 | 이름·확정자·사유·검색 | 레이블 필수 |
|
||||
| `modal` | 1 | S08, S10 | 포커스 트랩·Esc |
|
||||
| `toast` | 1 | 보조 알림 | 단독 전달 금지 |
|
||||
| `disclosure` | 1 | 참고 묶음 접기, 제외 목록 | 인쇄 시 전개 |
|
||||
| `chartframe` | — | **쓰지 않는다** | |
|
||||
|
||||
`hero`, `footer`는 미선별이며 이 앱에서 쓰지 않는다.
|
||||
|
||||
### 7.2 조합 컴포넌트 (이 문서가 정의)
|
||||
|
||||
Prism 카탈로그의 컴포넌트를 **조합·제약**해 만든 앱 전용 구성이다. 새 원시 컴포넌트가 아니므로 카탈로그 외 0건 원칙(webapp A-검증)에 어긋나지 않는다. 단, 조합이 반복되므로 이름을 붙여 일관성을 보장한다.
|
||||
|
||||
#### C-1 요약 띠 (`summary-strip`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | `stat`(숫자 3개 이하의 강조) + `badge`(상태별 개수) + 갱신 시각 캡션 |
|
||||
| 해부 | 결론 문장 → 상태별 개수 → 시각·버전 |
|
||||
| 규칙 | 각 개수는 필터 버튼이다(`button quiet`). 합계는 표와 같은 집계를 쓴다(PRD 수용 기준). 숫자는 등폭 |
|
||||
| 접근성 | 전체를 `role="group"` + 요약 문장 `aria-label`. 개수 변경은 `aria-live="polite"` |
|
||||
| 쓰는 곳 | S03, S04, S07 |
|
||||
|
||||
#### C-2 묶음 표 (`section-table`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | `table compact` + sticky 헤더 + 첫 열 고정 + 행 선택 |
|
||||
| 열 | 묶음 · 변수 등급 · 값 출처 · 앱 확인 · 사람 확정 · 확인 |
|
||||
| 규칙 | 값 출처 열은 배지가 아닌 글자다(5종, ST-2). 단 '원본값'만 `badge marked`(▲ warn)를 단다. 정렬 기본값 불일치 → 근거 없음 → 주의 → 일치. 빈 셀은 `—` (TB-4) |
|
||||
| 행 상태 | hover(보강), selected(GRADE 시프트), focus(링). 행 전체가 링크가 아니라 **묶음 이름만** 링크다 |
|
||||
| 접근성 | `caption` "입력 묶음", `th scope="col"`, 정렬 가능 열은 `aria-sort` |
|
||||
| 쓰는 곳 | S04 |
|
||||
|
||||
#### C-3 근거 행 (`evidence-row`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | 검사 ID(C1~C8) · 검사 이름 · 결과 배지 · 근거 문장 · 도면 위치 칩 · [도면 보기] |
|
||||
| 규칙 | 근거 문장은 40어절 이내, **비교한 두 값과 출처를 모두 쓴다**. 문장 끝에 독립성(낮음)이 있으면 괄호로 쓴다. 해당 없는 검사는 한 줄로 접는다 |
|
||||
| 한계 문장 | 표 하단에 고정 `directive tip`: "일치는 후보를 만든 규칙과 값이 같다는 뜻이다. 도면이 맞다는 판정이 아니다." |
|
||||
| 쓰는 곳 | S05, S10 근거표 |
|
||||
|
||||
#### C-4 값 비교 (`diff-row`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | 항목 이름 · 현재 값 · 비교 값(후보/원본) · 차이 표시 |
|
||||
| 규칙 | 차이는 색이 아닌 **글자(`다름`) + 마커 `▲`**로 표시한다. 같은 항목은 기본으로 접는다. 수치는 우측 정렬·등폭, 단위는 열 헤더에 1회(TB-6) |
|
||||
| 쓰는 곳 | S05, S07 |
|
||||
|
||||
#### C-5 확정 폼 (`confirm-form`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | `input`(확정자) + `input`(사유, 조건부 필수) + `button primary` |
|
||||
| 규칙 | 필수 표시는 `*`가 아니라 글자 "필수"(IN-4). 조건이 모자라도 버튼은 활성이고 클릭 시 오류 메시지를 낸다(BT-6). 오류는 무엇이·왜·어떻게 + 색·기호·텍스트 3중(IN-2·IN-3) |
|
||||
| 사유 필수 조건 | 앱 확인이 주의·불일치·근거 없음이거나 F 항목 변경일 때 |
|
||||
| 쓰는 곳 | S05, S06(F 변경 사유), S10(확정자만) |
|
||||
|
||||
#### C-6 일괄 확정 대화상자 (`bulk-confirm-dialog`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | `modal` + 결론 문장 + 근거표(스크롤 영역) + `disclosure`(제외 묶음) + `confirm-form` |
|
||||
| 규칙 | 근거표 스크롤 끝 도달 여부를 기록하고, 도달 전 [확정]은 오류 메시지를 낸다(비활성 아님). 포커스 트랩·Esc 닫기·배경 스크롤 잠금 (MD-1~5). 모달 안에 모달을 열지 않는다. 제외 묶음 링크는 모달을 닫고 S05로 이동한다 |
|
||||
| 쓰는 곳 | S10 |
|
||||
|
||||
#### C-7 도면 뷰 (`drawing-view`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | `mediaframe` + 캡션(도면·뷰·좌표·핸들 칩) + 확대·축소·이동 버튼 |
|
||||
| 규칙 | 이미지 대체 텍스트는 대상 설명, 캡션은 필수. 대상 위치 표시는 **링 + 십자선**(색 단독 금지). 이동은 방향키 지원. 새 윈도우 이미지 생성은 `dxftools.render`와 같은 결과를 쓴다 |
|
||||
| 쓰는 곳 | S08, S05(축소 보기) |
|
||||
|
||||
#### C-8 경고 띠 (`state-banner`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | `directive` (`caution`) + 한 문장 + 조치 링크 |
|
||||
| 종류 | 형식 불일치(닫을 수 없음) · 원본값 포함 · 값 변경됨(재확인 필요) · 앱 확인 규칙 버전 변경 |
|
||||
| 규칙 | 한 viewport에 2개를 넘기지 않는다. 겹치면 가장 심각한 한 개 + "외 N건" |
|
||||
| 쓰는 곳 | S03, S04, S05 |
|
||||
|
||||
#### C-9 진행 표시 (`run-progress`)
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 구성 | 단계 문장("11/27 묶음 확인 중") + 막대 + [중단] |
|
||||
| 규칙 | 색이 아닌 **길이와 숫자**로 읽는다. `role="progressbar"` + `aria-valuenow`. 완료는 토스트가 아니라 결과 영역에 남긴다. 모션은 `transform`만 쓴다 |
|
||||
| 쓰는 곳 | S04 전체 확인, S11 게이트 실행 |
|
||||
|
||||
### 7.3 버튼 배치
|
||||
|
||||
| 규칙 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 화면당 `primary` 1개 | 다음에 할 일을 가리키는 한 개. S04는 [일치분 확정], S05는 [확정], S10은 [확정] |
|
||||
| 위험 동작 | `danger` + 확인 절차. 교각 삭제, 반려, 형식 변경 확정 |
|
||||
| 순서 | 취소·보조 → 주 버튼(우측 끝). 키보드 순서도 같다 |
|
||||
| 레이블 | 동사로 시작, 8자 이내, 결과를 말한다 (9.2절 표) |
|
||||
| 면 | `primary`는 `surface-emphasis`(green 면 + white 글자), `secondary`는 보더만, `quiet`는 보더 없는 텍스트, `danger`는 `bad` 상태색 보더 + 확인 절차. hover·active의 면 변화는 3.4절 산출 뒤에 적용한다 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 인터랙션과 접근성
|
||||
|
||||
### 8.1 상태와 포커스
|
||||
|
||||
| 항목 | 규칙 |
|
||||
| :--- | :--- |
|
||||
| 상태 | default · hover(보강) · focus(링만) · active · selected · disabled(저강조 토큰, 불투명도 금지) · loading(진행 표시, 색 유지) |
|
||||
| 포커스 링 | 3px 이중 링 + 오프셋 2px, `:focus-visible`. 대체 없는 `outline: none` 금지 (FC-1·FC-2) |
|
||||
| disabled | 가능하면 활성 + 오류 메시지. 비활성은 권한·물리적 불가능일 때만 |
|
||||
| hover | `@media (hover: hover)`에 격리. hover 없이 전 기능이 성립해야 한다 |
|
||||
| 히트 영역 | 44px 이상, 인접 간격 8px 이상 |
|
||||
|
||||
### 8.2 키보드
|
||||
|
||||
| 화면 | 필수 동작 |
|
||||
| :--- | :--- |
|
||||
| S04 | Tab으로 필터 → 도구 줄 → 표(행의 링크·[확인 실행]) 순. 열 머리 Enter로 정렬. 요약 띠 필터는 Enter·Space |
|
||||
| S05 | 좌측 목록은 위/아래 방향키 이동, Enter로 묶음 전환. [이전][다음] 단축 버튼 제공 |
|
||||
| S08 | 방향키 이동, `+`/`-` 확대·축소, Esc 닫기 |
|
||||
| S10 | 포커스 트랩. 근거표는 방향키·PageDown으로 끝까지 내릴 수 있다 |
|
||||
|
||||
### 8.3 오류 메시지
|
||||
|
||||
3요소(무엇이·왜·어떻게)를 한 문장군으로 쓴다. 색·기호·텍스트를 함께 쓴다.
|
||||
|
||||
| 상황 | 문구 예시(60자 이내) |
|
||||
| :--- | :--- |
|
||||
| 확정자 없음 | "확정자 이름이 비어 있다. 확정은 사람의 기록이다. 이름을 입력한다" |
|
||||
| 근거표 미열람 | "근거표를 읽지 않았다. 일치 묶음의 근거를 읽어야 확정한다. 근거표를 끝까지 내린다" |
|
||||
| 사유 없음 | "사유가 비어 있다. 주의 묶음은 사유가 있어야 확정된다. 사유를 입력한다" |
|
||||
| 도면 없음 | "도면이 없다. 앱 확인에 쓸 색인이 없다. 1단계에서 도면을 올린다" |
|
||||
|
||||
### 8.4 모션
|
||||
|
||||
`transform`·`opacity`만 애니메이션한다. `prefers-reduced-motion`에서는 모션을 제거하고 상태 변화는 즉시 반영한다 (MT-3·RM-1). 진행 표시는 정지 상태에서도 숫자로 읽혀야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 9. 문안 규정
|
||||
|
||||
### 9.1 보이스
|
||||
|
||||
Prism `saman` 인스턴스를 따른다.
|
||||
|
||||
| 속성 | 규칙 | 금지 예 |
|
||||
| :--- | :--- | :--- |
|
||||
| 자세 | 단정적·3인칭 | "~인 것 같습니다" → "~이다" 또는 `[추론]` |
|
||||
| 거리 | 전문가 대 전문가 | "우리 모두", "여러분" |
|
||||
| 격 | 규정문체(평서 종결). 조작 안내도 같다 | "꼭 확인해 주세요!" → "확인한다" |
|
||||
| 절제 | 과장·감탄·마케팅 어휘 금지 | "혁신적", "간편하게" |
|
||||
|
||||
어미는 4종만 쓴다: `~한다`(의무) · `~할 수 있다`(재량) · `~를 권장한다`(권고) · `~하지 않는다`(금지).
|
||||
|
||||
### 9.2 버튼 레이블 (동사로 시작, 8자 이내)
|
||||
|
||||
| 동작 | 레이블 | 근거 |
|
||||
| :--- | :--- | :--- |
|
||||
| 앱 확인 한 묶음 | 확인 실행 | 검사한다 ≠ 확정한다 |
|
||||
| 앱 확인 전체 | 전체 확인 | |
|
||||
| 일치 묶음 확정 | 일치분 확정 | "확정"은 사람의 기록임을 명시. 대상을 앞에 둔다 |
|
||||
| 확정 | 확정 | |
|
||||
| 후보 반영 | 후보 반영 | 반영 = 기존 문서에 편입(동사 판별표) |
|
||||
| 값 저장 | 저장 | |
|
||||
| 도면 확대 | 도면 보기 | |
|
||||
| 교각 만들기 | 교각 만들기 | |
|
||||
| 삭제 | 삭제 | `danger` |
|
||||
|
||||
'변경'과 '수정'을 구분한다: 승인 효력에 영향이 있으면 **변경**, 오기 정정이면 **수정**(동사 판별표). 형식 불일치 확정은 "변경"이다.
|
||||
|
||||
### 9.3 상태 문구
|
||||
|
||||
| 대상 | 문구 |
|
||||
| :--- | :--- |
|
||||
| 일치 근거 | "<항목>은 <출처>의 <대상>과 같다" |
|
||||
| 주의 근거 | "<항목>이 <기준>과 <차이>만큼 다르다. 허용 범위 안이다" |
|
||||
| 불일치 근거 | "<항목>이 <출처>의 <대상>과 다르다 (현재 <값>, 도면 <값>)" |
|
||||
| 근거 없음 | "<항목>의 출처를 찾지 못했다. 사람이 도면에서 직접 확인한다" |
|
||||
| 원본값 | "템플릿 원본과 같다. 새 도면 근거가 없다" |
|
||||
| 값 변경됨 | "값이 바뀌었다. 이전 확인과 확정이 무효가 되었다" |
|
||||
|
||||
### 9.4 금지 목록
|
||||
|
||||
| # | 금지 | 근거 |
|
||||
| :-: | :--- | :--- |
|
||||
| 1 | 이모지 | IC-3 |
|
||||
| 2 | 순백 전면 배경 (캔버스는 sand, 카드만 white) | 브랜드 금지 |
|
||||
| 3 | 오렌지 본문 텍스트, 오렌지 상태 표시 | 브랜드 금지, ST-4 |
|
||||
| 4 | 다크 배경 위 그린 텍스트 | CSS-4 |
|
||||
| 5 | 악센트 3개 이상(viewport당) | 악센트 경제 |
|
||||
| 6 | 그라디언트 배경, 장식 이미지 | 브랜드 금지 |
|
||||
| 7 | 방향어("위의 표", "아래 버튼", "앞서") | 5.7. 절 번호·항목 이름으로 참조한다 |
|
||||
| 8 | "100% 자동", "완벽", "간편" 등 과장 표현 | 절제 |
|
||||
| 9 | 앱이 "확정했다"고 쓰는 문장 | PP-1 |
|
||||
| 10 | 상태를 색만으로 알리는 표현 | PP-4 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 디자인 시스템의 공백과 제안
|
||||
|
||||
PRD 구현에 필요하지만 Prism에 없는 요소이다. 승인 전에는 **조합 컴포넌트(7.2절)와 임시 규칙**으로 대신한다. 새 아이콘과 새 서체는 Prism 거버넌스(제안 → 승인 대기 → 등재)를 따른다.
|
||||
|
||||
| ID | 공백 | 쓰는 곳 | 임시 처리 | 제안 |
|
||||
| :-: | :--- | :--- | :--- | :--- |
|
||||
| G1 | 라디오·체크박스·셀렉트 (`input` 변형) | S02 6문항, 필터, S07 반영 선택 | 네이티브 요소 + 토큰 보더·포커스 링 | `input` 변형 3종 신설 |
|
||||
| G2 | 파일 선택 | 도면 올리기 | 네이티브 `input[type=file]` + 레이블·도움말 | `input` 변형 |
|
||||
| G3 | 등폭 서체 | 해시·핸들·좌표·JSON | 5.3절 칩 | 등폭 패밀리를 `ref.type`에 편입 |
|
||||
| G4 | 진행 표시 | 앱 확인, 게이트 실행 | C-9 | `progress` 컴포넌트 신설 |
|
||||
| G5 | 행 선택·정렬이 있는 표 | S04, S07 | C-2 | `table` 변형 `selectable` |
|
||||
| G6 | 확대 뷰어 | S08 | C-7 | `mediaframe` 변형 `zoomable` |
|
||||
| G7 | 상태 시프트 HEX | 전 상호작용 | 3.4절 | foundations 산출표 등재 |
|
||||
| G8 | 상태 마커 도형(●▲■◆◇) | 6장 | CSS 도형 | 아이콘 거버넌스 승인 (PRD UX4) |
|
||||
| G9 | 코드·로그 블록 | S11 로그, S06 편집창 | 본문 서체 + 보더 | `codeblock` 신설 |
|
||||
| G10 | 탭 | (사용하지 않음) | 별도 화면으로 분리 | 필요 시 제안 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 적용 점검표
|
||||
|
||||
PRD 수용 기준 7번의 근거이다. 시안·구현 모두 전항을 확인한다.
|
||||
|
||||
### 11.1 자동
|
||||
|
||||
| # | 항목 | 기준 |
|
||||
| :-: | :--- | :--- |
|
||||
| A1 | 색·치수 리터럴 | 변수 정의부 밖 0건 |
|
||||
| A2 | root font-size 고정 | 0건 |
|
||||
| A3 | `outline: none` (대체 없음) | 0건 |
|
||||
| A4 | z-index, tabindex | 토큰 외 0건 / 양수 0건 |
|
||||
| A5 | 그리드 정합 | 전 BP 잔차 0 |
|
||||
| A6 | axe 대비·이름·alt | 위반 0건 |
|
||||
| A7 | 헤딩 레벨 건너뜀 | 0건 |
|
||||
| A8 | `prefers-reduced-motion` | 구현 존재 |
|
||||
| A9 | 히트 영역 | 44px 이상 |
|
||||
| A10 | 본문 max-width | 38em 이하 |
|
||||
| A11 | 다크 페어 | green 텍스트 0건 |
|
||||
| A12 | 텍스트 알파·hover 의존 | 0건 |
|
||||
| **A13** | **상태 셀 글자** | 모든 상태 셀에 글자 100% |
|
||||
| **A14** | **배지 길이** | 6자 초과 0건 |
|
||||
| **A15** | **primary 버튼** | 화면당 1개 |
|
||||
| **A16** | **orange 사용** | 상태·본문 0건 |
|
||||
|
||||
### 11.2 수동
|
||||
|
||||
| # | 항목 |
|
||||
| :-: | :--- |
|
||||
| V1 | 키보드만으로 PRD 5장 모든 화면의 모든 행동 수행 |
|
||||
| V2 | 스크린리더로 S04 → S05 → S10 흐름 낭독 |
|
||||
| V3 | 200% 확대 시 손실·가로 스크롤 없음(표 제외) |
|
||||
| V4 | 그레이스케일로 6장 모든 상태 구분 |
|
||||
| V5 | 1280px에서 가로 스크롤(표 외) 0 |
|
||||
| V6 | 9.4절 금지 목록 0건 |
|
||||
| V7 | 도면 이미지 캡션 100% |
|
||||
| V8 | '앱이 확정했다'로 읽히는 문장 0건 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 거버넌스
|
||||
|
||||
| 항목 | 내용 |
|
||||
| :--- | :--- |
|
||||
| 변경 | 이 문서의 개정은 버전표에 기록한다. Prism 개정은 이 문서에 자동 소급되지 않는다 (Prism DC-1) |
|
||||
| 소유 | 앱 화면 규칙은 담당자(심영표). 공백 G1~G10의 Prism 반영은 Prism 소유자가 결정한다 |
|
||||
| 드리프트 | 3.2절 sys 블록이 `design.tokens.json`·`webapp` 3절과 달라지면 이 문서를 고친다. 정합은 구현 시 빌드 게이트(A1)로 검증한다 |
|
||||
| R8 | 확정 시 `디자인지침_경로.md`의 R8 행을 대체 표기하고 '디자인 지침 폴더 경로'를 `RAG/design system_v0.1.1`로 갱신한다 (사용자 결정 UX1 후) |
|
||||
| 면책 | 이 프로파일 준수가 서비스의 접근성·적합성을 보증하지 않는다 (Prism DC-3). 서비스 단위 검증은 별도로 한다 |
|
||||
|
||||
---
|
||||
|
||||
## 부록 A. 핵심 컴포넌트 스타일 골격
|
||||
|
||||
토큰만 참조하는 CSS 골격이다. 값은 3장 변수에서 온다.
|
||||
|
||||
```css
|
||||
.pier-badge {
|
||||
display: inline-flex; align-items: center; gap: var(--prism-space-050);
|
||||
padding: var(--prism-comp-badge-pad-y) var(--prism-comp-badge-pad-x);
|
||||
font-size: var(--prism-comp-badge-font); font-weight: 500;
|
||||
border: var(--prism-comp-badge-line-solid) solid var(--prism-comp-line);
|
||||
border-radius: var(--prism-comp-badge-radius);
|
||||
color: var(--prism-text-body); background: transparent; white-space: nowrap;
|
||||
}
|
||||
.pier-badge[data-kind="good"] { color: var(--prism-comp-state-good-fg); background: var(--prism-comp-state-good-bg); border-color: var(--prism-comp-state-good-fg); }
|
||||
.pier-badge[data-kind="warn"] { color: var(--prism-comp-state-warn-fg); background: var(--prism-comp-state-warn-bg); border-color: var(--prism-comp-state-warn-fg); }
|
||||
.pier-badge[data-kind="info"] { color: var(--prism-comp-state-info-fg); background: var(--prism-comp-state-info-bg); border-color: var(--prism-comp-state-info-fg); }
|
||||
.pier-badge[data-kind="bad"] { color: var(--prism-comp-state-bad-fg); background: var(--prism-comp-state-bad-bg); border-color: var(--prism-comp-state-bad-fg);
|
||||
border-width: var(--prism-comp-badge-line-bold); }
|
||||
.pier-badge[data-kind="none"] { border-style: dashed; }
|
||||
.pier-badge[data-kind="fixed"] { color: var(--prism-bg-card); background: var(--prism-text-heading); border-color: var(--prism-text-heading); }
|
||||
/* 선행 마커: CSS 도형, 아이콘이 아님 */
|
||||
.pier-badge[data-mark]::before { content: attr(data-mark); aria-hidden: true; }
|
||||
|
||||
.pier-table { width: 100%; border-collapse: collapse; font-size: var(--prism-comp-table-font); font-variant-numeric: tabular-nums; }
|
||||
.pier-table caption { text-align: left; font-weight: 700; padding-block: var(--prism-space-100); }
|
||||
.pier-table th, .pier-table td { padding: var(--prism-comp-table-cell-pad-y) var(--prism-comp-table-cell-pad-x);
|
||||
border-bottom: var(--prism-comp-table-rule) solid var(--prism-comp-line); text-align: left; vertical-align: top; }
|
||||
.pier-table thead th { position: sticky; top: 0; z-index: var(--prism-z-sticky); background: var(--prism-comp-table-head-bg); }
|
||||
.pier-table td.num { text-align: right; }
|
||||
|
||||
:focus-visible { outline: var(--prism-focus-ring) solid var(--prism-comp-line-strong); outline-offset: var(--prism-focus-offset); }
|
||||
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
|
||||
```
|
||||
|
||||
`.pier-badge[data-mark]::before`의 `aria-hidden`은 CSS 속성이 아니므로 구현 시 마커를 `<span aria-hidden="true">`로 둔다(골격 단순화를 위해 표기).
|
||||
|
||||
## 부록 B. 화면별 소비 매트릭스
|
||||
|
||||
| 화면 | 패턴 | 주요 컴포넌트 (7.1·7.2절) |
|
||||
| :--- | :--- | :--- |
|
||||
| S01 | W5 | table, button, badge, xref |
|
||||
| S02 | W3 | input(G1), directive, button, scope |
|
||||
| S03 | W2 | card, stat, summary-strip, stepper, state-banner, button |
|
||||
| S04 | W5 | summary-strip, section-table, badge, state-banner, disclosure, button |
|
||||
| S05 | W5 | evidence-row, diff-row, confirm-form, specsheet, directive, xref |
|
||||
| S06 | W3 | input, confirm-form, state-banner, button, codeblock(G9) |
|
||||
| S07 | W5 | summary-strip, table(selectable), diff-row, badge, button |
|
||||
| S08 | 모달 | modal, drawing-view, mediaframe |
|
||||
| S09 | W3 + W5 | input, table, button |
|
||||
| S10 | 모달 | bulk-confirm-dialog, evidence-row, disclosure, confirm-form |
|
||||
| S11 | W2 | card, table, badge, run-progress, button |
|
||||
| S12 | W4 | toc, table, badge, directive |
|
||||
| S13 | W4 | toc, table, badge, directive, scope |
|
||||
|
||||
## 부록 C. 근거 파일
|
||||
|
||||
| 구분 | 경로 |
|
||||
| :--- | :--- |
|
||||
| Prism 진입점 | `RAG/design system_v0.1.1/Design.md` |
|
||||
| 토큰 정본 | `RAG/design system_v0.1.1/design.tokens.json` |
|
||||
| 웹앱 모듈 | `RAG/design system_v0.1.1/webapp/module.webapp.md` |
|
||||
| 컴포넌트·브랜드 | `RAG/design system_v0.1.1/design.components.md`, `design.brand.md` |
|
||||
| 화면 요구사항 | `DOC/01_기획/웹앱디자인시안/PRD.md` |
|
||||
| 기존 디자인 기준 | `context-migration/디자인지침_경로.md` (R8) |
|
||||
Binary file not shown.
@@ -0,0 +1,431 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PierProof 시안 — 입력 묶음·새 교각 판별</title>
|
||||
<link rel="stylesheet" href="pier.css">
|
||||
<style>
|
||||
@font-face { font-family: "Pretendard"; src: url("fonts/PretendardVariable.woff2") format("woff2-variations"); font-weight: 45 920; font-display: swap; }
|
||||
body { margin: 0; }
|
||||
.toast { position: fixed; right: var(--prism-space-300); bottom: var(--prism-space-300); z-index: 700; max-width: 28rem; background: var(--prism-bg-card);
|
||||
border: var(--prism-stroke-regular) solid var(--prism-comp-line-strong); border-radius: var(--prism-radius-md); padding: var(--prism-space-150) var(--prism-space-200); }
|
||||
.toast[hidden] { display: none; }
|
||||
.scrim { position: fixed; }
|
||||
.demo { background: var(--prism-text-heading); color: var(--prism-comp-on-emphasis); font-size: var(--prism-type-caption); padding: var(--prism-space-050) var(--prism-space-300); }
|
||||
.rail a.step { cursor: pointer; }
|
||||
a[data-r] { cursor: pointer; }
|
||||
td .btn.sm { min-height: var(--prism-comp-hit-min); }
|
||||
.pbar.run > i { transition: width var(--prism-motion-fast) linear; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip" href="#main">본문 바로가기</a>
|
||||
<div class="demo">시안 — 동작 흐름 확인용. 데이터는 예시이며 실제 앱 결과가 아니다. 새로고침하면 처음 상태로 돌아간다.</div>
|
||||
<div class="app" id="app">
|
||||
<header class="top">
|
||||
<div class="brand"><i></i>PierProof</div>
|
||||
<span class="where" id="where"></span>
|
||||
<div class="meta"><span>권한 담당자</span><span>저장 14:32</span><span>환경 로컬</span></div>
|
||||
</header>
|
||||
<div class="body">
|
||||
<nav class="rail" aria-label="교각 작업 단계">
|
||||
<span class="cap">교각</span>
|
||||
<span class="pier">옥성교P1</span>
|
||||
<ol>
|
||||
<li><a class="step done" tabindex="0"><span class="n" aria-hidden="true">✓</span>1 도면</a></li>
|
||||
<li><a class="step" id="nav-bundles" href="#/bundles"><span class="n">2</span>2 입력 묶음</a></li>
|
||||
<li><a class="step" tabindex="0"><span class="n">3</span>3 게이트</a></li>
|
||||
<li><a class="step" tabindex="0"><span class="n">4</span>4 보고서</a></li>
|
||||
</ol>
|
||||
<hr>
|
||||
<div class="sub"><a href="#/candidates" id="nav-cand">표 후보값</a></div>
|
||||
<div class="sub"><a href="#/new" id="nav-new">새 교각 (S02)</a></div>
|
||||
</nav>
|
||||
<main class="main" id="main" tabindex="-1"></main>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modalHost"></div>
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
/* ---------- data (sample) ---------- */
|
||||
var GRADE = { V1: ['', 'V1 값'], V2: ['', 'V2 목록'], V3: ['dash', 'V3 생성'], F: ['fixed', 'F 고정'] };
|
||||
var ST = { g: ['good', '●', '일치', 3], w: ['warn', '▲', '주의', 2], b: ['bad', '■', '불일치', 0], n: ['dash', '◇', '근거 없음', 1] };
|
||||
var RAW = [
|
||||
['params · lean_concrete', 'V1', '표 밖 치수', 'g'], ['params · footing', 'V1', '표 밖 치수', 'g'], ['params · column', 'V1', '표 밖 치수', 'g'],
|
||||
['params · materials', 'V1', '표', 'g'], ['params · coping', 'V2', '표 밖 치수', 'w'], ['params · bearing_seats', 'V1', '표', 'g'],
|
||||
['params · sole_plates', 'V1', '표', 'g'], ['params · rebar_marks', 'V1', '표', 'g'], ['params · column_rebar', 'V2', '표 밖 치수', 'g'],
|
||||
['params · footing_rebar', 'V3', '판독 스크립트', 'g'], ['params · coping_rebar', 'V3', '판독 스크립트', 'w'], ['params · display', 'V1', '', 'n', true],
|
||||
['expected · G1', 'V1', '표 밖 치수', 'g'], ['expected · G1.pedestal', 'V1', '표', 'g'], ['expected · G2', 'V2', '표 밖 치수', 'g'],
|
||||
['expected · G3', 'V2', '표 밖 치수', 'g'], ['expected · G4', 'V2', '표 밖 치수', 'b'], ['expected · G5', 'V1', '표', 'g'],
|
||||
['expected · planned_fail', 'V1', '사람 입력', 'n'], ['expected · rebar_palette', 'V1', '', 'n', true], ['expected · checkers', 'F', '템플릿 고정', 'g'],
|
||||
['expected · schedule', 'V1', '표', 'g'], ['expected · dia', 'V1', '표', 'g'], ['expected · fixed_length', 'V2', '표 밖 치수', 'w'],
|
||||
['expected · loops', 'V2', '표 밖 치수', 'w'], ['expected · seats', 'V2', '표 밖 치수', 'g'], ['expected · c18', 'V2', '표 밖 치수', 'g']
|
||||
];
|
||||
var rows = RAW.map(function (r, i) {
|
||||
return { i: i, key: r[0], g: r[1], src: r[2], st: r[3], orig: !!r[4], human: null, stale: false };
|
||||
});
|
||||
var SRC_TEXT = { '표': '도면 표(D1)', '표 밖 치수': '구조도 정면·단면의 치수 문자(D1)', '판독 스크립트': '판독 스크립트 실행 기록', '사람 입력': '담당자 입력 기록', '템플릿 고정': '템플릿 고정값' };
|
||||
var CAND = [
|
||||
{ id: 1, bundle: 'params · coping', file: 'params', target: 'coping.z_tip', cur: 31232, cand: 31231.5, kind: '표 밖 치수', rule: 'R1 레벨', trust: 'B', pos: 'D1 (225817, 1802)', pair: 'ztip' },
|
||||
{ id: 2, bundle: 'expected · G1', file: 'expected', target: 'G1.coping.z_tip', cur: 31232, cand: 31231.5, kind: '표 밖 치수', rule: 'R1 레벨', trust: 'B', pos: 'D1 (225817, 1802)', pair: 'ztip' },
|
||||
{ id: 3, bundle: 'params · coping', file: 'params', target: 'coping.z_top_right', cur: 32936, cand: 32936, kind: '표 밖 치수', rule: 'R1 레벨', trust: 'A', pos: 'D1 (225817, 4366)' },
|
||||
{ id: 4, bundle: 'params · footing', file: 'params', target: 'footing.height', cur: 2500, cand: 2450, kind: '표 밖 치수', rule: 'R2 사슬', trust: 'A', pos: 'D1 (231004, 902)' },
|
||||
{ id: 5, bundle: 'params · bearing_seats', file: 'params', target: 'bearing_seats[0].h1', cur: 209, cand: 209, kind: '표', rule: 'ELEVATION TABLE', trust: 'T', pos: 'D1 표 행 1' },
|
||||
{ id: 6, bundle: 'params · sole_plates', file: 'params', target: 'sole_plates[3].S', cur: -0.46, cand: -0.52, kind: '표', rule: 'SOLE PLATE TABLE', trust: 'T', pos: 'D1 표 행 4' },
|
||||
{ id: 7, bundle: 'params · column_rebar', file: 'params', target: 'column_rebar.hoops[2]', cur: '4@300', cand: '4@300', kind: '표 밖 치수', rule: 'R2 사슬', trust: 'A', pos: 'D2 (224410, 7120)' },
|
||||
{ id: 8, bundle: 'params · coping_rebar', file: 'params', target: 'coping_rebar.stirrups.count', cur: 104, cand: 106, kind: '표 밖 치수', rule: 'R6 수량 대조', trust: 'C', pos: 'D1 점 94곳' }
|
||||
];
|
||||
var TRUST = { A: ['', '등급 A'], B: ['warn', '등급 B'], C: ['dash', '등급 C'], T: ['', '표'] };
|
||||
|
||||
/* ---------- state ---------- */
|
||||
var S = { filter: 'all', fGrade: '', fSrc: '', by: '심영표', checking: false, checkedAt: '14:30', sel: {}, lastBulk: '', flash: null };
|
||||
|
||||
/* ---------- helpers ---------- */
|
||||
function $(sel, root) { return (root || document).querySelector(sel); }
|
||||
function esc(t) { return String(t).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); }
|
||||
function badge(kind, mark, label) { return '<span class="b ' + kind + '">' + (mark ? '<span class="m" aria-hidden="true">' + mark + '</span>' : '') + esc(label) + '</span>'; }
|
||||
function stBadge(r) {
|
||||
if (r.stale) return badge('warn', '◆', '변경됨');
|
||||
var s = ST[r.st]; return badge(s[0], s[1], s[2]);
|
||||
}
|
||||
function humanBadge(r) { return r.human ? '<span class="b fixed"><span class="m" aria-hidden="true">●</span>확정</span>' : (r.stale && r.staleHuman ? badge('warn', '◆', '변경됨') : '<span class="b dash">미확정</span>'); }
|
||||
function fmt(v) { return typeof v === 'number' ? v.toLocaleString('ko-KR') : v; }
|
||||
function toast(msg) {
|
||||
var t = $('#toast'); t.textContent = msg; t.hidden = false;
|
||||
clearTimeout(toast._t); toast._t = setTimeout(function () { t.hidden = true; }, 6000);
|
||||
}
|
||||
function counts() {
|
||||
var c = { g: 0, w: 0, b: 0, n: 0, human: 0 };
|
||||
rows.forEach(function (r) { c[r.st]++; if (r.human) c.human++; });
|
||||
return c;
|
||||
}
|
||||
function sorted() { return rows.slice().sort(function (a, b) { return (ST[a.st][3] - ST[b.st][3]) || (a.i - b.i); }); }
|
||||
|
||||
/* ---------- evidence ---------- */
|
||||
function evidence(r) {
|
||||
var src = SRC_TEXT[r.src] || '';
|
||||
var hex = (0x2c41 + r.i * 37).toString(16).toUpperCase();
|
||||
var pos = 'D1 · 핸들 ' + hex;
|
||||
var isExp = r.key.indexOf('expected') === 0;
|
||||
var ev = [
|
||||
{ id: 'C1', name: '출처 있음', st: 'g', text: '이 묶음의 값은 ' + src + '에서 읽었다.', pos: pos },
|
||||
{ id: 'C2', name: '도면 재대조', st: 'g', text: '현재 값이 ' + src + '의 후보 값과 같다.', pos: pos },
|
||||
{ id: 'C3', name: '템플릿 원본 잔존', st: 'g', text: '원본과 다른 값은 ' + (30 + r.i) + '개이고 같은 값 ' + (r.i % 4) + '개는 모두 도면 근거가 있다.', pos: '—' },
|
||||
{ id: 'C4', name: '내부 정합', st: 'g', text: '치수 사슬 합이 전체 치수와 같고 본수가 재료표 수량과 같다.', pos: '재료표' },
|
||||
{ id: 'C5', name: 'params와 독립', st: isExp ? 'g' : 'x', text: isExp ? 'expected의 출처가 params의 출처와 다르다.' : '해당 없음. params 묶음이다.', pos: '—' },
|
||||
{ id: 'C6', name: '범위·단위', st: 'g', text: 'EL과 길이가 상식 범위 안에 있고 단위가 mm이다.', pos: '—' },
|
||||
{ id: 'C7', name: '변수 등급 위반', st: 'g', text: r.g === 'F' ? 'F 고정 항목이 템플릿 원본과 같다.' : 'F 고정 항목이 템플릿 원본과 다르지 않다.', pos: '—' },
|
||||
{ id: 'C8', name: '재생성 일치', st: r.g === 'V3' ? 'g' : 'x', text: r.g === 'V3' ? '재생성 목록의 해시가 현재 값과 같다 (독립성 낮음).' : '해당 없음. V3 묶음이 아니다.', pos: '판독 스크립트' }
|
||||
];
|
||||
function set(id, st, text) { ev.forEach(function (e) { if (e.id === id) { e.st = st; e.text = text; } }); }
|
||||
if (r.src === '표') set('C1', 'g', '이 묶음의 값은 도면 표(D1)의 행에서 읽었다.');
|
||||
if (r.key === 'expected · G4') set('C4', 'b', '스터럽 곳 수 합 104가 재료표 C8~C13 수량 합 106(39 + 27 + 40)과 다르다.');
|
||||
if (r.key === 'params · coping') set('C2', 'w', '단 높이가 표 값과 1.1 mm 어긋난다. 허용 범위 안이다.');
|
||||
if (r.key === 'params · coping_rebar') set('C8', 'w', '재생성 목록의 해시가 같다. 독립성이 낮아 도면 직접 대조가 필요하다.');
|
||||
if (r.key === 'expected · fixed_length') set('C4', 'w', '길이가 재료표 L과 겹이음만큼 다르다. 허용 범위 안이다.');
|
||||
if (r.key === 'expected · loops') set('C2', 'w', '측면 고리 한 단이 점 목록과 3 mm 다르다. 허용 범위 안이다.');
|
||||
if (r.st === 'n') {
|
||||
set('C1', 'n', r.key === 'expected · planned_fail' ? '사람 결정 기록이다. 도면 출처가 없다.' : '출처를 찾지 못했다. 사람이 도면에서 직접 확인한다.');
|
||||
set('C2', 'x', '비교할 후보가 없다.'); set('C4', 'x', '비교할 항목이 없다.'); set('C6', 'x', '검사 대상 수치가 없다.');
|
||||
if (r.orig) set('C3', 'n', '현재 값이 템플릿 원본과 같고 새 도면 근거가 없다.');
|
||||
}
|
||||
return ev;
|
||||
}
|
||||
var EVST = { g: ['good', '●', '일치'], w: ['warn', '▲', '주의'], b: ['bad', '■', '불일치'], n: ['dash', '◇', '근거 없음'], x: ['', '', '—'] };
|
||||
|
||||
/* ---------- views ---------- */
|
||||
var main = $('#main');
|
||||
function setWhere(t) { $('#where').textContent = t; }
|
||||
|
||||
function viewBundles() {
|
||||
setWhere('S04 · 옥성교P1 · 입력 묶음');
|
||||
var c = counts();
|
||||
var list = sorted().filter(function (r) {
|
||||
if (S.filter !== 'all' && r.st !== S.filter) return false;
|
||||
if (S.fGrade && r.g !== S.fGrade) return false;
|
||||
if (S.fSrc && (r.orig ? '원본값' : r.src) !== S.fSrc) return false;
|
||||
return true;
|
||||
});
|
||||
var origN = rows.filter(function (r) { return r.orig; }).length;
|
||||
var html = '<div class="head"><span class="eyebrow">2단계</span><h1>입력 묶음 확인·확정</h1><p class="lead">앱이 묶음을 도면과 대조해 근거를 보인다. 확정은 근거를 읽은 담당자가 한다.</p></div>';
|
||||
if (S.flash) { html += '<div class="banner good" role="status"><span class="m" aria-hidden="true">●</span><div>' + esc(S.flash) + '</div></div>'; }
|
||||
html += '<section class="strip" aria-label="요약"><p class="concl">필요 27개 중 앱 확인 일치 ' + c.g + ' · 주의 ' + c.w + ' · 불일치 ' + c.b + ' · 근거 없음 ' + c.n + ' · 사람 확정 ' + c.human + '</p><div class="counts">';
|
||||
[['all', '전체', '', 27], ['g', '일치', ST.g, c.g], ['w', '주의', ST.w, c.w], ['b', '불일치', ST.b, c.b], ['n', '근거 없음', ST.n, c.n]].forEach(function (f) {
|
||||
var lab = f[2] ? badge(f[2][0], f[2][1], f[1]) : '<span>' + f[1] + '</span>';
|
||||
html += '<button class="cnt' + (S.filter === f[0] ? ' sel' : '') + '" type="button" data-filter="' + f[0] + '" aria-pressed="' + (S.filter === f[0]) + '">' + lab + '<b class="num">' + f[3] + '</b></button>';
|
||||
});
|
||||
html += '</div>';
|
||||
if (S.checking) html += '<div class="stack" style="gap:0.25rem"><span class="cap" id="runtxt">앱 확인 중 0/27 묶음</span><div class="pbar run" role="progressbar" aria-valuemin="0" aria-valuemax="27" aria-valuenow="0" id="runbar"><i style="width:0%"></i></div></div>';
|
||||
else html += '<span class="cap muted">마지막 앱 확인 ' + S.checkedAt + ' · 검사 v0.1 · 재확인 필요 ' + rows.filter(function (r) { return r.stale; }).length + '개</span>';
|
||||
html += '</section>';
|
||||
html += '<div class="banner"><span class="m" aria-hidden="true">▲</span><div><b>원본값 ' + origN + '개.</b> 템플릿 원본과 같고 새 도면 근거가 없다. 일괄 확정에서 제외되며, 새 도면과 대조해 값이 바뀌거나 근거가 붙어야 풀린다.</div></div>';
|
||||
html += '<div class="row"><div class="field"><label for="fg">변수 등급</label><select id="fg"><option value="">전체</option>' +
|
||||
['V1', 'V2', 'V3', 'F'].map(function (g) { return '<option value="' + g + '"' + (S.fGrade === g ? ' selected' : '') + '>' + GRADE[g][1] + '</option>'; }).join('') + '</select></div>' +
|
||||
'<div class="field"><label for="fs">값 출처</label><select id="fs"><option value="">전체</option>' +
|
||||
['표', '표 밖 치수', '판독 스크립트', '원본값', '사람 입력', '템플릿 고정'].map(function (g) { return '<option value="' + g + '"' + (S.fSrc === g ? ' selected' : '') + '>' + g + '</option>'; }).join('') + '</select></div>' +
|
||||
'<span class="grow"></span><button class="btn" type="button" id="runAll">전체 확인</button><button class="btn primary" type="button" id="bulk">일치분 확정</button></div>';
|
||||
html += '<div class="tw"><table class="t"><caption>입력 묶음 ' + list.length + '개 · 불일치 → 근거 없음 → 주의 → 일치 순</caption><thead><tr><th scope="col">묶음</th><th scope="col">변수 등급</th><th scope="col">값 출처</th><th scope="col">앱 확인</th><th scope="col">사람 확정</th><th scope="col">근거</th></tr></thead><tbody>';
|
||||
if (!list.length) html += '<tr><td colspan="6">조건에 맞는 묶음이 없다. 필터를 바꾼다.</td></tr>';
|
||||
list.forEach(function (r) {
|
||||
html += '<tr><td><a href="#/evidence/' + r.i + '">' + esc(r.key) + '</a></td><td>' + badge(GRADE[r.g][0], '', GRADE[r.g][1]) + '</td><td>' + esc(r.src) +
|
||||
(r.orig ? badge('warn', '▲', '원본값') : '') + '</td><td>' + stBadge(r) + (r.stale ? ' <button class="btn quiet sm" type="button" data-recheck="' + r.i + '">확인 실행</button>' : '') +
|
||||
'</td><td>' + (r.human ? humanBadge(r) + ' <span class="cap muted">' + esc(r.human.by) + ' ' + esc(r.human.at) + '</span>' : humanBadge(r)) +
|
||||
'</td><td><a class="btn quiet sm" href="#/evidence/' + r.i + '">근거 보기</a></td></tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
html += '<details class="card" style="padding:0"><summary style="min-height:2.75rem;display:flex;align-items:center;cursor:pointer;font-weight:700;padding:0 0.75rem">확정 불필요(참고) 5개</summary><div style="padding:0 1rem 1rem" class="muted">_schema · _meta · pier_id_src · hitl_decisions_note · display_notes. 확정 대상이 아니며 앱 확인도 하지 않는다.</div></details>';
|
||||
main.innerHTML = html;
|
||||
|
||||
main.querySelectorAll('[data-filter]').forEach(function (b) { b.onclick = function () { S.filter = b.getAttribute('data-filter'); viewBundles(); $('[data-filter="' + S.filter + '"]').focus(); }; });
|
||||
$('#fg').onchange = function (e) { S.fGrade = e.target.value; viewBundles(); $('#fg').focus(); };
|
||||
$('#fs').onchange = function (e) { S.fSrc = e.target.value; viewBundles(); $('#fs').focus(); };
|
||||
$('#runAll').onclick = runAll;
|
||||
$('#bulk').onclick = openBulk;
|
||||
main.querySelectorAll('[data-recheck]').forEach(function (b) { b.onclick = function () { var r = rows[+b.getAttribute('data-recheck')]; r.stale = false; r.staleHuman = false; toast(r.key + ' 앱 확인을 다시 실행했다. 결과는 이전과 같다.'); viewBundles(); }; });
|
||||
S.flash = null;
|
||||
}
|
||||
|
||||
function runAll() {
|
||||
if (S.checking) return;
|
||||
S.checking = true; viewBundles();
|
||||
var n = 0, bar = $('#runbar'), txt = $('#runtxt');
|
||||
var timer = setInterval(function () {
|
||||
n += 3; if (n > 27) n = 27;
|
||||
bar.firstElementChild.style.width = (n / 27 * 100) + '%'; bar.setAttribute('aria-valuenow', n); txt.textContent = '앱 확인 중 ' + n + '/27 묶음';
|
||||
if (n >= 27) {
|
||||
clearInterval(timer); S.checking = false; S.checkedAt = '14:36';
|
||||
rows.forEach(function (r) { r.stale = false; r.staleHuman = false; });
|
||||
toast('앱 확인을 마쳤다. 결과는 요약 띠에 반영되었다.'); viewBundles();
|
||||
}
|
||||
}, 120);
|
||||
}
|
||||
|
||||
function viewEvidence(idx) {
|
||||
var r = rows[idx]; if (!r) { location.hash = '#/bundles'; return; }
|
||||
setWhere('S05 · 옥성교P1 · 근거 보기');
|
||||
var ev = evidence(r), s = ST[r.st];
|
||||
var order = sorted();
|
||||
var pos = order.indexOf(r);
|
||||
var next = null;
|
||||
for (var k = 1; k <= order.length; k++) { var cand = order[(pos + k) % order.length]; if (!cand.human && cand !== r) { next = cand; break; } }
|
||||
var html = '<div class="head"><span class="eyebrow">2단계 · 입력 묶음</span><h1>근거 보기</h1></div><div class="split"><aside class="card" style="padding:0" aria-label="묶음 이동"><h3 style="padding:1rem 1rem 0.5rem">묶음 이동</h3><ul class="list">';
|
||||
order.forEach(function (o) {
|
||||
html += '<li class="' + (o === r ? 'cur' : '') + '"><a href="#/evidence/' + o.i + '"' + (o === r ? ' aria-current="true"' : '') + '>' + esc(o.key) + '</a>' + stBadge(o) + '</li>';
|
||||
});
|
||||
html += '</ul><div class="row" style="padding:0.75rem 1rem"><a class="btn sm" href="#/bundles">목록으로</a></div></aside><div class="stack">';
|
||||
var concl = r.stale ? esc(r.key) + ' — 값이 바뀌었다. 이전 확인과 확정이 무효가 되었다.' :
|
||||
esc(r.key) + ' — 앱 확인 ' + s[2] + '. ' + (r.st === 'g' ? '근거를 읽고 확정한다.' : '사람 확정 전에 사유가 필요하다.');
|
||||
html += '<section class="strip"><p class="concl">' + concl + '</p><dl class="kv"><dt>변수 등급</dt><dd>' + badge(GRADE[r.g][0], '', GRADE[r.g][1]) + '</dd><dt>앱 확인</dt><dd>' + stBadge(r) + '</dd><dt>사람 확정</dt><dd>' +
|
||||
(r.human ? humanBadge(r) + ' ' + esc(r.human.by) + ' · ' + esc(r.human.at) + (r.human.why ? ' · 사유: ' + esc(r.human.why) : '') : '<span class="b dash">미확정</span>') + '</dd><dt>검사</dt><dd>v0.1 · ' + S.checkedAt + '</dd></dl></section>';
|
||||
html += '<div class="tw"><table class="t"><caption>앱 확인 근거 C1 ~ C8</caption><thead><tr><th scope="col">검사</th><th scope="col">결과</th><th scope="col">근거</th><th scope="col">도면 위치</th></tr></thead><tbody>';
|
||||
ev.forEach(function (e) { var q = EVST[e.st]; html += '<tr><td style="white-space:nowrap"><b>' + e.id + '</b> ' + e.name + '</td><td>' + (e.st === 'x' ? '—' : badge(q[0], q[1], q[2])) + '</td><td style="max-width:36rem">' + esc(e.text) + '</td><td>' + esc(e.pos) + '</td></tr>'; });
|
||||
html += '</tbody></table></div><div class="banner tip"><span class="m" aria-hidden="true">◆</span><div>일치는 후보를 만든 규칙과 값이 같다는 뜻이다. 도면이 맞다는 판정이 아니다.</div></div>';
|
||||
if (r.st === 'b' || r.st === 'w') {
|
||||
html += '<section class="card"><h3>값 비교</h3><table class="t"><caption class="cap">다른 항목만 표시</caption><thead><tr><th scope="col">항목</th><th class="num" scope="col">현재</th><th class="num" scope="col">기준</th><th scope="col">차이</th></tr></thead><tbody>' +
|
||||
(r.st === 'b' ? '<tr><td>스터럽 곳 수 합(개)</td><td class="num">104</td><td class="num">106</td><td>' + badge('warn', '▲', '다름') + '</td></tr>' : '<tr><td>표시된 항목</td><td class="num">1.1</td><td class="num">0</td><td>' + badge('warn', '▲', '다름') + '</td></tr>') + '</tbody></table></section>';
|
||||
}
|
||||
if (!r.human) {
|
||||
html += '<section class="card"><h3>확정</h3><div class="field"><label for="by">확정자<span class="req">필수</span></label><input id="by" type="text" value="' + esc(S.by) + '" style="max-width:20rem"></div>' +
|
||||
'<div class="field" id="whyf"><label for="why">사유<span class="req">' + (r.st === 'g' ? '선택' : '주의·불일치·근거 없음 확정에는 필수') + '</span></label><textarea id="why" rows="3"></textarea><span class="err" id="err" hidden></span></div>' +
|
||||
'<div class="row"><button class="btn primary" type="button" id="conf">확정</button>' + (next ? '<a class="btn" href="#/evidence/' + next.i + '">다음 미확정 묶음</a>' : '') + '</div></section>';
|
||||
}
|
||||
html += '</div></div>';
|
||||
main.innerHTML = html;
|
||||
var cb = $('#conf');
|
||||
if (cb) cb.onclick = function () {
|
||||
var by = $('#by').value.trim(), why = $('#why').value.trim(), err = $('#err'), f = $('#whyf');
|
||||
function fail(m) { err.hidden = false; err.innerHTML = '<span aria-hidden="true">■</span>' + esc(m); f.classList.add('bad'); }
|
||||
f.classList.remove('bad'); err.hidden = true;
|
||||
if (!by) { fail('확정자 이름이 비어 있다. 확정은 사람의 기록이다. 이름을 입력한다'); $('#by').focus(); return; }
|
||||
if (r.st !== 'g' && !why) { fail('사유가 비어 있다. ' + ST[r.st][2] + ' 묶음은 사유가 있어야 확정된다. 사유를 입력한다'); $('#why').focus(); return; }
|
||||
S.by = by; r.human = { by: by, at: '14:41', why: why };
|
||||
toast(r.key + ' 확정을 기록했다. 확정자 ' + by + '.'); viewEvidence(idx);
|
||||
};
|
||||
}
|
||||
|
||||
function viewCandidates() {
|
||||
setWhere('S07 · 옥성교P1 · 표 후보값');
|
||||
var diffN = CAND.filter(function (c) { return c.cur !== c.cand; }).length;
|
||||
var html = '<div class="head"><span class="eyebrow">2단계 · 입력 묶음</span><h1>표 후보값·표 밖 치수</h1><p class="lead">도면 표와 표 밖 치수에서 찾은 후보를 현재 값과 비교한다. 반영은 선택한 행에만 적용되고 자동 반영은 없다.</p></div>';
|
||||
html += '<section class="strip"><p class="concl">후보 ' + CAND.length + '건 — 다름 ' + diffN + '건, 근거 없음 3건</p><div class="counts"><span class="b">표 2</span><span class="b">표 밖 치수 6</span><span class="b dash">등급 C 1</span></div></section>';
|
||||
html += '<div class="banner tip"><span class="m" aria-hidden="true">◆</span><div>params와 expected에 같은 후보를 함께 반영하면 검사의 독립성이 사라진다. 반영 대상 줄은 하나씩 고른다.</div></div>';
|
||||
html += '<div class="tw"><table class="t"><caption>후보 비교 — 선택 ' + Object.keys(S.sel).filter(function (k) { return S.sel[k]; }).length + '건</caption><thead><tr><th scope="col">반영</th><th scope="col">대상</th><th scope="col">줄</th><th class="num" scope="col">현재</th><th class="num" scope="col">후보</th><th scope="col">구분</th><th scope="col">규칙</th><th scope="col">상태</th><th scope="col">신뢰</th><th scope="col">도면 위치</th></tr></thead><tbody>';
|
||||
CAND.forEach(function (c) {
|
||||
var d = c.cur !== c.cand;
|
||||
html += '<tr' + (S.sel[c.id] ? ' class="sel"' : '') + '><td><input type="checkbox" data-c="' + c.id + '"' + (S.sel[c.id] ? ' checked' : '') + ' aria-label="' + esc(c.target) + ' 반영" style="width:1.25rem;height:1.25rem;accent-color:var(--prism-text-heading)"></td><td><b>' + esc(c.target) + '</b></td><td>' + c.file + '</td><td class="num">' + fmt(c.cur) + '</td><td class="num">' + fmt(c.cand) + '</td><td>' + c.kind + '</td><td>' + c.rule + '</td><td>' +
|
||||
(d ? badge('warn', '▲', '다름') : '같음') + '</td><td>' + badge(TRUST[c.trust][0], c.trust === 'B' ? '▲' : '', TRUST[c.trust][1]) + '</td><td><span class="chip">' + esc(c.pos) + '</span></td></tr>';
|
||||
});
|
||||
html += '</tbody></table></div><div class="field" id="cwhy" hidden><label for="cw">반영 사유<span class="req">등급 C 반영에는 필수</span></label><input id="cw" type="text" style="max-width:36rem"></div><span class="err" id="cerr" hidden></span>';
|
||||
html += '<div class="row"><button class="btn primary" type="button" id="apply">후보 반영</button></div>';
|
||||
main.innerHTML = html;
|
||||
function needWhy() { return CAND.some(function (c) { return S.sel[c.id] && c.trust === 'C'; }); }
|
||||
$('#cwhy').hidden = !needWhy();
|
||||
main.querySelectorAll('[data-c]').forEach(function (cb) { cb.onchange = function () { S.sel[cb.getAttribute('data-c')] = cb.checked; var y = window.scrollY; viewCandidates(); window.scrollTo(0, y); $('[data-c="' + cb.getAttribute('data-c') + '"]').focus(); }; });
|
||||
$('#apply').onclick = function () {
|
||||
var picks = CAND.filter(function (c) { return S.sel[c.id]; }), err = $('#cerr');
|
||||
function fail(m) { err.hidden = false; err.innerHTML = '<span aria-hidden="true">■</span>' + esc(m); }
|
||||
err.hidden = true;
|
||||
if (!picks.length) { fail('선택한 행이 없다. 반영할 후보가 없다. 행을 선택한다'); return; }
|
||||
var pairs = {}, bad = null;
|
||||
picks.forEach(function (c) { if (c.pair) { if (pairs[c.pair] && pairs[c.pair] !== c.file) bad = c; pairs[c.pair] = c.file; } });
|
||||
if (bad) { fail('같은 후보를 params와 expected에 함께 반영할 수 없다. 두 줄의 값이 같은 근거에서 나오면 검사가 무의미해진다. 한 줄만 선택한다'); return; }
|
||||
if (needWhy() && !$('#cw').value.trim()) { fail('반영 사유가 비어 있다. 등급 C 후보는 사유가 있어야 반영된다. 사유를 입력한다'); $('#cw').focus(); return; }
|
||||
var touched = {};
|
||||
picks.forEach(function (c) { c.cur = c.cand; touched[c.bundle] = true; });
|
||||
rows.forEach(function (r) { if (touched[r.key]) { r.stale = true; if (r.human) r.staleHuman = true; r.human = null; } });
|
||||
S.sel = {};
|
||||
toast(picks.length + '건을 반영했다. 영향받은 묶음 ' + Object.keys(touched).length + '개의 앱 확인과 확정이 무효가 되었다.');
|
||||
viewCandidates();
|
||||
};
|
||||
}
|
||||
|
||||
/* ---------- bulk confirm (S10) ---------- */
|
||||
var lastFocus = null;
|
||||
function closeModal() { $('#modalHost').innerHTML = ''; document.removeEventListener('keydown', trap, true); if (lastFocus) lastFocus.focus(); }
|
||||
function trap(e) {
|
||||
var m = $('#modalHost .modal'); if (!m) return;
|
||||
if (e.key === 'Escape') { e.preventDefault(); closeModal(); return; }
|
||||
if (e.key !== 'Tab') return;
|
||||
var f = m.querySelectorAll('button, [href], input, select, textarea, summary, [tabindex="0"]');
|
||||
if (!f.length) return;
|
||||
var first = f[0], last = f[f.length - 1];
|
||||
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
|
||||
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
|
||||
}
|
||||
function openBulk() {
|
||||
lastFocus = document.activeElement;
|
||||
var targets = rows.filter(function (r) { return r.st === 'g' && !r.orig && !r.human && !r.stale; });
|
||||
var ex = rows.filter(function (r) { return !(r.st === 'g' && !r.orig) && !r.human || r.stale; });
|
||||
var read = false;
|
||||
var html = '<div class="scrim"><div class="modal" role="dialog" aria-modal="true" aria-labelledby="mt"><header><h2 id="mt">일치 묶음 확정</h2><button class="btn quiet sm" type="button" id="mx" aria-label="닫기">닫기</button></header>';
|
||||
if (!targets.length) {
|
||||
html += '<p>확정할 일치 묶음이 없다. 모든 일치 묶음이 확정되었거나 재확인이 필요하다.</p><div class="row end"><button class="btn" type="button" id="mc">닫기</button></div></div></div>';
|
||||
$('#modalHost').innerHTML = html; $('#mx').onclick = $('#mc').onclick = closeModal; document.addEventListener('keydown', trap, true); $('#mx').focus(); return;
|
||||
}
|
||||
html += '<p class="concl" style="font-size:var(--prism-type-body-lg);font-weight:700;color:var(--prism-text-heading)">일치 ' + targets.length + '개를 확정한다. 주의·불일치·근거 없음·변경됨 ' + ex.length + '개는 대상이 아니다.</p>' +
|
||||
'<div><h3>근거표 <span class="cap muted">끝까지 내려 확인한다</span></h3><div class="scroll" id="sc" tabindex="0" role="region" aria-label="일치 묶음 근거표"><table class="t"><caption class="cap" style="position:absolute;left:-999rem">일치 묶음 근거</caption><thead><tr><th scope="col">묶음</th><th scope="col">근거</th><th scope="col">출처</th></tr></thead><tbody>';
|
||||
targets.forEach(function (r) { html += '<tr><td><b>' + esc(r.key) + '</b></td><td>' + esc(evidence(r)[1].text) + '</td><td>' + esc(r.src) + '</td></tr>'; });
|
||||
html += '</tbody></table></div></div><details class="card sunken" style="padding:0 1rem"><summary style="min-height:2.75rem;display:flex;align-items:center;cursor:pointer;font-weight:700">제외된 묶음 ' + ex.length + '개 — 누르면 근거 보기로 이동</summary><table class="t" style="margin-bottom:1rem"><caption class="cap" style="position:absolute;left:-999rem">제외된 묶음</caption><thead><tr><th scope="col">묶음</th><th scope="col">사유</th></tr></thead><tbody>';
|
||||
ex.forEach(function (r) { html += '<tr><td><a href="#/evidence/' + r.i + '" data-close="1">' + esc(r.key) + '</a></td><td>' + stBadge(r) + (r.orig ? ' 원본값. 새 도면 근거가 없다' : '') + '</td></tr>'; });
|
||||
html += '</tbody></table></details><div class="field"><label for="mby">확정자<span class="req">필수</span></label><input id="mby" type="text" value="' + esc(S.by) + '" style="max-width:20rem"><span class="help">확정 기록에는 입력한 이름만 남는다. 앱은 확정하지 않는다</span></div>' +
|
||||
'<div class="err" id="merr" hidden></div><div class="row end"><button class="btn" type="button" id="mc">취소</button><button class="btn primary" type="button" id="mok">확정</button></div></div></div>';
|
||||
$('#modalHost').innerHTML = html;
|
||||
var sc = $('#sc');
|
||||
function chk() { if (sc.scrollTop + sc.clientHeight >= sc.scrollHeight - 2) read = true; }
|
||||
sc.addEventListener('scroll', chk); chk();
|
||||
$('#mx').onclick = $('#mc').onclick = closeModal;
|
||||
$('#modalHost').querySelectorAll('[data-close]').forEach(function (a) { a.onclick = function () { closeModal(); }; });
|
||||
$('#mok').onclick = function () {
|
||||
var err = $('#merr'), by = $('#mby').value.trim();
|
||||
function fail(m, el) { err.hidden = false; err.innerHTML = '<span aria-hidden="true">■</span>' + esc(m); if (el) el.focus(); }
|
||||
if (!read) { fail('근거표를 읽지 않았다. 일치 묶음의 근거를 읽어야 확정할 수 있다. 근거표를 끝까지 내린다', sc); return; }
|
||||
if (!by) { fail('확정자 이름이 비어 있다. 확정은 사람의 기록이다. 이름을 입력한다', $('#mby')); return; }
|
||||
S.by = by;
|
||||
targets.forEach(function (r) { r.human = { by: by, at: '14:41', why: '' }; });
|
||||
S.flash = targets.length + '개를 ' + by + '이(가) 확정했다. 확정 기록에는 입력한 이름만 남았다.';
|
||||
closeModal(); route();
|
||||
};
|
||||
document.addEventListener('keydown', trap, true); $('#mx').focus();
|
||||
}
|
||||
|
||||
|
||||
/* ---------- S02 new pier fit (sample: Ungok P1 drawings uploaded) ---------- */
|
||||
var QS = [['foundation', '기초 형식(말뚝 유무)'], ['soffit', '코핑 하면 형상'], ['bearings', '받침 개수']];
|
||||
var OPTS = { foundation: ['기초판+말뚝', '기초판만', '말뚝만'], soffit: ['곡선', '직선 경사', '나팔형'], bearings: ['2', '4', '10'] };
|
||||
var TPLS = [
|
||||
{ n: '운곡교P1형', p: { foundation: '기초판+말뚝', soffit: '곡선', bearings: '10' } },
|
||||
{ n: '옥성교P1형', p: { foundation: '기초판+말뚝', soffit: '직선 경사', bearings: '10' } },
|
||||
{ n: '새재1교P1형', p: { foundation: '기초판만', soffit: '직선 경사', bearings: '10' } },
|
||||
{ n: 'Ramp-A교P1형', p: { foundation: '말뚝만', soffit: '나팔형', bearings: '2' } },
|
||||
{ n: '산외교울산P1형', p: { foundation: '기초판만', soffit: '직선 경사', bearings: '4' } }
|
||||
];
|
||||
var EST = {
|
||||
foundation: ['기초판+말뚝', "'말뚝' 문자 7개: D1 CS-TEXT (복합말뚝)", '보통'],
|
||||
soffit: ['곡선', "R 12000 mm: D1 CS-DIMT (8257, 16513) 'R=12.000'", '보통'],
|
||||
bearings: ['10', 'ELEVATION TABLE 행 10개: D2 (26179, 9342)', '보통']
|
||||
};
|
||||
var N = { up: false, ans: null, tpl: '운곡교P1형', name: '', rs: '미확인', rm: '미확인', skew: '', brot: '', frot: '', err: '' };
|
||||
function nMatch(t) { return QS.filter(function (q) { return t.p[q[0]] === EST[q[0]][0]; }).length; }
|
||||
function nBad() {
|
||||
var t = TPLS.filter(function (x) { return x.n === N.tpl; })[0], bad = [];
|
||||
QS.forEach(function (q) { if (t.p[q[0]] !== N.ans[q[0]]) bad.push(q[1]); });
|
||||
if (N.rs === '아니오') bad.push('철근 형상 종류');
|
||||
if (N.rm === '아니오') bad.push('철근 생성 방식');
|
||||
return bad;
|
||||
}
|
||||
function viewNew() {
|
||||
setWhere('S02 · 새 교각 적합성 판별');
|
||||
var html = '<div class="head"><span class="eyebrow">새 교각</span><h1>도면으로 템플릿 추천</h1><p class="lead">도면을 먼저 올리면 앱이 가장 비슷한 템플릿을 추천한다. 추천은 제안이고, 6문항은 사람이 확인한다.</p></div>';
|
||||
if (!N.up) {
|
||||
html += '<section class="card" style="max-width:38em"><div class="field"><label for="dw">교각 도면(DWG, 여러 장)</label><input id="dw" type="file" accept=".dwg" multiple><span class="help">시안에서는 운곡교 P1 도면 3장을 올린 것으로 본다</span></div><div class="row"><button class="btn primary" type="button" id="up">도면 읽고 추천 받기</button></div></section>';
|
||||
main.innerHTML = html;
|
||||
$('#up').onclick = function () { N.up = true; N.ans = {}; QS.forEach(function (q) { N.ans[q[0]] = EST[q[0]][0]; }); viewNew(); };
|
||||
return;
|
||||
}
|
||||
var ranked = TPLS.slice().sort(function (a, b) { return nMatch(b) - nMatch(a); });
|
||||
html += '<section class="strip"><p class="concl">도면 3장, 문자 2,366개를 읽었다 — 1위 ' + ranked[0].n + ' (3 / 3 일치)</p></section>';
|
||||
html += '<h2>1 추천 순위</h2><div class="tw"><table class="t"><caption>추정한 3문항과 일치한 수</caption><thead><tr><th scope="col">순위</th><th scope="col">템플릿</th><th class="num" scope="col">일치</th>' + QS.map(function (q) { return '<th scope="col">' + q[1] + '</th>'; }).join('') + '</tr></thead><tbody>';
|
||||
ranked.forEach(function (t, i) {
|
||||
html += '<tr><td class="num">' + (i + 1) + '</td><td><b>' + t.n + '</b></td><td class="num">' + nMatch(t) + ' / 3</td>' + QS.map(function (q) {
|
||||
var ok = t.p[q[0]] === EST[q[0]][0];
|
||||
return '<td>' + esc(t.p[q[0]]) + ' ' + (ok ? badge('good', '●', '일치') : badge('bad', '■', '다름')) + '</td>';
|
||||
}).join('') + '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
html += '<h2>2 추정 근거</h2><div class="tw"><table class="t"><thead><tr><th scope="col">문항</th><th scope="col">추정</th><th scope="col">근거(도면 위치)</th><th scope="col">신뢰</th></tr></thead><tbody>';
|
||||
QS.forEach(function (q) { var e = EST[q[0]]; html += '<tr><td>' + q[1] + '</td><td><b>' + e[0] + '</b></td><td><span class="chip">' + esc(e[1]) + '</span></td><td>' + e[2] + '</td></tr>'; });
|
||||
html += '<tr><td>기둥 단면·철근 형상·철근 생성 방식</td><td>' + badge('dash', '◇', '판단 불가') + '</td><td>도면 문자로 판별할 근거가 없다. 사람이 답한다</td><td>-</td></tr></tbody></table></div>';
|
||||
html += '<div class="banner tip"><span class="m" aria-hidden="true">◆</span><div>코핑 하면 형상은 정면도의 큰 R 문자(10 m 이상)만 본다. 평면 모서리의 작은 R은 제외한다. 평면의 사각·직각과 각도는 문항이 아니라 아래 값으로 입력한다.</div></div>';
|
||||
html += '<h2>3 6문항 확인 후 교각 만들기</h2><div style="max-width:38em;display:flex;flex-direction:column;gap:var(--prism-space-200)">';
|
||||
QS.forEach(function (q) {
|
||||
html += '<div class="field"><label for="a_' + q[0] + '">' + q[1] + '</label><select id="a_' + q[0] + '" data-a="' + q[0] + '">' + OPTS[q[0]].map(function (o) { return '<option' + (N.ans[q[0]] === o ? ' selected' : '') + '>' + o + '</option>'; }).join('') + '</select></div>';
|
||||
});
|
||||
[['rs', '철근 형상 종류가 템플릿의 재료표 형식 열과 같은가'], ['rm', '철근 생성 방식(규칙 값 / 철근별 중심선)이 같은가']].forEach(function (q) {
|
||||
html += '<div class="field"><label for="' + q[0] + '">' + q[1] + '</label><select id="' + q[0] + '" data-r="' + q[0] + '">' + ['미확인', '예', '아니오'].map(function (o) { return '<option' + (N[q[0]] === o ? ' selected' : '') + '>' + o + '</option>'; }).join('') + '</select></div>';
|
||||
});
|
||||
html += '<fieldset class="card sunken"><legend class="lbl">각도 값 확인 <span class="muted">템플릿을 가르지 않는 값. 비워도 된다</span></legend><div class="grid3">' +
|
||||
[['skew', '코핑 평면 skew (°)'], ['brot', '받침 회전 (°)'], ['frot', '기초 회전 (°)']].map(function (q) { return '<div class="field"><label for="g_' + q[0] + '">' + q[1] + '</label><input id="g_' + q[0] + '" type="text" inputmode="decimal" data-g="' + q[0] + '" value="' + esc(N[q[0]]) + '"></div>'; }).join('') + '</div></fieldset>';
|
||||
var bad = nBad();
|
||||
html += '<section class="strip" role="status" aria-live="polite"><h3 style="margin:0 0 .25rem">결과 판정</h3><p class="concl">' + (bad.length ? badge('bad', '■', '같은 템플릿으로 만들 수 없음') + ' 다른 문항: ' + esc(bad.join(', ')) : badge('good', '●', '이 템플릿으로 만들 수 있음')) + '</p>' +
|
||||
(bad.length ? '<p class="muted" style="margin:.25rem 0 0">가장 가까운 템플릿에 생성기·검사기를 추가해야 한다. 그래도 진행하면 교각 이름 옆과 모든 화면 상단에 형식 불일치 경고가 계속 표시된다(닫을 수 없다).</p>' : '') + '</section>';
|
||||
html += '<div class="field' + (N.err ? ' bad' : '') + '"><label for="nm">교각 이름<span class="req">필수</span></label><input id="nm" type="text" value="' + esc(N.name) + '" style="max-width:20rem">' + (N.err ? '<span class="err"><span aria-hidden="true">■</span>' + esc(N.err) + '</span>' : '') + '</div>';
|
||||
html += '<div class="field"><label for="tp">템플릿</label><select id="tp" style="max-width:20rem">' + ranked.map(function (t, i) { return '<option value="' + t.n + '"' + (N.tpl === t.n ? ' selected' : '') + '>' + (i + 1) + '위 ' + t.n + '</option>'; }).join('') + '</select></div>';
|
||||
html += '<div class="row"><button class="btn primary" type="button" id="mk">' + (bad.length ? '그래도 만들기' : '교각 만들기') + '</button></div></div>';
|
||||
main.innerHTML = html;
|
||||
var y = window.scrollY;
|
||||
main.querySelectorAll('[data-a]').forEach(function (e) { e.onchange = function () { N.ans[e.getAttribute('data-a')] = e.value; viewNew(); window.scrollTo(0, y); $('#' + e.id).focus(); }; });
|
||||
main.querySelectorAll('[data-r]').forEach(function (e) { e.onchange = function () { N[e.getAttribute('data-r')] = e.value; viewNew(); window.scrollTo(0, y); $('#' + e.id).focus(); }; });
|
||||
main.querySelectorAll('[data-g]').forEach(function (e) { e.oninput = function () { N[e.getAttribute('data-g')] = e.value; }; });
|
||||
$('#nm').oninput = function () { N.name = this.value; };
|
||||
$('#tp').onchange = function () { N.tpl = this.value; viewNew(); window.scrollTo(0, y); $('#tp').focus(); };
|
||||
$('#mk').onclick = function () {
|
||||
N.name = $('#nm').value.trim();
|
||||
if (!N.name) { N.err = '교각 이름이 비어 있다. 폴더를 만들 이름이 필요하다. 이름을 입력한다'; viewNew(); $('#nm').focus(); return; }
|
||||
N.err = '';
|
||||
toast("교각 '" + N.name + "'을(를) 만들었다. 올린 도면이 교각 폴더로 옮겨졌고 6문항 답과 각도 값이 저장되었다." + (bad.length ? ' 형식 불일치 경고가 표시된다.' : ''));
|
||||
viewNew();
|
||||
};
|
||||
}
|
||||
|
||||
/* ---------- router ---------- */
|
||||
function route() {
|
||||
var h = location.hash || '#/bundles', m;
|
||||
$('#nav-bundles').classList.toggle('cur', h.indexOf('#/bundles') === 0 || h.indexOf('#/evidence') === 0);
|
||||
$('#nav-cand').style.fontWeight = h.indexOf('#/candidates') === 0 ? '700' : '500';
|
||||
$('#nav-new').style.fontWeight = h.indexOf('#/new') === 0 ? '700' : '500';
|
||||
if ((m = h.match(/^#\/evidence\/(\d+)/))) viewEvidence(+m[1]);
|
||||
else if (h.indexOf('#/candidates') === 0) viewCandidates();
|
||||
else if (h.indexOf('#/new') === 0) viewNew();
|
||||
else viewBundles();
|
||||
if (document.activeElement === document.body) main.focus();
|
||||
}
|
||||
window.addEventListener('hashchange', function () { window.scrollTo(0, 0); route(); });
|
||||
route();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,182 @@
|
||||
/* PierProof — Prism app profile tokens (design.md 3.2 / 3.3) and components. Literals live only in :root. */
|
||||
:root {
|
||||
--prism-bg-canvas: #F4F3F0; --prism-bg-card: #FFFFFF;
|
||||
--prism-text-body: #474235; --prism-text-heading: #1F524B; --prism-text-subtle: #474235; --prism-text-accent: #D05C26;
|
||||
--prism-border-default: #474235; --prism-border-strong: #1F524B;
|
||||
--prism-status-do: #005A4E; --prism-status-do-bg: #DCEAE6;
|
||||
--prism-status-dont: #8F2A11; --prism-status-dont-bg: #F6DFDA;
|
||||
--prism-status-caution: #6B4A00; --prism-status-caution-bg: #F5E7CC;
|
||||
--prism-status-tip: #0A4E86; --prism-status-tip-bg: #DDE8F3;
|
||||
--prism-space-025: 0.125rem; --prism-space-050: 0.25rem; --prism-space-100: 0.5rem; --prism-space-150: 0.75rem;
|
||||
--prism-space-200: 1rem; --prism-space-300: 1.5rem; --prism-space-400: 2rem; --prism-space-600: 3rem; --prism-space-800: 4rem;
|
||||
--prism-type-caption: 0.8125rem; --prism-lh-caption: 1.55; --prism-type-body-sm: 0.875rem; --prism-lh-body: 1.7;
|
||||
--prism-type-body: 1rem; --prism-type-body-lg: 1.125rem; --prism-type-title-sm: 1.25rem; --prism-lh-title: 1.5;
|
||||
--prism-type-title: 1.625rem; --prism-type-title-lg: 2rem;
|
||||
--prism-radius-sm: 4px; --prism-radius-md: 8px; --prism-radius-lg: 12px; --prism-radius-pill: 9999px;
|
||||
--prism-stroke-hairline: 1px; --prism-stroke-regular: 2px; --prism-stroke-bold: 3px;
|
||||
--prism-z-raised: 100; --prism-z-sticky: 200; --prism-z-scrim: 400; --prism-z-modal: 500;
|
||||
--prism-scrim: rgba(0, 0, 0, 0.32);
|
||||
--prism-motion-fast: 100ms; --prism-ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--prism-focus-ring: 3px; --prism-focus-offset: 2px; --prism-measure-max: 38em;
|
||||
--prism-font: Pretendard, -apple-system, BlinkMacSystemFont, "Noto Sans KR", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
|
||||
|
||||
--prism-comp-line: var(--prism-border-default); --prism-comp-line-strong: var(--prism-border-strong);
|
||||
--prism-comp-state-good-fg: var(--prism-status-do); --prism-comp-state-good-bg: var(--prism-status-do-bg);
|
||||
--prism-comp-state-bad-fg: var(--prism-status-dont); --prism-comp-state-bad-bg: var(--prism-status-dont-bg);
|
||||
--prism-comp-state-warn-fg: var(--prism-status-caution); --prism-comp-state-warn-bg: var(--prism-status-caution-bg);
|
||||
--prism-comp-state-info-fg: var(--prism-status-tip); --prism-comp-state-info-bg: var(--prism-status-tip-bg);
|
||||
--prism-comp-rail-width: 15rem; --prism-comp-header-height: 3.5rem; --prism-comp-hit-min: 2.75rem;
|
||||
--prism-comp-table-head-bg: var(--prism-bg-canvas);
|
||||
--prism-comp-on-emphasis: var(--prism-bg-card);
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
--prism-bg-canvas: #111111; --prism-bg-card: #111111; --prism-text-body: #F4F3F0; --prism-text-heading: #F4F3F0;
|
||||
--prism-text-subtle: #D3D3D3; --prism-border-default: #D3D3D3; --prism-border-strong: #F4F3F0;
|
||||
--prism-status-do: #DCEAE6; --prism-status-do-bg: #005A4E; --prism-status-dont: #F6DFDA; --prism-status-dont-bg: #8F2A11;
|
||||
--prism-status-caution: #F5E7CC; --prism-status-caution-bg: #6B4A00; --prism-status-tip: #DDE8F3; --prism-status-tip-bg: #0A4E86;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: var(--prism-bg-canvas); color: var(--prism-text-body); font-family: var(--prism-font);
|
||||
font-size: var(--prism-type-body-sm); line-height: var(--prism-lh-body); font-variant-numeric: tabular-nums; }
|
||||
a { color: var(--prism-text-heading); }
|
||||
h1, h2, h3, p, dl, dd, ol, ul { margin: 0; padding: 0; }
|
||||
:focus-visible { outline: var(--prism-focus-ring) solid var(--prism-comp-line-strong); outline-offset: var(--prism-focus-offset); }
|
||||
.skip { position: absolute; left: -999rem; }
|
||||
|
||||
/* shell */
|
||||
.app { min-height: 100%; display: flex; flex-direction: column; position: relative; }
|
||||
.top { height: var(--prism-comp-header-height); background: var(--prism-bg-card); border-bottom: var(--prism-stroke-hairline) solid var(--prism-comp-line);
|
||||
display: flex; align-items: center; gap: var(--prism-space-300); padding-inline: var(--prism-space-300); position: sticky; top: 0; z-index: var(--prism-z-sticky); }
|
||||
.brand { display: flex; align-items: center; gap: var(--prism-space-100); font-weight: 700; font-size: var(--prism-type-body-lg); color: var(--prism-text-heading); letter-spacing: -0.01em; }
|
||||
.brand i { width: 0.7rem; height: 0.7rem; background: var(--prism-text-accent); transform: rotate(45deg); display: block; }
|
||||
.top .where { font-size: var(--prism-type-caption); font-weight: 500; }
|
||||
.top .meta { margin-left: auto; display: flex; gap: var(--prism-space-300); font-size: var(--prism-type-caption); }
|
||||
.body { display: flex; flex: 1; min-height: 0; }
|
||||
.rail { width: var(--prism-comp-rail-width); flex: none; background: var(--prism-bg-card); border-right: var(--prism-stroke-hairline) solid var(--prism-comp-line);
|
||||
padding: var(--prism-space-300) var(--prism-space-200); display: flex; flex-direction: column; gap: var(--prism-space-200); }
|
||||
.rail .cap { font-size: var(--prism-type-caption); font-weight: 500; letter-spacing: 0.06em; }
|
||||
.rail .pier { font-weight: 700; font-size: var(--prism-type-body); color: var(--prism-text-heading); }
|
||||
.rail ol { list-style: none; display: flex; flex-direction: column; gap: var(--prism-space-050); }
|
||||
.step { display: flex; align-items: center; gap: var(--prism-space-150); min-height: var(--prism-comp-hit-min); padding: 0 var(--prism-space-100);
|
||||
border-radius: var(--prism-radius-sm); color: var(--prism-text-body); text-decoration: none; }
|
||||
.step .n { width: 1.5rem; height: 1.5rem; flex: none; border: var(--prism-stroke-regular) dashed var(--prism-comp-line); border-radius: var(--prism-radius-pill);
|
||||
display: grid; place-items: center; font-size: var(--prism-type-caption); font-weight: 700; }
|
||||
.step.done .n { background: var(--prism-text-heading); color: var(--prism-comp-on-emphasis); border: var(--prism-stroke-regular) solid var(--prism-text-heading); }
|
||||
.step.cur { background: var(--prism-bg-canvas); font-weight: 700; }
|
||||
.step.cur .n { border: var(--prism-stroke-bold) solid var(--prism-comp-line-strong); }
|
||||
.rail .sub { display: flex; flex-direction: column; margin-left: 2.1rem; }
|
||||
.rail .sub a { min-height: 2rem; display: flex; align-items: center; font-size: var(--prism-type-caption); color: var(--prism-text-body); }
|
||||
.rail hr { border: 0; border-top: var(--prism-stroke-hairline) solid var(--prism-comp-line); width: 100%; margin: 0; }
|
||||
.main { flex: 1; min-width: 0; padding: var(--prism-space-400); display: flex; flex-direction: column; gap: var(--prism-space-300); }
|
||||
.main > .head { display: flex; flex-direction: column; gap: var(--prism-space-100); }
|
||||
h1 { font-size: var(--prism-type-title); line-height: var(--prism-lh-title); color: var(--prism-text-heading); font-weight: 700; }
|
||||
h2 { font-size: var(--prism-type-title-sm); line-height: var(--prism-lh-title); font-weight: 500; color: var(--prism-text-heading); }
|
||||
h3 { font-size: var(--prism-type-body-lg); font-weight: 700; color: var(--prism-text-heading); }
|
||||
.eyebrow { font-size: var(--prism-type-caption); font-weight: 500; letter-spacing: 0.06em; }
|
||||
.lead { max-width: var(--prism-measure-max); font-size: var(--prism-type-body); }
|
||||
.cap { font-size: var(--prism-type-caption); }
|
||||
.muted { color: var(--prism-text-subtle); }
|
||||
.row { display: flex; gap: var(--prism-space-200); align-items: center; flex-wrap: wrap; }
|
||||
.row.end { justify-content: flex-end; }
|
||||
.stack { display: flex; flex-direction: column; gap: var(--prism-space-200); }
|
||||
.grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--prism-space-300); }
|
||||
.grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--prism-space-300); }
|
||||
.split { display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: var(--prism-space-300); align-items: start; }
|
||||
.grow { flex: 1; }
|
||||
|
||||
/* scope, card, strip, banner */
|
||||
.scope { background: var(--prism-bg-card); border: var(--prism-stroke-hairline) solid var(--prism-comp-line); padding: var(--prism-space-150) var(--prism-space-200);
|
||||
display: flex; gap: var(--prism-space-400); font-size: var(--prism-type-body-sm); border-radius: var(--prism-radius-sm); }
|
||||
.scope b { font-weight: 700; }
|
||||
.card { background: var(--prism-bg-card); border: var(--prism-stroke-hairline) solid var(--prism-comp-line); border-radius: var(--prism-radius-md); padding: var(--prism-space-200);
|
||||
display: flex; flex-direction: column; gap: var(--prism-space-150); }
|
||||
.card.sunken { background: var(--prism-bg-canvas); }
|
||||
.strip { background: var(--prism-bg-card); border: var(--prism-stroke-regular) solid var(--prism-comp-line-strong); border-radius: var(--prism-radius-md);
|
||||
padding: var(--prism-space-200) var(--prism-space-300); display: flex; flex-direction: column; gap: var(--prism-space-150); }
|
||||
.strip .concl { font-size: var(--prism-type-body-lg); font-weight: 700; color: var(--prism-text-heading); max-width: none; }
|
||||
.counts { display: flex; gap: var(--prism-space-200); flex-wrap: wrap; align-items: stretch; }
|
||||
.cnt { display: flex; align-items: baseline; gap: var(--prism-space-100); min-height: var(--prism-comp-hit-min); padding: 0 var(--prism-space-200);
|
||||
background: var(--prism-bg-card); border: var(--prism-stroke-regular) solid var(--prism-comp-line); border-radius: var(--prism-radius-sm); font: inherit; color: inherit; cursor: pointer; }
|
||||
.cnt b { font-size: var(--prism-type-title); font-weight: 700; line-height: 1.2; }
|
||||
.cnt.sel { background: var(--prism-text-heading); color: var(--prism-comp-on-emphasis); border-color: var(--prism-text-heading); }
|
||||
.banner { border: var(--prism-stroke-regular) solid var(--prism-comp-state-warn-fg); background: var(--prism-comp-state-warn-bg); color: var(--prism-comp-state-warn-fg);
|
||||
border-radius: var(--prism-radius-sm); padding: var(--prism-space-150) var(--prism-space-200); display: flex; gap: var(--prism-space-150); align-items: flex-start; }
|
||||
.banner b { font-weight: 700; }
|
||||
.banner.tip { border-color: var(--prism-comp-state-info-fg); background: var(--prism-comp-state-info-bg); color: var(--prism-comp-state-info-fg); }
|
||||
.banner.dont { border-color: var(--prism-comp-state-bad-fg); background: var(--prism-comp-state-bad-bg); color: var(--prism-comp-state-bad-fg); }
|
||||
.banner.good { border-color: var(--prism-comp-state-good-fg); background: var(--prism-comp-state-good-bg); color: var(--prism-comp-state-good-fg); }
|
||||
.banner .m { flex: none; }
|
||||
|
||||
/* badge */
|
||||
.b { display: inline-flex; align-items: center; gap: var(--prism-space-050); padding: var(--prism-space-025) var(--prism-space-100); white-space: nowrap;
|
||||
font-size: var(--prism-type-caption); font-weight: 500; line-height: 1.5; border: var(--prism-stroke-regular) solid var(--prism-comp-line);
|
||||
border-radius: var(--prism-radius-sm); color: var(--prism-text-body); background: transparent; }
|
||||
.b.good { color: var(--prism-comp-state-good-fg); background: var(--prism-comp-state-good-bg); border-color: var(--prism-comp-state-good-fg); }
|
||||
.b.warn { color: var(--prism-comp-state-warn-fg); background: var(--prism-comp-state-warn-bg); border-color: var(--prism-comp-state-warn-fg); }
|
||||
.b.info { color: var(--prism-comp-state-info-fg); background: var(--prism-comp-state-info-bg); border-color: var(--prism-comp-state-info-fg); }
|
||||
.b.bad { color: var(--prism-comp-state-bad-fg); background: var(--prism-comp-state-bad-bg); border-color: var(--prism-comp-state-bad-fg); border-width: var(--prism-stroke-bold); }
|
||||
.b.dash { border-style: dashed; }
|
||||
.b.fixed { color: var(--prism-comp-on-emphasis); background: var(--prism-text-heading); border-color: var(--prism-text-heading); }
|
||||
.b .m { font-size: 0.7em; }
|
||||
|
||||
/* buttons, fields */
|
||||
.btn { min-height: var(--prism-comp-hit-min); padding: 0 var(--prism-space-200); border-radius: var(--prism-radius-sm); font: inherit; font-weight: 500;
|
||||
cursor: pointer; border: var(--prism-stroke-regular) solid var(--prism-text-heading); background: var(--prism-bg-card); color: var(--prism-text-heading);
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: var(--prism-space-100); text-decoration: none; white-space: nowrap; }
|
||||
.btn.primary { background: var(--prism-text-heading); color: var(--prism-comp-on-emphasis); }
|
||||
.btn.quiet { border-color: transparent; background: transparent; text-decoration: underline; }
|
||||
.btn.danger { border-color: var(--prism-comp-state-bad-fg); color: var(--prism-comp-state-bad-fg); }
|
||||
.btn.sm { min-height: var(--prism-comp-hit-min); padding: 0 var(--prism-space-150); font-size: var(--prism-type-caption); }
|
||||
.field { display: flex; flex-direction: column; gap: var(--prism-space-050); }
|
||||
.field label, .lbl { font-weight: 700; font-size: var(--prism-type-body-sm); }
|
||||
.req { font-weight: 500; font-size: var(--prism-type-caption); margin-left: var(--prism-space-100); }
|
||||
.field input, .field select, .field textarea { min-height: var(--prism-comp-hit-min); padding: var(--prism-space-100) var(--prism-space-150); font: inherit;
|
||||
color: var(--prism-text-body); background: var(--prism-bg-card); border: var(--prism-stroke-regular) solid var(--prism-comp-line); border-radius: var(--prism-radius-sm); }
|
||||
.field textarea { font-size: var(--prism-type-body-sm); line-height: 1.6; resize: none; }
|
||||
.field .help { font-size: var(--prism-type-caption); }
|
||||
.field.bad input, .field.bad textarea { border-color: var(--prism-comp-state-bad-fg); border-width: var(--prism-stroke-bold); }
|
||||
.err { color: var(--prism-comp-state-bad-fg); font-size: var(--prism-type-caption); font-weight: 500; display: flex; gap: var(--prism-space-050); }
|
||||
.choice { display: flex; gap: var(--prism-space-300); align-items: center; }
|
||||
.choice label { display: inline-flex; align-items: center; gap: var(--prism-space-100); min-height: var(--prism-comp-hit-min); font-weight: 500; }
|
||||
.choice input { width: 1.25rem; height: 1.25rem; accent-color: var(--prism-text-heading); }
|
||||
|
||||
/* table */
|
||||
.tw { overflow-x: auto; background: var(--prism-bg-card); border: var(--prism-stroke-hairline) solid var(--prism-comp-line); border-radius: var(--prism-radius-sm); }
|
||||
table.t { width: 100%; border-collapse: collapse; font-size: var(--prism-type-body-sm); }
|
||||
table.t caption { text-align: left; font-weight: 700; padding: var(--prism-space-100) var(--prism-space-150); color: var(--prism-text-heading); }
|
||||
table.t th, table.t td { padding: var(--prism-space-100) var(--prism-space-150); border-bottom: var(--prism-stroke-hairline) solid var(--prism-comp-line); text-align: left; vertical-align: middle; }
|
||||
table.t thead th { position: sticky; top: 0; background: var(--prism-comp-table-head-bg); font-weight: 700; white-space: nowrap; border-bottom: var(--prism-stroke-regular) solid var(--prism-comp-line); }
|
||||
table.t td.num, table.t th.num { text-align: right; }
|
||||
table.t tr.sel td { background: var(--prism-comp-table-head-bg); }
|
||||
table.t tr.sep td { background: var(--prism-comp-table-head-bg); font-weight: 700; color: var(--prism-text-heading); }
|
||||
table.t td a { font-weight: 700; }
|
||||
.chip { display: inline-block; border: var(--prism-stroke-hairline) solid var(--prism-comp-line); border-radius: var(--prism-radius-sm);
|
||||
padding: 0 var(--prism-space-100); font-size: var(--prism-type-caption); white-space: nowrap; background: var(--prism-bg-card); }
|
||||
|
||||
/* key-value, evidence, diff */
|
||||
dl.kv { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--prism-space-100) var(--prism-space-300); font-size: var(--prism-type-body-sm); }
|
||||
dl.kv dt { font-weight: 700; }
|
||||
.pbar { height: 0.75rem; border: var(--prism-stroke-regular) solid var(--prism-comp-line); border-radius: var(--prism-radius-pill); overflow: hidden; background: var(--prism-bg-card); }
|
||||
.pbar > i { display: block; height: 100%; background: var(--prism-text-heading); }
|
||||
.list { list-style: none; display: flex; flex-direction: column; }
|
||||
.list li { display: flex; align-items: center; gap: var(--prism-space-100); min-height: var(--prism-comp-hit-min); padding: 0 var(--prism-space-150); border-bottom: var(--prism-stroke-hairline) solid var(--prism-comp-line); }
|
||||
.list li a { flex: 1; font-weight: 500; text-decoration: none; }
|
||||
.list li.cur { background: var(--prism-bg-canvas); }
|
||||
.list li.cur a { font-weight: 700; }
|
||||
|
||||
/* modal */
|
||||
.scrim { position: absolute; inset: 0; background: var(--prism-scrim); z-index: var(--prism-z-scrim); display: flex; align-items: center; justify-content: center; padding: var(--prism-space-400); }
|
||||
.modal { background: var(--prism-bg-card); border: var(--prism-stroke-regular) solid var(--prism-comp-line-strong); border-radius: var(--prism-radius-lg);
|
||||
width: 100%; max-width: 62rem; padding: var(--prism-space-300); display: flex; flex-direction: column; gap: var(--prism-space-200); z-index: var(--prism-z-modal); }
|
||||
.modal header { display: flex; align-items: center; gap: var(--prism-space-200); }
|
||||
.modal header h2 { flex: 1; }
|
||||
.scroll { max-height: 18rem; overflow-y: auto; border: var(--prism-stroke-hairline) solid var(--prism-comp-line); border-radius: var(--prism-radius-sm); }
|
||||
.drawing { background: var(--prism-bg-card); border: var(--prism-stroke-regular) solid var(--prism-comp-line); border-radius: var(--prism-radius-sm); display: block; width: 100%; height: auto; }
|
||||
.toc a { display: flex; align-items: center; min-height: var(--prism-comp-hit-min); padding: 0 var(--prism-space-150); text-decoration: none; font-weight: 500; border-radius: var(--prism-radius-sm); }
|
||||
.toc a.cur { background: var(--prism-bg-canvas); font-weight: 700; }
|
||||
.toc ol { list-style: none; }
|
||||
.num { font-variant-numeric: tabular-nums; }
|
||||
pre.code { margin: 0; background: var(--prism-bg-canvas); border: var(--prism-stroke-hairline) solid var(--prism-comp-line); border-radius: var(--prism-radius-sm);
|
||||
padding: var(--prism-space-150); font-family: var(--prism-font); font-size: var(--prism-type-caption); line-height: 1.6; white-space: pre-wrap; }
|
||||
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
|
||||
Reference in new issue
Block a user