diff --git a/resources/master_template/report/stab_report.json b/resources/master_template/report/stab_report.json new file mode 100644 index 000000000..9ded03dad --- /dev/null +++ b/resources/master_template/report/stab_report.json @@ -0,0 +1,2606 @@ +{ + "종류": "통합문서", + "판": 1, + "열": "stab_report", + "기본": { + "글꼴": "맑은 고딕", + "크기": 10, + "행높이": 18 + }, + "서식": [ + {}, + { + "크기": 18, + "굵게": true, + "가로": "center", + "세로": "center" + }, + { + "크기": 10, + "글자색": "#555555", + "가로": "center", + "세로": "center", + "줄바꿈": true + }, + { + "크기": 11, + "굵게": true, + "세로": "center", + "테두리": { + "아래": { + "선": "medium" + } + } + }, + { + "굵게": true, + "채움": "#F2F2F2", + "가로": "center", + "세로": "center", + "줄바꿈": true, + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "가로": "left", + "세로": "center", + "줄바꿈": true, + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "굵게": true, + "채움": "#DDEBF7", + "가로": "center", + "세로": "center", + "줄바꿈": true, + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "가로": "center", + "세로": "center", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "가로": "center", + "세로": "center", + "형식": "0.00", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "가로": "center", + "세로": "center", + "형식": "0", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "굵게": true, + "글자색": "#C00000", + "채움": "#FFC7CE", + "가로": "center", + "세로": "center", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "굵게": true, + "글자색": "#006100", + "채움": "#E2EFDA", + "가로": "center", + "세로": "center", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "크기": 12, + "굵게": true, + "글자색": "#C00000", + "가로": "center", + "세로": "center", + "줄바꿈": true + }, + { + "가로": "center", + "세로": "center", + "형식": "#,##0.0", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "가로": "center", + "세로": "center", + "형식": "#,##0", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "가로": "left", + "세로": "top", + "줄바꿈": true, + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "가로": "center", + "세로": "center", + "형식": "0.0", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + }, + { + "가로": "center", + "세로": "center", + "형식": "#,##0.00", + "테두리": { + "위": { + "선": "thin" + }, + "아래": { + "선": "thin" + }, + "왼": { + "선": "thin" + }, + "오른": { + "선": "thin" + } + } + } + ], + "시트": [ + { + "id": "s1", + "이름": "표지", + "칸": { + "B4": { + "값": "비탈면 안정성 검토 보고서", + "서식": 1 + }, + "C4": { + "서식": 1 + }, + "D4": { + "서식": 1 + }, + "E4": { + "서식": 1 + }, + "B5": { + "값": "임도 노선 비탈면 안정해석 결과", + "서식": 2 + }, + "C5": { + "서식": 2 + }, + "D5": { + "서식": 2 + }, + "E5": { + "서식": 2 + }, + "B8": { + "값": "사업명", + "서식": 4 + }, + "C8": { + "서식": 5 + }, + "D8": { + "서식": 5 + }, + "E8": { + "서식": 5 + }, + "B9": { + "값": "노선명", + "서식": 4 + }, + "C9": { + "서식": 5 + }, + "D9": { + "서식": 5 + }, + "E9": { + "서식": 5 + }, + "B10": { + "값": "발주처", + "서식": 4 + }, + "C10": { + "서식": 5 + }, + "D10": { + "서식": 5 + }, + "E10": { + "서식": 5 + }, + "B11": { + "값": "작성일", + "서식": 4 + }, + "C11": { + "서식": 5 + }, + "D11": { + "서식": 5 + }, + "E11": { + "서식": 5 + }, + "B12": { + "값": "작성자", + "서식": 4 + }, + "C12": { + "서식": 5 + }, + "D12": { + "서식": 5 + }, + "E12": { + "서식": 5 + }, + "B14": { + "값": "문헌 추정치 · 현장 시험 없음", + "서식": 12 + }, + "C14": { + "서식": 12 + }, + "D14": { + "서식": 12 + }, + "E14": { + "서식": 12 + }, + "B15": { + "값": "※ 지반 물성치는 문헌 · 기준값을 적용한 추정치이며 현장 조사 · 시험 성과를 반영하지 않았음.", + "서식": 2 + }, + "C15": { + "서식": 2 + }, + "D15": { + "서식": 2 + }, + "E15": { + "서식": 2 + } + }, + "병합": [ + "B4:E4", + "B5:E5", + "C8:E8", + "C9:E9", + "C10:E10", + "C11:E11", + "C12:E12", + "B14:E14", + "B15:E15" + ], + "열": { + "A": { + "폭": 4 + }, + "B": { + "폭": 18 + }, + "C": { + "폭": 22 + }, + "D": { + "폭": 22 + }, + "E": { + "폭": 22 + }, + "F": { + "폭": 4 + } + }, + "행": { + "4": { + "높이": 60 + }, + "5": { + "높이": 24 + }, + "14": { + "높이": 30 + }, + "15": { + "높이": 30 + }, + "8": { + "높이": 30 + }, + "9": { + "높이": 30 + }, + "10": { + "높이": 30 + }, + "11": { + "높이": 30 + }, + "12": { + "높이": 30 + } + }, + "보기": { + "눈금선": false + }, + "인쇄": { + "용지": "A4", + "방향": "portrait", + "맞춤": { + "폭": 1, + "높이": 0 + }, + "여백": { + "위": 25, + "아래": 20, + "왼": 20, + "오른": 20 + } + } + }, + { + "id": "s2", + "이름": "개요", + "칸": { + "A1": { + "값": "개요 및 판정 요약", + "서식": 1 + }, + "B1": { + "서식": 1 + }, + "C1": { + "서식": 1 + }, + "D1": { + "서식": 1 + }, + "E1": { + "서식": 1 + }, + "F1": { + "서식": 1 + }, + "A2": { + "값": "수치는 「측점표」 시트를 따라감 — 해석이 바뀌면 [보고서 갱신] 뒤 새 값으로 바뀜", + "서식": 2 + }, + "B2": { + "서식": 2 + }, + "C2": { + "서식": 2 + }, + "D2": { + "서식": 2 + }, + "E2": { + "서식": 2 + }, + "F2": { + "서식": 2 + }, + "A4": { + "값": "1. 해석 대상", + "서식": 3 + }, + "B4": { + "서식": 3 + }, + "C4": { + "서식": 3 + }, + "D4": { + "서식": 3 + }, + "E4": { + "서식": 3 + }, + "F4": { + "서식": 3 + }, + "A5": { + "값": "대상 측점 수", + "서식": 4 + }, + "B5": { + "식": "COUNTA(측점표!A6:A20000)", + "서식": 9 + }, + "C5": { + "값": "개소", + "서식": 7 + }, + "A6": { + "값": "해석 구간 연장", + "서식": 4 + }, + "B6": { + "식": "IF(B5=0,0,MAX(측점표!B6:B20000)-MIN(측점표!B6:B20000))", + "서식": 13 + }, + "C6": { + "값": "m", + "서식": 7 + }, + "A8": { + "값": "2. 조건별 최소 안전율", + "서식": 3 + }, + "B8": { + "서식": 3 + }, + "C8": { + "서식": 3 + }, + "D8": { + "서식": 3 + }, + "E8": { + "서식": 3 + }, + "F8": { + "서식": 3 + }, + "A9": { + "값": "조건", + "서식": 6 + }, + "B9": { + "값": "최소 안전율", + "서식": 6 + }, + "C9": { + "값": "기준 안전율", + "서식": 6 + }, + "D9": { + "값": "판정", + "서식": 6 + }, + "E9": { + "값": "최소 안전율 측점", + "서식": 6 + }, + "F9": { + "서식": 6 + }, + "A10": { + "값": "건기", + "서식": 4 + }, + "B10": { + "식": "IF(COUNT(측점표!D6:D20000)=0,\"-\",MIN(측점표!D6:D20000))", + "서식": 8 + }, + "C10": { + "식": "입력조건!B12", + "서식": 8 + }, + "D10": { + "식": "IF(B10=\"-\",\"-\",IF(B10>=C10,\"O.K\",\"N.G\"))", + "서식": 7 + }, + "E10": { + "식": "IF(B10=\"-\",\"-\",INDEX(측점표!A6:A20000,MATCH(B10,측점표!D6:D20000,0)))", + "서식": 7 + }, + "F10": { + "서식": 7 + }, + "A11": { + "값": "우기", + "서식": 4 + }, + "B11": { + "식": "IF(COUNT(측점표!E6:E20000)=0,\"-\",MIN(측점표!E6:E20000))", + "서식": 8 + }, + "C11": { + "식": "입력조건!B13", + "서식": 8 + }, + "D11": { + "식": "IF(B11=\"-\",\"-\",IF(B11>=C11,\"O.K\",\"N.G\"))", + "서식": 7 + }, + "E11": { + "식": "IF(B11=\"-\",\"-\",INDEX(측점표!A6:A20000,MATCH(B11,측점표!E6:E20000,0)))", + "서식": 7 + }, + "F11": { + "서식": 7 + }, + "A12": { + "값": "지진", + "서식": 4 + }, + "B12": { + "식": "IF(COUNT(측점표!F6:F20000)=0,\"-\",MIN(측점표!F6:F20000))", + "서식": 8 + }, + "C12": { + "식": "입력조건!B14", + "서식": 8 + }, + "D12": { + "식": "IF(B12=\"-\",\"-\",IF(B12>=C12,\"O.K\",\"N.G\"))", + "서식": 7 + }, + "E12": { + "식": "IF(B12=\"-\",\"-\",INDEX(측점표!A6:A20000,MATCH(B12,측점표!F6:F20000,0)))", + "서식": 7 + }, + "F12": { + "서식": 7 + }, + "A14": { + "값": "3. 종합 판정", + "서식": 3 + }, + "B14": { + "서식": 3 + }, + "C14": { + "서식": 3 + }, + "D14": { + "서식": 3 + }, + "E14": { + "서식": 3 + }, + "F14": { + "서식": 3 + }, + "A15": { + "값": "기준 미달(N.G) 측점 수", + "서식": 4 + }, + "B15": { + "식": "COUNTIF(측점표!J6:J20000,\"N.G\")", + "서식": 9 + }, + "C15": { + "값": "개소", + "서식": 7 + }, + "A16": { + "값": "종합 판정", + "서식": 4 + }, + "B16": { + "식": "IF(B5=0,\"-\",IF(B15=0,\"O.K\",\"N.G\"))", + "서식": 7 + }, + "A17": { + "값": "판정 글", + "서식": 4 + }, + "B17": { + "식": "IF(B5=0,\"해석 결과 없음\",\"대상 측점 \"&B5&\"개소 중 \"&B15&\"개소가 기준 안전율에 미달함. \"&IF(B15=0,\"전 구간이 기준을 만족함.\",\"미달 구간은 보강 대책 검토가 필요함.\"))", + "서식": 15 + }, + "C17": { + "서식": 15 + }, + "D17": { + "서식": 15 + }, + "E17": { + "서식": 15 + }, + "F17": { + "서식": 15 + }, + "B18": { + "서식": 15 + }, + "C18": { + "서식": 15 + }, + "D18": { + "서식": 15 + }, + "E18": { + "서식": 15 + }, + "F18": { + "서식": 15 + }, + "B19": { + "서식": 15 + }, + "C19": { + "서식": 15 + }, + "D19": { + "서식": 15 + }, + "E19": { + "서식": 15 + }, + "F19": { + "서식": 15 + }, + "A18": { + "서식": 4 + }, + "A19": { + "서식": 4 + }, + "A21": { + "값": "4. 의견", + "서식": 3 + }, + "B21": { + "서식": 3 + }, + "C21": { + "서식": 3 + }, + "D21": { + "서식": 3 + }, + "E21": { + "서식": 3 + }, + "F21": { + "서식": 3 + }, + "A22": { + "서식": 15 + }, + "B22": { + "서식": 15 + }, + "C22": { + "서식": 15 + }, + "D22": { + "서식": 15 + }, + "E22": { + "서식": 15 + }, + "F22": { + "서식": 15 + }, + "A23": { + "서식": 15 + }, + "B23": { + "서식": 15 + }, + "C23": { + "서식": 15 + }, + "D23": { + "서식": 15 + }, + "E23": { + "서식": 15 + }, + "F23": { + "서식": 15 + }, + "A24": { + "서식": 15 + }, + "B24": { + "서식": 15 + }, + "C24": { + "서식": 15 + }, + "D24": { + "서식": 15 + }, + "E24": { + "서식": 15 + }, + "F24": { + "서식": 15 + }, + "A25": { + "서식": 15 + }, + "B25": { + "서식": 15 + }, + "C25": { + "서식": 15 + }, + "D25": { + "서식": 15 + }, + "E25": { + "서식": 15 + }, + "F25": { + "서식": 15 + }, + "A26": { + "서식": 15 + }, + "B26": { + "서식": 15 + }, + "C26": { + "서식": 15 + }, + "D26": { + "서식": 15 + }, + "E26": { + "서식": 15 + }, + "F26": { + "서식": 15 + }, + "A27": { + "서식": 15 + }, + "B27": { + "서식": 15 + }, + "C27": { + "서식": 15 + }, + "D27": { + "서식": 15 + }, + "E27": { + "서식": 15 + }, + "F27": { + "서식": 15 + }, + "A28": { + "서식": 15 + }, + "B28": { + "서식": 15 + }, + "C28": { + "서식": 15 + }, + "D28": { + "서식": 15 + }, + "E28": { + "서식": 15 + }, + "F28": { + "서식": 15 + }, + "A29": { + "서식": 15 + }, + "B29": { + "서식": 15 + }, + "C29": { + "서식": 15 + }, + "D29": { + "서식": 15 + }, + "E29": { + "서식": 15 + }, + "F29": { + "서식": 15 + }, + "A30": { + "서식": 15 + }, + "B30": { + "서식": 15 + }, + "C30": { + "서식": 15 + }, + "D30": { + "서식": 15 + }, + "E30": { + "서식": 15 + }, + "F30": { + "서식": 15 + } + }, + "병합": [ + "A1:F1", + "A2:F2", + "A4:F4", + "A8:F8", + "E9:F9", + "E10:F10", + "E11:F11", + "E12:F12", + "A14:F14", + "B17:F19", + "A17:A19", + "A21:F21", + "A22:F30" + ], + "열": { + "A": { + "폭": 20 + }, + "B": { + "폭": 16 + }, + "C": { + "폭": 16 + }, + "D": { + "폭": 14 + }, + "E": { + "폭": 16 + }, + "F": { + "폭": 16 + } + }, + "행": { + "1": { + "높이": 36 + }, + "2": { + "높이": 20 + }, + "9": { + "높이": 22 + }, + "5": { + "높이": 22 + }, + "6": { + "높이": 22 + }, + "10": { + "높이": 22 + }, + "11": { + "높이": 22 + }, + "12": { + "높이": 22 + }, + "15": { + "높이": 22 + }, + "16": { + "높이": 22 + }, + "17": { + "높이": 22 + }, + "18": { + "높이": 22 + }, + "19": { + "높이": 22 + } + }, + "보기": { + "눈금선": false + }, + "인쇄": { + "용지": "A4", + "방향": "portrait", + "맞춤": { + "폭": 1, + "높이": 0 + }, + "여백": { + "위": 20, + "아래": 15, + "왼": 15, + "오른": 15 + } + }, + "조건부서식": [ + { + "id": "ng_d", + "범위": [ + { + "r0": 9, + "c0": 3, + "r1": 11, + "c1": 3 + } + ], + "조건": { + "종류": "글포함", + "글": "N.G" + }, + "스타일": { + "채움": "#FFC7CE", + "글자색": "#C00000", + "굵게": true + } + }, + { + "id": "ng_b16", + "범위": [ + { + "r0": 15, + "c0": 1, + "r1": 15, + "c1": 1 + } + ], + "조건": { + "종류": "글포함", + "글": "N.G" + }, + "스타일": { + "채움": "#FFC7CE", + "글자색": "#C00000", + "굵게": true + } + } + ] + }, + { + "id": "s3", + "이름": "입력조건", + "칸": { + "A1": { + "값": "입력 조건", + "서식": 1 + }, + "B1": { + "서식": 1 + }, + "C1": { + "서식": 1 + }, + "D1": { + "서식": 1 + }, + "E1": { + "서식": 1 + }, + "F1": { + "서식": 1 + }, + "G1": { + "서식": 1 + }, + "A2": { + "값": "문헌 추정치 · 현장 시험 없음 — 지반 물성치는 문헌 · 기준값 적용", + "서식": 2 + }, + "B2": { + "서식": 2 + }, + "C2": { + "서식": 2 + }, + "D2": { + "서식": 2 + }, + "E2": { + "서식": 2 + }, + "F2": { + "서식": 2 + }, + "G2": { + "서식": 2 + }, + "A4": { + "값": "1. 해석 방법", + "서식": 3 + }, + "B4": { + "서식": 3 + }, + "C4": { + "서식": 3 + }, + "D4": { + "서식": 3 + }, + "E4": { + "서식": 3 + }, + "F4": { + "서식": 3 + }, + "G4": { + "서식": 3 + }, + "A5": { + "값": "해석 기법", + "서식": 4 + }, + "B5": { + "서식": 5 + }, + "C5": { + "서식": 5 + }, + "D5": { + "서식": 5 + }, + "E5": { + "서식": 5 + }, + "F5": { + "서식": 5 + }, + "G5": { + "서식": 5 + }, + "A6": { + "값": "활동면 탐색", + "서식": 4 + }, + "B6": { + "서식": 5 + }, + "C6": { + "서식": 5 + }, + "D6": { + "서식": 5 + }, + "E6": { + "서식": 5 + }, + "F6": { + "서식": 5 + }, + "G6": { + "서식": 5 + }, + "A7": { + "값": "3D 해석", + "서식": 4 + }, + "B7": { + "서식": 5 + }, + "C7": { + "서식": 5 + }, + "D7": { + "서식": 5 + }, + "E7": { + "서식": 5 + }, + "F7": { + "서식": 5 + }, + "G7": { + "서식": 5 + }, + "A8": { + "값": "적용 기준", + "서식": 4 + }, + "B8": { + "서식": 5 + }, + "C8": { + "서식": 5 + }, + "D8": { + "서식": 5 + }, + "E8": { + "서식": 5 + }, + "F8": { + "서식": 5 + }, + "G8": { + "서식": 5 + }, + "A10": { + "값": "2. 기준 안전율", + "서식": 3 + }, + "B10": { + "서식": 3 + }, + "C10": { + "서식": 3 + }, + "D10": { + "서식": 3 + }, + "E10": { + "서식": 3 + }, + "F10": { + "서식": 3 + }, + "G10": { + "서식": 3 + }, + "A11": { + "값": "조건", + "서식": 6 + }, + "B11": { + "값": "기준 안전율", + "서식": 6 + }, + "C11": { + "값": "비고", + "서식": 6 + }, + "D11": { + "서식": 6 + }, + "E11": { + "서식": 6 + }, + "F11": { + "서식": 6 + }, + "G11": { + "서식": 6 + }, + "A12": { + "값": "건기", + "서식": 4 + }, + "B12": { + "서식": 8 + }, + "C12": { + "서식": 5 + }, + "D12": { + "서식": 5 + }, + "E12": { + "서식": 5 + }, + "F12": { + "서식": 5 + }, + "G12": { + "서식": 5 + }, + "A13": { + "값": "우기", + "서식": 4 + }, + "B13": { + "서식": 8 + }, + "C13": { + "서식": 5 + }, + "D13": { + "서식": 5 + }, + "E13": { + "서식": 5 + }, + "F13": { + "서식": 5 + }, + "G13": { + "서식": 5 + }, + "A14": { + "값": "지진", + "서식": 4 + }, + "B14": { + "서식": 8 + }, + "C14": { + "서식": 5 + }, + "D14": { + "서식": 5 + }, + "E14": { + "서식": 5 + }, + "F14": { + "서식": 5 + }, + "G14": { + "서식": 5 + }, + "A16": { + "값": "3. 지하수 규칙", + "서식": 3 + }, + "B16": { + "서식": 3 + }, + "C16": { + "서식": 3 + }, + "D16": { + "서식": 3 + }, + "E16": { + "서식": 3 + }, + "F16": { + "서식": 3 + }, + "G16": { + "서식": 3 + }, + "A17": { + "값": "건기", + "서식": 4 + }, + "B17": { + "서식": 5 + }, + "C17": { + "서식": 5 + }, + "D17": { + "서식": 5 + }, + "E17": { + "서식": 5 + }, + "F17": { + "서식": 5 + }, + "G17": { + "서식": 5 + }, + "A18": { + "값": "우기", + "서식": 4 + }, + "B18": { + "서식": 5 + }, + "C18": { + "서식": 5 + }, + "D18": { + "서식": 5 + }, + "E18": { + "서식": 5 + }, + "F18": { + "서식": 5 + }, + "G18": { + "서식": 5 + }, + "A19": { + "값": "지진 시", + "서식": 4 + }, + "B19": { + "서식": 5 + }, + "C19": { + "서식": 5 + }, + "D19": { + "서식": 5 + }, + "E19": { + "서식": 5 + }, + "F19": { + "서식": 5 + }, + "G19": { + "서식": 5 + }, + "A21": { + "값": "4. 노면 하중", + "서식": 3 + }, + "B21": { + "서식": 3 + }, + "C21": { + "서식": 3 + }, + "D21": { + "서식": 3 + }, + "E21": { + "서식": 3 + }, + "F21": { + "서식": 3 + }, + "G21": { + "서식": 3 + }, + "A22": { + "값": "노면 하중", + "서식": 4 + }, + "B22": { + "서식": 16 + }, + "C22": { + "값": "kPa", + "서식": 7 + }, + "A24": { + "값": "5. 지반 물성치", + "서식": 3 + }, + "B24": { + "서식": 3 + }, + "C24": { + "서식": 3 + }, + "D24": { + "서식": 3 + }, + "E24": { + "서식": 3 + }, + "F24": { + "서식": 3 + }, + "G24": { + "서식": 3 + }, + "A25": { + "값": "지층", + "서식": 6 + }, + "B25": { + "값": "적용 구간", + "서식": 6 + }, + "C25": { + "값": "γt (kN/m³)", + "서식": 6 + }, + "D25": { + "값": "γsat (kN/m³)", + "서식": 6 + }, + "E25": { + "값": "c (kPa)", + "서식": 6 + }, + "F25": { + "값": "φ (°)", + "서식": 6 + }, + "G25": { + "값": "출처", + "서식": 6 + }, + "A26": { + "서식": 7 + }, + "B26": { + "서식": 7 + }, + "C26": { + "서식": 16 + }, + "D26": { + "서식": 16 + }, + "E26": { + "서식": 16 + }, + "F26": { + "서식": 16 + }, + "G26": { + "서식": 5 + } + }, + "병합": [ + "A1:G1", + "A2:G2", + "A4:G4", + "B5:G5", + "B6:G6", + "B7:G7", + "B8:G8", + "A10:G10", + "C11:G11", + "C12:G12", + "C13:G13", + "C14:G14", + "A16:G16", + "B17:G17", + "B18:G18", + "B19:G19", + "A21:G21", + "A24:G24" + ], + "열": { + "A": { + "폭": 18 + }, + "B": { + "폭": 16 + }, + "C": { + "폭": 12 + }, + "D": { + "폭": 12 + }, + "E": { + "폭": 12 + }, + "F": { + "폭": 12 + }, + "G": { + "폭": 28 + } + }, + "행": { + "1": { + "높이": 36 + }, + "2": { + "높이": 20 + }, + "25": { + "높이": 32 + }, + "5": { + "높이": 22 + }, + "6": { + "높이": 22 + }, + "7": { + "높이": 22 + }, + "8": { + "높이": 22 + }, + "12": { + "높이": 22 + }, + "13": { + "높이": 22 + }, + "14": { + "높이": 22 + }, + "17": { + "높이": 22 + }, + "18": { + "높이": 22 + }, + "19": { + "높이": 22 + } + }, + "보기": { + "눈금선": false + }, + "인쇄": { + "용지": "A4", + "방향": "portrait", + "맞춤": { + "폭": 1, + "높이": 0 + }, + "여백": { + "위": 15, + "아래": 15, + "왼": 10, + "오른": 10 + }, + "머리행": [ + 25, + 25 + ] + } + }, + { + "id": "s4", + "이름": "측점표", + "칸": { + "A1": { + "값": "측점별 안전율", + "서식": 1 + }, + "B1": { + "서식": 1 + }, + "C1": { + "서식": 1 + }, + "D1": { + "서식": 1 + }, + "E1": { + "서식": 1 + }, + "F1": { + "서식": 1 + }, + "G1": { + "서식": 1 + }, + "H1": { + "서식": 1 + }, + "I1": { + "서식": 1 + }, + "J1": { + "서식": 1 + }, + "A2": { + "값": "판정 — 모든 조건의 안전율이 기준 이상이면 O.K · 하나라도 미달이면 N.G", + "서식": 2 + }, + "B2": { + "서식": 2 + }, + "C2": { + "서식": 2 + }, + "D2": { + "서식": 2 + }, + "E2": { + "서식": 2 + }, + "F2": { + "서식": 2 + }, + "G2": { + "서식": 2 + }, + "H2": { + "서식": 2 + }, + "I2": { + "서식": 2 + }, + "J2": { + "서식": 2 + }, + "A4": { + "값": "측점", + "서식": 6 + }, + "A5": { + "서식": 6 + }, + "B4": { + "값": "누가거리 (m)", + "서식": 6 + }, + "B5": { + "서식": 6 + }, + "C4": { + "값": "구분", + "서식": 6 + }, + "C5": { + "서식": 6 + }, + "D4": { + "값": "산정 안전율 Fs", + "서식": 6 + }, + "E4": { + "서식": 6 + }, + "F4": { + "서식": 6 + }, + "G4": { + "값": "기준 안전율", + "서식": 6 + }, + "H4": { + "서식": 6 + }, + "I4": { + "서식": 6 + }, + "J4": { + "값": "판정", + "서식": 6 + }, + "J5": { + "서식": 6 + }, + "D5": { + "값": "건기", + "서식": 6 + }, + "E5": { + "값": "우기", + "서식": 6 + }, + "F5": { + "값": "지진", + "서식": 6 + }, + "G5": { + "값": "건기", + "서식": 6 + }, + "H5": { + "값": "우기", + "서식": 6 + }, + "I5": { + "값": "지진", + "서식": 6 + }, + "A6": { + "서식": 7 + }, + "B6": { + "서식": 17 + }, + "C6": { + "서식": 7 + }, + "D6": { + "서식": 8 + }, + "E6": { + "서식": 8 + }, + "F6": { + "서식": 8 + }, + "G6": { + "서식": 8 + }, + "H6": { + "서식": 8 + }, + "I6": { + "서식": 8 + }, + "J6": { + "서식": 7 + } + }, + "병합": [ + "A1:J1", + "A2:J2", + "A4:A5", + "B4:B5", + "C4:C5", + "D4:F4", + "G4:I4", + "J4:J5" + ], + "열": { + "A": { + "폭": 16 + }, + "B": { + "폭": 13 + }, + "C": { + "폭": 9 + }, + "D": { + "폭": 9 + }, + "E": { + "폭": 9 + }, + "F": { + "폭": 9 + }, + "G": { + "폭": 9 + }, + "H": { + "폭": 9 + }, + "I": { + "폭": 9 + }, + "J": { + "폭": 9 + } + }, + "행": { + "1": { + "높이": 36 + }, + "2": { + "높이": 20 + }, + "3": { + "높이": 8 + }, + "4": { + "높이": 22 + }, + "5": { + "높이": 22 + } + }, + "틀고정": { + "행": 5 + }, + "보기": { + "눈금선": false + }, + "인쇄": { + "용지": "A4", + "방향": "landscape", + "맞춤": { + "폭": 1, + "높이": 0 + }, + "여백": { + "위": 15, + "아래": 15, + "왼": 10, + "오른": 10 + }, + "머리행": [ + 4, + 5 + ] + }, + "조건부서식": [ + { + "id": "ng_j", + "범위": [ + { + "r0": 5, + "c0": 9, + "r1": 19999, + "c1": 9 + } + ], + "조건": { + "종류": "글포함", + "글": "N.G" + }, + "스타일": { + "채움": "#FFC7CE", + "글자색": "#C00000", + "굵게": true + } + } + ] + }, + { + "id": "s5", + "이름": "대표단면", + "칸": { + "A1": { + "값": "대표 단면 ①", + "서식": 3 + }, + "B1": { + "서식": 3 + }, + "C1": { + "서식": 3 + }, + "D1": { + "서식": 3 + }, + "E1": { + "서식": 3 + }, + "F1": { + "서식": 3 + }, + "G1": { + "서식": 3 + }, + "H1": { + "서식": 3 + }, + "A2": { + "값": "선정 사유", + "서식": 4 + }, + "B2": { + "서식": 5 + }, + "C2": { + "서식": 5 + }, + "D2": { + "서식": 5 + }, + "E2": { + "서식": 5 + }, + "F2": { + "서식": 5 + }, + "G2": { + "서식": 5 + }, + "H2": { + "서식": 5 + }, + "A23": { + "값": "조건", + "서식": 6 + }, + "B23": { + "값": "해석 방법", + "서식": 6 + }, + "C23": { + "값": "안전율 Fs", + "서식": 6 + }, + "D23": { + "값": "기준", + "서식": 6 + }, + "E23": { + "값": "판정", + "서식": 6 + }, + "F23": { + "값": "활동면 반경 R (m)", + "서식": 6 + }, + "G23": { + "값": "중심 x (m)", + "서식": 6 + }, + "H23": { + "값": "중심 y (m)", + "서식": 6 + }, + "A24": { + "값": "건기", + "서식": 4 + }, + "B24": { + "서식": 7 + }, + "C24": { + "서식": 8 + }, + "D24": { + "서식": 8 + }, + "E24": { + "서식": 7 + }, + "F24": { + "서식": 13 + }, + "G24": { + "서식": 13 + }, + "H24": { + "서식": 13 + }, + "A25": { + "값": "우기", + "서식": 4 + }, + "B25": { + "서식": 7 + }, + "C25": { + "서식": 8 + }, + "D25": { + "서식": 8 + }, + "E25": { + "서식": 7 + }, + "F25": { + "서식": 13 + }, + "G25": { + "서식": 13 + }, + "H25": { + "서식": 13 + }, + "A26": { + "값": "지진", + "서식": 4 + }, + "B26": { + "서식": 7 + }, + "C26": { + "서식": 8 + }, + "D26": { + "서식": 8 + }, + "E26": { + "서식": 7 + }, + "F26": { + "서식": 13 + }, + "G26": { + "서식": 13 + }, + "H26": { + "서식": 13 + }, + "A27": { + "값": "비고", + "서식": 4 + }, + "B27": { + "서식": 5 + }, + "C27": { + "서식": 5 + }, + "D27": { + "서식": 5 + }, + "E27": { + "서식": 5 + }, + "F27": { + "서식": 5 + }, + "G27": { + "서식": 5 + }, + "H27": { + "서식": 5 + } + }, + "병합": [ + "A1:H1", + "B2:H2", + "B27:H27" + ], + "열": { + "A": { + "폭": 12 + }, + "B": { + "폭": 12 + }, + "C": { + "폭": 12 + }, + "D": { + "폭": 12 + }, + "E": { + "폭": 12 + }, + "F": { + "폭": 12 + }, + "G": { + "폭": 12 + }, + "H": { + "폭": 12 + } + }, + "행": { + "1": { + "높이": 26 + }, + "2": { + "높이": 22 + }, + "3": { + "높이": 20 + }, + "4": { + "높이": 20 + }, + "5": { + "높이": 20 + }, + "6": { + "높이": 20 + }, + "7": { + "높이": 20 + }, + "8": { + "높이": 20 + }, + "9": { + "높이": 20 + }, + "10": { + "높이": 20 + }, + "11": { + "높이": 20 + }, + "12": { + "높이": 20 + }, + "13": { + "높이": 20 + }, + "14": { + "높이": 20 + }, + "15": { + "높이": 20 + }, + "16": { + "높이": 20 + }, + "17": { + "높이": 20 + }, + "18": { + "높이": 20 + }, + "19": { + "높이": 20 + }, + "20": { + "높이": 20 + }, + "21": { + "높이": 20 + }, + "22": { + "높이": 20 + }, + "23": { + "높이": 32 + }, + "24": { + "높이": 22 + }, + "25": { + "높이": 22 + }, + "26": { + "높이": 22 + }, + "27": { + "높이": 22 + }, + "28": { + "높이": 6 + } + }, + "그림": [ + { + "열쇠": "fig/{측점키}.png", + "닻": "A3:H22", + "이름": "대표 단면 그림" + } + ], + "보기": { + "눈금선": false + }, + "인쇄": { + "용지": "A4", + "방향": "portrait", + "맞춤": { + "폭": 1, + "높이": 0 + }, + "여백": { + "위": 15, + "아래": 15, + "왼": 10, + "오른": 10 + }, + "쪽나눔": [ + 28 + ] + }, + "조건부서식": [ + { + "id": "ng_e", + "범위": [ + { + "r0": 23, + "c0": 4, + "r1": 25, + "c1": 4 + } + ], + "조건": { + "종류": "글포함", + "글": "N.G" + }, + "스타일": { + "채움": "#FFC7CE", + "글자색": "#C00000", + "굵게": true + } + } + ] + }, + { + "id": "s6", + "이름": "3D요약", + "칸": { + "A1": { + "값": "3D 결과 요약", + "서식": 1 + }, + "B1": { + "서식": 1 + }, + "C1": { + "서식": 1 + }, + "D1": { + "서식": 1 + }, + "E1": { + "서식": 1 + }, + "F1": { + "서식": 1 + }, + "G1": { + "서식": 1 + }, + "H1": { + "서식": 1 + }, + "A2": { + "값": "3D 최소 안전율은 2D 결과와 대조 — 3D/2D 비가 1 보다 크면 2D 가 안전 쪽", + "서식": 2 + }, + "B2": { + "서식": 2 + }, + "C2": { + "서식": 2 + }, + "D2": { + "서식": 2 + }, + "E2": { + "서식": 2 + }, + "F2": { + "서식": 2 + }, + "G2": { + "서식": 2 + }, + "H2": { + "서식": 2 + }, + "A4": { + "값": "1. 평면 위험 띠", + "서식": 3 + }, + "B4": { + "서식": 3 + }, + "C4": { + "서식": 3 + }, + "D4": { + "서식": 3 + }, + "E4": { + "서식": 3 + }, + "F4": { + "서식": 3 + }, + "G4": { + "서식": 3 + }, + "H4": { + "서식": 3 + }, + "A18": { + "값": "2. 구간별 3D 안전율", + "서식": 3 + }, + "B18": { + "서식": 3 + }, + "C18": { + "서식": 3 + }, + "D18": { + "서식": 3 + }, + "E18": { + "서식": 3 + }, + "F18": { + "서식": 3 + }, + "G18": { + "서식": 3 + }, + "H18": { + "서식": 3 + }, + "A19": { + "값": "구간", + "서식": 6 + }, + "B19": { + "값": "시작 측점", + "서식": 6 + }, + "C19": { + "값": "끝 측점", + "서식": 6 + }, + "D19": { + "값": "3D 최소 Fs", + "서식": 6 + }, + "E19": { + "값": "2D 최소 Fs", + "서식": 6 + }, + "F19": { + "값": "3D/2D 비", + "서식": 6 + }, + "G19": { + "값": "임계 체적 (m³)", + "서식": 6 + }, + "H19": { + "값": "판정", + "서식": 6 + }, + "A20": { + "서식": 7 + }, + "B20": { + "서식": 7 + }, + "C20": { + "서식": 7 + }, + "D20": { + "서식": 8 + }, + "E20": { + "서식": 8 + }, + "F20": { + "식": "IF(E20>0,D20/E20,\"\")", + "서식": 8 + }, + "G20": { + "서식": 14 + }, + "H20": { + "서식": 7 + } + }, + "병합": [ + "A1:H1", + "A2:H2", + "A4:H4", + "A18:H18" + ], + "열": { + "A": { + "폭": 12 + }, + "B": { + "폭": 13 + }, + "C": { + "폭": 13 + }, + "D": { + "폭": 11 + }, + "E": { + "폭": 11 + }, + "F": { + "폭": 10 + }, + "G": { + "폭": 13 + }, + "H": { + "폭": 9 + } + }, + "행": { + "1": { + "높이": 36 + }, + "2": { + "높이": 20 + }, + "4": { + "높이": 22 + }, + "18": { + "높이": 22 + }, + "19": { + "높이": 32 + }, + "5": { + "높이": 20 + }, + "6": { + "높이": 20 + }, + "7": { + "높이": 20 + }, + "8": { + "높이": 20 + }, + "9": { + "높이": 20 + }, + "10": { + "높이": 20 + }, + "11": { + "높이": 20 + }, + "12": { + "높이": 20 + }, + "13": { + "높이": 20 + }, + "14": { + "높이": 20 + }, + "15": { + "높이": 20 + }, + "16": { + "높이": 20 + } + }, + "그림": [ + { + "열쇠": "fig/3d_band.png", + "닻": "A5:H16", + "이름": "평면 위험 띠" + } + ], + "보기": { + "눈금선": false + }, + "인쇄": { + "용지": "A4", + "방향": "portrait", + "맞춤": { + "폭": 1, + "높이": 0 + }, + "여백": { + "위": 15, + "아래": 15, + "왼": 10, + "오른": 10 + }, + "머리행": [ + 19, + 19 + ] + }, + "조건부서식": [ + { + "id": "ng_h", + "범위": [ + { + "r0": 19, + "c0": 7, + "r1": 19999, + "c1": 7 + } + ], + "조건": { + "종류": "글포함", + "글": "N.G" + }, + "스타일": { + "채움": "#FFC7CE", + "글자색": "#C00000", + "굵게": true + } + } + ] + } + ], + "활성": "s1" +} diff --git a/resources/master_template/report/stab_report_slots.md b/resources/master_template/report/stab_report_slots.md new file mode 100644 index 000000000..3fb11f4c7 --- /dev/null +++ b/resources/master_template/report/stab_report_slots.md @@ -0,0 +1,81 @@ +# E05 보고서 양식 자리표 — `stab_report.json` + +빌더(534-3 뒤쪽)가 읽는 표. 시트 id · 칸 주소 · 서식 번호는 양식 그대로 쓰고 바꾸지 않음. + +## 공통 + +- 몫: 사용자 몫(표지 · 개요) = 처음 만들 때만 채움 · 다시 만들 때(보고서 갱신) 안 건드림. 프로그램 몫(입력조건 · 측점표 · 대표단면 · 3D요약) = 갱신 때 통째 다시 씀. +- 서식 번호(`서식` 표): 1 title · 2 sub · 3 section · 4 label · 5 text · 6 head · 7 body(가운데 글) · 8 fs(0.00) · 9 int(0) · 10 ng(N.G 빨강 채움) · 11 ok(O.K 초록) · 12 notice · 13 num1(#,##0.0) · 14 num0(#,##0) · 15 para(위 맞춤 긴 글) · 16 prop(0.0) · 17 chain(#,##0.00). +- 판정 글(`O.K` · `N.G`) 칸 = 엔진 판정을 값으로 씀 · N.G 칸은 서식 10 · O.K 칸은 서식 11 을 직접 붙임 — 인쇄 HTML · xlsx 는 `조건부서식` 을 안 먹이므로(화면만). 양식의 `조건부서식`(글포함 `N.G` → 빨강)은 사용자가 화면에서 고친 판정 글용. +- 수식이 보는 끝 행 = 20000(측점표 · 3D요약). 측점이 이보다 많으면 개요 수식의 `20000` 을 같이 늘림. +- 빈 값은 칸을 안 적거나 `값` 을 안 둠(빈 글 `""` 금지). +- 그림 열쇠 = `fig/<측점 키>.png`(측점 키 = `common_util_station_key`) · 3D 띠 = `fig/3d_band.png`. 양식의 `fig/{측점키}.png` 는 자리 표시일 뿐 — 빌더가 목록을 새로 씀. + +## s1 표지 (사용자 몫) + +| 칸 | 내용 | +| --- | --- | +| B4:E4 | 제목(고정 글 · 서식 1) | +| C8:E8 · C9:E9 · C10:E10 · C11:E11 · C12:E12 | 사업명 · 노선명 · 발주처 · 작성일 · 작성자 값(병합 · 서식 5) — 왼위 칸 `C8` … `C12` 에 값 | +| B14:E14 | 「문헌 추정치 · 현장 시험 없음」(고정 글 · 건드리지 않음) | + +## s2 개요 (사용자 몫 · 수식은 양식에 들어 있음) + +- 빌더가 쓸 것: 없음(수식이 `측점표` · `입력조건` 을 가리킴). 의견 칸 `A22:F30`(병합 · 서식 15) = 사용자 글. +- 수식 칸: `B5` 대상 측점 수 · `B6` 연장 · `B10:B12` 조건별 최소 Fs · `C10:C12` = `입력조건!B12:B14` · `D10:D12` 판정 · `E10`·`E11`·`E12` 최소 측점 · `B15` N.G 수 · `B16` 종합 판정 · `B17`(병합 B17:F19) 판정 글. +- 지진을 안 쓰면 `측점표!F` 열을 비움 → 개요 지진 행은 `-`. + +## s3 입력조건 (프로그램 몫) + +| 구역 | 칸 | 내용 | +| --- | --- | --- | +| 해석 방법 | `B5` `B6` `B7` `B8` (각 B:G 병합) | 해석 기법 · 활동면 탐색 · 3D 해석 · 적용 기준 글 | +| 기준 안전율 | `B12` `B13` `B14` | 건기 · 우기 · 지진 기준 값(수 · 서식 8) · 지진 끔 = 글 `-` | +| | `C12` `C13` `C14` (C:G 병합) | 비고 글 | +| 지하수 규칙 | `B17` `B18` `B19` (B:G 병합) | 건기 · 우기 · 지진 시 규칙 글 | +| 노면 하중 | `B22` | 수(kPa · 서식 16) | +| 지반 물성치 | **26 행부터 한 줄씩** · 머리 25 행(머리 줄 반복) | A 지층 · B 적용 구간(전체 또는 구간 글) · C γt · D γsat · E c · F φ(서식 16) · G 출처(서식 5) · A·B = 서식 7 | + +- 지반 물성치가 표의 맨 끝이라 줄 수가 늘어도 다른 칸이 안 밀림. 구간별 덮어쓰기 줄 = 같은 표에 줄 추가(B 열에 구간). + +## s4 측점표 (프로그램 몫 · 머리 줄 4~5 행 쪽마다 반복 · 가로 · 폭 1쪽) + +- 자료는 **6 행부터 측점 하나 = 한 줄** · 서식은 6 행 서식을 줄마다 그대로. + +| 열 | 내용 | 서식 | +| --- | --- | --- | +| A | 측점 이름 | 7 | +| B | 누가거리(m · 수) | 17 | +| C | 구분(`깎기` · `쌓기`) | 7 | +| D · E · F | 건기 · 우기 · 지진 안전율 Fs(지진 끔 = 칸 없음) | 8 | +| G · H · I | 건기 · 우기 · 지진 기준 안전율 | 8 | +| J | 판정 `O.K` · `N.G`(엔진 판정 값) | N.G = 10 · O.K = 11 | + +- 개요 · 3D 수식이 `A6:A20000` · `B` · `D` · `E` · `F` · `J` 열을 읽음 → 이 열 배치 바꾸지 않음. +- 줄 수 = 측점 수(시험: 측점 수 = 줄 수). 쪽 나눔은 자동(`인쇄.쪽나눔` 안 씀). + +## s5 대표단면 (프로그램 몫 · 단면 하나 = 28 행 덩이 = 한 쪽) + +덩이 `k`(0 부터)의 시작 오프셋 `dr = 28 × k` — 양식의 1~28 행 덩이를 `dr` 만큼 아래로 복사해 단면 수만큼 만듦(칸 · 병합 · 행 높이 · `조건부서식` 범위 포함). + +| 덩이 안 행 | 칸 | 내용 | +| --- | --- | --- | +| 1 | `A1:H1` 병합 | 단면 제목(`대표 단면 ① — 측점 · 구분 · 높이`) | +| 2 | `B2:H2` 병합 | 선정 사유 글 | +| 3~22 | 그림 `A3:H22` | 그림 한 장 — 그림 칸 크기 약 712 × 533 px(가로 : 세로 = 4 : 3) · 열쇠 `fig/<측점 키>.png` · 닻은 `dr` 만큼 내림 | +| 23 | 머리(고정 글) | 조건 · 해석 방법 · 안전율 · 기준 · 판정 · 활동면 반경 R · 중심 x · 중심 y | +| 24 · 25 · 26 | A = 건기 · 우기 · 지진(고정 글) | B 해석 방법 글(서식 7) · C Fs(8) · D 기준(8) · E 판정(7→ N.G 10 · O.K 11) · F 반경 · G 중심 x · H 중심 y(13) | +| 27 | `B27:H27` 병합 | 비고 글 | +| 28 | 빈 줄 | 쪽 끝 | + +- `인쇄.쪽나눔` = `[28, 56, …, 28 × 단면 수]`(마지막 덩이 끝 쪽 나눔은 있어도 빈 쪽이 안 생김). +- 지진을 안 쓰면 26 행(덩이 안) 칸을 비움. + +## s6 3D요약 (프로그램 몫 · 머리 줄 19 행 반복) + +| 칸 | 내용 | +| --- | --- | +| 그림 `A5:H16` | 평면 위험 띠 — 그림 칸 약 684 × 320 px(2.1 : 1) · 열쇠 `fig/3d_band.png` | +| 20 행부터 구간 하나 = 한 줄 | A 구간 · B 시작 측점 · C 끝 측점(서식 7) · D 3D 최소 Fs · E 2D 최소 Fs(서식 8) · **F 3D/2D 비 = 수식 `IF(E{r}>0,D{r}/E{r},"")`**(서식 8) · G 임계 체적 m³(서식 14) · H 판정(서식 7 → N.G 10 · O.K 11) | + +- 20 행 서식을 줄마다 복사. diff --git a/resources/tester/test_534_3_stab_report_form.py b/resources/tester/test_534_3_stab_report_form.py new file mode 100644 index 000000000..89ac63e32 --- /dev/null +++ b/resources/tester/test_534_3_stab_report_form.py @@ -0,0 +1,252 @@ +"""534-3(양식) — E05 안정성평가 보고서 양식 `resources/master_template/report/stab_report.json`. + +계약 검사(키 · 서식 번호 · A1 · 병합 · 쪽 나눔) + 가짜 값 사본으로 수식 · 인쇄 HTML · PDF 쪽 수 · xlsx 되읽기. +""" + +import copy +import io +import json +import re +import shutil +from pathlib import Path + +import pytest +from openpyxl import load_workbook + +from common_util import common_util_spreadsheet as spreadsheet +from common_util.common_util_print_pdf import PrintError +from common_util.common_util_svg_png import svgs_to_pngs +from common_util.common_util_workbook_xlsx import workbook_to_xlsx + +ROOT = Path(__file__).resolve().parents[2] +FORM = ROOT / "resources/master_template/report/stab_report.json" +BLOCK = 28 # 대표단면 한 쪽 = 28 행 +SVG = ( + '' + '' +) +BOOK_KEYS = {"종류", "판", "열", "기본", "서식", "시트", "활성", "계산값"} +SHEET_KEYS = { + "id", "이름", "칸", "병합", "열", "행", "틀고정", "보기", "기본", "comments", "필터", + "조건부서식", "유효성", "이름정의", "숨김", "탭색", "연결", "인쇄", "그림", +} # fmt: skip +STYLE_KEYS = { + "글꼴", "크기", "굵게", "기울임", "밑줄", "취소선", "글자색", "채움", "가로", "세로", "줄바꿈", + "테두리", "형식", +} # fmt: skip +PRINT_KEYS = {"용지", "방향", "배율", "맞춤", "여백", "머리행", "쪽나눔"} +A1 = re.compile(r"^([A-Z]{1,3})(\d+)$") +NAMES = ["표지", "개요", "입력조건", "측점표", "대표단면", "3D요약"] + +needs_node = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") + + +def _form() -> dict: + return json.loads(FORM.read_text(encoding="utf-8")) + + +def _rc(a1: str) -> tuple[int, int]: + m = A1.match(a1) + assert m, a1 + col = 0 + for ch in m.group(1): + col = col * 26 + ord(ch) - 64 + return int(m.group(2)), col + + +def _span(text: str) -> tuple[int, int, int, int]: + a, _, b = text.partition(":") + (r0, c0), (r1, c1) = _rc(a), _rc(b or a) + assert r0 <= r1 and c0 <= c1, text + return r0, c0, r1, c1 + + +def _contract(book: dict) -> None: + assert set(book) <= BOOK_KEYS and book["종류"] == "통합문서" and book["판"] == 1 + assert book["서식"][0] == {} + for st in book["서식"]: + assert set(st) <= STYLE_KEYS + ids = [s["id"] for s in book["시트"]] + assert len(set(ids)) == len(ids) and book["활성"] in ids + for sheet in book["시트"]: + assert set(sheet) <= SHEET_KEYS + for a1, cell in sheet["칸"].items(): + _rc(a1) + assert set(cell) <= {"값", "식", "서식"} and not ({"값", "식"} <= set(cell)) + assert 0 <= cell.get("서식", 0) < len(book["서식"]) + used: set[tuple[int, int]] = set() + for m in sheet.get("병합", []): + r0, c0, r1, c1 = _span(m) + box = {(r, c) for r in range(r0, r1 + 1) for c in range(c0, c1 + 1)} + assert len(box) > 1 and not (used & box), m + used |= box + pr = sheet.get("인쇄", {}) + assert set(pr) <= PRINT_KEYS + assert pr.get("쪽나눔", []) == sorted(set(pr.get("쪽나눔", []))) + if "머리행" in pr: + assert 1 <= pr["머리행"][0] <= pr["머리행"][1] + for fig in sheet.get("그림", []): + _span(fig["닻"]) + assert fig["열쇠"] + for rule in sheet.get("조건부서식", []): + assert rule["조건"]["종류"] == "글포함" and rule["범위"] + + +# ── 가짜 값 사본 ───────────────────────────────────────────────────────────── +def _shift(a1: str, dr: int) -> str: + m = A1.match(a1) + return f"{m.group(1)}{int(m.group(2)) + dr}" + + +def _fill(book: dict, stations: int, sections: int) -> tuple[dict, int]: + """측점표 `stations` 줄 · 대표단면 `sections` 쪽 · 3D 두 줄을 채운 사본 · N.G 수.""" + book = copy.deepcopy(book) + by = {s["이름"]: s for s in book["시트"]} + s3, s4, s5, s6 = by["입력조건"], by["측점표"], by["대표단면"], by["3D요약"] + for r, v in ((12, 1.5), (13, 1.2), (14, 1.1)): + s3["칸"][f"B{r}"]["값"] = v + ng = 0 + for i in range(stations): + r = 6 + i + bad = i % 7 == 3 + ng += bad + fs = [1.32 if bad else 1.8 + i / 100, 1.5 + i / 100, 1.3 + i / 100] + for letter, v, st in ( + ("A", f"No.{i}", 7), ("B", 20.0 * i, 17), ("C", "깎기" if i % 2 else "쌓기", 7), + ("D", fs[0], 8), ("E", fs[1], 8), ("F", fs[2], 8), + ("G", 1.5, 8), ("H", 1.2, 8), ("I", 1.1, 8), ("J", "N.G" if bad else "O.K", 7), + ): # fmt: skip + s4["칸"][f"{letter}{r}"] = {"값": v, "서식": st} + for k in range(1, sections): # 첫 덩이를 아래로 복사 + dr = BLOCK * k + for a1, cell in list(s5["칸"].items()): + if int(A1.match(a1).group(2)) <= BLOCK: + s5["칸"][_shift(a1, dr)] = copy.deepcopy(cell) + for m in list(s5["병합"]): + if int(re.search(r"\d+", m).group()) <= BLOCK: + s5["병합"].append(":".join(_shift(p, dr) for p in m.split(":"))) + for r, info in list(s5["행"].items()): + if int(r) <= BLOCK: + s5["행"][str(int(r) + dr)] = dict(info) + a, _, b = s5["그림"][0]["닻"].partition(":") + s5["그림"].append({"열쇠": f"fig/s{k}.png", "닻": f"{_shift(a, dr)}:{_shift(b, dr)}"}) + s5["인쇄"]["쪽나눔"].append(BLOCK * (k + 1)) + s5["그림"][0]["열쇠"] = "fig/s0.png" + for r, row in ( + (20, ("1구간", "No.0", "No.9", 1.9, 1.7, 1200)), + (21, ("2구간", "No.9", "No.19", 1.4, 1.5, 800)), + ): + for letter, v in zip("ABCDEG", row): + s6["칸"][f"{letter}{r}"] = {"값": v, "서식": s6["칸"][f"{letter}20"]["서식"]} + s6["칸"][f"F{r}"] = {"식": f'IF(E{r}>0,D{r}/E{r},"")', "서식": s6["칸"]["F20"]["서식"]} + s6["칸"][f"H{r}"] = {"값": "O.K" if r == 20 else "N.G", "서식": s6["칸"]["H20"]["서식"]} + return book, ng + + +# ── 시험 ───────────────────────────────────────────────────────────────────── +def test_양식_계약(): + book = _form() + _contract(book) + assert [s["이름"] for s in book["시트"]] == NAMES + assert [s["id"] for s in book["시트"]] == ["s1", "s2", "s3", "s4", "s5", "s6"] + + +def test_양식_인쇄_쪽나눔_머리행_그림(): + by = {s["이름"]: s for s in _form()["시트"]} + for sheet in by.values(): + pr = sheet["인쇄"] + assert pr["용지"] == "A4" and pr["맞춤"]["폭"] == 1 and pr["여백"] + assert by["측점표"]["인쇄"]["방향"] == "landscape" and by["측점표"]["인쇄"]["머리행"] == [4, 5] + assert by["대표단면"]["인쇄"]["쪽나눔"] == [BLOCK] + figs = {n: len(s.get("그림", [])) for n, s in by.items()} + assert figs == {"표지": 0, "개요": 0, "입력조건": 0, "측점표": 0, "대표단면": 1, "3D요약": 1} + # N.G 빨강 조건부 서식 — 측점표 판정 열 · 서식 표에 N.G 칸 서식(빌더가 인쇄 · xlsx 용으로 직접 씀) + assert by["측점표"]["조건부서식"][0]["범위"][0]["c0"] == 9 + assert any(st.get("글자색") == "#C00000" and st.get("채움") for st in _form()["서식"]) + # 안전율 칸 숫자 형식 0.00 + assert _form()["서식"][by["측점표"]["칸"]["D6"]["서식"]]["형식"] == "0.00" + + +@needs_node +def test_빈_양식도_풀림(): + book = _form() + vals = spreadsheet.recalc_workbooks([book])[0] + assert vals["s2"]["B5"] == {"수": "0"} + assert vals["s2"]["B10"] == {"글": "-"} + assert vals["s2"]["B16"] == {"글": "-"} + assert vals["s2"]["B17"] == {"글": "해석 결과 없음"} + assert not [v for s in vals.values() for v in s.values() if "오류" in v] + + +@needs_node +def test_채운_양식_수식(): + book, ng = _fill(_form(), 40, 2) + s2 = spreadsheet.recalc_workbooks([book])[0]["s2"] + assert s2["B5"] == {"수": "40"} and s2["B6"] == {"수": "780"} + assert s2["B15"] == {"수": str(ng)} and ng == 6 + assert ( + s2["B10"] == {"수": "1.32"} and s2["D10"] == {"글": "N.G"} and s2["E10"] == {"글": "No.3"} + ) + assert s2["D11"] == {"글": "O.K"} and s2["B11"] == {"수": "1.5"} + assert s2["B16"] == {"글": "N.G"} + assert s2["B17"]["글"].startswith("대상 측점 40개소 중 6개소가") + s6 = spreadsheet.recalc_workbooks([book])[0]["s6"] + assert s6["F21"] == {"수": "0.933333333333333"} or s6["F21"]["수"].startswith("0.93333") + + +@pytest.fixture(scope="module") +def png() -> bytes: + try: + return svgs_to_pngs([SVG])[0] + except PrintError as error: + pytest.skip(f"브라우저 없음 — {error}") + + +@needs_node +def test_견본_pdf_쪽수_머리행(png): + book, _ = _fill(_form(), 40, 2) + figs = {"fig/s0.png": png, "fig/s1.png": png, "fig/3d_band.png": png} + try: + pdf = spreadsheet.workbook_pdf(book, figs) + except PrintError as error: + pytest.skip(f"브라우저 없음 — {error}") + from pypdf import PdfReader + + pages = PdfReader(io.BytesIO(pdf)).pages + texts = [p.extract_text() for p in pages] + stab = [i for i, t in enumerate(texts) if "측점별 안전율" in t or "산정 안전율" in t] + assert len(stab) >= 2 and all("산정 안전율" in texts[i] for i in stab) # 머리 줄 쪽마다 + assert ( + texts[0].count("비탈면 안정성 검토 보고서") == 1 + and "문헌 추정치 · 현장 시험 없음" in texts[0] + ) + sections = [i for i, t in enumerate(texts) if "대표 단면" in t and "안전율 Fs" in t] + assert len(sections) == 2 # 단면마다 한 쪽 + # 표지 1 + 개요 1 + 입력조건 1 + 측점표 n + 대표단면 2 + 3D 1 + assert len(pages) == 1 + 1 + 1 + len(stab) + 2 + 1 + assert all(float(pages[i].mediabox.width) > float(pages[i].mediabox.height) for i in stab) + html = spreadsheet.print_htmls([{"book": book, "figures": {k: png for k in figs}}])[0] + assert html.count("