chore(tester): scratch 분석·감사 도구를 resources/tester/scratch 로 이동 및 conftest 등록
This commit is contained in:
@@ -4,3 +4,5 @@ import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
collect_ignore = ["scratch"]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load basis_missing_2026-01-01.json
|
||||
missing_file = Path('resources/data_work_item_master/basis_missing_2026-01-01.json')
|
||||
with open(missing_file, 'r', encoding='utf-8') as f:
|
||||
missing_data = json.load(f)
|
||||
|
||||
print("basis_missing keys:", list(missing_data.keys()))
|
||||
missing_records = missing_data.get('items', [])
|
||||
print(f"Total missing records: {len(missing_records)}")
|
||||
|
||||
# Load original MD lines
|
||||
md_path = Path("resources/knowledge/original/행정규칙/임도 품셈 적용기준 (현 산림사업 표준품셈)/첨부/(산림청고시 제2025-82호) 산림사업 표준품셈.md")
|
||||
with open(md_path, "r", encoding="utf-8") as f:
|
||||
md_lines = f.readlines()
|
||||
|
||||
# Load enriched tables
|
||||
sys.path.insert(0, '.')
|
||||
from scratch.enrich_tables import tables
|
||||
|
||||
tables_by_id = {t['table_id']: t for t in tables}
|
||||
|
||||
# Check each of the 88 missing tables
|
||||
# Category 1: Dimensionless / Coefficient / Reference / Pure text guide (원문에 밑수 개념 자체가 없음)
|
||||
# Category 2: Header / Pre-context contains explicit quantity/unit (원문에 있는데 파서가 못 읽음)
|
||||
category_no_basis_in_md = []
|
||||
category_has_basis_in_md = []
|
||||
|
||||
basis_pattern = re.compile(r'\[\s*([0-9\.\,]*)\s*([a-zA-Z㎡㎥㏊ha본개공mkm㎏gtonLℓ대인組]+)\s*당\s*\]')
|
||||
basis_pattern2 = re.compile(r'\(단위\s*:\s*([^\)]+)\)')
|
||||
|
||||
for rec in missing_records:
|
||||
tid = rec.get('pum_table_id')
|
||||
t = tables_by_id.get(tid)
|
||||
if not t:
|
||||
continue
|
||||
|
||||
pre_lines = t.get('pre_context', [])
|
||||
header_line = t.get('lines', [''])[0]
|
||||
|
||||
# Check if pre_lines or headers have basis
|
||||
found_basis = None
|
||||
for pl in reversed(pre_lines):
|
||||
m = basis_pattern.search(pl)
|
||||
if m:
|
||||
found_basis = pl
|
||||
break
|
||||
m2 = basis_pattern2.search(pl)
|
||||
if m2:
|
||||
found_basis = pl
|
||||
break
|
||||
|
||||
# Check table form or characteristics
|
||||
pform = rec.get('pum_form')
|
||||
|
||||
if found_basis:
|
||||
category_has_basis_in_md.append({
|
||||
'table_id': tid,
|
||||
'section': rec.get('section'),
|
||||
'pum_form': pform,
|
||||
'line': t.get('start_line'),
|
||||
'evidence': found_basis
|
||||
})
|
||||
else:
|
||||
category_no_basis_in_md.append({
|
||||
'table_id': tid,
|
||||
'section': rec.get('section'),
|
||||
'pum_form': pform,
|
||||
'line': t.get('start_line'),
|
||||
'reason': '계수표·할증률표·규격기준표 또는 원문에 단위수량 없음'
|
||||
})
|
||||
|
||||
print(f"\nAnalysis of 88 Missing Basis Tables:")
|
||||
print(f"1. 원문에 밑수가 분명히 명시되어 있으나 파서가 아직 못 읽은 것: {len(category_has_basis_in_md)}개")
|
||||
print(f"2. 원문 자체에 밑수(단위수량)가 없는 무차원·계수·참고 표: {len(category_no_basis_in_md)}개")
|
||||
|
||||
print("\n--- [그룹 1: 원문에 있는데 아직 못 읽은 표] 전수 명세 ---")
|
||||
for it in category_has_basis_in_md:
|
||||
print(f" [{it['table_id']} | line {it['line']}] {it['section']} (form: {it['pum_form']}) -> 증거: {it['evidence']}")
|
||||
|
||||
print("\n--- [그룹 2: 원문에 밑수 개념이 없는 표] 샘플 15개 ---")
|
||||
for it in category_no_basis_in_md[:15]:
|
||||
print(f" [{it['table_id']} | line {it['line']}] {it['section']} (form: {it['pum_form']}) -> {it['reason']}")
|
||||
@@ -0,0 +1,36 @@
|
||||
import os, sys, re, json
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ch13_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md"
|
||||
|
||||
with open(ch13_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
print(f"Total lines in 제13장: {len(lines)}")
|
||||
|
||||
collapsed_rows = []
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
# Find if any cell has >= 10 space-separated tokens that look like numbers or specifications
|
||||
for c_idx, c in enumerate(cells):
|
||||
tokens = c.split()
|
||||
if len(tokens) >= 8 and any(tok[0].isdigit() for tok in tokens):
|
||||
collapsed_rows.append({
|
||||
"line": idx + 1,
|
||||
"col_idx": c_idx,
|
||||
"token_count": len(tokens),
|
||||
"first_cell": cells[0][:30],
|
||||
"cell_snippet": c[:100],
|
||||
"tokens": tokens
|
||||
})
|
||||
break
|
||||
|
||||
print(f"Found {len(collapsed_rows)} collapsed multi-token rows in 제13장")
|
||||
for r in collapsed_rows:
|
||||
print(f"L{r['line']} (tokens: {r['token_count']}) [{r['first_cell']}]: {r['cell_snippet'][:80]}")
|
||||
|
||||
with open('scratch/ch13_collapsed_rows.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(collapsed_rows, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,18 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
with open('resources/data_work_item_link/work_item_link_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
links = data.get('links', [])
|
||||
print(f"Total links: {len(links)}")
|
||||
|
||||
const_keys = set()
|
||||
for link in links:
|
||||
ck = link.get('const_key')
|
||||
if ck:
|
||||
const_keys.add(ck)
|
||||
|
||||
print(f"Unique const_keys: {len(const_keys)}")
|
||||
for ck in sorted(const_keys):
|
||||
print(ck)
|
||||
@@ -0,0 +1,18 @@
|
||||
import json, os, sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
data = json.load(open('scratch/deep_scan_flaws.json', encoding='utf-8'))
|
||||
|
||||
by_file = {}
|
||||
for d in data:
|
||||
f = os.path.basename(d['file'])
|
||||
by_file.setdefault(f, []).append(d)
|
||||
|
||||
print(f"Total Flaws: {len(data)}")
|
||||
for f, items in sorted(by_file.items()):
|
||||
print(f"\n=== {f} ({len(items)} items) ===")
|
||||
for item in items:
|
||||
line_no = item.get('line', item.get('start_line', '?'))
|
||||
itype = item.get('type')
|
||||
detail = item.get('detail', '')
|
||||
print(f" Line {line_no}: [{itype}] {detail[:100]}")
|
||||
@@ -0,0 +1,66 @@
|
||||
import sys, os, re, json
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
with open('scratch/confirmed_attached_headers.json', 'r', encoding='utf-8') as f:
|
||||
fixes = json.load(f)
|
||||
|
||||
fixes_by_file = {}
|
||||
for fix in fixes:
|
||||
f = fix['file']
|
||||
if f not in fixes_by_file:
|
||||
fixes_by_file[f] = []
|
||||
fixes_by_file[f].append(fix)
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
|
||||
stats = []
|
||||
|
||||
for rel_file, file_fixes in fixes_by_file.items():
|
||||
fpath = os.path.join(base_dir, rel_file)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
lines = text.split('\n')
|
||||
orig_count = len(lines)
|
||||
|
||||
# Sort fixes descending by line
|
||||
file_fixes_sorted = sorted(file_fixes, key=lambda x: (x['line'], x['matched_pos']), reverse=True)
|
||||
|
||||
modified_lines = list(lines)
|
||||
fixed_count = 0
|
||||
|
||||
for fix in file_fixes_sorted:
|
||||
l_idx = fix['line'] - 1
|
||||
sec_num = fix['sec_num']
|
||||
cur_line = modified_lines[l_idx]
|
||||
|
||||
# Regex to find exact boundary: not space, not quote/bracket, then sec_num + space
|
||||
pattern = re.compile(rf'([^\s#\-\*\>\|\(‘\'"\[])({re.escape(sec_num)}\s+)')
|
||||
m = pattern.search(cur_line)
|
||||
if m:
|
||||
new_line = cur_line[:m.start(2)] + '\n' + cur_line[m.start(2):]
|
||||
modified_lines[l_idx] = new_line
|
||||
fixed_count += 1
|
||||
|
||||
new_text = '\n'.join(modified_lines)
|
||||
new_lines = new_text.split('\n')
|
||||
added_lines = len(new_lines) - orig_count
|
||||
|
||||
with open(fpath, 'w', encoding='utf-8') as f:
|
||||
f.write(new_text)
|
||||
|
||||
stats.append({
|
||||
"file": rel_file,
|
||||
"original_lines": orig_count,
|
||||
"new_lines": len(new_lines),
|
||||
"lines_added": added_lines,
|
||||
"fixes_applied": fixed_count
|
||||
})
|
||||
print(f"Fixed {rel_file}: {fixed_count} headers restored (lines {orig_count} -> {len(new_lines)})")
|
||||
|
||||
with open('scratch/headers_fix_stats.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(stats, f, ensure_ascii=False, indent=2)
|
||||
|
||||
total_fixes = sum(s['fixes_applied'] for s in stats)
|
||||
total_lines_added = sum(s['lines_added'] for s in stats)
|
||||
print(f"\n=== Completed: {total_fixes} attached headers restored, {total_lines_added} lines added across {len(stats)} files ===")
|
||||
@@ -0,0 +1,680 @@
|
||||
[
|
||||
{
|
||||
"key": "CW-00009",
|
||||
"section": "1-2-2",
|
||||
"name": "단위표준",
|
||||
"chapter": "공통_제1장",
|
||||
"pdf_pages": [
|
||||
60
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "8. 절토(切土)량은 자연상태의 설계도의 양으로 한다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (15개 단어): | 공 사 연 장 | m | 2 | m | - | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 공 사 폭 원 | - | - | m | 1 | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 직 공 인 부 | - | - | 인 | 2 | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 공 사 면 적 | - | - | ㎡ | 1 | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 용 지 면 적 | - | - | ㎡ | - | |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | 토 적 ( 높 이 , 너 비 ) | - | - | m | 2 | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 토 적 ( 단 면 적 ) | - | - | ㎡ | 1 | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 토 적 ( 체 적 ) | - | - | ㎥ | 2 | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 토 적 ( 체 적 합 계 ) | - | - | ㎥ | - | |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 모 래 , 자 갈 | ㎝ | - | ㎥ | 2 | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 견 치 돌 , 깬 돌 | ㎝ | - | ㎡ | 1 | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 견 치 돌 , 깬 돌 | ㎝ | - | 개 | - | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 야 면 석 ( 野 面 石 ) | ㎝ | - | 개 | - | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 야 면 석 ( 야 面 石 ) | ㎝ | - | ㎥ | 1 | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 야 면 석 ( 野 面 石 ) | ㎝ | - | ㎡ | 1 | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 돌 쌓 기 및 돌 붙 임 | ㎝ | - | ㎥ | 1 | |...",
|
||||
"페이지 전이 찌꺼기 삽입: →4공통부문",
|
||||
"표 한 줄 뭉침 (311개 단어): | 돌 쌓 기 및 돌 붙 임 사 석 ( 捨 石 ) 다 듬 돌 ( 切 石 , 板 石 ) 벽 돌 블 록 시 멘 ...",
|
||||
"페이지 전이 찌꺼기 삽입: →5제1장 적용기준",
|
||||
"표 한 줄 뭉침 (126개 단어): | 도 료 ( 塗 料 ) 도 장 ( 塗 裝 ) 관 류 ( 管 類 ) 수 로 연 장 옹 벽 승강장옹벽 및 울타...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 설 계 서 의 금 액 란 | 원 | 1 | 미만버림 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 일 위 대 가 표 의 계 금 | 원 | 1 | 미만버림 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 일 위 대 가 표 의 금 액 란 | 원 | 0.1 | 미만버림 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00010",
|
||||
"section": "1-2-3",
|
||||
"name": "토질",
|
||||
"chapter": "공통_제1장",
|
||||
"pdf_pages": [
|
||||
62
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "이 불가피할 경우에는 소수자리의 정도를 조정 계산한다.",
|
||||
"flaws_detected": [
|
||||
"페이지 전이 찌꺼기 삽입: - ⑦ 깬돌(割石) : 견치돌에 준한 재두방추형(栽頭方錐形)으로서 견치돌보다 치수가 불규칙하고 일반적으로뒷면",
|
||||
"표 한 줄 뭉침 (15개 단어): | 보 통 암 ( 普 通 硬 岩 ) | 1.55∼1.70 | 1.20∼1.40 |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 고 결 ( 固 結 ) 된 역 질 토 ( 礫 質 土 ) | 1.25∼1.45 | 1.10∼1.30 |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 암 괴 ( 岩 塊 ) 나 호 박 돌 이 섞 인 모 래 | 1.15∼1.20 | 0.90∼1.00 |...",
|
||||
"표 한 줄 뭉침 (23개 단어): | 암 괴 ( 岩 塊 ) 나 호 박 돌 이 섞 인 모 래 질 흙 | 1.40∼1.45 | 0.90∼0.95 ...",
|
||||
"페이지 전이 찌꺼기 삽입: →8공통부문",
|
||||
"표 한 줄 뭉침 (21개 단어): | 역 ( 礫 ) 이 섞 인 점 질 토 ( 粘 質 土 ) | 1.35∼1.40 | 0.90∼1.00 |...",
|
||||
"표 한 줄 뭉침 (22개 단어): | 암 괴 ( 岩 塊 ) 나 호 박 돌 이 섞 인 점 질 토 | 1.40∼1.45 | 0.90∼0.95 |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 암 괴 ( 岩 塊 ) 나 호 박 돌 이 섞 인 점 토 | 1.40∼1.45 | 0.90∼0.95 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 자 연 상 태 의 체 적 | 1 | L | C |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00014",
|
||||
"section": "1-2-7",
|
||||
"name": "운반",
|
||||
"chapter": "공통_제1장",
|
||||
"pdf_pages": [
|
||||
71,
|
||||
630
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "계경비산정표에 명시되지 아니한 소규모의 것.14공통부문",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (17개 단어): | 토 사 류 석 재 류 | 1.5분 2분 | 3,000 | 2,500 | 2,000 |...",
|
||||
"페이지 전이 찌꺼기 삽입: - ⑥ 적재운반 적하는 1인을 기준으로 한다.15제1장 적용기준",
|
||||
"표 한 줄 뭉침 (18개 단어): | 보 통 인 부 | 인 | 0.44 | 0.56 | 0.74 | 0.96 | 1.19 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 비 고 | - 리프트를 사용할 경우 보통인부 0.31인을 적용한다. | | | | | |...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 경사도 | % | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100...",
|
||||
"표 한 줄 뭉침 (24개 단어): | | 각도 | 6 | 11 | 17 | 22 | 27 | 31 | 35 | 39 | 42 | 45 |...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 환산계수(α) | | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 종 별 | 규 격 | 단위 | 적 재 량 | | | | 비고 |...",
|
||||
"표 한 줄 뭉침 (22개 단어): | 목 재 (원 목) | 길이가 긴 것은 낱개 | ㎥ | 7.7 | 10 | 13 | - | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 목 재 (제재목) | 〃 | 〃 | 9.0 | 12 | 16 | - | |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 경유・휘발유 | 200ℓ | 드럼 | 30 | 40 | 55 | - | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 아 스 팔 트 | 〃 | 〃 | 24 | 35 | 50 | - | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 새 끼 | 12㎜, 9.4㎏ | 다발 | 480 | 640 | - | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 벽 돌 | 19㎝×9㎝×5.7㎝(표준형) | 개 | 2,930 | 3,900 | 5,300 | - | ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 기 와 | 34㎝×30㎝×1.5㎝ | 매 | 1,860 | 2,480 | 3,400 | - | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 보 도 블 록 | 30㎝×45㎝×6㎝ | 개 | 490 | 650 | 890 | - | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 견 치 돌 | 뒷길이 45㎝ | 개 | 100 | 135 | 180 | - | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 블 록 | 두께 10㎝ | 〃 | 650 | 860 | 1,180 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 두께 15㎝ | 〃 | 450 | 600 | 820 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 두께 20㎝ | 〃 | 350 | 460 | 630 | - | |...",
|
||||
"표 한 줄 뭉침 (41개 단어): | 타 일 크 링 커 타 일 | 두께 6㎜ (8㎜) 두께 24㎜ | ㎡ 〃 | 500 (350) 150 | ...",
|
||||
"표 한 줄 뭉침 (49개 단어): | 합 판 유 리 페 인 트 아 스 타 일 | 12×900×1,800㎜ 두께 3㎜ 4ℓ(18ℓ)/통 3㎜×3...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 흄 관 | ø300㎜ L=2.5m | 본 | 27 | 36 | 52 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 450 〃 | 〃 | 15 | 20 | 27 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 600 〃 | 〃 | 8 | 12 | 15 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 800 〃 | 〃 | 4 | 6 | 9 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 900 〃 | 〃 | 4 | 5 | 7 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 1,000 〃 | 〃 | 3 | 4 | 5 | 10 | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 1,200 〃 | 〃 | 2 | 3 | 4 | 7 | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 1,500 〃 | 〃 | 1 | 2 | 2 | 5 | |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 콘 크 리 트 관 | ø250㎜ L=1m | 본 | 60 | 80 | 110 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 300 〃 | 〃 | 52 | 70 | 96 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 350 〃 | 〃 | 42 | 60 | 82 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 450 〃 | 〃 | 25 | 30 | 41 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 600 〃 | 〃 | 16 | 20 | 27 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 900 〃 | 〃 | 9 | 12 | 16 | - | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 〃 | 1,000∼1,500 〃 | 〃 | 3∼6 | 4∼8 | 5∼10 | 12 | |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00018",
|
||||
"section": "1-3-1",
|
||||
"name": "재료의 할증",
|
||||
"chapter": "공통_제1장",
|
||||
"pdf_pages": [
|
||||
76,
|
||||
631,
|
||||
923
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": " 않는다.19제1장 적용기준1-3 재료 및 노임의 할증",
|
||||
"flaws_detected": [
|
||||
"페이지 전이 찌꺼기 삽입: 공종 시공 시에는 본 할증을 적용하지 않는다.19제1장 적용기준1-3 재료 및 노임의 할증1-3-1 재료의 ",
|
||||
"표 한 줄 뭉침 (48개 단어): | 치 환 모 래 ( 置 換 砂 ) 깔 모 래 ( 敷 砂 ) 사 항 용 모 래 ( 砂 抗 用 砂 ) 압 입 ...",
|
||||
"표 한 줄 뭉침 (16개 단어): | | 사석두께 | 2m미만 | 2m이상 | 2m미만 | 2m이상 | 2m미만 | 2m이상 |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 기 초 사 석 | | 25% | 20% | 30% | 25% | 50% | 40% |...",
|
||||
"표 한 줄 뭉침 (23개 단어): | 피 복 석 ( 被 覆 石 ) | | 15% | 15% | 15% | 15% | 20% | 20% |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | 뒤 채 움 사 석 | | 20% | 20% | 20% | 20% | 25% | 25% |...",
|
||||
"표 한 줄 뭉침 (120개 단어): | 원 형 철 근 이 형 철 근 이 형 철 근 ( 교 량 ・ 지 하 철 및 이 와 유 사 한 복 잡 한 구 ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 프 레 스 접 합 식 스 테 인 리 스 강 관 | 5 |...",
|
||||
"페이지 전이 찌꺼기 삽입: - ④ 현장 여건상 절단 및 가공 등이 불필요한 경우, 상기 할증률을 조정하여 적용할 수 있다.21제1장 적",
|
||||
"표 한 줄 뭉침 (275개 단어): | 각 재 목 재 판 재 일 반 용 합 판 합 판 수 장 용 합 판 쉬 즈 관 쉬 즈 판 P V C 관 / ...",
|
||||
"페이지 전이 찌꺼기 삽입: →22공통부문",
|
||||
"표 한 줄 뭉침 (72개 단어): | 테 라 콧 타 블 록 기 와 슬 레 이 트 모 자 이 크 도 기 자 기 아 스 팔 트 타 일 리 노 륨 ...",
|
||||
"페이지 전이 찌꺼기 삽입: - 5. 할증율(%)은 요소별 일반적인 작업조건을 기준으로 제시하였으며, 일부의 작업에 영향을 미치는경우 할"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00151",
|
||||
"section": "3-3-6",
|
||||
"name": "암발파(대규모발파 TYPE-Ⅵ)",
|
||||
"chapter": "공통_제3장",
|
||||
"pdf_pages": [
|
||||
127
|
||||
],
|
||||
"header_separation": "정상 분리",
|
||||
"preceding_context": "",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (16개 단어): | 유 압 식 크 롤 러 드 릴 | 110㎾ | hr | 0.012 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 보 통 인 부 굴 착 기 | 1.0㎥ | 인 hr | 0.125 1.000 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 굴 착 기 + 대 형 브 레 이 커 | 0.6∼0.8㎥ | 9 | 11 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 유 압 식 크 롤 러 드 릴 | 110㎾ | hr | 0.121 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 굴 착 기 + 대 형 브 레 이 커 | 1.0㎥ | hr | 0.121 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 소 형 브 레 이 커 | 2.7㎥/min | hr | 0.474 | 0.313 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 공 기 압 축 기 | 10.3㎥/min | hr | 0.158 | 0.104 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) | |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 특 별 인 부 | - | 인 | 1 | 1,150 | 770 |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 진 동 롤 러 ( 자 주 식 ) | 10.0ton | 대 | 1 | | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 물 탱 크 ( 살 수 차 ) | 16,000ℓ | 대 | 0.5 | | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00158",
|
||||
"section": "3-4-3",
|
||||
"name": "흙 다지기",
|
||||
"chapter": "공통_제3장",
|
||||
"pdf_pages": [
|
||||
131
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "- ② 포설 및 다짐작업을 포함한다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (18개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 다짐두께 | 시공량(㎥) | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 특 별 인 부 | - | 인 | 1 | 15cm | 18 | 11 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 보 통 인 부 | - | 인 | 1 | 30cm | 24 | 15 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 특 별 인 부 | - | 인 | 1 | 15cm | 14 | 9 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 보 통 인 부 | - | 인 | 1 | 30cm | 20 | 13 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 1 1 | 110 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 1 1 | 250 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 특 별 인 부 보 통 인 부 | | 인 | 1 1 | 130 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 1 1 | 290 |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 구 분 | 규 격 | 단 위 | 모래지정 | | 자갈지정 | | 잡석지정 | |...",
|
||||
"표 한 줄 뭉침 (22개 단어): | | | | 수 량 | 시공량 (㎥) | 수 량 | 시공량 (㎥) | 수 량 | 시공량 (㎥) |...",
|
||||
"표 한 줄 뭉침 (29개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 1 1 | 110 | 1 1 | 100 | 1 1 | 9...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 굴 착 기 | 0.2㎥ | 대 | 1 | | 1 | | 1 | |...",
|
||||
"표 한 줄 뭉침 (22개 단어): | 플 레 이 트 콤 팩 터 | 1.5ton | 대 | 1 | | - | | - | |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 진동롤러(핸드가이드식) | 0.7ton | 대 | - | | 1 | | 1 | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 토질(암) 분류 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (23개 단어): | 굴 착 기 | 1.0㎥ | 대 | 1 | 모래・사질토・점토・점질토 연질토・불순자갈 호박돌 섞인 고결토・경...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 굴 착 기 | 1.0㎥ | 대 | 1 | 연암 | 80 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) | |...",
|
||||
"표 한 줄 뭉침 (23개 단어): | 특 별 인 부 보 통 인 부 | - - | 인 인 | 2 5 | 19 | 25 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 조 경 공 보 통 인 부 | 인 인 | 1 5 | 670 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00165",
|
||||
"section": "3-5-1",
|
||||
"name": "절토면 고르기",
|
||||
"chapter": "공통_제3장",
|
||||
"pdf_pages": [
|
||||
133
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "경비 산정시 조정원은 계상하지 않는다.3-5 절토부대공",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (18개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 토질(암) 분류 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (23개 단어): | 굴 착 기 | 1.0㎥ | 대 | 1 | 모래・사질토・점토・점질토 연질토・불순자갈 호박돌 섞인 고결토・경...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 굴 착 기 | 1.0㎥ | 대 | 1 | 연암 | 80 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) | |...",
|
||||
"표 한 줄 뭉침 (23개 단어): | 특 별 인 부 보 통 인 부 | - - | 인 인 | 2 5 | 19 | 25 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 조 경 공 보 통 인 부 | 인 인 | 1 5 | 670 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | | 단 위 | 수 량 | 시공량(㎡) | | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | | | | | 비탈경사 1:1.5 이상 | 비탈경사 1:1.0이상∼ 1:1.5 미만 | 비탈경사 ...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 인력 | 특 별 인 부 보 통 인 부 | 인 인 | 2 3 | 27 | 24 | 22 |...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 기계 | 특 별 인 부 보 통 인 부 | 인 인 | 2 2 | 41 | 38 | 35 |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | 비 고 | | - 비탈틀을 고정하기 위한 유항(留杭)을 설치하는 경우는 보통인부 0.4인/10본당을 계...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(개소) |...",
|
||||
"표 한 줄 뭉침 (35개 단어): | 중 급 기 술 자 보 링 공 특 별 인 부 보 통 인 부 | - - - - | 인 인 인 인 | 1 1 ...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 강 연 선 인 장 기 | 60ton | 대 | 1 | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 특 별 인 부 보 통 인 부 | 인 인 | 3 2 | 290 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 단 위 | 수 량 | 뿜어붙이기 두께 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 비 고 | - 수직고 20m 이상인 경우 시공량에 다음 할증률을 감한다. 수직고 20 ∼ 30m 30 ∼...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 규격 | ø16, 0.5m | ø16, 0.35m | ø3.258×58 PVC코팅 | #8 PVC코팅 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00168",
|
||||
"section": "3-6-1",
|
||||
"name": "성토면 고르기",
|
||||
"chapter": "공통_제3장",
|
||||
"pdf_pages": [
|
||||
134
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": " 기계경비는 인력품의 2%로 계상한다.3-6 성토부대공",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 조 경 공 보 통 인 부 | 인 인 | 1 5 | 670 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | | 단 위 | 수 량 | 시공량(㎡) | | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | | | | | 비탈경사 1:1.5 이상 | 비탈경사 1:1.0이상∼ 1:1.5 미만 | 비탈경사 ...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 인력 | 특 별 인 부 보 통 인 부 | 인 인 | 2 3 | 27 | 24 | 22 |...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 기계 | 특 별 인 부 보 통 인 부 | 인 인 | 2 2 | 41 | 38 | 35 |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | 비 고 | | - 비탈틀을 고정하기 위한 유항(留杭)을 설치하는 경우는 보통인부 0.4인/10본당을 계...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(개소) |...",
|
||||
"표 한 줄 뭉침 (35개 단어): | 중 급 기 술 자 보 링 공 특 별 인 부 보 통 인 부 | - - - - | 인 인 인 인 | 1 1 ...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 강 연 선 인 장 기 | 60ton | 대 | 1 | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 특 별 인 부 보 통 인 부 | 인 인 | 3 2 | 290 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 단 위 | 수 량 | 뿜어붙이기 두께 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 비 고 | - 수직고 20m 이상인 경우 시공량에 다음 할증률을 감한다. 수직고 20 ∼ 30m 30 ∼...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 규격 | ø16, 0.5m | ø16, 0.35m | ø3.258×58 PVC코팅 | #8 PVC코팅 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 뿜어붙이기 두께 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 조 경 공 | - - - - | 인 인 인 인 | 1 1 2 2 | 5cm 7cm 10cm 15cm | ...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 취 부 기 ( 녹 생 토 ) | 18.65kW | 대 | 1 | | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 공 기 압 축 기 | 21㎥/min | 대 | 1 | | |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00196",
|
||||
"section": "4-1-3",
|
||||
"name": "초류종자 살포(기계살포)",
|
||||
"chapter": "공통_제4장",
|
||||
"pdf_pages": [
|
||||
147
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "차 등의 장비가 필요한 경우 기계경비는 별도 계상한다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 조 경 공 보 통 인 부 | | 인 인 | 2 1 | 3,100 |...",
|
||||
"표 한 줄 뭉침 (35개 단어): | 종 자 비 료 피 복 제 침 식 방 지 안 정 제 색 소 | 복합비료 화이버/펄프류 합성접착제 착색제 |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 2,700 | 1,800 | 1,100 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 1,600 |...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 0.3미만 0.3~0.7이하 0.8~1.1이하 1.2~...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 0.3미만 0.3~0.7이하 0.8~1.1이하 1.2~...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 0.3미만 0.3~0.7이하 0.8~1.1이하 1.2~...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 수관폭(㎝) | 20 | 30 | 40 | 50 | 60 | 80 | 100 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 주수 | 32 | 14 | 8 | 5 | 4 | 2 | 1 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00198",
|
||||
"section": "4-1-5",
|
||||
"name": "거적덮기",
|
||||
"chapter": "공통_제4장",
|
||||
"pdf_pages": [
|
||||
148
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "하천변・ 절개지 등 안전사고의 ㉰ 불량위험이 있는 경우",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (17개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 1,600 |...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 0.3미만 0.3~0.7이하 0.8~1.1이하 1.2~...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 0.3미만 0.3~0.7이하 0.8~1.1이하 1.2~...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 조 경 공 보 통 인 부 | 인 인 | 3 1 | 0.3미만 0.3~0.7이하 0.8~1.1이하 1.2~...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 수관폭(㎝) | 20 | 30 | 40 | 50 | 60 | 80 | 100 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 주수 | 32 | 14 | 8 | 5 | 4 | 2 | 1 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 구 분 | | 규 격 | 단 위 | 수 량 | 나무높이(m) | 시공량(주) |...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 인 력 시 공 | 조경공 보통인부 | | 인 인 | 4 2 | 2.0이하 3.0이하 5.0이하 | 70...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 기 계 시 공 | 조경공 보통인부 | | 인 인 | 3 1 | 2.0이하 3.0이하 5.0이하 | 90...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 비 고 | - 분이 없는 경우 시공량의 25%를 가산한다. | | | | | |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | 구 분 | | 규 격 | 단 위 | 수 량 | 근원(흉고)직경 (㎝) | 시공량 (주) |...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 인 력 시 공 | 조경공 보통인부 | | 인 인 | 4 2 | 5(4)이하 6~7(5~6) 8~9(7~...",
|
||||
"표 한 줄 뭉침 (28개 단어): | 기 계 시 공 | 조경공 보통인부 | | 인 인 | 3 1 | 5(4)이하 6~7(5~6) 8~9(7~...",
|
||||
"표 한 줄 뭉침 (26개 단어): | 기 계 시 공 | 조경공 보통인부 | | 인 인 | 3 1 | 20~29(17~24) 30~39(25~...",
|
||||
"표 한 줄 뭉침 (15개 단어): | | 굴착기 크레인 | 0.6㎥ | 대 대 | 1 1 | | |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00244",
|
||||
"section": "6-1-1",
|
||||
"name": "레디믹스트콘크리트 타설",
|
||||
"chapter": "공통_제6장",
|
||||
"pdf_pages": [
|
||||
185
|
||||
],
|
||||
"header_separation": "❌ MD 본문 완전 부재",
|
||||
"preceding_context": "",
|
||||
"flaws_detected": []
|
||||
},
|
||||
{
|
||||
"key": "CW-00267",
|
||||
"section": "6-3-9",
|
||||
"name": "지수판 설치",
|
||||
"chapter": "공통_제6장",
|
||||
"pdf_pages": [
|
||||
198
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "후 최상층전까지 각 층마다 조립 후 해체하는 기준이다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (27개 단어): | P V C 지 수 판 P V C 용 접 봉 철 선 | 200×5t #8 | m ㎏ ㎏ | 1.04 0.0...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(m) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 형 틀 목 공 보 통 인 부 | | 인 인 | 2 1 | 45 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 철 근 공 보 통 인 부 | 인 인 | 2 1 | 90 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 형 틀 목 공 보 통 인 부 | 인 인 | 1 1 | 12 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 단 위 | 수 량 | 시공량(강연선 규격, ton) | |...",
|
||||
"표 한 줄 뭉침 (26개 단어): | 기 계 설 비 공 철 근 공 보 통 인 부 | 인 인 인 | 1 3 1 | 5.0 | 6.0 |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | 구 분 | | 규 격 | 단 위 | 수 량 | 수 량(강연선 규격, 개소) | |...",
|
||||
"표 한 줄 뭉침 (31개 단어): | 인 력 | 기 계 설 비 공 특 별 인 부 보 통 인 부 | | 인 인 인 | 1 3 1 | 22 | ...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 장 비 | 강 연 선 인 장 기 크 레 인 | 250t 5ton | 대 대 | 1 1 | | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(m) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 2 4 | 400 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00416",
|
||||
"section": "1-6-6",
|
||||
"name": "포장줄눈 절단",
|
||||
"chapter": "토목_제1장",
|
||||
"pdf_pages": [
|
||||
373,
|
||||
931
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": ", 용접기 등)의 기계경비는 인력품의 3%로 계상한다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(m) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 1 1 | 600 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 특 별 인 부 보 통 인 부 | 인 인 | 3 2 | 900 |...",
|
||||
"페이지 전이 찌꺼기 삽입: - ③ 줄눈재, 백업재 등 재료비는 별도 계상한다.317제1장 도로포장공사1-7 저속도로포장('08년 신설)",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 규 격 | 단 위 | A-Type | | B-Type | |...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 포 장 공 특 별 인 부 보 통 인 부 | | 인 인 인 | 3 2 2 | 300 | 2 2 1 | 1...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 굴 착 기 | 0.6㎥ | 대 | 1 | | - | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 굴 착 기 | 0.4㎥ | 대 | - | | 1 | |...",
|
||||
"표 한 줄 뭉침 (29개 단어): | 비 고 | - 유도・점자블록을 설치하는 경우 시공량의 10%를 감하여 적용한다. - 블록 정밀절단(전동절...",
|
||||
"표 한 줄 뭉침 (19개 단어): | A - T y p e | - 공원, 단지・택지조성공사의 보도 등 장비이동 및 적재가 용이한 구간 |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | B - T y p e | - 차도인접, 주택가 보도 등 장비이동 및 적재 공간이 협소한 구간 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 규 격 | 단 위 | A-Type | | B-Type | |...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 포 장 공 특 별 인 부 보 통 인 부 | | 인 인 인 | 3 2 2 | 190 | 2 2 1 | 1...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 굴 착 기 | 0.6㎥ | 대 | 1 | | - | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 굴 착 기 | 0.4㎥ | 대 | - | | 1 | |...",
|
||||
"표 한 줄 뭉침 (29개 단어): | 비 고 | - 유도・점자블록을 설치하는 경우 시공량의 10%를 감하여 적용한다. - 블록 정밀절단(전동절...",
|
||||
"표 한 줄 뭉침 (19개 단어): | A - T y p e | - 공원, 단지・택지조성공사의 보도 등 장비이동 및 적재가 용이한 구간 |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | B - T y p e | - 차도인접, 주택가 보도 등 장비이동 및 적재 공간이 협소한 구간 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 포 장 공 보 통 인 부 | | 인 인 | 2 1 | 250 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 로 더 ( 타 이 어 ) | 0.57㎥ | 대 | 1 | |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 플 레 이 트 콤 팩 터 | 1.5ton | 대 | 1 | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 포 장 공 보 통 인 부 | | 인 인 | 3 1 | 1,200 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 아 스 팔 트 피 니 셔 | 1.7m | 대 | 1 | |...",
|
||||
"페이지 전이 찌꺼기 삽입: - ⑤ 현장여건에 따라 장비조합 및 규격을 변경하여 적용할 수 있다.319제1장 도로포장공사1-7-5 탄성포",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 5 3 | 120 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(개소) | |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 2 1 | 12 | - |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00538",
|
||||
"section": "6-6",
|
||||
"name": "원심력 철근콘크리트관",
|
||||
"chapter": "토목_제6장",
|
||||
"pdf_pages": [
|
||||
428
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "력품의 5%로 계상한다.371제6장 관부설 및 접합공사",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (45개 단어): | 배 관 공 ( 수 도 ) 보 통 인 부 양 중 장 비 | 인 인 대 | 2 1 1 | 250 300 35...",
|
||||
"표 한 줄 뭉침 (37개 단어): | 배 관 공 ( 수 도 ) 보 통 인 부 양 중 장 비 | 인 인 대 | 3 1 1 | 900 1,000 ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 배 관 공 ( 수 도 ) | 인 | 4 | 1,500 | 3.5 |...",
|
||||
"표 한 줄 뭉침 (45개 단어): | 배 관 공 ( 수 도 ) 보 통 인 부 양 중 장 비 | 인 인 대 | 2 1 1 | 250 300 35...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 배 관 공 ( 수 도 ) | 인 | 3 | 900~1,000 | 5 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 배 관 공 ( 수 도 ) | 인 | 4 | 1,500 | 3.5 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 500이하 | 150 200 250 300 | 0.050 0.070 0.090 0.120 | 0.050 ...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 500초과~ 900이하 | 150 200 250 300 | 0.070 0.090 0.110 0.130 |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 900초과~ 1200이하 | 150 200 250 300 | 0.080 0.110 0.120 0.150 ..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00539",
|
||||
"section": "6-6-1",
|
||||
"name": "소켓관 부설 및 접합",
|
||||
"chapter": "토목_제6장",
|
||||
"pdf_pages": [
|
||||
428
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "사6-6 원심력 철근콘크리트관('10, '18년 보완)",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (45개 단어): | 배 관 공 ( 수 도 ) 보 통 인 부 양 중 장 비 | 인 인 대 | 2 1 1 | 250 300 35...",
|
||||
"표 한 줄 뭉침 (37개 단어): | 배 관 공 ( 수 도 ) 보 통 인 부 양 중 장 비 | 인 인 대 | 3 1 1 | 900 1,000 ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 배 관 공 ( 수 도 ) | 인 | 4 | 1,500 | 3.5 |...",
|
||||
"표 한 줄 뭉침 (45개 단어): | 배 관 공 ( 수 도 ) 보 통 인 부 양 중 장 비 | 인 인 대 | 2 1 1 | 250 300 35...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 배 관 공 ( 수 도 ) | 인 | 3 | 900~1,000 | 5 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 배 관 공 ( 수 도 ) | 인 | 4 | 1,500 | 3.5 |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 500이하 | 150 200 250 300 | 0.050 0.070 0.090 0.120 | 0.050 ...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 500초과~ 900이하 | 150 200 250 300 | 0.070 0.090 0.110 0.130 |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 900초과~ 1200이하 | 150 200 250 300 | 0.080 0.110 0.120 0.150 ..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-00844",
|
||||
"section": "9-1-1",
|
||||
"name": "모르타르 배합",
|
||||
"chapter": "건축_제9장",
|
||||
"pdf_pages": [
|
||||
635,
|
||||
636,
|
||||
638,
|
||||
639
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "미장공사제9장9-1 모르타르 바름 및 타설",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | | 단 위 | 수 량 | 시공량(㎡) | | |...",
|
||||
"표 한 줄 뭉침 (31개 단어): | 시 공 높 이 3 . 6 m 이하 | 미 장 공 보 통 인 부 | 인 인 | 2 1 | 40 | 29 |...",
|
||||
"표 한 줄 뭉침 (32개 단어): | 시 공 높 이 3 . 6 m 초과 ~7.2m이하 | 미 장 공 보 통 인 부 | 인 인 | 2 2 | 3...",
|
||||
"표 한 줄 뭉침 (32개 단어): | 비 고 | - 바탕의 폭 30㎝이하이거나 원주 바름면일 때에는 시공량의 17%를 감한다. - 비계사용 시...",
|
||||
"표 한 줄 뭉침 (26개 단어): | 미 장 공 기 계 설 비 공 보 통 인 부 | 인 인 인 | 2 1 2 | 50 | 65 |...",
|
||||
"표 한 줄 뭉침 (34개 단어): | 모 르 타 르 타 설 장 비 | 모 르 타 르 펌 프 믹 서 양 수 기 배 관 파 이 프 | 37㎾ 0....",
|
||||
"표 한 줄 뭉침 (29개 단어): | 비 고 | - 현장 조건에 따라 작업대기 등이 발생되는 경우, 인력품에 다음 할증까지 가산하여 적용한다....",
|
||||
"표 한 줄 뭉침 (25개 단어): | 비 고 | - 천장은 본 품의 20%를 가산한다. - 비계사용 시 높이 7.2m초과하는 경우 3.6m마다...",
|
||||
"표 한 줄 뭉침 (28개 단어): | 시 공 높 이 3 . 6 m 이 하 | 미 장 공 보 통 인 부 | 인 인 | 2 1 | 170 |...",
|
||||
"표 한 줄 뭉침 (35개 단어): | 시 공 높 이 3 . 6 m 초 과 ~ 7 . 2 m 이 하 | 미 장 공 보 통 인 부 | 인 인 | ...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 비 고 | - 천장은 시공량의 17%를 감한다. - 비계사용 시 높이 7.2m초과하는 경우 3.6m마다 ...",
|
||||
"표 한 줄 뭉침 (28개 단어): | 시 공 높 이 3 . 6 m 이 하 | 미 장 공 보 통 인 부 | 인 인 | 2 1 | 120 |...",
|
||||
"표 한 줄 뭉침 (35개 단어): | 시 공 높 이 3 . 6 m 초 과 ~ 7 . 2 m 이 하 | 미 장 공 보 통 인 부 | 인 인 | ...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 비 고 | - 천장은 시공량의 17%를 감한다. - 비계사용 시 높이 7.2m초과하는 경우 3.6m마다 ..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-01225",
|
||||
"section": "1-2-5",
|
||||
"name": "관목 전정",
|
||||
"chapter": "유지관리_제1장",
|
||||
"pdf_pages": [
|
||||
917
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "비(전정기 등)의 기계경비는 인력품의 3%로 계상한다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (19개 단어): | 조 경 공 보 통 인 부 | 인 인 | 2 1 | 540 | 330 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 구 분 | | 단 위 | 수 량 | 시공량(주) | | | | |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | | | | | 11㎝미만 | 11∼21㎝미만 | 21∼31㎝미만 | 31∼41㎝미만 | 41∼51...",
|
||||
"표 한 줄 뭉침 (28개 단어): | 수간보호 ( 조 형 ) | 조 경 공 보통인부 | 인 인 | 2 1 | 24 | 13 | 6 | 3 | ...",
|
||||
"표 한 줄 뭉침 (28개 단어): | 수간보호 ( 일 반 ) | 조 경 공 보통인부 | 인 인 | 2 1 | 38 | 24 | 16 | 11 ...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 단 위 | 수 량 | 시공량(주) | | | | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | | | | 11㎝미만 | 11∼ 21㎝미만 | 21∼ 31㎝미만 | 31∼ 41㎝미만 | 41∼ 5...",
|
||||
"표 한 줄 뭉침 (25개 단어): | 조 경 공 보 통 인 부 | 인 인 | 2 1 | 85 | 68 | 54 | 42 | 30 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 단 위 | 수 량 | 시공량(주) | | | | |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | 보 통 인 부 | 인 | 1 | 33 | 25 | 17 | 13 | 10 |...",
|
||||
"표 한 줄 뭉침 (22개 단어): | 구 분 | 규격 | | 단 위 | 수 량 | | | | 시공량(식재면적 ㎡) | | | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | | | | | 소형장비 | | 중형장비 | 대형장비 | 소형장비 | | 중형장비 | 대형장비 ...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 보 통 인 부 | | | 인 | 1 | | 1 | 1 | 700 | | 1,100 | 2,200 ...",
|
||||
"표 한 줄 뭉침 (28개 단어): | 물탱크( 살수차) 물탱크( 살수차) 물탱크( 살수차) | 1,800L 3,800L 5,500~6,500L...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 비 고 | - 이동거리가 5㎞를 초과하면 5㎞마다 다음을 가산한다. 구 분 1,800ℓ 3,800ℓ 5,...",
|
||||
"표 한 줄 뭉침 (18개 단어): | | | 구 분 | | | 1,800ℓ | | 3,800ℓ | | 5,500~6,500ℓ | ...",
|
||||
"표 한 줄 뭉침 (16개 단어): | | | 물탱크(살수차) | | | 0.07h/100㎡ | | | | 0.04h/100㎡ | ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 구 분 | | | 단 위 | | 수 량 | | 시공량(㎡) | |...",
|
||||
"표 한 줄 뭉침 (26개 단어): | 배 부 식 | 특 별 인 부 보 통 인 부 | | 인 인 | | 3 1 | | 3,300 | |...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 핸드가이드식 | 특 별 인 부 보 통 인 부 | | 인 인 | | 1 1 | | 4,000 | |...",
|
||||
"표 한 줄 뭉침 (47개 단어): | 비 고 | - 보행자 보호 등 돌튐방지 조치를 위해 그물막이 필요한 경우 현장조건을 고려하여 보통인부 2...",
|
||||
"표 한 줄 뭉침 (16개 단어): | | | 구 분 | | 연1회 | | 연2회 | | 연3회 이상 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | | | 시공량 할증율 | | -30% | | -20% | | - |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | | | 단 위 | 수 량 | | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 특 별 인 부 보 통 인 부 | | | 인 인 | 3 1 | | 2,500 |...",
|
||||
"표 한 줄 뭉침 (57개 단어): | 비 고 | - 보행자 보호 등 돌튐방지 조치를 위해 그물막이 필요한 경우 현장조건을 고려하여 보통인부 2..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-01228",
|
||||
"section": "1-2-8",
|
||||
"name": "인력관수",
|
||||
"chapter": "유지관리_제1장",
|
||||
"pdf_pages": [
|
||||
918
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "- ② 설치폭은 30cm~45cm를 설치하는 기준이다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 단 위 | 수 량 | 시공량(주) | | | | |...",
|
||||
"표 한 줄 뭉침 (20개 단어): | 보 통 인 부 | 인 | 1 | 33 | 25 | 17 | 13 | 10 |...",
|
||||
"표 한 줄 뭉침 (22개 단어): | 구 분 | 규격 | | 단 위 | 수 량 | | | | 시공량(식재면적 ㎡) | | | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | | | | | 소형장비 | | 중형장비 | 대형장비 | 소형장비 | | 중형장비 | 대형장비 ...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 보 통 인 부 | | | 인 | 1 | | 1 | 1 | 700 | | 1,100 | 2,200 ...",
|
||||
"표 한 줄 뭉침 (28개 단어): | 물탱크( 살수차) 물탱크( 살수차) 물탱크( 살수차) | 1,800L 3,800L 5,500~6,500L...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 비 고 | - 이동거리가 5㎞를 초과하면 5㎞마다 다음을 가산한다. 구 분 1,800ℓ 3,800ℓ 5,...",
|
||||
"표 한 줄 뭉침 (18개 단어): | | | 구 분 | | | 1,800ℓ | | 3,800ℓ | | 5,500~6,500ℓ | ...",
|
||||
"표 한 줄 뭉침 (16개 단어): | | | 물탱크(살수차) | | | 0.07h/100㎡ | | | | 0.04h/100㎡ | ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 구 분 | | | 단 위 | | 수 량 | | 시공량(㎡) | |...",
|
||||
"표 한 줄 뭉침 (26개 단어): | 배 부 식 | 특 별 인 부 보 통 인 부 | | 인 인 | | 3 1 | | 3,300 | |...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 핸드가이드식 | 특 별 인 부 보 통 인 부 | | 인 인 | | 1 1 | | 4,000 | |...",
|
||||
"표 한 줄 뭉침 (47개 단어): | 비 고 | - 보행자 보호 등 돌튐방지 조치를 위해 그물막이 필요한 경우 현장조건을 고려하여 보통인부 2...",
|
||||
"표 한 줄 뭉침 (16개 단어): | | | 구 분 | | 연1회 | | 연2회 | | 연3회 이상 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | | | 시공량 할증율 | | -30% | | -20% | | - |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | | | 단 위 | 수 량 | | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 특 별 인 부 보 통 인 부 | | | 인 인 | 3 1 | | 2,500 |...",
|
||||
"표 한 줄 뭉침 (57개 단어): | 비 고 | - 보행자 보호 등 돌튐방지 조치를 위해 그물막이 필요한 경우 현장조건을 고려하여 보통인부 2...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 구 분 | | 단 위 | 수 량 | 시공량(주) | | | | | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | | | | | 11㎝ | 11∼21㎝ | 21∼31㎝ | 31∼41㎝ | 41∼51㎝ | 51㎝ ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | | | | | 미만 | 미만 | 미만 | 미만 | 미만 | 이상 |...",
|
||||
"표 한 줄 뭉침 (32개 단어): | 환 상 시 비 | 조 경 공 보 통 인 부 | 인 인 | 2 1 | 76 | 61 | 51 | 44 | ...",
|
||||
"표 한 줄 뭉침 (29개 단어): | 방사형시비 | 조 경 공 보 통 인 부 | 인 인 | 2 1 | 100 | 82 | 69 | 59 | 5...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 조 경 공 보 통 인 부 | 인 인 | 2 1 | 300 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 조 경 공 보 통 인 부 | | 인 인 | 2 1 | 22,500 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(ℓ) |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-01229",
|
||||
"section": "1-2-9",
|
||||
"name": "살수차관수",
|
||||
"chapter": "유지관리_제1장",
|
||||
"pdf_pages": [
|
||||
918
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "[주] 본 품은 인력에 의한 교목관수 기준이다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (22개 단어): | 구 분 | 규격 | | 단 위 | 수 량 | | | | 시공량(식재면적 ㎡) | | | |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | | | | | 소형장비 | | 중형장비 | 대형장비 | 소형장비 | | 중형장비 | 대형장비 ...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 보 통 인 부 | | | 인 | 1 | | 1 | 1 | 700 | | 1,100 | 2,200 ...",
|
||||
"표 한 줄 뭉침 (28개 단어): | 물탱크( 살수차) 물탱크( 살수차) 물탱크( 살수차) | 1,800L 3,800L 5,500~6,500L...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 비 고 | - 이동거리가 5㎞를 초과하면 5㎞마다 다음을 가산한다. 구 분 1,800ℓ 3,800ℓ 5,...",
|
||||
"표 한 줄 뭉침 (18개 단어): | | | 구 분 | | | 1,800ℓ | | 3,800ℓ | | 5,500~6,500ℓ | ...",
|
||||
"표 한 줄 뭉침 (16개 단어): | | | 물탱크(살수차) | | | 0.07h/100㎡ | | | | 0.04h/100㎡ | ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 구 분 | | | 단 위 | | 수 량 | | 시공량(㎡) | |...",
|
||||
"표 한 줄 뭉침 (26개 단어): | 배 부 식 | 특 별 인 부 보 통 인 부 | | 인 인 | | 3 1 | | 3,300 | |...",
|
||||
"표 한 줄 뭉침 (24개 단어): | 핸드가이드식 | 특 별 인 부 보 통 인 부 | | 인 인 | | 1 1 | | 4,000 | |...",
|
||||
"표 한 줄 뭉침 (47개 단어): | 비 고 | - 보행자 보호 등 돌튐방지 조치를 위해 그물막이 필요한 경우 현장조건을 고려하여 보통인부 2...",
|
||||
"표 한 줄 뭉침 (16개 단어): | | | 구 분 | | 연1회 | | 연2회 | | 연3회 이상 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | | | 시공량 할증율 | | -30% | | -20% | | - |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | | | 단 위 | 수 량 | | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (21개 단어): | 특 별 인 부 보 통 인 부 | | | 인 인 | 3 1 | | 2,500 |...",
|
||||
"표 한 줄 뭉침 (57개 단어): | 비 고 | - 보행자 보호 등 돌튐방지 조치를 위해 그물막이 필요한 경우 현장조건을 고려하여 보통인부 2...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 구 분 | | 단 위 | 수 량 | 시공량(주) | | | | | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | | | | | 11㎝ | 11∼21㎝ | 21∼31㎝ | 31∼41㎝ | 41∼51㎝ | 51㎝ ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | | | | | 미만 | 미만 | 미만 | 미만 | 미만 | 이상 |...",
|
||||
"표 한 줄 뭉침 (32개 단어): | 환 상 시 비 | 조 경 공 보 통 인 부 | 인 인 | 2 1 | 76 | 61 | 51 | 44 | ...",
|
||||
"표 한 줄 뭉침 (29개 단어): | 방사형시비 | 조 경 공 보 통 인 부 | 인 인 | 2 1 | 100 | 82 | 69 | 59 | 5...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 조 경 공 보 통 인 부 | 인 인 | 2 1 | 300 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 조 경 공 보 통 인 부 | | 인 인 | 2 1 | 22,500 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(ℓ) |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 조 경 공 보 통 인 부 | | 인 인 | 1 1 | 2,600 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-01325",
|
||||
"section": "3-1-1",
|
||||
"name": "콘크리트구조물 헐기(인력)",
|
||||
"chapter": "유지관리_제3장",
|
||||
"pdf_pages": [
|
||||
967
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "건 축제3장3-1 구조물 철거공사",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) | |...",
|
||||
"표 한 줄 뭉침 (18개 단어): | 착 암 공 보 통 인 부 | | | 2 1 | 2.7 | 2.3 |...",
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | | 규 격 | 단 위 | 수 량 | 시공량(㎥) |...",
|
||||
"표 한 줄 뭉침 (26개 단어): | 장 애 물 미 제 거 | 특 별 인 부 보 통 인 부 | | 인 인 | 2 1 | 50 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | | 굴 착 기 + 압 쇄 기 | 1.0㎥ | 대 | 1 | |...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 장 애 물 제 거 | 용 접 공 특 별 인 부 보 통 인 부 | | 인 인 인 | 1 2 1 | 45 ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | | 굴 착 기 + 압 쇄 기 | 1.0㎥ | 대 | 1 | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 용 접 공 보 통 인 부 | 인 인 | 3 2 | 1.4 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(ton) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 2 1 | 22 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-01326",
|
||||
"section": "3-1-2",
|
||||
"name": "콘크리트구조물 헐기(기계)",
|
||||
"chapter": "유지관리_제3장",
|
||||
"pdf_pages": [
|
||||
967
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "- ④ 잡재료비(치즐 등)는 인력품의 1%로 계상한다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (16개 단어): | 구 분 | | 규 격 | 단 위 | 수 량 | 시공량(㎥) |...",
|
||||
"표 한 줄 뭉침 (26개 단어): | 장 애 물 미 제 거 | 특 별 인 부 보 통 인 부 | | 인 인 | 2 1 | 50 |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | | 굴 착 기 + 압 쇄 기 | 1.0㎥ | 대 | 1 | |...",
|
||||
"표 한 줄 뭉침 (30개 단어): | 장 애 물 제 거 | 용 접 공 특 별 인 부 보 통 인 부 | | 인 인 인 | 1 2 1 | 45 ...",
|
||||
"표 한 줄 뭉침 (17개 단어): | | 굴 착 기 + 압 쇄 기 | 1.0㎥ | 대 | 1 | |...",
|
||||
"표 한 줄 뭉침 (17개 단어): | 용 접 공 보 통 인 부 | 인 인 | 3 2 | 1.4 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(ton) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 2 1 | 22 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-01327",
|
||||
"section": "3-1-3",
|
||||
"name": "철골재 철거(인력)",
|
||||
"chapter": "유지관리_제3장",
|
||||
"pdf_pages": [
|
||||
968
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "150기압으로 압축용기에 넣어 사용하는 것을 기준한다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (17개 단어): | 용 접 공 보 통 인 부 | 인 인 | 3 2 | 1.4 |...",
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(ton) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 2 1 | 22 |..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-01328",
|
||||
"section": "3-1-4",
|
||||
"name": "철골재 철거(기계)",
|
||||
"chapter": "유지관리_제3장",
|
||||
"pdf_pages": [
|
||||
968
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": "150기압으로 압축용기에 넣어 사용하는 것을 기준한다.",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (15개 단어): | 구 분 | 규 격 | 단 위 | 수 량 | 시공량(ton) |...",
|
||||
"표 한 줄 뭉침 (19개 단어): | 특 별 인 부 보 통 인 부 | | 인 인 | 2 1 | 22 |...",
|
||||
"표 한 줄 뭉침 (33개 단어): | 착 암 공 보 통 인 부 소 형 브 레 이 커 공 기 압 축 기 | 1.3㎥/min 3.5㎥/min | ..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CW-01329",
|
||||
"section": "3-1-5",
|
||||
"name": "석축 헐기(인력)",
|
||||
"chapter": "유지관리_제3장",
|
||||
"pdf_pages": [
|
||||
56,
|
||||
969
|
||||
],
|
||||
"header_separation": "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)",
|
||||
"preceding_context": " 기계경비는 인력품의 4%로 계상한다.유지관리부문912",
|
||||
"flaws_detected": [
|
||||
"표 한 줄 뭉침 (33개 단어): | 착 암 공 보 통 인 부 소 형 브 레 이 커 공 기 압 축 기 | 1.3㎥/min 3.5㎥/min | ..."
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,114 @@
|
||||
import sys, os, re, json
|
||||
import fitz # PyMuPDF
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load target items
|
||||
with open('scratch/located_const_sections.json', 'r', encoding='utf-8') as f:
|
||||
located_items = json.load(f)
|
||||
|
||||
md_map = {
|
||||
"공통_제1장": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제1장_적용기준.md",
|
||||
"공통_제3장": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제3장_토공사.md",
|
||||
"공통_제4장": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제4장_조경공사.md",
|
||||
"공통_제6장": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제6장_철근콘크리트공사.md",
|
||||
"토목_제1장": "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md",
|
||||
"토목_제6장": "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md",
|
||||
"건축_제9장": "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제9장_미장공사.md",
|
||||
"유지관리_제1장": "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md",
|
||||
"유지관리_제3장": "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md",
|
||||
}
|
||||
|
||||
md_texts = {}
|
||||
for k, p in md_map.items():
|
||||
if os.path.exists(p):
|
||||
with open(p, 'r', encoding='utf-8') as f:
|
||||
md_texts[k] = f.read()
|
||||
|
||||
pdf_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.pdf"
|
||||
doc = fitz.open(pdf_path)
|
||||
|
||||
audit_details = []
|
||||
|
||||
for item in located_items:
|
||||
k = item['key']
|
||||
num = item['number']
|
||||
name = item['name']
|
||||
pn = item['path_name']
|
||||
pdf_pages = item.get('pdf_pages', [])
|
||||
|
||||
# Exclude chapter 8
|
||||
if "공통부문 › 건설기계" in pn:
|
||||
continue
|
||||
|
||||
# Determine chapter
|
||||
md_key = None
|
||||
if "공통부문 › 적용기준" in pn: md_key = "공통_제1장"
|
||||
elif "공통부문 › 토공사" in pn: md_key = "공통_제3장"
|
||||
elif "공통부문 › 조경공사" in pn: md_key = "공통_제4장"
|
||||
elif "공통부문 › 철근콘크리트공사" in pn: md_key = "공통_제6장"
|
||||
elif "토목부문 › 도로포장공사" in pn: md_key = "토목_제1장"
|
||||
elif "토목부문 › 관부설" in pn: md_key = "토목_제6장"
|
||||
elif "건축부문 › 미장공사" in pn: md_key = "건축_제9장"
|
||||
elif "유지관리부문 › 공 통" in pn: md_key = "유지관리_제1장"
|
||||
elif "유지관리부문 › 건 축" in pn: md_key = "유지관리_제3장"
|
||||
|
||||
raw_md = md_texts.get(md_key, "")
|
||||
|
||||
# Find how this section starts in raw_md
|
||||
# Look for num in raw_md
|
||||
escaped_num = re.escape(num)
|
||||
# Search with regex
|
||||
m = re.search(rf'([^\n]{{0,50}})({escaped_num}\s+[^\n]+)', raw_md)
|
||||
header_status = "정상 분리"
|
||||
preceding_context = ""
|
||||
if m:
|
||||
preceding = m.group(1)
|
||||
header_line = m.group(2)
|
||||
if preceding.strip():
|
||||
header_status = "⚠ 앞 문장과 경계 붙음 (줄바꿈 누락)"
|
||||
preceding_context = preceding.strip()[-30:]
|
||||
else:
|
||||
# Check if number appears at all
|
||||
if num in raw_md:
|
||||
header_status = "본문 중 인라인 출현"
|
||||
else:
|
||||
header_status = "❌ MD 본문 완전 부재"
|
||||
|
||||
# Find PDF text for relevant pages
|
||||
actual_pages = [p for p in pdf_pages if p > 50]
|
||||
pdf_text = ""
|
||||
for p in actual_pages:
|
||||
pdf_text += doc[p - 1].get_text()
|
||||
|
||||
# Check table collapse in this section's neighborhood
|
||||
# Grab 50 lines around the match
|
||||
section_flaws = []
|
||||
if m:
|
||||
start_pos = m.start()
|
||||
# look forward 3000 characters
|
||||
chunk = raw_md[start_pos:start_pos + 4000]
|
||||
# check collapsed table rows in chunk
|
||||
for line in chunk.split('\n'):
|
||||
if line.strip().startswith('|') and line.strip().endswith('|'):
|
||||
tokens = line.split()
|
||||
if len(tokens) >= 15:
|
||||
section_flaws.append(f"표 한 줄 뭉침 ({len(tokens)}개 단어): {line.strip()[:60]}...")
|
||||
if '→' in line or '제1장' in line or '공통부문' in line:
|
||||
if re.search(r'→\d+|\d+제\d+장', line):
|
||||
section_flaws.append(f"페이지 전이 찌꺼기 삽입: {line.strip()[:60]}")
|
||||
|
||||
audit_entry = {
|
||||
"key": k,
|
||||
"section": num,
|
||||
"name": name,
|
||||
"chapter": md_key,
|
||||
"pdf_pages": actual_pages,
|
||||
"header_separation": header_status,
|
||||
"preceding_context": preceding_context,
|
||||
"flaws_detected": section_flaws
|
||||
}
|
||||
audit_details.append(audit_entry)
|
||||
print(f"[{k}] {num} {name}: {header_status} | flaws={len(section_flaws)}")
|
||||
|
||||
with open('scratch/audit_42_detailed_breakdown.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(audit_details, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,103 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Strict Audit of Duplicate Substantive Data Rows across 14 Target Chapters ===")
|
||||
|
||||
strict_candidates = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Collect all table rows
|
||||
all_rows = []
|
||||
for idx, l in enumerate(lines):
|
||||
line_str = l.strip()
|
||||
if not (line_str.startswith('|') and line_str.endswith('|')):
|
||||
continue
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
if any(c in ["구 분", "구분", "품 명", "품명", "규 격", "규격", "단 위", "단위", "수 량", "수량", "직 종", "직종"] for c in cells):
|
||||
continue
|
||||
|
||||
# Is it a substantive row?
|
||||
# Must have at least one specific item name, spec, or multi-values
|
||||
# Filter out generic worker rows with no spec (e.g. '| 보통인부 | 인 | 1 | |')
|
||||
non_empty = [c for c in cells if c and c not in ["-", "〃"]]
|
||||
if len(non_empty) <= 1:
|
||||
continue
|
||||
|
||||
# Check if it's just a generic worker row
|
||||
if len(non_empty) <= 3 and any(w in line_str for w in ["보통인부", "특별인부", "인부", "기계설비공", "용접공"]) and not any(re.search(r'\d+\.\d{2,}', c) for c in cells):
|
||||
# Check if all numbers are small ints like 1, 2, 3
|
||||
nums = re.findall(r'\b\d+(?:\.\d+)?\b', line_str)
|
||||
if all(n in ["1", "2", "3", "4", "5", "10", "20"] for n in nums):
|
||||
continue
|
||||
|
||||
all_rows.append((idx + 1, line_str, cells))
|
||||
|
||||
# Now check for duplicates within distance of 80 rows
|
||||
for i in range(len(all_rows)):
|
||||
lno_i, str_i, cells_i = all_rows[i]
|
||||
for j in range(i + 1, min(len(all_rows), i + 80)):
|
||||
lno_j, str_j, cells_j = all_rows[j]
|
||||
|
||||
# Exact match of non-trivial row
|
||||
if cells_i == cells_j:
|
||||
strict_candidates.append({
|
||||
'file': fname,
|
||||
'line_1': lno_i,
|
||||
'line_2': lno_j,
|
||||
'type': 'exact_duplicate_substantive',
|
||||
'row': str_i[:90]
|
||||
})
|
||||
# Check if str_j is a squashed row containing str_i
|
||||
# E.g. cells_j has multiple values per cell and covers cells_i
|
||||
else:
|
||||
words_i = set(re.findall(r'[가-힣A-Za-z0-9.]+', str_i))
|
||||
words_j = set(re.findall(r'[가-힣A-Za-z0-9.]+', str_j))
|
||||
# Remove generic tokens
|
||||
clean_i = {w for w in words_i if w not in ["인부", "보통인부", "특별인부", "인", "대", "개", "m", "㎡", "㎥", "ton"]}
|
||||
if len(clean_i) >= 4 and clean_i.issubset(words_j) and len(words_j) > len(words_i) + 4:
|
||||
strict_candidates.append({
|
||||
'file': fname,
|
||||
'line_1': lno_i,
|
||||
'line_2': lno_j,
|
||||
'type': 'squashed_superset_of_line_1',
|
||||
'row_1': str_i[:80],
|
||||
'row_2': str_j[:80]
|
||||
})
|
||||
|
||||
print(f"Total strict candidates found: {len(strict_candidates)}")
|
||||
for sc in strict_candidates:
|
||||
print(f"\n[{sc['file']}] L{sc['line_1']} & L{sc['line_2']} - {sc['type']}")
|
||||
if sc['type'] == 'exact_duplicate_substantive':
|
||||
print(f" Row: {sc['row']}")
|
||||
else:
|
||||
print(f" Row 1: {sc['row_1']}")
|
||||
print(f" Row 2: {sc['row_2']}")
|
||||
@@ -0,0 +1,102 @@
|
||||
import subprocess, sys, re, os
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Get diff of our recent commits where we split worker rows and squashed tables
|
||||
# 3cd61cad: wide criteria fix
|
||||
# f354ba22: civil squashed
|
||||
# 41a15e54: maint squashed
|
||||
# 948dd509: mech/plant squashed
|
||||
|
||||
commits_to_check = ['3cd61cad', 'f354ba22', '41a15e54', '948dd509']
|
||||
|
||||
print("=== Checking all our restored locations for potential duplication with existing lines ===")
|
||||
|
||||
all_modifications = []
|
||||
|
||||
for c in commits_to_check:
|
||||
res = subprocess.run(['git', '-c', 'core.quotepath=false', 'diff', '-U3', f'{c}~1..{c}'], capture_output=True, text=True, encoding='utf-8')
|
||||
diff_text = res.stdout
|
||||
file_diffs = diff_text.split('diff --git ')
|
||||
|
||||
for fd in file_diffs[1:]:
|
||||
lines = fd.split('\n')
|
||||
header = lines[0]
|
||||
filename = header.split(' ')[0][2:]
|
||||
if "2026년_건설공사_표준품셈.md" in filename or "resources/knowledge/technical_info" in filename:
|
||||
continue
|
||||
|
||||
raw_hunks = re.split(r'\n@@\s+[^@]+\s+@@\n', fd)[1:]
|
||||
for h_idx, h in enumerate(raw_hunks):
|
||||
h_lines = h.split('\n')
|
||||
del_lines = [l[1:].strip() for l in h_lines if l.startswith('-') and l[1:].strip().startswith('|')]
|
||||
add_lines = [l[1:].strip() for l in h_lines if l.startswith('+') and l[1:].strip().startswith('|')]
|
||||
ctx_lines = [l[1:].strip() for l in h_lines if l.startswith(' ') and l[1:].strip().startswith('|')]
|
||||
|
||||
all_modifications.append({
|
||||
'commit': c,
|
||||
'file': filename,
|
||||
'del': del_lines,
|
||||
'add': add_lines,
|
||||
'context': ctx_lines
|
||||
})
|
||||
|
||||
print(f"Total hunk modifications checked: {len(all_modifications)}")
|
||||
|
||||
# Now for each modification, inspect the file at HEAD to see if the table has DUPLICATE items
|
||||
files_cache = {}
|
||||
duplication_alerts = []
|
||||
|
||||
for mod in all_modifications:
|
||||
fpath = mod['file']
|
||||
if fpath not in files_cache:
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
files_cache[fpath] = f.readlines()
|
||||
f_lines = files_cache[fpath]
|
||||
|
||||
# Check if added lines duplicate any other lines in the same table
|
||||
for al in mod['add']:
|
||||
if not al.startswith('|'):
|
||||
continue
|
||||
cells = [c.strip() for c in al.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
|
||||
# Find line number of al in f_lines
|
||||
for idx, fl in enumerate(f_lines):
|
||||
if fl.strip() == al:
|
||||
# Look 30 lines up and 30 lines down within the same table
|
||||
up_bound = idx
|
||||
while up_bound > 0 and f_lines[up_bound-1].strip().startswith('|'):
|
||||
up_bound -= 1
|
||||
down_bound = idx
|
||||
while down_bound < len(f_lines)-1 and f_lines[down_bound+1].strip().startswith('|'):
|
||||
down_bound += 1
|
||||
|
||||
table_lines = [f_lines[k].strip() for k in range(up_bound, down_bound+1)]
|
||||
|
||||
# Check how many times cells (or c0, c1, and key numbers) match other rows in table_lines
|
||||
c0 = cells[0] if len(cells) > 0 else ""
|
||||
c1 = cells[1] if len(cells) > 1 else ""
|
||||
key_cells = [c for c in cells if c and c not in ["-", "〃", "인", "대", "1", "2"]]
|
||||
|
||||
matches = 0
|
||||
for tl in table_lines:
|
||||
t_cells = [c.strip() for c in tl.split('|')[1:-1]]
|
||||
if t_cells == cells:
|
||||
matches += 1
|
||||
|
||||
if matches > 1:
|
||||
duplication_alerts.append({
|
||||
'commit': mod['commit'],
|
||||
'file': os.path.basename(fpath),
|
||||
'line': idx + 1,
|
||||
'added_row': al[:80],
|
||||
'matches_in_table': matches
|
||||
})
|
||||
break
|
||||
|
||||
print(f"\nTotal Duplication Alerts in Restored Locations: {len(duplication_alerts)}")
|
||||
for da in duplication_alerts:
|
||||
print(f"[{da['file']}:L{da['line']}] (Commit {da['commit']}) appears {da['matches_in_table']} times in table!")
|
||||
print(f" Row: {da['added_row']}")
|
||||
@@ -0,0 +1,123 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load MD
|
||||
with open("resources/knowledge/original/행정규칙/임도 품셈 적용기준 (현 산림사업 표준품셈)/첨부/(산림청고시 제2025-82호) 산림사업 표준품셈.md", "r", encoding="utf-8") as f:
|
||||
md_lines = f.readlines()
|
||||
|
||||
# Load Master
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from scratch.enrich_tables import tables, get_chapter
|
||||
|
||||
# Map master tables
|
||||
master_tables = {}
|
||||
wi_of_table = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = t
|
||||
wi_of_table[tid] = wi
|
||||
|
||||
for t in master.get('orphan_tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = t
|
||||
|
||||
# Patterns for basis in pre_context:
|
||||
# e.g., "[100㎡ 당]", "[10a 당]", "[100m 당]", "[10㎥ 당]", "[본당]", "[1공당]", "[개소당]", "[1m3 당]"
|
||||
# Also patterns in table header or first line
|
||||
basis_pattern = re.compile(r'\[\s*([0-9\.\,]*)\s*([a-zA-Z㎡㎥㏊ha본개공mkm㎏gtonLℓ대인組]+)\s*당\s*\]')
|
||||
basis_pattern2 = re.compile(r'\(단위\s*:\s*([^\)]+)\)')
|
||||
|
||||
basis_audit = []
|
||||
|
||||
for i, t in enumerate(tables):
|
||||
tid = t['table_id']
|
||||
chapter = t['chapter']
|
||||
pre_lines = t['pre_context']
|
||||
|
||||
# 1. Search pre-context for basis
|
||||
found_basis_qty = None
|
||||
found_basis_unit = None
|
||||
found_basis_src = None
|
||||
|
||||
# Search backwards in pre_lines
|
||||
for pl in reversed(pre_lines):
|
||||
m = basis_pattern.search(pl)
|
||||
if m:
|
||||
qty_str = m.group(1).replace(',', '').strip()
|
||||
found_basis_qty = float(qty_str) if qty_str else 1.0
|
||||
found_basis_unit = m.group(2).strip()
|
||||
found_basis_src = f"pre_context: '{pl}'"
|
||||
break
|
||||
m2 = basis_pattern2.search(pl)
|
||||
if m2:
|
||||
u = m2.group(1).strip()
|
||||
found_basis_qty = 1.0
|
||||
found_basis_unit = u
|
||||
found_basis_src = f"unit_parenthesis: '{pl}'"
|
||||
break
|
||||
|
||||
# 2. Search table header cells
|
||||
header_line = t['lines'][0] if t['lines'] else ""
|
||||
# Look for "단위수량", "단위", etc.
|
||||
|
||||
# 3. Compare with master
|
||||
mt = master_tables.get(tid)
|
||||
if not mt:
|
||||
continue
|
||||
|
||||
m_qty = mt.get('basis_quantity')
|
||||
m_unit = mt.get('basis_unit')
|
||||
pform = mt.get('pum_form')
|
||||
|
||||
# Check if MD has explicit basis but master is None or different
|
||||
if found_basis_unit is not None:
|
||||
if m_unit is None or m_qty is None:
|
||||
basis_audit.append({
|
||||
'table_id': tid,
|
||||
'chapter': chapter,
|
||||
'line': t['start_line'],
|
||||
'section': t['section'],
|
||||
'pum_form': pform,
|
||||
'issue': 'MD_HAS_BASIS_MASTER_MISSING',
|
||||
'md_qty': found_basis_qty,
|
||||
'md_unit': found_basis_unit,
|
||||
'md_src': found_basis_src,
|
||||
'master_qty': m_qty,
|
||||
'master_unit': m_unit
|
||||
})
|
||||
elif m_qty != found_basis_qty or m_unit != found_basis_unit:
|
||||
# Check unit normalization (e.g. ㎡ vs m2)
|
||||
if not (str(found_basis_qty) == str(m_qty) and found_basis_unit.replace('㎡','m2').replace('㎥','m3') == m_unit.replace('㎡','m2').replace('㎥','m3')):
|
||||
basis_audit.append({
|
||||
'table_id': tid,
|
||||
'chapter': chapter,
|
||||
'line': t['start_line'],
|
||||
'section': t['section'],
|
||||
'pum_form': pform,
|
||||
'issue': 'BASIS_MISMATCH',
|
||||
'md_qty': found_basis_qty,
|
||||
'md_unit': found_basis_unit,
|
||||
'md_src': found_basis_src,
|
||||
'master_qty': m_qty,
|
||||
'master_unit': m_unit
|
||||
})
|
||||
|
||||
print(f"Total Basis issues identified: {len(basis_audit)}")
|
||||
from collections import Counter
|
||||
print("By issue type:", Counter(b['issue'] for b in basis_audit))
|
||||
print("By chapter:", Counter(b['chapter'] for b in basis_audit))
|
||||
|
||||
print("\n--- Sample Basis Issues (First 15) ---")
|
||||
for b in basis_audit[:15]:
|
||||
print(f"[{b['table_id']} | Line {b['line']}] {b['chapter']} | {b['section']}")
|
||||
print(f" MD: {b['md_qty']} {b['md_unit']} ({b['md_src']}) vs Master: {b['master_qty']} {b['master_unit']} (form: {b['pum_form']})")
|
||||
@@ -0,0 +1,124 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load files
|
||||
md_path = Path("resources/knowledge/original/행정규칙/임도 품셈 적용기준 (현 산림사업 표준품셈)/첨부/(산림청고시 제2025-82호) 산림사업 표준품셈.md")
|
||||
with open(md_path, "r", encoding="utf-8") as f:
|
||||
md_lines = f.readlines()
|
||||
|
||||
with open('resources/data_cost_input_value/pum_forest_2026.json', 'r', encoding='utf-8') as f:
|
||||
pum_data = json.load(f)
|
||||
|
||||
pum_tables = pum_data.get('variables', {}).get('pum', {}).get('tables', [])
|
||||
pum_map = {t['table_id']: t for t in pum_tables}
|
||||
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
master_tables = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = t
|
||||
for t in master.get('orphan_tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = t
|
||||
|
||||
# Extract all markdown tables from MD
|
||||
md_tables = []
|
||||
current_table = []
|
||||
start_line = -1
|
||||
|
||||
for idx, line in enumerate(md_lines):
|
||||
line_str = line.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
if not current_table:
|
||||
start_line = idx + 1
|
||||
current_table.append((idx + 1, line))
|
||||
else:
|
||||
if current_table:
|
||||
has_sep = any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', l.strip()) for _, l in current_table)
|
||||
if has_sep:
|
||||
md_tables.append({
|
||||
'start_line': start_line,
|
||||
'end_line': current_table[-1][0],
|
||||
'lines': [l for _, l in current_table]
|
||||
})
|
||||
current_table = []
|
||||
if current_table:
|
||||
has_sep = any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', l.strip()) for _, l in current_table)
|
||||
if has_sep:
|
||||
md_tables.append({
|
||||
'start_line': start_line,
|
||||
'end_line': current_table[-1][0],
|
||||
'lines': [l for _, l in current_table]
|
||||
})
|
||||
|
||||
print(f"MD tables: {len(md_tables)}, pum_forest tables: {len(pum_tables)}")
|
||||
|
||||
# Function to parse table rows from md lines
|
||||
def parse_md_cells(lines):
|
||||
rows = []
|
||||
for l in lines:
|
||||
l_str = l.strip()
|
||||
if re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', l_str):
|
||||
continue
|
||||
cells = [c.strip() for c in l_str.split('|')[1:-1]]
|
||||
rows.append(cells)
|
||||
return rows
|
||||
|
||||
# Compare each table's cells between MD and pum_forest
|
||||
cell_mismatches = []
|
||||
row_count_mismatches = []
|
||||
|
||||
for i, mdt in enumerate(md_tables):
|
||||
tid = f"F{i+1:04d}"
|
||||
pt = pum_map.get(tid)
|
||||
if not pt:
|
||||
continue
|
||||
|
||||
md_rows = parse_md_cells(mdt['lines'])
|
||||
# In pum_tables, headers is first row(s), rows is data rows
|
||||
pt_headers = pt.get('headers', [])
|
||||
pt_data_rows = pt.get('rows', [])
|
||||
pt_all_rows = []
|
||||
if pt_headers:
|
||||
pt_all_rows.append(pt_headers)
|
||||
pt_all_rows.extend(pt_data_rows)
|
||||
|
||||
# Check row counts
|
||||
if len(md_rows) != len(pt_all_rows):
|
||||
row_count_mismatches.append((tid, mdt['start_line'], len(md_rows), len(pt_all_rows)))
|
||||
continue
|
||||
|
||||
for r_idx, (mr, pr) in enumerate(zip(md_rows, pt_all_rows)):
|
||||
if len(mr) != len(pr):
|
||||
cell_mismatches.append((tid, mdt['start_line'] + r_idx, f"Col count diff: md={len(mr)} vs pt={len(pr)}"))
|
||||
continue
|
||||
for c_idx, (mc, pc) in enumerate(zip(mr, pr)):
|
||||
# Normalize strings for comparison
|
||||
# remove spaces, trailing .0, etc.
|
||||
mc_norm = mc.replace(" ", "").replace(",", "")
|
||||
pc_norm = str(pc).replace(" ", "").replace(",", "")
|
||||
if mc_norm != pc_norm:
|
||||
# Check numeric equivalence
|
||||
try:
|
||||
if float(mc_norm) == float(pc_norm):
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
cell_mismatches.append((tid, mdt['start_line'] + r_idx, f"Cell[{r_idx},{c_idx}] diff: md='{mc}' vs pt='{pc}'"))
|
||||
|
||||
print(f"Row count mismatches: {len(row_count_mismatches)}")
|
||||
for r in row_count_mismatches[:10]:
|
||||
print(f" {r[0]} line {r[1]}: md rows={r[2]} vs pt rows={r[3]}")
|
||||
|
||||
print(f"\nCell mismatches: {len(cell_mismatches)}")
|
||||
for cm in cell_mismatches[:20]:
|
||||
print(f" {cm[0]} line {cm[1]}: {cm[2]}")
|
||||
@@ -0,0 +1,65 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# 1. Load coef_2026.json
|
||||
coef_path = Path('resources/data_cost_input_value/coef_2026.json')
|
||||
with open(coef_path, 'r', encoding='utf-8') as f:
|
||||
coef = json.load(f)
|
||||
|
||||
# 2. Check soil conversion factors in MD (Table F0004, Line 709~727)
|
||||
# MD Table 4 has: 종별, L, C
|
||||
with open('resources/data_cost_input_value/pum_forest_2026.json', 'r', encoding='utf-8') as f:
|
||||
pum = json.load(f)
|
||||
|
||||
pum_tables = {t['table_id']: t for t in pum.get('variables', {}).get('pum', {}).get('tables', [])}
|
||||
|
||||
print("=== 기초값 대조: 토량환산계수 L, C (F0004) ===")
|
||||
f0004 = pum_tables.get('F0004')
|
||||
if f0004:
|
||||
print(f"F0004 headers: {f0004['headers']}")
|
||||
print("F0004 rows count:", len(f0004['rows']))
|
||||
for r in f0004['rows'][:5]:
|
||||
print(" ", r)
|
||||
|
||||
coef_vars = coef.get('variables', {})
|
||||
soil_L = coef_vars.get('coef_soil_L', {})
|
||||
soil_C = coef_vars.get('coef_soil_C', {})
|
||||
print(f"coef_2026 soil_L entries: {len(soil_L)}, soil_C entries: {len(soil_C)}")
|
||||
for k in list(soil_L.keys())[:5]:
|
||||
print(f" Soil: {k} -> L={soil_L.get(k)}, C={soil_C.get(k)}")
|
||||
|
||||
# Check discrepancies between F0004 rows and coef_soil_L/C
|
||||
soil_mismatches = []
|
||||
for r in f0004['rows']:
|
||||
name = r[0].strip()
|
||||
l_val_str = r[1].strip()
|
||||
c_val_str = r[2].strip()
|
||||
|
||||
# Try match with soil_L
|
||||
matched_l = None
|
||||
for k, v in soil_L.items():
|
||||
if k in name or name in k:
|
||||
matched_l = v
|
||||
break
|
||||
# print diff if any
|
||||
print(f"MD [{name}]: L={l_val_str}, C={c_val_str} | coef_2026 match: {matched_l}")
|
||||
|
||||
print("\n=== 기초값 대조: 자재할증률 (F0008, F0009, F0010, F0011, F0012, F0013) ===")
|
||||
mat_surcharges = coef_vars.get('surcharge_mat', {})
|
||||
print(f"coef_2026 surcharge_mat count: {len(mat_surcharges)}")
|
||||
for k, v in list(mat_surcharges.items())[:10]:
|
||||
print(f" {k}: {v}")
|
||||
|
||||
print("\n=== 기초값 대조: 공구손료 및 경장비 (rate_tool) ===")
|
||||
rate_tool = coef_vars.get('rate_tool', {})
|
||||
print(f"coef_2026 rate_tool: {rate_tool}")
|
||||
|
||||
print("\n=== 기초값 대조: 노임할증 (surcharge_labor) ===")
|
||||
surcharge_labor = coef_vars.get('surcharge_labor', {})
|
||||
print(f"coef_2026 surcharge_labor entries: {len(surcharge_labor)}")
|
||||
for k, v in list(surcharge_labor.items())[:10]:
|
||||
print(f" {k}: {v}")
|
||||
@@ -0,0 +1,77 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load enriched tables
|
||||
sys.path.insert(0, '.')
|
||||
from scratch.enrich_tables import tables
|
||||
|
||||
print("=== 원문 전체에서 '분모가 둘'이거나 복합 단위인 표 전수 조사 ===")
|
||||
|
||||
complex_units = []
|
||||
|
||||
# Pattern to find parenthesis units in pre_context or headers
|
||||
unit_regex = re.compile(r'[\(\[\{]\s*단위\s*[:\=]?\s*([^\)\]\}]+)[\)\]\}]')
|
||||
|
||||
for t in tables:
|
||||
tid = t['table_id']
|
||||
pre_lines = t['pre_context']
|
||||
header_line = t['lines'][0] if t['lines'] else ""
|
||||
|
||||
found_u = None
|
||||
src_line = None
|
||||
|
||||
for pl in reversed(pre_lines):
|
||||
m = unit_regex.search(pl)
|
||||
if m:
|
||||
found_u = m.group(1).strip()
|
||||
src_line = pl
|
||||
break
|
||||
|
||||
if not found_u:
|
||||
m_h = unit_regex.search(header_line)
|
||||
if m_h:
|
||||
found_u = m_h.group(1).strip()
|
||||
src_line = header_line
|
||||
|
||||
if found_u:
|
||||
# Check if it has multiple slashes or complex compound units
|
||||
# e.g. "인/1일 1km당", "㎥/1인/1일", "인/ha", "인/개당", "㎥/㎡당", "100본/ha당", "ℓ/일,대"
|
||||
is_double_denom = False
|
||||
# double denominator pattern: has two '/' or '/일' and 'km당' etc.
|
||||
slash_count = found_u.count('/')
|
||||
has_per_day_and_unit = ('일' in found_u and any(k in found_u for k in ['km', 'm', 'ha', '개', '본', '㎡', '㎥']))
|
||||
has_compound_slash = slash_count >= 2 or ('/' in found_u and '당' in found_u and any(k in found_u for k in ['1일', '일,', '일당', '인당']))
|
||||
|
||||
if slash_count >= 2 or has_per_day_and_unit or '㎥/㎡' in found_u or '인/10개' in found_u or '인/100m' in found_u:
|
||||
is_double_denom = True
|
||||
|
||||
complex_units.append({
|
||||
'table_id': tid,
|
||||
'chapter': t['chapter'],
|
||||
'section': t['section'],
|
||||
'line': t['start_line'],
|
||||
'raw_unit': found_u,
|
||||
'src_line': src_line,
|
||||
'slash_count': slash_count,
|
||||
'is_double_denom': is_double_denom
|
||||
})
|
||||
|
||||
print(f"Total tables with parenthesis units found: {len(complex_units)}")
|
||||
|
||||
double_denoms = [u for u in complex_units if u['is_double_denom']]
|
||||
print(f"Total tables with '분모가 둘인 꼴 (복합 분모)' : {len(double_denoms)}개\n")
|
||||
|
||||
print("--- [분모가 둘인 꼴 전수 명세] ---")
|
||||
for idx, d in enumerate(double_denoms):
|
||||
print(f"{idx+1:2d}. [{d['table_id']} | line {d['line']}] {d['section']}")
|
||||
print(f" 원문 단위 표기: '{d['raw_unit']}' (출처: {d['src_line']})")
|
||||
|
||||
print("\n--- [슬래시 1개인 단순 몫 단위(인/ha, 인/개 등) 표들] ---")
|
||||
single_slash = [u for u in complex_units if not u['is_double_denom'] and u['slash_count'] == 1]
|
||||
print(f"Total single slash units: {len(single_slash)}개 (sample 15):")
|
||||
for s in single_slash[:15]:
|
||||
print(f" [{s['table_id']}] {s['section']} -> '{s['raw_unit']}'")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,140 @@
|
||||
import sys, os, json, re
|
||||
import fitz # PyMuPDF
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
pdf_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.pdf"
|
||||
doc = fitz.open(pdf_path)
|
||||
|
||||
# MD file mapping for chapters
|
||||
md_map = {
|
||||
"공통_제1장": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제1장_적용기준.md",
|
||||
"공통_제3장": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제3장_토공사.md",
|
||||
"공통_제4장": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제4장_조경공사.md",
|
||||
"공통_제6장": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제6장_철근콘크리트공사.md",
|
||||
"토목_제1장": "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md",
|
||||
"토목_제6장": "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md",
|
||||
"건축_제9장": "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제9장_미장공사.md",
|
||||
"유지관리_제1장": "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md",
|
||||
"유지관리_제3장": "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md",
|
||||
}
|
||||
|
||||
# Read MD contents into memory
|
||||
md_contents = {}
|
||||
for k, p in md_map.items():
|
||||
if os.path.exists(p):
|
||||
with open(p, 'r', encoding='utf-8') as f:
|
||||
md_contents[k] = f.read()
|
||||
else:
|
||||
print(f"MD missing: {p}")
|
||||
|
||||
with open('scratch/located_const_sections.json', 'r', encoding='utf-8') as f:
|
||||
located_items = json.load(f)
|
||||
|
||||
print(f"Loaded located items: {len(located_items)}")
|
||||
|
||||
results = []
|
||||
|
||||
for item in located_items:
|
||||
k = item['key']
|
||||
num = item['number']
|
||||
name = item['name']
|
||||
pn = item['path_name']
|
||||
pdf_pages = item.get('pdf_pages', [])
|
||||
|
||||
# Exclude chapter 8 (already audited by codex)
|
||||
if "공통부문 › 건설기계" in pn:
|
||||
continue
|
||||
|
||||
print(f"\n==================================================")
|
||||
print(f"Auditing: {k} | {num} {name} | {pn}")
|
||||
print(f"PDF Pages: {pdf_pages}")
|
||||
|
||||
# Determine MD chapter
|
||||
md_key = None
|
||||
if "공통부문 › 적용기준" in pn:
|
||||
md_key = "공통_제1장"
|
||||
elif "공통부문 › 토공사" in pn:
|
||||
md_key = "공통_제3장"
|
||||
elif "공통부문 › 조경공사" in pn:
|
||||
md_key = "공통_제4장"
|
||||
elif "공통부문 › 철근콘크리트공사" in pn:
|
||||
md_key = "공통_제6장"
|
||||
elif "토목부문 › 도로포장공사" in pn:
|
||||
md_key = "토목_제1장"
|
||||
elif "토목부문 › 관부설" in pn:
|
||||
md_key = "토목_제6장"
|
||||
elif "건축부문 › 미장공사" in pn:
|
||||
md_key = "건축_제9장"
|
||||
elif "유지관리부문 › 공 통" in pn:
|
||||
md_key = "유지관리_제1장"
|
||||
elif "유지관리부문 › 건 축" in pn:
|
||||
md_key = "유지관리_제3장"
|
||||
|
||||
md_text = md_contents.get(md_key, "")
|
||||
|
||||
# Extract MD section chunk
|
||||
# Usually in MD, sections start with "### 1-2-2" or "## 1-2-2" or "1-2-2."
|
||||
escaped_num = re.escape(num)
|
||||
sec_pattern = re.compile(rf"(^|\n)(#+\s+{escaped_num}[\.\s].*?)(?=\n#+\s+\d+-\d+-\d+[\.\s]|\Z)", re.DOTALL)
|
||||
m = sec_pattern.search(md_text)
|
||||
if not m:
|
||||
# try without leading hash
|
||||
sec_pattern2 = re.compile(rf"(^|\n)({escaped_num}[\.\s].*?)(?=\n\d+-\d+-\d+[\.\s]|\Z)", re.DOTALL)
|
||||
m = sec_pattern2.search(md_text)
|
||||
|
||||
md_sec_text = m.group(2) if m else ""
|
||||
if not md_sec_text:
|
||||
print(f"⚠ Warning: Could not isolate section in MD: {num}")
|
||||
else:
|
||||
print(f"MD Section found (len={len(md_sec_text)} chars)")
|
||||
|
||||
# Extract PDF page text
|
||||
pdf_text = ""
|
||||
# Use the relevant page (typically the 2nd match if first was toc/index, or the page > 50)
|
||||
actual_pages = [p for p in pdf_pages if p > 50]
|
||||
for p in actual_pages:
|
||||
pdf_text += f"\n--- PDF Page {p} ---\n" + doc[p - 1].get_text()
|
||||
|
||||
# Check 8 branches
|
||||
audit = {
|
||||
"work_item_key": k,
|
||||
"section_number": num,
|
||||
"section_name": name,
|
||||
"path_name": pn,
|
||||
"md_file": md_map.get(md_key),
|
||||
"pdf_pages": actual_pages,
|
||||
"branch1_basis_unit": {"status": "ok", "notes": []},
|
||||
"branch2_multilevel_header": {"status": "ok", "notes": []},
|
||||
"branch3_notes_boundary": {"status": "ok", "notes": []},
|
||||
"branch4_page_break_row_merge": {"status": "ok", "notes": []},
|
||||
"branch5_cell_values": {"status": "ok", "notes": []},
|
||||
"branch6_missing_sections": {"status": "ok", "notes": []},
|
||||
"branch7_notes_formula_boundary": {"status": "ok", "notes": []},
|
||||
"branch8_hidden_materials_ref": {"status": "ok", "notes": []}
|
||||
}
|
||||
|
||||
# 1. Check if section exists in MD
|
||||
if not md_sec_text:
|
||||
audit["branch6_missing_sections"]["status"] = "missing_in_md"
|
||||
audit["branch6_missing_sections"]["notes"].append(f"Section {num} not found in MD {md_key}")
|
||||
else:
|
||||
# Check notes [주]
|
||||
pdf_notes = re.findall(r"\[주\]|\[\s*주\s*\]|[①-⑩]", pdf_text)
|
||||
md_notes = re.findall(r"\[주\]|\[\s*주\s*\]|[①-⑩]", md_sec_text)
|
||||
if len(pdf_notes) > 0 and len(md_notes) == 0:
|
||||
audit["branch3_notes_boundary"]["status"] = "suspicious_notes_missing"
|
||||
audit["branch3_notes_boundary"]["notes"].append(f"PDF has {len(pdf_notes)} note markers, MD has 0")
|
||||
|
||||
# Check units in PDF vs MD
|
||||
units_in_pdf = re.findall(r"\((?:단위\s*:\s*)?([^\)]+당|[^\)]+)\)", pdf_text)
|
||||
# Check table presence
|
||||
if "|" in pdf_text and "|" not in md_sec_text:
|
||||
audit["branch4_page_break_row_merge"]["status"] = "table_flattened_to_text"
|
||||
audit["branch4_page_break_row_merge"]["notes"].append("Table in PDF not rendered as markdown table")
|
||||
|
||||
results.append(audit)
|
||||
|
||||
with open('scratch/audit_const_linked_results.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\nAudit completed for {len(results)} linked non-machine sections.")
|
||||
@@ -0,0 +1,51 @@
|
||||
import glob, re, os, sys, fitz
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
pdf_path = 'resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.pdf'
|
||||
doc = fitz.open(pdf_path)
|
||||
|
||||
# Ditot / remarks targets
|
||||
target_sections = [
|
||||
('제4장_궤도공사.md', '4-5-2', 205),
|
||||
('제7장_항만공사.md', '7-1-2', 29),
|
||||
('제7장_항만공사.md', '7-1-4', 86),
|
||||
('제8장_지반조사.md', '8-4-2', 271),
|
||||
('제9장_측량.md', '9-3-1', 703),
|
||||
('제9장_측량.md', '9-5-4', 2701),
|
||||
('제13장_플랜트설비공사.md', '13-4-3', 1544),
|
||||
('제13장_플랜트설비공사.md', '13-5-1', 1703),
|
||||
('제13장_플랜트설비공사.md', '13-5-2', 1734),
|
||||
('제13장_플랜트설비공사.md', '13-7-14', 3012),
|
||||
('제13장_플랜트설비공사.md', '13-7-16', 3040),
|
||||
('제13장_플랜트설비공사.md', '13-7-17', 3053),
|
||||
('제13장_플랜트설비공사.md', '13-7-18', 3067),
|
||||
('제13장_플랜트설비공사.md', '13-7-19', 3079),
|
||||
('제13장_플랜트설비공사.md', '13-7-20', 3096),
|
||||
('제13장_플랜트설비공사.md', '13-7-21', 3110),
|
||||
('제13장_플랜트설비공사.md', '13-10-2', 3513),
|
||||
('제13장_플랜트설비공사.md', '13-10-4', 3586)
|
||||
]
|
||||
|
||||
print("=== Checking Ditot Sections in PDF ===")
|
||||
for fname, sec, line in target_sections:
|
||||
# Find page in PDF
|
||||
found_pages = []
|
||||
for p_num in range(len(doc)):
|
||||
text = doc[p_num].get_text()
|
||||
if sec in text:
|
||||
found_pages.append(p_num)
|
||||
print(f"\n[{fname}] Section {sec} (md line {line}) -> Found in PDF page(s): {[p+1 for p in found_pages]}")
|
||||
for p in found_pages[:2]:
|
||||
page = doc[p]
|
||||
tabs = page.find_tables()
|
||||
for t_idx, tab in enumerate(tabs):
|
||||
df = tab.extract()
|
||||
# print first 3 and last 2 rows
|
||||
print(f" --- Table {t_idx} on Page {p+1} (rows: {len(df)}) ---")
|
||||
for r in df[:5]:
|
||||
print(f" {r}")
|
||||
if len(df) > 5:
|
||||
print(" ...")
|
||||
for r in df[-2:]:
|
||||
print(f" {r}")
|
||||
@@ -0,0 +1,83 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load enriched tables
|
||||
sys.path.insert(0, '.')
|
||||
from scratch.enrich_tables import tables
|
||||
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
master_tables = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = (wi, t)
|
||||
|
||||
print("=== 1. 마스터에서 basis_unit 이 '인'으로 들어간 표 전수 조사 ===")
|
||||
master_in_tables = []
|
||||
for tid, (wi, t) in master_tables.items():
|
||||
bu = t.get('basis_unit')
|
||||
if bu and '인' in str(bu):
|
||||
master_in_tables.append((tid, t.get('section'), t.get('basis_quantity'), bu, t.get('pum_form'), t.get('basis_source')))
|
||||
|
||||
print(f"Total tables with '인' in basis_unit in Master: {len(master_in_tables)}")
|
||||
for it in master_in_tables:
|
||||
print(f" [{it[0]}] {it[1]} -> {it[2]} {it[3]} (form: {it[4]}, src: {it[5]})")
|
||||
|
||||
print("\n=== 2. 원문 MD/PDF 에서 '인' 또는 '인당'이 단위로 쓰인 표 전수 조사 ===")
|
||||
unit_in_tables = []
|
||||
for t in tables:
|
||||
tid = t['table_id']
|
||||
pre_lines = t['pre_context']
|
||||
header_line = t['lines'][0] if t['lines'] else ""
|
||||
|
||||
# search for parenthesis unit containing '인'
|
||||
found = []
|
||||
for pl in reversed(pre_lines):
|
||||
m = re.search(r'[\(\[\{]\s*단위\s*[:\=]?\s*([^\)\]\}]+)[\)\]\}]', pl)
|
||||
if m:
|
||||
u_str = m.group(1).strip()
|
||||
if '인' in u_str:
|
||||
found.append(('pre_context', pl, u_str))
|
||||
break
|
||||
# also check headers
|
||||
m_h = re.search(r'[\(\[]\s*단위\s*[:\=]?\s*([^\)\]]+)[\)\]]', header_line)
|
||||
if m_h:
|
||||
u_str = m_h.group(1).strip()
|
||||
if '인' in u_str:
|
||||
found.append(('header', header_line, u_str))
|
||||
|
||||
# Also check if table row has "인당" in applicable standard (적용기준)
|
||||
has_apply_in = any('1인당' in l or '인당' in l for l in t['lines'])
|
||||
|
||||
if found or has_apply_in:
|
||||
unit_in_tables.append({
|
||||
'table_id': tid,
|
||||
'chapter': t['chapter'],
|
||||
'section': t['section'],
|
||||
'line': t['start_line'],
|
||||
'found_units': found,
|
||||
'has_apply_in': has_apply_in
|
||||
})
|
||||
|
||||
print(f"Total tables where 원문 has '인' in unit or apply text: {len(unit_in_tables)}")
|
||||
print("\n--- [A. 순수 '인' 또는 '인당' (무복합) 단위 표] ---")
|
||||
pure_in = []
|
||||
for it in unit_in_tables:
|
||||
for src, pl, u in it['found_units']:
|
||||
if u in ['인', '인당', '인/일', '인 / 일', '1인/1일']:
|
||||
pure_in.append((it['table_id'], it['section'], u, pl))
|
||||
print(f" [{it['table_id']}] {it['section']} -> unit: '{u}' (from: {pl})")
|
||||
|
||||
print(f"\n--- [B. 적용기준 셀에 '1인당'이 들어있어 마스터가 '인'으로 잘못 뽑은 표] ---")
|
||||
for it in unit_in_tables:
|
||||
if it['has_apply_in']:
|
||||
t_master = master_tables.get(it['table_id'])
|
||||
m_bu = t_master[1].get('basis_unit') if t_master else None
|
||||
print(f" [{it['table_id']}] {it['section']} -> Master basis_unit: {m_bu}")
|
||||
@@ -0,0 +1,93 @@
|
||||
import os, sys, re, json
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
|
||||
all_md_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
if '01_공통부문\\제8장' in os.path.join(root, f) or '01_공통부문/제8장' in os.path.join(root, f):
|
||||
continue
|
||||
all_md_files.append(os.path.join(root, f))
|
||||
|
||||
# Also add merged file
|
||||
all_md_files.append(os.path.join(base_dir, "2026년_건설공사_표준품셈.md"))
|
||||
|
||||
print(f"Auditing {len(all_md_files)} files for 2 flaws...")
|
||||
|
||||
header_divider_mismatches = []
|
||||
spec_3row_shift_candidates = []
|
||||
|
||||
for fpath in all_md_files:
|
||||
rel_path = os.path.relpath(fpath, base_dir)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
in_table = False
|
||||
header_rows = []
|
||||
divider_cols = 0
|
||||
divider_line = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_num = idx + 1
|
||||
line_str = line.strip()
|
||||
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
|
||||
# Check if this is a divider row
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
divider_cols = len(cells)
|
||||
divider_line = line_num
|
||||
# Check preceding header row
|
||||
if header_rows:
|
||||
last_hdr = header_rows[-1]
|
||||
if len(last_hdr['cells']) != divider_cols:
|
||||
header_divider_mismatches.append({
|
||||
"file": rel_path,
|
||||
"line": divider_line,
|
||||
"header_line": last_hdr['line'],
|
||||
"header_cols": len(last_hdr['cells']),
|
||||
"divider_cols": divider_cols,
|
||||
"header_snippet": last_hdr['snippet']
|
||||
})
|
||||
in_table = True
|
||||
elif not in_table:
|
||||
header_rows.append({
|
||||
"line": line_num,
|
||||
"cells": cells,
|
||||
"snippet": line_str[:80]
|
||||
})
|
||||
else:
|
||||
# Table body row
|
||||
# Check Flaw 1: 3-row spec shifted (e.g. output ㎾ on a row alone or pushed to next line)
|
||||
# Looking for orphaned spec line with ㎾, ton, HP etc. where rest of row is empty or mismatched
|
||||
if len(cells) > 2:
|
||||
c0 = cells[0]
|
||||
c1 = cells[1] if len(cells) > 1 else ""
|
||||
# If first cell is just a unit/spec like "출력 ㎾" or "㎾" and following data columns are misaligned
|
||||
if re.match(r'^(출력|규격|용량)?\s*(\(?㎾\)?|\(?ton\)?|\(?HP\)?|\(?PS\)?)$', c0):
|
||||
spec_3row_shift_candidates.append({
|
||||
"file": rel_path,
|
||||
"line": line_num,
|
||||
"snippet": line_str[:100]
|
||||
})
|
||||
else:
|
||||
in_table = False
|
||||
header_rows = []
|
||||
|
||||
print(f"\n1. Header vs Divider column mismatches: {len(header_divider_mismatches)}")
|
||||
for m in header_divider_mismatches:
|
||||
print(f" {m['file']} L{m['line']}: header={m['header_cols']} vs divider={m['divider_cols']} ({m['header_snippet']})")
|
||||
|
||||
print(f"\n2. Spec 3-row shifted candidates: {len(spec_3row_shift_candidates)}")
|
||||
for s in spec_3row_shift_candidates:
|
||||
print(f" {s['file']} L{s['line']}: {s['snippet']}")
|
||||
|
||||
with open('scratch/laptop_sub_flaws_audit.json', 'w', encoding='utf-8') as f:
|
||||
json.dump({
|
||||
"header_divider_mismatches": header_divider_mismatches,
|
||||
"spec_3row_shift_candidates": spec_3row_shift_candidates
|
||||
}, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,81 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load enriched tables from scratch
|
||||
sys.path.insert(0, '.')
|
||||
from scratch.enrich_tables import tables
|
||||
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
master_tables = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = t
|
||||
|
||||
# Compare MD notes vs Master notes for all 476 tables
|
||||
truncated_or_missing_notes = []
|
||||
empty_in_master_has_in_md = []
|
||||
fewer_lines_in_master = []
|
||||
|
||||
for t in tables:
|
||||
tid = t['table_id']
|
||||
mt = master_tables.get(tid)
|
||||
if not mt:
|
||||
continue
|
||||
|
||||
md_notes = t.get('notes', [])
|
||||
master_notes = mt.get('notes', [])
|
||||
|
||||
# Clean up md_notes
|
||||
# Filter out empty or pure markdown formatting if any
|
||||
md_clean = [n.strip() for n in md_notes if n.strip()]
|
||||
m_clean = [n.strip() for n in master_notes if n.strip()]
|
||||
|
||||
if md_clean and not m_clean:
|
||||
empty_in_master_has_in_md.append({
|
||||
'table_id': tid,
|
||||
'chapter': t['chapter'],
|
||||
'section': t['section'],
|
||||
'line': t['start_line'],
|
||||
'md_notes_count': len(md_clean),
|
||||
'md_notes': md_clean
|
||||
})
|
||||
elif len(md_clean) > len(m_clean):
|
||||
# Master has fewer lines than MD. Check what was lost!
|
||||
lost_lines = []
|
||||
for line in md_clean:
|
||||
# Check if this line is in master_notes
|
||||
if not any(line in mn or mn in line for mn in m_clean):
|
||||
lost_lines.append(line)
|
||||
if lost_lines:
|
||||
fewer_lines_in_master.append({
|
||||
'table_id': tid,
|
||||
'chapter': t['chapter'],
|
||||
'section': t['section'],
|
||||
'line': t['start_line'],
|
||||
'md_count': len(md_clean),
|
||||
'master_count': len(m_clean),
|
||||
'lost_lines': lost_lines
|
||||
})
|
||||
|
||||
print(f"Tables where MD has notes but Master is completely EMPTY: {len(empty_in_master_has_in_md)}")
|
||||
print(f"Tables where Master lost lines compared to MD: {len(fewer_lines_in_master)}")
|
||||
|
||||
print("\n--- 1. Tables where MD has notes but Master is EMPTY (First 15) ---")
|
||||
for it in empty_in_master_has_in_md[:15]:
|
||||
print(f"[{it['table_id']} | line {it['line']}] {it['chapter']} | {it['section']} (MD {it['md_notes_count']} lines)")
|
||||
for n in it['md_notes'][:3]:
|
||||
print(f" {n}")
|
||||
|
||||
print("\n--- 2. Tables where Master lost lines (Sample 10) ---")
|
||||
for it in fewer_lines_in_master[:10]:
|
||||
print(f"[{it['table_id']} | line {it['line']}] {it['chapter']} | {it['section']} (MD: {it['md_count']} vs Master: {it['master_count']})")
|
||||
for l in it['lost_lines'][:3]:
|
||||
print(f" LOST: {l}")
|
||||
@@ -0,0 +1,23 @@
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
md_path = Path(r"resources/knowledge/original/행정규칙/임도 품셈 적용기준 (현 산림사업 표준품셈)/첨부/(산림청고시 제2025-82호) 산림사업 표준품셈.md")
|
||||
master_path = Path(r"resources/data_work_item_master/work_item_master_2026-01-01.json")
|
||||
pum_forest_path = Path(r"resources/data_cost_input_value/pum_forest_2026.json")
|
||||
coef_path = Path(r"resources/data_cost_input_value/coef_2026.json")
|
||||
|
||||
with open(master_path, "r", encoding="utf-8") as f:
|
||||
master = json.load(f)
|
||||
|
||||
with open(pum_forest_path, "r", encoding="utf-8") as f:
|
||||
pum_forest = json.load(f)
|
||||
|
||||
with open(coef_path, "r", encoding="utf-8") as f:
|
||||
coef = json.load(f)
|
||||
|
||||
with open(md_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
print(f"Loaded master with {len(master['work_items'])} work items, {master['stats']['tables_attached']} attached tables, {len(master['orphan_tables'])} orphan tables.")
|
||||
print(f"Loaded markdown with {len(lines)} lines.")
|
||||
@@ -0,0 +1,64 @@
|
||||
import sys, os, re, json
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
merged_file = os.path.join(base_dir, "2026년_건설공사_표준품셈.md")
|
||||
|
||||
with open(merged_file, 'r', encoding='utf-8') as f:
|
||||
merged_text = f.read()
|
||||
|
||||
all_md_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
all_md_files.append(os.path.join(root, f))
|
||||
|
||||
# 1. Check divider column mismatch in all MD files (including merged)
|
||||
files_to_check = all_md_files + [merged_file]
|
||||
|
||||
divider_mismatches = []
|
||||
for fpath in files_to_check:
|
||||
rel = os.path.relpath(fpath, base_dir)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for i in range(len(lines) - 1):
|
||||
l1 = lines[i].strip()
|
||||
l2 = lines[i+1].strip()
|
||||
|
||||
# Check if l2 is divider
|
||||
if l1.startswith('|') and l1.endswith('|') and l2.startswith('|') and l2.endswith('|'):
|
||||
# check if l2 consists of --- and |
|
||||
if re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', l2):
|
||||
c1 = len(l1.split('|')) - 2
|
||||
c2 = len(l2.split('|')) - 2
|
||||
if c1 != c2:
|
||||
divider_mismatches.append({
|
||||
"file": rel,
|
||||
"line": i + 2,
|
||||
"header_cols": c1,
|
||||
"divider_cols": c2,
|
||||
"header": l1[:60],
|
||||
"divider": l2[:40]
|
||||
})
|
||||
|
||||
print(f"=== Divider Column Mismatches across all files: {len(divider_mismatches)} ===")
|
||||
for dm in divider_mismatches[:20]:
|
||||
print(f" - {dm['file']} (L{dm['line']}): header={dm['header_cols']} vs divider={dm['divider_cols']} | {dm['header']}")
|
||||
|
||||
# 2. Check 3-line spec displacement
|
||||
# Tables where header has 형식 and 출력(kW) or similar
|
||||
spec_displacements = []
|
||||
for fpath in files_to_check:
|
||||
rel = os.path.relpath(fpath, base_dir)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for i, l in enumerate(lines):
|
||||
if '출력' in l and '㎾' in l and '|' in l:
|
||||
# check subsequent rows
|
||||
# if next rows have misplaced cell values or shifted empty cells
|
||||
pass
|
||||
|
||||
with open('scratch/divider_mismatches.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(divider_mismatches, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,69 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load master
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
# Load pum_forest
|
||||
with open('resources/data_cost_input_value/pum_forest_2026.json', 'r', encoding='utf-8') as f:
|
||||
pum_data = json.load(f)
|
||||
|
||||
pum_tables = pum_data.get('variables', {}).get('pum', {}).get('tables', [])
|
||||
pum_map = {t['table_id']: t for t in pum_tables}
|
||||
|
||||
# Map master tables
|
||||
master_wi_tables = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_wi_tables[tid] = (wi, t)
|
||||
|
||||
orphan_map = {t.get('pum_table_id'): t for t in master.get('orphan_tables', []) if t.get('pum_table_id')}
|
||||
|
||||
print("=== 1. 표(Table) 누락 및 매핑 현황 ===")
|
||||
all_f_ids = [f"F{i+1:04d}" for i in range(476)]
|
||||
|
||||
in_wi = []
|
||||
in_orphan = []
|
||||
missing = []
|
||||
|
||||
for tid in all_f_ids:
|
||||
if tid in master_wi_tables:
|
||||
in_wi.append(tid)
|
||||
elif tid in orphan_map:
|
||||
in_orphan.append(tid)
|
||||
else:
|
||||
missing.append(tid)
|
||||
|
||||
print(f"Total Tables in MD/pum_forest: {len(all_f_ids)}")
|
||||
print(f"Tables mapped to Work Items: {len(in_wi)}")
|
||||
print(f"Tables in orphan_tables: {len(in_orphan)}")
|
||||
print(f"Tables missing from master completely: {len(missing)}")
|
||||
for tid in missing:
|
||||
pt = pum_map.get(tid, {})
|
||||
print(f" Missing: {tid} | line {pt.get('line')} | section: {pt.get('section')}")
|
||||
|
||||
print("\n--- Orphan Tables Analysis (22 tables) ---")
|
||||
orphan_by_section = defaultdict(list)
|
||||
for tid in in_orphan:
|
||||
ot = orphan_map[tid]
|
||||
pt = pum_map.get(tid, {})
|
||||
orphan_by_section[pt.get('section', ot.get('section'))].append((tid, pt.get('line'), ot.get('pum_form')))
|
||||
|
||||
for sec, tbls in orphan_by_section.items():
|
||||
print(f"Section: {sec}")
|
||||
for tid, line, pform in tbls:
|
||||
print(f" - {tid} (line {line}): form={pform}")
|
||||
|
||||
print("\n--- Sections in MD vs Work Items ---")
|
||||
# Check which chapters/sections exist in MD but have 0 work items or orphan only
|
||||
wi_sections = {wi.get('toc_number'): wi for wi in master['work_items']}
|
||||
wi_codes = {wi.get('work_item_code'): wi for wi in master['work_items']}
|
||||
print(f"Work items count: {len(master['work_items'])}")
|
||||
@@ -0,0 +1,96 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Scanning for Page Boundary Duplicate/Overlapped Rows ===")
|
||||
|
||||
page_markers = [
|
||||
r'^\s*→\s*\d+',
|
||||
r'^\s*\d+\s*[가-힣]+부문',
|
||||
r'^\s*[가-힣]+부문\s*\d+',
|
||||
r'^\s*제\d+장\s*[가-힣]+\s*\d+',
|
||||
r'^\s*\d+\s*제\d+장',
|
||||
r'^\s*→\s*$',
|
||||
r'^\s*→[가-힣]+부문\d+',
|
||||
]
|
||||
page_regex = re.compile('|'.join(page_markers))
|
||||
|
||||
boundary_cases = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if page_regex.search(line_str):
|
||||
l_no = idx + 1
|
||||
# Found page boundary at l_no
|
||||
# Look at 30 lines before and 30 lines after
|
||||
before_lines = [lines[i].strip() for i in range(max(0, idx-30), idx) if lines[i].strip().startswith('|')]
|
||||
after_lines = [lines[i].strip() for i in range(idx+1, min(len(lines), idx+31)) if lines[i].strip().startswith('|')]
|
||||
|
||||
# Filter out divider lines
|
||||
before_data = [l for l in before_lines if not all(re.match(r'^:?-+:?$', c) for c in [x.strip() for x in l.split('|')[1:-1]] if c)]
|
||||
after_data = [l for l in after_lines if not all(re.match(r'^:?-+:?$', c) for c in [x.strip() for x in l.split('|')[1:-1]] if c)]
|
||||
|
||||
if not before_data or not after_data:
|
||||
continue
|
||||
|
||||
# Check for duplicate rows or squashed rows between before and after
|
||||
for b_idx, bl in enumerate(before_data):
|
||||
b_cells = [c.strip() for c in bl.split('|')[1:-1]]
|
||||
b_nums = set(re.findall(r'\b\d+(?:\.\d+)?\b', bl))
|
||||
b_words = set(re.findall(r'[가-힣A-Za-z0-9]+', bl))
|
||||
|
||||
for a_idx, al in enumerate(after_data):
|
||||
a_cells = [c.strip() for c in al.split('|')[1:-1]]
|
||||
# 1. Exact match
|
||||
if b_cells == a_cells and len([c for c in b_cells if c not in ["", "-", "〃"]]) >= 2:
|
||||
boundary_cases.append({
|
||||
'file': fname,
|
||||
'boundary_line': l_no,
|
||||
'marker': line_str,
|
||||
'type': 'exact_match_across_boundary',
|
||||
'before_row': bl[:80],
|
||||
'after_row': al[:80]
|
||||
})
|
||||
# 2. After row contains multiple before rows (squashed overlap)
|
||||
a_words = set(re.findall(r'[가-힣A-Za-z0-9]+', al))
|
||||
a_nums = set(re.findall(r'\b\d+(?:\.\d+)?\b', al))
|
||||
if len(b_nums) >= 2 and b_nums.issubset(a_nums) and len(b_words.intersection(a_words)) >= 2:
|
||||
boundary_cases.append({
|
||||
'file': fname,
|
||||
'boundary_line': l_no,
|
||||
'marker': line_str,
|
||||
'type': 'squashed_overlap_across_boundary',
|
||||
'before_row': bl[:80],
|
||||
'after_row': al[:80]
|
||||
})
|
||||
|
||||
print(f"Total boundary cases found: {len(boundary_cases)}")
|
||||
for bc in boundary_cases:
|
||||
print(f"[{bc['file']}:L{bc['boundary_line']}] {bc['marker']} - {bc['type']}")
|
||||
print(f" Before: {bc['before_row']}")
|
||||
print(f" After: {bc['after_row']}")
|
||||
@@ -0,0 +1,473 @@
|
||||
[
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제1장_도로포장공사",
|
||||
"tables": 213,
|
||||
"standalone_secs": 57,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"1-1-1 교통통제",
|
||||
"1-8-1 교통",
|
||||
"1-9-1 방음벽"
|
||||
],
|
||||
"flat_headers": 18
|
||||
},
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제2장_하천공사",
|
||||
"tables": 19,
|
||||
"standalone_secs": 8,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"2-1-1 사석부설",
|
||||
"2-2-1 타원형",
|
||||
"2-3-1 식생매트"
|
||||
],
|
||||
"flat_headers": 0
|
||||
},
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제3장_터널공사",
|
||||
"tables": 45,
|
||||
"standalone_secs": 12,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"3-1-1 터널노임",
|
||||
"3-3-1 터널",
|
||||
"3-4-1 터널"
|
||||
],
|
||||
"flat_headers": 8
|
||||
},
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제4장_궤도공사",
|
||||
"tables": 73,
|
||||
"standalone_secs": 24,
|
||||
"stuck_secs": 7,
|
||||
"sample_stuck": [
|
||||
"4-1-1 철도안전처리",
|
||||
"4-2-1 궤광조립",
|
||||
"4-3-1 궤광조립"
|
||||
],
|
||||
"flat_headers": 27
|
||||
},
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제5장_강구조공사",
|
||||
"tables": 74,
|
||||
"standalone_secs": 11,
|
||||
"stuck_secs": 5,
|
||||
"sample_stuck": [
|
||||
"5-2-1 소재",
|
||||
"5-3-1 강재거더",
|
||||
"6-1-1 적용기준"
|
||||
],
|
||||
"flat_headers": 12
|
||||
},
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제6장_관부설및접합공사",
|
||||
"tables": 111,
|
||||
"standalone_secs": 27,
|
||||
"stuck_secs": 6,
|
||||
"sample_stuck": [
|
||||
"6-3-1 부설",
|
||||
"6-4 P",
|
||||
"6-4-1 T"
|
||||
],
|
||||
"flat_headers": 17
|
||||
},
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제7장_항만공사",
|
||||
"tables": 73,
|
||||
"standalone_secs": 20,
|
||||
"stuck_secs": 4,
|
||||
"sample_stuck": [
|
||||
"7-1-1 수중공사",
|
||||
"7-3-1 케이슨",
|
||||
"7-4-1 배송관"
|
||||
],
|
||||
"flat_headers": 2
|
||||
},
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제8장_지반조사",
|
||||
"tables": 83,
|
||||
"standalone_secs": 15,
|
||||
"stuck_secs": 4,
|
||||
"sample_stuck": [
|
||||
"8-1-1 기계기구",
|
||||
"8-2-1 표준관입시험",
|
||||
"8-3-1 굴절법"
|
||||
],
|
||||
"flat_headers": 13
|
||||
},
|
||||
{
|
||||
"div": "2 토목부문",
|
||||
"chap": "제9장_측량",
|
||||
"tables": 1269,
|
||||
"standalone_secs": 64,
|
||||
"stuck_secs": 17,
|
||||
"sample_stuck": [
|
||||
"9-1-1 GNSS에",
|
||||
"9-2-1 기본",
|
||||
"9-3-1 지형현황"
|
||||
],
|
||||
"flat_headers": 41
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제10장_창호및유리공사",
|
||||
"tables": 31,
|
||||
"standalone_secs": 10,
|
||||
"stuck_secs": 2,
|
||||
"sample_stuck": [
|
||||
"0-2-1 도어체크",
|
||||
"0-3-1 창호유리"
|
||||
],
|
||||
"flat_headers": 10
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제11장_칠공사",
|
||||
"tables": 43,
|
||||
"standalone_secs": 19,
|
||||
"stuck_secs": 2,
|
||||
"sample_stuck": [
|
||||
"1-1-1 콘크리트",
|
||||
"1-3-1 무늬코트칠"
|
||||
],
|
||||
"flat_headers": 19
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제1장_철골공사",
|
||||
"tables": 134,
|
||||
"standalone_secs": 18,
|
||||
"stuck_secs": 2,
|
||||
"sample_stuck": [
|
||||
"1-1-1 기본철골공수",
|
||||
"1-4-1 부대철골"
|
||||
],
|
||||
"flat_headers": 16
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제2장_조적공사",
|
||||
"tables": 48,
|
||||
"standalone_secs": 9,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"2-1-1 벽돌",
|
||||
"2-2-1 블록쌓기",
|
||||
"2-3-1 ALC블록"
|
||||
],
|
||||
"flat_headers": 13
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제3장_타일공사",
|
||||
"tables": 23,
|
||||
"standalone_secs": 6,
|
||||
"stuck_secs": 2,
|
||||
"sample_stuck": [
|
||||
"3-1-1 바탕",
|
||||
"3-2-1 떠붙이기"
|
||||
],
|
||||
"flat_headers": 7
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제4장_목공사",
|
||||
"tables": 29,
|
||||
"standalone_secs": 13,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"4-1-1 먹매김",
|
||||
"4-2-1 벽체틀",
|
||||
"4-3-1 토대설치"
|
||||
],
|
||||
"flat_headers": 11
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제5장_수장공사",
|
||||
"tables": 107,
|
||||
"standalone_secs": 25,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"5-1-1 PVC계",
|
||||
"5-2-1 흡음텍스",
|
||||
"5-4-1 단열재"
|
||||
],
|
||||
"flat_headers": 28
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제6장_방수공사",
|
||||
"tables": 70,
|
||||
"standalone_secs": 23,
|
||||
"stuck_secs": 7,
|
||||
"sample_stuck": [
|
||||
"6-1-1 바탕처리",
|
||||
"6-2-1 도막바름",
|
||||
"6-3-1 가열식시트"
|
||||
],
|
||||
"flat_headers": 20
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제7장_지붕및홈통공사",
|
||||
"tables": 27,
|
||||
"standalone_secs": 11,
|
||||
"stuck_secs": 0,
|
||||
"sample_stuck": [],
|
||||
"flat_headers": 9
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제8장_금속공사",
|
||||
"tables": 50,
|
||||
"standalone_secs": 14,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"8-1-1 계단논슬립",
|
||||
"8-2-1 용접식난간",
|
||||
"8-3-1 잡철물"
|
||||
],
|
||||
"flat_headers": 15
|
||||
},
|
||||
{
|
||||
"div": "3 건축부문",
|
||||
"chap": "제9장_미장공사",
|
||||
"tables": 62,
|
||||
"standalone_secs": 14,
|
||||
"stuck_secs": 5,
|
||||
"sample_stuck": [
|
||||
"9-1-1 모르타르",
|
||||
"9-2-1 콘크리트면",
|
||||
"9-3-1 창호주위"
|
||||
],
|
||||
"flat_headers": 17
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제10장_소방설비공사",
|
||||
"tables": 37,
|
||||
"standalone_secs": 16,
|
||||
"stuck_secs": 7,
|
||||
"sample_stuck": [
|
||||
"0-3-1 지하식",
|
||||
"0-4-1 일반송수구",
|
||||
"0-5-1 압력공기탱크설치"
|
||||
],
|
||||
"flat_headers": 4
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제11장_가스설비공사",
|
||||
"tables": 26,
|
||||
"standalone_secs": 8,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"1-2-1 버트",
|
||||
"1-3-1 분기공",
|
||||
"2-1-1 계기반"
|
||||
],
|
||||
"flat_headers": 2
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제12장_자동제어설비공사",
|
||||
"tables": 33,
|
||||
"standalone_secs": 9,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"2-2-1 자동제어기기",
|
||||
"2-3-1 중앙처리장치",
|
||||
"3-1-1 플랜트"
|
||||
],
|
||||
"flat_headers": 10
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제13장_플랜트설비공사",
|
||||
"tables": 785,
|
||||
"standalone_secs": 52,
|
||||
"stuck_secs": 39,
|
||||
"sample_stuck": [
|
||||
"3-2-1 강관절단",
|
||||
"3-3-1 pipe보온",
|
||||
"3-4-1 보통"
|
||||
],
|
||||
"flat_headers": 413
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제1장_배관공사",
|
||||
"tables": 70,
|
||||
"standalone_secs": 19,
|
||||
"stuck_secs": 6,
|
||||
"sample_stuck": [
|
||||
"1-1-1 용접접합",
|
||||
"1-2-1 용접접합",
|
||||
"1-3-1 용접접합"
|
||||
],
|
||||
"flat_headers": 19
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제2장_덕트공사",
|
||||
"tables": 40,
|
||||
"standalone_secs": 12,
|
||||
"stuck_secs": 2,
|
||||
"sample_stuck": [
|
||||
"2-1-1 아연도금강판덕트",
|
||||
"2-2-1 취출구"
|
||||
],
|
||||
"flat_headers": 8
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제3장_보온공사",
|
||||
"tables": 34,
|
||||
"standalone_secs": 11,
|
||||
"stuck_secs": 4,
|
||||
"sample_stuck": [
|
||||
"3-1-1 일반마감",
|
||||
"3-2-1 일반마감",
|
||||
"3-4-1 발열선"
|
||||
],
|
||||
"flat_headers": 7
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제4장_펌프및공기설비공사",
|
||||
"tables": 24,
|
||||
"standalone_secs": 9,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"4-2-1 송풍기",
|
||||
"5-1-1 일반밸브",
|
||||
"5-2-1 스팀트랩"
|
||||
],
|
||||
"flat_headers": 5
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제5장_밸브설비공사",
|
||||
"tables": 10,
|
||||
"standalone_secs": 5,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"5-3-1 익스팬션조인트",
|
||||
"5-4-1 수격방지기",
|
||||
"6-1-1 유량계"
|
||||
],
|
||||
"flat_headers": 1
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제6장_측정기기공사",
|
||||
"tables": 20,
|
||||
"standalone_secs": 7,
|
||||
"stuck_secs": 2,
|
||||
"sample_stuck": [
|
||||
"6-2-1 세대용",
|
||||
"7-1-1 소변기"
|
||||
],
|
||||
"flat_headers": 5
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제7장_위생기구설비공사",
|
||||
"tables": 46,
|
||||
"standalone_secs": 16,
|
||||
"stuck_secs": 3,
|
||||
"sample_stuck": [
|
||||
"7-2-1 매립형",
|
||||
"7-3-1 욕실거울",
|
||||
"8-1-1 냉동기"
|
||||
],
|
||||
"flat_headers": 14
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제8장_공기조화설비공사",
|
||||
"tables": 66,
|
||||
"standalone_secs": 19,
|
||||
"stuck_secs": 5,
|
||||
"sample_stuck": [
|
||||
"8-2-1 공기가열기",
|
||||
"8-3-1 보일러",
|
||||
"8-4-1 전기온수기"
|
||||
],
|
||||
"flat_headers": 7
|
||||
},
|
||||
{
|
||||
"div": "4 기계설비부문",
|
||||
"chap": "제9장_기타공사",
|
||||
"tables": 41,
|
||||
"standalone_secs": 13,
|
||||
"stuck_secs": 6,
|
||||
"sample_stuck": [
|
||||
"9-1-1 입상관",
|
||||
"9-3-1 슬리브",
|
||||
"9-4-1 기밀시험"
|
||||
],
|
||||
"flat_headers": 17
|
||||
},
|
||||
{
|
||||
"div": "5 유지관리부문",
|
||||
"chap": "제1장_공통",
|
||||
"tables": 141,
|
||||
"standalone_secs": 26,
|
||||
"stuck_secs": 7,
|
||||
"sample_stuck": [
|
||||
"1-1-1 비탈면",
|
||||
"1-2 조경공사",
|
||||
"1-2-3 조형전정"
|
||||
],
|
||||
"flat_headers": 10
|
||||
},
|
||||
{
|
||||
"div": "5 유지관리부문",
|
||||
"chap": "제2장_토목",
|
||||
"tables": 309,
|
||||
"standalone_secs": 51,
|
||||
"stuck_secs": 16,
|
||||
"sample_stuck": [
|
||||
"2-1-1 교통통제",
|
||||
"1-6-7 포장줄눈",
|
||||
"2-1-6 아스팔트"
|
||||
],
|
||||
"flat_headers": 40
|
||||
},
|
||||
{
|
||||
"div": "5 유지관리부문",
|
||||
"chap": "제3장_건축",
|
||||
"tables": 64,
|
||||
"standalone_secs": 22,
|
||||
"stuck_secs": 5,
|
||||
"sample_stuck": [
|
||||
"3-1-1 콘크리트구조물",
|
||||
"3-2 해체공사",
|
||||
"3-3 칠공사"
|
||||
],
|
||||
"flat_headers": 15
|
||||
},
|
||||
{
|
||||
"div": "5 유지관리부문",
|
||||
"chap": "제4장_기계설비",
|
||||
"tables": 34,
|
||||
"standalone_secs": 9,
|
||||
"stuck_secs": 19,
|
||||
"sample_stuck": [
|
||||
"4-1-1 배관",
|
||||
"4-1-3 스파이럴덕트",
|
||||
"4-1-5 덕트보온"
|
||||
],
|
||||
"flat_headers": 2
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Run git diff for const_work_item_master_2026-01-01.json in commit 32c4c5cf
|
||||
cmd = ["git", "diff", "32c4c5cf~1", "32c4c5cf", "--", "resources/data_work_item_master/const_work_item_master_2026-01-01.json"]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
|
||||
|
||||
diff_lines = res.stdout.splitlines()
|
||||
print(f"Total diff lines in const master: {len(diff_lines)}")
|
||||
|
||||
# Extract lines starting with '-' (removed lines) inside "notes": [...]
|
||||
removed_notes = []
|
||||
current_section = "미식별"
|
||||
for l in diff_lines:
|
||||
if '"section":' in l:
|
||||
current_section = l.strip()
|
||||
if l.startswith('-') and not l.startswith('---'):
|
||||
cleaned = l[1:].strip()
|
||||
if cleaned.startswith('"') and cleaned.endswith('",'):
|
||||
cleaned = cleaned[1:-2]
|
||||
elif cleaned.startswith('"') and cleaned.endswith('"'):
|
||||
cleaned = cleaned[1:-1]
|
||||
if cleaned and cleaned not in ['notes": [', ']']:
|
||||
removed_notes.append((current_section, cleaned))
|
||||
|
||||
print(f"Total removed notes lines: {len(removed_notes)}")
|
||||
|
||||
# Filter lines that look like actual rules (containing %, 할증, 손료, 가산, 공제, etc.)
|
||||
rule_keywords = ['%', '가산', '할증', '공제', '손료', '손율', '감한다', '할인', '포함한다', 'Q=', '적용한다']
|
||||
removed_rules = []
|
||||
for sec, line in removed_notes:
|
||||
if any(k in line for k in rule_keywords):
|
||||
# check if it was not just a title
|
||||
if not re.match(r'^[0-9]+[\.\)]\s*[^:%]+$', line):
|
||||
removed_rules.append((sec, line))
|
||||
|
||||
print(f"Removed lines containing pricing keywords in const master: {len(removed_rules)}")
|
||||
print("\n--- Sample 20 Removed Rules in Const Master ---")
|
||||
for sec, r in removed_rules[:20]:
|
||||
print(f"{sec} -> {r}")
|
||||
@@ -0,0 +1,63 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
sys.path.insert(0, '.')
|
||||
from scratch.enrich_tables import tables
|
||||
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
master_tables = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = t
|
||||
|
||||
# We want to know: Did the trimming cut away REAL pricing coefficients/conditions?
|
||||
# Keywords that indicate actual pricing rules:
|
||||
rule_keywords = ['%', '가산', '할증', '공제', '손료', '손율', '감한다', '할인', '포함한다', '별도', '규격', '작업량']
|
||||
|
||||
cut_real_rules = []
|
||||
|
||||
for t in tables:
|
||||
tid = t['table_id']
|
||||
mt = master_tables.get(tid)
|
||||
if not mt:
|
||||
continue
|
||||
|
||||
md_notes = t.get('notes', [])
|
||||
master_notes = mt.get('notes', [])
|
||||
|
||||
# Check lines in md_notes that are NOT in master_notes
|
||||
for line in md_notes:
|
||||
line_clean = line.strip()
|
||||
if not line_clean:
|
||||
continue
|
||||
# check if line_clean is present in master_notes
|
||||
if not any(line_clean in mn for mn in master_notes):
|
||||
# Is this line a section title like "- 3. ..." or "- 라. ..."?
|
||||
# Check if it looks like a section heading
|
||||
is_heading = bool(re.match(r'^[-*•]?\s*(?:[0-9]+[\.\)]|[가-힣][\.\)]|[A-Z][\.\)])\s*[^:%~]+$', line_clean))
|
||||
is_example = '예시' in line_clean or line_clean.startswith('>')
|
||||
|
||||
# Check if it has real pricing rules
|
||||
has_rule = any(kw in line_clean for kw in rule_keywords)
|
||||
|
||||
if has_rule and not is_example and not is_heading:
|
||||
cut_real_rules.append({
|
||||
'table_id': tid,
|
||||
'chapter': t['chapter'],
|
||||
'section': t['section'],
|
||||
'line': t['start_line'],
|
||||
'lost_rule': line_clean
|
||||
})
|
||||
|
||||
print(f"Total potential REAL RULES cut away: {len(cut_real_rules)}")
|
||||
for cr in cut_real_rules:
|
||||
print(f"[{cr['table_id']} | line {cr['line']}] {cr['section']}")
|
||||
print(f" CUT: {cr['lost_rule']}")
|
||||
@@ -0,0 +1,46 @@
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load updated master
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
tables = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
tables[tid] = t
|
||||
|
||||
# Check the critical 34 lost rules from round 2
|
||||
test_targets = [
|
||||
('F0251', '고철공제', '9-8-1 철근절단 T=30cm 미만'),
|
||||
('F0252', '고철공제', '9-8-2 철근절단 T=30cm 이상'),
|
||||
('F0088', '100%를 가산', '4-3 위험목 베기 인력 가산'),
|
||||
('F0086', '30%를 할증', '4-2-1 벌채 관목 할증'),
|
||||
('F0086', '20%를 할증', '4-2-1 벌채 조재 할증'),
|
||||
('F0191', '10%', '7-11 트랙터 집재 주행/잔존목 할증'),
|
||||
('F0080', '1-4-5', '3-6 산물 임내정리 경사도 할증'),
|
||||
('F0164', '1-4-3', '6-6 가지치기 할증 연계'),
|
||||
('F0173', '1-4-5', '7-2 수라집재 할증 연계')
|
||||
]
|
||||
|
||||
print("=== 2바퀴 34건 계수 절단 복원 여부 점검 ===")
|
||||
for tid, keyword, desc in test_targets:
|
||||
t = tables.get(tid)
|
||||
if not t:
|
||||
print(f"[{tid}] {desc}: TABLE NOT FOUND")
|
||||
continue
|
||||
notes = t.get('notes', [])
|
||||
notes_str = " ".join(notes)
|
||||
if keyword in notes_str:
|
||||
print(f"[{tid}] {desc}: [복원됨/포함됨] ({len(notes)} notes)")
|
||||
else:
|
||||
print(f"[{tid}] {desc}: [여전히 잘려있음/누락] ({len(notes)} notes)")
|
||||
if notes:
|
||||
print(f" 현재 notes: {notes[:2]}")
|
||||
else:
|
||||
print(f" 현재 notes: [] (공백)")
|
||||
@@ -0,0 +1,11 @@
|
||||
import json, sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
with open('scratch/all_candidates_collapsed_ex8.json', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
embedded = [d for d in data if d.get('is_embedded_table')]
|
||||
print(f"Total embedded table rows: {len(embedded)}")
|
||||
for e in embedded:
|
||||
print(f"{e['file']} L{e['line']}: {e['cell_snippet'][:100]}")
|
||||
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Run git diff between 02544795 and HEAD for work_item_master_2026-01-01.json
|
||||
cmd = ["git", "diff", "02544795", "HEAD", "--", "resources/data_work_item_master/work_item_master_2026-01-01.json"]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
|
||||
|
||||
diff_lines = res.stdout.splitlines()
|
||||
print(f"Total diff lines in work_item_master: {len(diff_lines)}")
|
||||
|
||||
# Parse what tables changed
|
||||
# Look for pum_table_id context
|
||||
changed_tables = {}
|
||||
cur_tid = None
|
||||
cur_changes = []
|
||||
|
||||
for l in diff_lines:
|
||||
if '"pum_table_id":' in l:
|
||||
m = l.split('"pum_table_id":')[1].strip().replace('"', '').replace(',', '')
|
||||
cur_tid = m
|
||||
if cur_tid not in changed_tables:
|
||||
changed_tables[cur_tid] = []
|
||||
if (l.startswith('+') or l.startswith('-')) and not l.startswith('+++') and not l.startswith('---'):
|
||||
if cur_tid:
|
||||
changed_tables[cur_tid].append(l)
|
||||
|
||||
print(f"Number of tables with diffs: {len(changed_tables)}")
|
||||
for tid, chs in list(changed_tables.items())[:15]:
|
||||
print(f"\n[{tid}] changes ({len(chs)} lines):")
|
||||
for c in chs[:4]:
|
||||
print(f" {c}")
|
||||
@@ -0,0 +1,52 @@
|
||||
import os, sys, re, json
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
merged_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.md"
|
||||
|
||||
with open(merged_path, 'r', encoding='utf-8') as f:
|
||||
text_merged = f.read()
|
||||
|
||||
# Check how many target strings from our previous fixes exist in merged file
|
||||
from scratch.fix_common_squashed_tables import (
|
||||
target_2_8_7, repl_2_8_7,
|
||||
target_2_8_10, repl_2_8_10,
|
||||
target_2_11_5, repl_2_11_5,
|
||||
target_2_11_7, repl_2_11_7,
|
||||
target_4_4_4, repl_4_4_4,
|
||||
target_5_1_3, repl_5_1_3,
|
||||
target_5_1_8, repl_5_1_8,
|
||||
target_5_pbd1, repl_5_pbd1,
|
||||
target_5_pbd2, repl_5_pbd2,
|
||||
target_5_pbd3, repl_5_pbd3,
|
||||
target_5_base, repl_5_base,
|
||||
target_6_slip, repl_6_slip,
|
||||
target_6_socket, repl_6_socket,
|
||||
target_6_grout, repl_6_grout,
|
||||
target_6_anchor, repl_6_anchor,
|
||||
target_6_sheath, repl_6_sheath,
|
||||
target_7_4_2, repl_7_4_2
|
||||
)
|
||||
|
||||
targets_common = [
|
||||
(target_2_8_7, repl_2_8_7),
|
||||
(target_2_8_10, repl_2_8_10),
|
||||
(target_2_11_5, repl_2_11_5),
|
||||
(target_2_11_7, repl_2_11_7),
|
||||
(target_4_4_4, repl_4_4_4),
|
||||
(target_5_1_3, repl_5_1_3),
|
||||
(target_5_1_8, repl_5_1_8),
|
||||
(target_5_pbd1, repl_5_pbd1),
|
||||
(target_5_pbd2, repl_5_pbd2),
|
||||
(target_5_pbd3, repl_5_pbd3),
|
||||
(target_5_base, repl_5_base),
|
||||
(target_6_slip, repl_6_slip),
|
||||
(target_6_socket, repl_6_socket),
|
||||
(target_6_grout, repl_6_grout),
|
||||
(target_6_anchor, repl_6_anchor),
|
||||
(target_6_sheath, repl_6_sheath),
|
||||
(target_7_4_2, repl_7_4_2),
|
||||
]
|
||||
|
||||
match_count = sum(1 for t, r in targets_common if t in text_merged)
|
||||
print(f"Common targets found in merged file: {match_count} / {len(targets_common)}")
|
||||
@@ -0,0 +1,48 @@
|
||||
import sys, os, re, json
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
merged_file = os.path.join(base_dir, "2026년_건설공사_표준품셈.md")
|
||||
|
||||
with open(merged_file, 'r', encoding='utf-8') as f:
|
||||
merged_text = f.read()
|
||||
|
||||
# Check key restored tables in merged_text:
|
||||
# 1. 제8장 손료표 11곳: 0230, 3601, 5220, 6532, 6801, 7101, 7120, 7830, 7995, 9020, 9060
|
||||
loss_codes = ['0230', '3601', '5220', '6532', '6801', '7101', '7120', '7830', '7995', '9020', '9060']
|
||||
|
||||
print("=== Checking 11 손료표 in 합본 ===")
|
||||
for code in loss_codes:
|
||||
# search for e.g. "({code})" followed by table
|
||||
pattern = re.compile(rf'\({code}\)[^\n]*\n+(\|[^\n]+\|\n)+', re.DOTALL)
|
||||
m = pattern.search(merged_text)
|
||||
if m:
|
||||
# count rows in table
|
||||
rows = [l for l in m.group(0).split('\n') if l.strip().startswith('|') and not re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', l.strip())]
|
||||
print(f" - ({code}): Restored as table with {len(rows)} rows in 합본")
|
||||
else:
|
||||
# check if it exists as text
|
||||
m2 = re.search(rf'\({code}\)', merged_text)
|
||||
if m2:
|
||||
print(f" ❌ ({code}): Exists as TEXT only (NOT TABLE) in 합본!")
|
||||
else:
|
||||
print(f" ❌ ({code}): Missing in 합본!")
|
||||
|
||||
# 2. Check 1-2-2 in merged_text
|
||||
print("\n=== Checking 1-2-2 in 합본 ===")
|
||||
if "돌 쌓 기 및 돌 붙 임" in merged_text:
|
||||
# check if it is collapsed or separate rows
|
||||
p_122 = re.compile(r'\|\s*돌\s*쌓\s*기\s*및\s*돌\s*붙\s*임\s*사\s*석')
|
||||
if p_122.search(merged_text):
|
||||
print(" ❌ 1-2-2 is COLLAPSED in 합본!")
|
||||
else:
|
||||
print(" ✅ 1-2-2 is properly expanded in 합본")
|
||||
else:
|
||||
print(" ❓ 1-2-2 not matched")
|
||||
|
||||
# 3. Check 6-1-1 in merged_text
|
||||
print("\n=== Checking 6-1-1 in 합본 ===")
|
||||
if "6-1-1 레디믹스트콘크리트 타설" in merged_text:
|
||||
print(" ✅ 6-1-1 exists in 합본")
|
||||
else:
|
||||
print(" ❌ 6-1-1 is MISSING in 합본!")
|
||||
@@ -0,0 +1,7 @@
|
||||
import sys
|
||||
from find_unclassified_grouped_tables import grouped_tables
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
for t in grouped_tables:
|
||||
if t['file'] == '제1장_도로포장공사.md':
|
||||
print(f"L{t['line']}: {t['title']} | {t['groups']}")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
import os, sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# 제5장 강구조공사
|
||||
ch5_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md"
|
||||
with open(ch5_path, 'r', encoding='utf-8') as f:
|
||||
text5 = f.read()
|
||||
|
||||
target_5_1 = "| 비 고 | - 인력에 의한 부설 및 접합을 수행하는 경우 다음 품을 적용한다. 구 분 단 위 수 량 관경(㎜) 시공량(본) 배관공(수도) 인 3 80 15 보 통 인 부 인 1 100 13 배관공(수도) 인 4 120 13 보 통 인 부 인 1 150 10 | | | |"
|
||||
repl_5_1 = "| 비 고 | - 인력에 의한 부설 및 접합을 수행하는 경우 다음 품을 적용한다. (아래 표 참조) | | | |"
|
||||
|
||||
target_5_2 = "| 비 고 | - 인력에 의한 부설 및 접합을 수행하는 경우 다음 품을 적용한다. 구 분 단 위 수 량 관경(㎜) 시공량(본) 배관공(수도) 인 3 80 13 보 통 인 부 인 1 100 12 배관공(수도) 인 4 120 13 보 통 인 부 인 1 150 9 | | | |"
|
||||
repl_5_2 = "| 비 고 | - 인력에 의한 부설 및 접합을 수행하는 경우 다음 품을 적용한다. (아래 표 참조) | | | |"
|
||||
|
||||
text5 = text5.replace(target_5_1, repl_5_1)
|
||||
text5 = text5.replace(target_5_2, repl_5_2)
|
||||
with open(ch5_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text5)
|
||||
print("Updated 제5장_강구조공사.md successfully.")
|
||||
|
||||
# 제6장 관부설및접합공사
|
||||
ch6_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md"
|
||||
with open(ch6_path, 'r', encoding='utf-8') as f:
|
||||
text6 = f.read()
|
||||
|
||||
target_6 = "| 비 고 | - 인력에 의한 부설을 수행하는 경우 다음 품을 적용한다. 구 분 단 위 수 량 관경(㎜) 시공량(본) 배관공(수도) 인 2 80 8 보 통 인 부 인 1 100 6 배관공(수도) 인 3 125 7 보 통 인 부 인 1 150 6 배관공(수도) 인 4 200 5 보 통 인 부 인 1 250 4 | | | | |"
|
||||
repl_6 = "| 비 고 | - 인력에 의한 부설을 수행하는 경우 다음 품을 적용한다. (아래 표 참조) | | | | |"
|
||||
|
||||
text6 = text6.replace(target_6, repl_6)
|
||||
with open(ch6_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text6)
|
||||
print("Updated 제6장_관부설및접합공사.md successfully.")
|
||||
@@ -0,0 +1,150 @@
|
||||
import glob, re, os, sys, fitz
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
pdf_path = 'resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.pdf'
|
||||
doc = fitz.open(pdf_path)
|
||||
|
||||
target_files = []
|
||||
target_files.extend(glob.glob('resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/*.md'))
|
||||
target_files.extend(glob.glob('resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/*.md'))
|
||||
target_files.append('resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md')
|
||||
|
||||
print(f"Target files: {len(target_files)}")
|
||||
|
||||
# Build PDF index of section headings
|
||||
print("Indexing PDF pages for section headings...")
|
||||
sec_to_pdf_page = {}
|
||||
for p_num in range(len(doc)):
|
||||
text = doc[p_num].get_text()
|
||||
# Find all patterns like X-Y-Z or X-Y
|
||||
matches = re.findall(r'(\d+-\d+(?:-\d+)?)', text)
|
||||
for m in matches:
|
||||
sec_to_pdf_page.setdefault(m, []).append(p_num)
|
||||
|
||||
print(f"Indexed {len(sec_to_pdf_page)} section numbers in PDF.")
|
||||
|
||||
# Now parse each markdown file's tables that have a Remarks column
|
||||
discrepancies = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
current_sec_num = 'UNKNOWN'
|
||||
current_sec_title = 'UNKNOWN'
|
||||
in_table = False
|
||||
headers = []
|
||||
table_rows = []
|
||||
start_line = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_s = line.strip()
|
||||
sec_m = re.search(r'(\d+-\d+(?:-\d+)?)(\s+[^\n(]+)?', line_s)
|
||||
if sec_m and not line_s.startswith('|'):
|
||||
current_sec_num = sec_m.group(1).strip()
|
||||
current_sec_title = line_s
|
||||
|
||||
if line_s.startswith('|') and not line_s.startswith('|---'):
|
||||
cols = [c.strip() for c in line_s.split('|')[1:-1]]
|
||||
if not in_table:
|
||||
# check if header has 비고 or 비 고
|
||||
if any('비고' in c or '비 고' in c for c in cols):
|
||||
headers = cols
|
||||
in_table = True
|
||||
start_line = idx + 1
|
||||
table_rows = []
|
||||
continue
|
||||
if in_table:
|
||||
table_rows.append((idx + 1, cols))
|
||||
else:
|
||||
if in_table and (line_s == '' or not line_s.startswith('|')):
|
||||
# Check this table against PDF!
|
||||
remarks_col_idx = -1
|
||||
for ci, h in enumerate(headers):
|
||||
if '비고' in h or '비 고' in h:
|
||||
remarks_col_idx = ci
|
||||
break
|
||||
|
||||
if remarks_col_idx != -1 and len(table_rows) > 0:
|
||||
md_remarks = [r[1][remarks_col_idx] if remarks_col_idx < len(r[1]) else '' for r in table_rows]
|
||||
|
||||
# Find matching page in PDF
|
||||
cand_pages = sec_to_pdf_page.get(current_sec_num, [])
|
||||
# filter to body pages (> 45)
|
||||
body_pages = [p for p in cand_pages if p > 45]
|
||||
|
||||
found_pdf_table = False
|
||||
for p in body_pages:
|
||||
page = doc[p]
|
||||
tabs = page.find_tables()
|
||||
for tab in tabs:
|
||||
df = tab.extract()
|
||||
if not df or len(df) < 2:
|
||||
continue
|
||||
pdf_header = [c if c else '' for c in df[0]]
|
||||
# Check if pdf_header has 비고
|
||||
pdf_rem_idx = -1
|
||||
for pci, ph in enumerate(pdf_header):
|
||||
if '비고' in ph or '비 고' in ph:
|
||||
pdf_rem_idx = pci
|
||||
break
|
||||
if pdf_rem_idx != -1:
|
||||
found_pdf_table = True
|
||||
# Extract pdf remarks
|
||||
pdf_remarks_raw = [r[pdf_rem_idx] if pdf_rem_idx < len(r) and r[pdf_rem_idx] else '' for r in df[1:]]
|
||||
# Flatten any multi-line in pdf remarks
|
||||
pdf_remarks_split = []
|
||||
for pr in pdf_remarks_raw:
|
||||
pdf_remarks_split.extend([sub.strip() for sub in pr.split('\n') if sub.strip()])
|
||||
|
||||
# Compare count of non-empty remarks or ditto presence
|
||||
md_non_empty = [m for m in md_remarks if m]
|
||||
pdf_non_empty = [p for p in pdf_remarks_split if p]
|
||||
|
||||
# Check if ditto marks or numbers mismatch
|
||||
has_ditto = any('〃' in m or '"' in m for m in md_remarks) or any('〃' in p for p in pdf_non_empty)
|
||||
|
||||
# If row count differs significantly or ditto count differs
|
||||
md_ditto_cnt = sum(1 for m in md_remarks if '〃' in m or '"' in m)
|
||||
pdf_ditto_cnt = sum(1 for p in pdf_non_empty if '〃' in p or '"' in p)
|
||||
|
||||
if has_ditto and md_ditto_cnt != pdf_ditto_cnt:
|
||||
discrepancies.append({
|
||||
'file': fname,
|
||||
'sec': current_sec_num,
|
||||
'line': start_line,
|
||||
'type': 'Ditto count mismatch',
|
||||
'md_dittos': md_ditto_cnt,
|
||||
'pdf_dittos': pdf_ditto_cnt,
|
||||
'md_remarks': md_remarks,
|
||||
'pdf_remarks': pdf_remarks_raw,
|
||||
'page': p + 1
|
||||
})
|
||||
elif len(md_remarks) != len(df) - 1:
|
||||
# Row count mismatch in table with remarks
|
||||
discrepancies.append({
|
||||
'file': fname,
|
||||
'sec': current_sec_num,
|
||||
'line': start_line,
|
||||
'type': 'Row count mismatch in remarks table',
|
||||
'md_rows': len(md_remarks),
|
||||
'pdf_rows': len(df) - 1,
|
||||
'md_remarks': md_remarks,
|
||||
'pdf_remarks': pdf_remarks_raw,
|
||||
'page': p + 1
|
||||
})
|
||||
in_table = False
|
||||
headers = []
|
||||
table_rows = []
|
||||
|
||||
print(f"\nTotal Discrepancies found: {len(discrepancies)}")
|
||||
for d in discrepancies:
|
||||
print(f"\n[{d['file']}] {d['sec']} (L{d['line']}, PDF page {d.get('page')}) -> {d['type']}")
|
||||
if 'md_dittos' in d:
|
||||
print(f" MD dittos: {d['md_dittos']} vs PDF dittos: {d['pdf_dittos']}")
|
||||
if 'md_rows' in d:
|
||||
print(f" MD rows: {d['md_rows']} vs PDF rows: {d['pdf_rows']}")
|
||||
print(f" MD remarks: {d['md_remarks'][:6]}")
|
||||
print(f" PDF remarks: {d['pdf_remarks'][:6]}")
|
||||
@@ -0,0 +1,134 @@
|
||||
import sys, os, re, json
|
||||
import fitz
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
merged_file = os.path.join(base_dir, "2026년_건설공사_표준품셈.md")
|
||||
|
||||
with open(merged_file, 'r', encoding='utf-8') as f:
|
||||
merged_text = f.read()
|
||||
|
||||
all_split_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
all_split_files.append(os.path.join(root, f))
|
||||
|
||||
print(f"Total split files: {len(all_split_files)}")
|
||||
|
||||
# Function to extract all markdown tables from text
|
||||
def extract_tables(text, file_label):
|
||||
lines = text.split('\n')
|
||||
tables = []
|
||||
curr_table = []
|
||||
start_line = 0
|
||||
preceding_header = ""
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_num = idx + 1
|
||||
l_strip = line.strip()
|
||||
|
||||
# Track last seen section header
|
||||
m_head = re.match(r'^(?:#+\s*)?(\d+-\d+(?:-\d+)?\s+[^\n]+|\(\d{4}\)\s+[^\n]+)', l_strip)
|
||||
if m_head:
|
||||
preceding_header = m_head.group(1).strip()
|
||||
|
||||
if l_strip.startswith('|') and l_strip.endswith('|'):
|
||||
if not curr_table:
|
||||
start_line = line_num
|
||||
curr_table.append((line_num, l_strip))
|
||||
else:
|
||||
if curr_table:
|
||||
# Check if it has a divider row
|
||||
has_divider = False
|
||||
for _, r in curr_table:
|
||||
if re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', r):
|
||||
has_divider = True
|
||||
break
|
||||
if has_divider:
|
||||
tables.append({
|
||||
"file": file_label,
|
||||
"start_line": start_line,
|
||||
"end_line": start_line + len(curr_table) - 1,
|
||||
"header_hint": preceding_header,
|
||||
"rows": curr_table,
|
||||
"row_count": len(curr_table),
|
||||
"col_count": len(curr_table[0][1].split('|')) - 2,
|
||||
"raw_header": curr_table[0][1]
|
||||
})
|
||||
curr_table = []
|
||||
|
||||
if curr_table:
|
||||
has_divider = any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', r) for _, r in curr_table)
|
||||
if has_divider:
|
||||
tables.append({
|
||||
"file": file_label,
|
||||
"start_line": start_line,
|
||||
"end_line": start_line + len(curr_table) - 1,
|
||||
"header_hint": preceding_header,
|
||||
"rows": curr_table,
|
||||
"row_count": len(curr_table),
|
||||
"col_count": len(curr_table[0][1].split('|')) - 2,
|
||||
"raw_header": curr_table[0][1]
|
||||
})
|
||||
return tables
|
||||
|
||||
merged_tables = extract_tables(merged_text, "합본")
|
||||
print(f"Total tables in 합본: {len(merged_tables)}")
|
||||
|
||||
split_tables = []
|
||||
for fpath in sorted(all_split_files):
|
||||
rel = os.path.relpath(fpath, base_dir)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
txt = f.read()
|
||||
t_list = extract_tables(txt, rel)
|
||||
split_tables.extend(t_list)
|
||||
|
||||
print(f"Total tables across 45 분할본: {len(split_tables)}")
|
||||
|
||||
# Now compare:
|
||||
# 1. Tables present in split but missing in merged
|
||||
# We match tables by header_hint or raw_header + first data row
|
||||
def get_table_signature(t):
|
||||
# take header hint, col_count, and first 30 chars of row 0
|
||||
h_hint = t['header_hint'].split('(')[0].strip()
|
||||
r0 = t['rows'][0][1][:40]
|
||||
return f"{h_hint} || {t['col_count']} cols || {r0}"
|
||||
|
||||
merged_signatures = set()
|
||||
for mt in merged_tables:
|
||||
merged_signatures.add(get_table_signature(mt))
|
||||
|
||||
split_signatures = set()
|
||||
for st in split_tables:
|
||||
split_signatures.add(get_table_signature(st))
|
||||
|
||||
only_in_split = []
|
||||
for st in split_tables:
|
||||
sig = get_table_signature(st)
|
||||
if sig not in merged_signatures:
|
||||
only_in_split.append(st)
|
||||
|
||||
only_in_merged = []
|
||||
for mt in merged_tables:
|
||||
sig = get_table_signature(mt)
|
||||
if sig not in split_signatures:
|
||||
only_in_merged.append(mt)
|
||||
|
||||
print(f"\nTables only in 분할본 (potential missing/collapsed in 합본): {len(only_in_split)}")
|
||||
for t in only_in_split[:15]:
|
||||
print(f" - {t['file']} (L{t['start_line']}): hint='{t['header_hint']}', cols={t['col_count']}, rows={t['row_count']}")
|
||||
|
||||
print(f"\nTables only in 합본 (potential missing/collapsed in 분할본): {len(only_in_merged)}")
|
||||
for t in only_in_merged[:15]:
|
||||
print(f" - {t['file']} (L{t['start_line']}): hint='{t['header_hint']}', cols={t['col_count']}, rows={t['row_count']}")
|
||||
|
||||
with open('scratch/tables_diff.json', 'w', encoding='utf-8') as f:
|
||||
json.dump({
|
||||
"merged_table_count": len(merged_tables),
|
||||
"split_table_count": len(split_tables),
|
||||
"only_in_split_count": len(only_in_split),
|
||||
"only_in_split": [{k: v for k, v in t.items() if k != 'rows'} for t in only_in_split],
|
||||
"only_in_merged_count": len(only_in_merged),
|
||||
"only_in_merged": [{k: v for k, v in t.items() if k != 'rows'} for t in only_in_merged]
|
||||
}, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,59 @@
|
||||
import sys, os, re
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
merged_file = os.path.join(base_dir, "2026년_건설공사_표준품셈.md")
|
||||
|
||||
with open(merged_file, 'r', encoding='utf-8') as f:
|
||||
merged_text = f.read()
|
||||
|
||||
def count_tables_in_text(text):
|
||||
lines = text.split('\n')
|
||||
tables = 0
|
||||
curr = []
|
||||
for l in lines:
|
||||
ls = l.strip()
|
||||
if ls.startswith('|') and ls.endswith('|'):
|
||||
curr.append(ls)
|
||||
else:
|
||||
if curr:
|
||||
if any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', r) for r in curr):
|
||||
tables += 1
|
||||
curr = []
|
||||
if curr and any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', r) for r in curr):
|
||||
tables += 1
|
||||
return tables
|
||||
|
||||
# Split merged_text by divisions:
|
||||
# 공통부문, 토목부문, 건축부문, 기계설비부문, 유지관리부문
|
||||
div_names = ["공통부문", "토목부문", "건축부문", "기계설비부문", "유지관리부문"]
|
||||
# find division boundaries in merged
|
||||
div_spans = []
|
||||
for dn in div_names:
|
||||
# search for '# ' or main header of division
|
||||
m = re.search(rf'(?:^|\n)#+\s*{dn}', merged_text)
|
||||
if not m:
|
||||
m = re.search(rf'(?:^|\n){dn}', merged_text)
|
||||
pos = m.start() if m else -1
|
||||
div_spans.append((dn, pos))
|
||||
|
||||
print(f"Division spans in merged: {div_spans}")
|
||||
|
||||
# Count per division folder in split
|
||||
all_split_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
all_split_files.append(os.path.join(root, f))
|
||||
|
||||
split_by_div = {}
|
||||
for p in all_split_files:
|
||||
rel = os.path.relpath(p, base_dir)
|
||||
div = rel.split(os.sep)[0]
|
||||
with open(p, 'r', encoding='utf-8') as f:
|
||||
txt = f.read()
|
||||
c = count_tables_in_text(txt)
|
||||
split_by_div[div] = split_by_div.get(div, 0) + c
|
||||
|
||||
for d, cnt in sorted(split_by_div.items()):
|
||||
print(f"Split {d}: {cnt} tables")
|
||||
@@ -0,0 +1,57 @@
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load master
|
||||
master_path = Path('resources/data_work_item_master/work_item_master_2026-01-01.json')
|
||||
with open(master_path, 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
# Load pum_forest
|
||||
pum_path = Path('resources/data_cost_input_value/pum_forest_2026.json')
|
||||
with open(pum_path, 'r', encoding='utf-8') as f:
|
||||
pum_data = json.load(f)
|
||||
|
||||
pum_tables = pum_data.get('variables', {}).get('pum', {}).get('tables', [])
|
||||
|
||||
print(f"pum_tables count: {len(pum_tables)}")
|
||||
print(f"master work_items: {len(master['work_items'])}")
|
||||
master_tables = []
|
||||
for wi in master['work_items']:
|
||||
master_tables.extend(wi.get('tables', []))
|
||||
orphan_tables = master.get('orphan_tables', [])
|
||||
print(f"master tables in work_items: {len(master_tables)}")
|
||||
print(f"master orphan_tables: {len(orphan_tables)}")
|
||||
total_master_tables = master_tables + orphan_tables
|
||||
print(f"total master tables: {len(total_master_tables)}")
|
||||
|
||||
# Compare pum_tables and master_tables by pum_table_id
|
||||
pum_ids = {t['pum_table_id'] for t in pum_tables if 'pum_table_id' in t}
|
||||
master_ids = {t['pum_table_id'] for t in total_master_tables if 'pum_table_id' in t}
|
||||
|
||||
print(f"Unique table IDs in pum_forest: {len(pum_ids)}")
|
||||
print(f"Unique table IDs in master: {len(master_ids)}")
|
||||
diff_pum_master = pum_ids - master_ids
|
||||
diff_master_pum = master_ids - pum_ids
|
||||
print(f"IDs in pum_forest but NOT in master: {diff_pum_master}")
|
||||
print(f"IDs in master but NOT in pum_forest: {diff_master_pum}")
|
||||
|
||||
# Check sample pum_table
|
||||
t0 = pum_tables[0]
|
||||
print("\nSample pum_table keys:", list(t0.keys()))
|
||||
print("Sample pum_table metadata:")
|
||||
for k in ['pum_table_id', 'section', 'source_line', 'pum_form', 'form_basis', 'basis_quantity', 'basis_unit', 'basis_source']:
|
||||
print(f" {k}: {t0.get(k)}")
|
||||
|
||||
# Check raw_markdown presence
|
||||
has_raw = sum(1 for t in pum_tables if 'raw_markdown' in t and t['raw_markdown'])
|
||||
print(f"\nTables with raw_markdown in pum_forest: {has_raw}/{len(pum_tables)}")
|
||||
|
||||
# Check pum_form distribution
|
||||
from collections import Counter
|
||||
form_counts = Counter(t.get('pum_form') for t in pum_tables)
|
||||
print("\npum_form distribution in pum_forest:")
|
||||
for form, cnt in form_counts.most_common():
|
||||
print(f" {form}: {cnt}")
|
||||
@@ -0,0 +1,47 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
merged_path = 'resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.md'
|
||||
with open(merged_path, 'r', encoding='utf-8') as f:
|
||||
text_merged = f.read()
|
||||
|
||||
target_files = [
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md'),
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md'),
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md'),
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md'),
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md'),
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md'),
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md'),
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md'),
|
||||
('02_토목부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md'),
|
||||
('05_유지관리부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md'),
|
||||
('05_유지관리부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md'),
|
||||
('05_유지관리부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md'),
|
||||
('05_유지관리부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md'),
|
||||
('04_기계설비부문', 'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md'),
|
||||
]
|
||||
|
||||
print("=== Checking Substantive Lines in Split Files vs Merged File ===")
|
||||
|
||||
for part, fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
split_lines = [l.rstrip() for l in f.readlines()]
|
||||
|
||||
missing_in_merged = []
|
||||
checked_count = 0
|
||||
for idx, l in enumerate(split_lines):
|
||||
l_str = l.strip()
|
||||
if not l_str or l_str.startswith('#') or l_str.startswith('> 원문:'):
|
||||
continue
|
||||
# Table rows or section headers
|
||||
checked_count += 1
|
||||
if l_str not in text_merged:
|
||||
missing_in_merged.append((idx+1, l_str))
|
||||
|
||||
print(f"[{fname}] Checked lines: {checked_count}, Missing in merged: {len(missing_in_merged)}")
|
||||
if missing_in_merged:
|
||||
for l_no, l_str in missing_in_merged[:5]:
|
||||
print(f" L{l_no}: {l_str[:80]}")
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
import json, re
|
||||
|
||||
with open('scratch/audit_42_detailed_breakdown.json', 'r', encoding='utf-8') as f:
|
||||
audit_42 = json.load(f)
|
||||
|
||||
# count attached headers and collapsed tables in the 42 linked sections
|
||||
attached_count = 0
|
||||
collapsed_count = 0
|
||||
sections_with_attached = []
|
||||
sections_with_collapsed = []
|
||||
|
||||
for item in audit_42:
|
||||
sec = item['section']
|
||||
name = item['name']
|
||||
header_sep = item['header_separation']
|
||||
flaws = item['flaws_detected']
|
||||
|
||||
is_attached = "경계 붙음" in header_sep
|
||||
if is_attached:
|
||||
attached_count += 1
|
||||
sections_with_attached.append(f"{sec} {name}")
|
||||
|
||||
has_collapsed = any("표 한 줄 뭉침" in f for f in flaws)
|
||||
if has_collapsed:
|
||||
collapsed_count += 1
|
||||
sections_with_collapsed.append(f"{sec} {name}")
|
||||
|
||||
print(f"=== Linked 42 sections (non-machine 24 target nodes) ===")
|
||||
print(f"Attached headers in our target: {attached_count} sections")
|
||||
print(f"Collapsed tables in our target neighborhood: {collapsed_count} sections")
|
||||
print("\nAttached sections list:")
|
||||
for s in sections_with_attached:
|
||||
print(f" - {s}")
|
||||
print("\nCollapsed sections list:")
|
||||
for s in sections_with_collapsed:
|
||||
print(f" - {s}")
|
||||
@@ -0,0 +1,47 @@
|
||||
import sys, os, re, json
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
merged_file = os.path.join(base_dir, "2026년_건설공사_표준품셈.md")
|
||||
|
||||
with open(merged_file, 'r', encoding='utf-8') as f:
|
||||
merged_text = f.read()
|
||||
|
||||
# Helper to count tables in text
|
||||
def count_tables_in_text(text):
|
||||
lines = text.split('\n')
|
||||
tables = 0
|
||||
curr = []
|
||||
for l in lines:
|
||||
ls = l.strip()
|
||||
if ls.startswith('|') and ls.endswith('|'):
|
||||
curr.append(ls)
|
||||
else:
|
||||
if curr:
|
||||
if any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', r) for r in curr):
|
||||
tables += 1
|
||||
curr = []
|
||||
if curr and any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', r) for r in curr):
|
||||
tables += 1
|
||||
return tables
|
||||
|
||||
# Count tables in each split file
|
||||
all_split_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
all_split_files.append(os.path.join(root, f))
|
||||
|
||||
total_split_tables = 0
|
||||
split_counts = {}
|
||||
for p in sorted(all_split_files):
|
||||
rel = os.path.relpath(p, base_dir)
|
||||
with open(p, 'r', encoding='utf-8') as f:
|
||||
t = f.read()
|
||||
c = count_tables_in_text(t)
|
||||
split_counts[rel] = c
|
||||
total_split_tables += c
|
||||
|
||||
print(f"Total split tables: {total_split_tables}")
|
||||
merged_total = count_tables_in_text(merged_text)
|
||||
print(f"Total merged tables: {merged_total}")
|
||||
@@ -0,0 +1,90 @@
|
||||
import glob, re, os, sys, fitz
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
pdf_path = 'resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.pdf'
|
||||
doc = fitz.open(pdf_path)
|
||||
|
||||
target_files = []
|
||||
target_files.extend(glob.glob('resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/*.md'))
|
||||
target_files.extend(glob.glob('resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/*.md'))
|
||||
target_files.append('resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md')
|
||||
|
||||
suspicious_cases = []
|
||||
|
||||
# Scan markdown tables for suspicious remarks or 조건 cells
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
current_sec = 'UNKNOWN'
|
||||
in_table = False
|
||||
headers = []
|
||||
table_rows = []
|
||||
header_idx = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_s = line.strip()
|
||||
sec_m = re.search(r'(\d+-\d+(?:-\d+)?(?:\s+[^\n(]+)?)', line_s)
|
||||
if sec_m and not line_s.startswith('|'):
|
||||
current_sec = sec_m.group(1).strip()
|
||||
|
||||
if line_s.startswith('|') and not line_s.startswith('|---'):
|
||||
cols = [c.strip() for c in line_s.split('|')[1:-1]]
|
||||
if not in_table:
|
||||
# Header row
|
||||
if any('비고' in c or '비 고' in c or '구분' in c or '직종' in c or '규격' in c for c in cols):
|
||||
headers = cols
|
||||
in_table = True
|
||||
header_idx = idx + 1
|
||||
table_rows = []
|
||||
continue
|
||||
if in_table:
|
||||
table_rows.append((idx + 1, cols))
|
||||
else:
|
||||
if in_table and (line_s == '' or not line_s.startswith('|')):
|
||||
# Check table_rows for suspicious patterns
|
||||
# Find remarks col index if any
|
||||
remarks_idx = -1
|
||||
for ci, h in enumerate(headers):
|
||||
if '비고' in h or '비 고' in h:
|
||||
remarks_idx = ci
|
||||
break
|
||||
|
||||
for ri, (ln, rcols) in enumerate(table_rows):
|
||||
# Check remarks cell
|
||||
if remarks_idx != -1 and remarks_idx < len(rcols):
|
||||
rval = rcols[remarks_idx]
|
||||
# Patterns: starts with %, contains lonely number/percentage, or short fragment
|
||||
if re.match(r'^[\d\.]+\s*[%%]', rval) or rval in ['45%', '50%', '30%'] or re.match(r'^[0-9]+[가-힣a-zA-Z]*$', rval) and len(rval) <= 4:
|
||||
suspicious_cases.append({
|
||||
'type': 'Suspicious Remarks Value',
|
||||
'file': fname,
|
||||
'sec': current_sec,
|
||||
'line': ln,
|
||||
'row': rcols,
|
||||
'remarks': rval,
|
||||
'reason': f'Remarks starts with percentage/number: {rval}'
|
||||
})
|
||||
# Check if rcols has ditto mark following an empty cell or strange transition
|
||||
for ci, cval in enumerate(rcols):
|
||||
if cval in ['〃', '"', '″'] and ri == 0:
|
||||
suspicious_cases.append({
|
||||
'type': 'Ditto on First Row',
|
||||
'file': fname,
|
||||
'sec': current_sec,
|
||||
'line': ln,
|
||||
'row': rcols,
|
||||
'col_idx': ci,
|
||||
'reason': f'Ditto mark on first row of table: {cval}'
|
||||
})
|
||||
|
||||
in_table = False
|
||||
headers = []
|
||||
table_rows = []
|
||||
|
||||
print(f"Total suspicious cases found in MD: {len(suspicious_cases)}")
|
||||
for sc in suspicious_cases:
|
||||
print(f"[{sc['file']}] {sc['sec']} (L{sc['line']}): {sc['reason']}")
|
||||
print(f" Row: {sc['row']}")
|
||||
@@ -0,0 +1,39 @@
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Load 1st audit results
|
||||
with open('resources/knowledge/technical_info/01_임도/05_원가정보/품셈_원문md_마스터데이터_대조_검증보고서.json', 'r', encoding='utf-8') as f:
|
||||
audit_v1 = json.load(f)
|
||||
|
||||
refs_27 = audit_v1['item5_references']
|
||||
|
||||
# Load enriched tables to get original MD lines
|
||||
sys.path.insert(0, '.')
|
||||
from scratch.enrich_tables import tables
|
||||
|
||||
tables_by_id = {t['table_id']: t for t in tables}
|
||||
|
||||
print(f"Analyzing all {len(refs_27)} suspect reference tables from original MD:\n")
|
||||
|
||||
for r in refs_27:
|
||||
tid = r['table_id']
|
||||
t = tables_by_id.get(tid)
|
||||
if not t:
|
||||
continue
|
||||
|
||||
print(f"==================== [{tid} | Line {t['start_line']}] {t['chapter']} | {t['section']} ====================")
|
||||
# Print pre-context (last 5 lines before table)
|
||||
print("--- Pre-context ---")
|
||||
for pl in t['pre_context'][-5:]:
|
||||
print(f" {pl}")
|
||||
print("--- Table Header & First 2 Rows ---")
|
||||
for tl in t['lines'][:4]:
|
||||
print(f" {tl.strip()}")
|
||||
if t['notes']:
|
||||
print("--- Notes ---")
|
||||
for n in t['notes'][:3]:
|
||||
print(f" {n}")
|
||||
print()
|
||||
@@ -0,0 +1,142 @@
|
||||
import os, sys, re, json, glob, fitz
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = 'resources/knowledge/original/원가계산/건설공사_표준품셈'
|
||||
divisions = ['02_토목부문', '03_건축부문', '04_기계설비부문', '05_유지관리부문']
|
||||
|
||||
flaws = []
|
||||
|
||||
# Known Col 0 fragments that indicate broken vertical merge
|
||||
fragment_patterns = [
|
||||
r'^(구\s*간|설\s*치|준\s*비|조\s*립|해\s*체|교\s*환|철\s*거|운\s*반|도\s*장|가\s*공|시\s*공)$',
|
||||
r'^(A-Type|B-Type|C-Type)$',
|
||||
r'^(3시간\s*차단|4시간\s*차단)$',
|
||||
r'^(수\s*상|수\s*중|육\s*상)$',
|
||||
r'^(충\s*적|난\s*적)$',
|
||||
r'^(50㎏|60㎏)$',
|
||||
r'^(직\s*종|공\s*종)$'
|
||||
]
|
||||
|
||||
for div in divisions:
|
||||
div_path = os.path.join(base_dir, div)
|
||||
if not os.path.exists(div_path): continue
|
||||
for fpath in sorted(glob.glob(os.path.join(div_path, '*.md'))):
|
||||
fname = os.path.basename(fpath)
|
||||
if fname.startswith('_') or '개정사항' in fname: continue
|
||||
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
current_sec = 'UNKNOWN'
|
||||
in_table = False
|
||||
headers = []
|
||||
table_rows = []
|
||||
start_line = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_s = line.strip()
|
||||
sec_m = re.search(r'(\d+-\d+(?:-\d+)?(?:\s+[^\n(]+)?)', line_s)
|
||||
if sec_m and not line_s.startswith('|'):
|
||||
current_sec = sec_m.group(1).strip()
|
||||
|
||||
# Check for inline squashed tables outside table
|
||||
if not line_s.startswith('|') and len(line_s) > 30:
|
||||
if (('직 종' in line_s or '직종' in line_s) and ('단위' in line_s or '단 위' in line_s) and ('수량' in line_s or '수 량' in line_s)) or \
|
||||
(('작업효율' in line_s or '효율(E)' in line_s) and any(d in line_s for d in ['0.', '1.'])):
|
||||
# Ignore known text lines
|
||||
if not any(k in line_s for k in ['참조', '따른다', '적용한다', '기준이다']) or '0.' in line_s:
|
||||
flaws.append({
|
||||
'div': div,
|
||||
'file': fname,
|
||||
'sec': current_sec,
|
||||
'line': idx + 1,
|
||||
'type': 'Inline Squashed Table in Text',
|
||||
'detail': line_s[:100]
|
||||
})
|
||||
|
||||
if line_s.startswith('|') and not line_s.startswith('|---'):
|
||||
cols = [c.strip() for c in line_s.split('|')[1:-1]]
|
||||
if not in_table:
|
||||
headers = cols
|
||||
in_table = True
|
||||
start_line = idx + 1
|
||||
table_rows = []
|
||||
continue
|
||||
if in_table:
|
||||
table_rows.append((idx + 1, cols))
|
||||
else:
|
||||
if in_table and (line_s == '' or not line_s.startswith('|')):
|
||||
# Check table for flaws
|
||||
col0_list = [r[1][0] if len(r[1]) > 0 else '' for r in table_rows]
|
||||
|
||||
# 1. Check Col 0 fragments
|
||||
for ri, (ln, rcols) in enumerate(table_rows):
|
||||
if len(rcols) == 0: continue
|
||||
c0 = rcols[0]
|
||||
for pat in fragment_patterns:
|
||||
if re.match(pat, c0):
|
||||
# Check if previous row had the first part of this word
|
||||
flaws.append({
|
||||
'div': div,
|
||||
'file': fname,
|
||||
'sec': current_sec,
|
||||
'line': ln,
|
||||
'type': 'Col 0 Fragment (Broken Vertical Merge)',
|
||||
'detail': f"Row has fragment '{c0}', prev row was '{col0_list[ri-1] if ri > 0 else 'None'}'"
|
||||
})
|
||||
|
||||
# 2. Check for remarks shifting / lonely percentage
|
||||
rem_idx = -1
|
||||
for ci, h in enumerate(headers):
|
||||
if '비고' in h or '비 고' in h:
|
||||
rem_idx = ci
|
||||
break
|
||||
if rem_idx != -1:
|
||||
for ri, (ln, rcols) in enumerate(table_rows):
|
||||
if rem_idx < len(rcols):
|
||||
rval = rcols[rem_idx]
|
||||
if re.match(r'^[\d\.]+\s*[%%]$', rval) or rval in ['45%', '50%', '30%', '65%', '100%']:
|
||||
flaws.append({
|
||||
'div': div,
|
||||
'file': fname,
|
||||
'sec': current_sec,
|
||||
'line': ln,
|
||||
'type': 'Lonely Percentage in Remarks',
|
||||
'detail': f"Remarks has isolated percentage '{rval}'"
|
||||
})
|
||||
|
||||
# 3. Check for description sentence occupying Col 0
|
||||
for ri, (ln, rcols) in enumerate(table_rows):
|
||||
if len(rcols) == 0: continue
|
||||
c0 = rcols[0]
|
||||
# Long explanation in Col 0 of a non-first row
|
||||
if len(c0) > 15 and any(k in c0 for k in ['하여', '하고', '후', '위한', '설치', '운반', '정리']) and ri > 0:
|
||||
# if other cols have workers/numbers, this might be a description pushed to Col 0
|
||||
if len(rcols) > 2 and any(re.search(r'[\d\.]+', c) for c in rcols[1:]):
|
||||
flaws.append({
|
||||
'div': div,
|
||||
'file': fname,
|
||||
'sec': current_sec,
|
||||
'line': ln,
|
||||
'type': 'Description Pushed into Col 0',
|
||||
'detail': f"Col 0 has description '{c0[:40]}...'"
|
||||
})
|
||||
|
||||
in_table = False
|
||||
headers = []
|
||||
table_rows = []
|
||||
|
||||
print(f"Total potential flaws found: {len(flaws)}")
|
||||
by_div = {}
|
||||
for f in flaws:
|
||||
by_div.setdefault(f['div'], []).append(f)
|
||||
|
||||
for d, flist in by_div.items():
|
||||
print(f"\n[{d}]: {len(flist)} potential flaws")
|
||||
for item in flist:
|
||||
print(f" - [{item['file']}] L{item['line']} {item['sec']} -> {item['type']}")
|
||||
print(f" {item['detail']}")
|
||||
|
||||
with open('scratch/deep_scan_flaws.json', 'w', encoding='utf-8') as fp:
|
||||
json.dump(flaws, fp, ensure_ascii=False, indent=2)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Deep Scan for Multi-item Rows Duplicating Preceding Items ===")
|
||||
|
||||
suspects = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
tables = []
|
||||
cur_t = []
|
||||
cur_start = 0
|
||||
for idx, l in enumerate(lines):
|
||||
line_str = l.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
if not cur_t:
|
||||
cur_start = idx + 1
|
||||
cur_t.append((idx + 1, line_str))
|
||||
else:
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
cur_t = []
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
|
||||
for t_start, t_rows in tables:
|
||||
data_rows = []
|
||||
for lno, r_str in t_rows:
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
# Header check
|
||||
if any(c in ["구 분", "구분", "품 명", "품명", "규 격", "규격", "단 위", "단위", "수 량", "수량", "직 종", "직종"] for c in cells):
|
||||
continue
|
||||
data_rows.append((lno, r_str, cells))
|
||||
|
||||
if len(data_rows) < 3:
|
||||
continue
|
||||
|
||||
# Collect individual item names from preceding rows (cell 0 or cell 1)
|
||||
preceding_items = {} # item_name -> list of line numbers
|
||||
for r_idx, (lno, r_str, cells) in enumerate(data_rows):
|
||||
c0 = cells[0] if len(cells) > 0 else ""
|
||||
c1 = cells[1] if len(cells) > 1 else ""
|
||||
|
||||
# Check if this row has multi-item in c0 or c1
|
||||
# E.g., "시 멘 트 전 주 〃" or "도 복 장 강 관 〃 〃 〃 〃"
|
||||
# Normalize whitespace: "시 멘 트" -> "시멘트"
|
||||
# If multiple items:
|
||||
c0_clean = re.sub(r'\s+', '', c0)
|
||||
c1_clean = re.sub(r'\s+', '', c1)
|
||||
|
||||
# Check against preceding items
|
||||
# Preceding items normalized
|
||||
matched_items = []
|
||||
for p_item, p_lnos in preceding_items.items():
|
||||
if len(p_item) >= 2 and p_item in c0_clean and p_item != c0_clean:
|
||||
matched_items.append((p_item, p_lnos))
|
||||
elif len(p_item) >= 2 and p_item in c1_clean and p_item != c1_clean:
|
||||
matched_items.append((p_item, p_lnos))
|
||||
|
||||
if len(matched_items) >= 2:
|
||||
# 2 or more distinct preceding items appear in this single row's item cell!
|
||||
suspects.append({
|
||||
'file': fname,
|
||||
'line': lno,
|
||||
'table_start': t_start,
|
||||
'row': r_str[:90],
|
||||
'matched_preceding_items': matched_items
|
||||
})
|
||||
|
||||
# Add single items from this row
|
||||
# If c0 looks like a single item (not too many spaces/words)
|
||||
words0 = c0.split()
|
||||
if 1 <= len(words0) <= 2 and len(c0_clean) >= 2:
|
||||
preceding_items.setdefault(c0_clean, []).append(lno)
|
||||
words1 = c1.split()
|
||||
if 1 <= len(words1) <= 2 and len(c1_clean) >= 2:
|
||||
preceding_items.setdefault(c1_clean, []).append(lno)
|
||||
|
||||
print(f"Total suspects found: {len(suspects)}")
|
||||
for s in suspects:
|
||||
print(f"\n[{s['file']}:L{s['line']}] (Table starting at L{s['table_start']})")
|
||||
print(f" Suspect Row: {s['row']}")
|
||||
print(f" Matches preceding items: {s['matched_preceding_items']}")
|
||||
@@ -0,0 +1,126 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Scanning 14 target files for duplicated/overlapped rows ===")
|
||||
|
||||
exact_duplicates = []
|
||||
squashed_overlaps = []
|
||||
header_repeats = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Track recent data rows (sliding window of 40 rows)
|
||||
recent_rows = [] # list of (line_no, line_str, cells, numbers_set, text_words_set)
|
||||
recent_headers = []
|
||||
|
||||
in_table = False
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
l_no = idx + 1
|
||||
|
||||
if not (line_str.startswith('|') and line_str.endswith('|')):
|
||||
in_table = False
|
||||
# If leaving table, keep recent_rows for a bit or clear?
|
||||
# A duplicate table might start right after!
|
||||
continue
|
||||
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
is_divider = all(re.match(r'^:?-+:?$', c) for c in cells if c)
|
||||
if is_divider:
|
||||
continue
|
||||
|
||||
# Is it a header row?
|
||||
is_header = any(c in ["구 분", "구분", "품 명", "품명", "규 격", "규격", "단 위", "단위", "수 량", "수량", "직 종", "직종"] for c in cells)
|
||||
if is_header:
|
||||
# Check if identical header was seen recently (within 50 lines)
|
||||
for h_lno, h_cells in recent_headers[-5:]:
|
||||
if h_cells == cells:
|
||||
header_repeats.append({
|
||||
'file': fname,
|
||||
'line': l_no,
|
||||
'prev_line': h_lno,
|
||||
'header': cells
|
||||
})
|
||||
recent_headers.append((l_no, cells))
|
||||
continue
|
||||
|
||||
# Skip notes
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
|
||||
# Extract numbers and words
|
||||
row_numbers = set(re.findall(r'\b\d+(?:\.\d+)?\b', line_str))
|
||||
row_words = set(w for w in re.findall(r'[가-힣A-Za-z0-9]+', line_str) if len(w) > 1)
|
||||
|
||||
# 1. Check exact duplicate in sliding window
|
||||
for r_lno, r_str, r_cells, r_nums, r_words in recent_rows[-30:]:
|
||||
# If cells match exactly, and not just all empty or standard dashes
|
||||
non_empty = [c for c in cells if c not in ["", "-", "〃"]]
|
||||
if len(non_empty) >= 2 and cells == r_cells:
|
||||
exact_duplicates.append({
|
||||
'file': fname,
|
||||
'line': l_no,
|
||||
'prev_line': r_lno,
|
||||
'row': line_str[:80]
|
||||
})
|
||||
break
|
||||
|
||||
# 2. Check squashed overlap:
|
||||
# If this row contains multiple words/numbers that appeared across MULTIPLE preceding separate rows
|
||||
# E.g., row_words has words from at least 3 distinct preceding rows, or numbers from 3 distinct rows
|
||||
matches_per_row = []
|
||||
for r_lno, r_str, r_cells, r_nums, r_words in recent_rows[-20:]:
|
||||
common_nums = row_numbers.intersection(r_nums)
|
||||
common_words = row_words.intersection(r_words)
|
||||
if len(common_nums) >= 2 or len(common_words) >= 2:
|
||||
matches_per_row.append((r_lno, common_words, common_nums))
|
||||
|
||||
if len(matches_per_row) >= 3:
|
||||
# This row overlaps with 3 or more distinct preceding rows!
|
||||
squashed_overlaps.append({
|
||||
'file': fname,
|
||||
'line': l_no,
|
||||
'matched_prev_lines': [m[0] for m in matches_per_row],
|
||||
'row': line_str[:90]
|
||||
})
|
||||
|
||||
recent_rows.append((l_no, line_str, cells, row_numbers, row_words))
|
||||
if len(recent_rows) > 40:
|
||||
recent_rows.pop(0)
|
||||
|
||||
print(f"\n1. Exact duplicate rows in window: {len(exact_duplicates)}")
|
||||
for d in exact_duplicates:
|
||||
print(f" [{d['file']}:L{d['line']} (prev L{d['prev_line']})] {d['row']}")
|
||||
|
||||
print(f"\n2. Squashed overlaps (matching 3+ preceding rows): {len(squashed_overlaps)}")
|
||||
for s in squashed_overlaps:
|
||||
print(f" [{s['file']}:L{s['line']} (matched {s['matched_prev_lines']})] {s['row']}")
|
||||
|
||||
print(f"\n3. Header repeats (same table header repeated within 50 lines): {len(header_repeats)}")
|
||||
for h in header_repeats:
|
||||
print(f" [{h['file']}:L{h['line']} (prev L{h['prev_line']})] {h['header']}")
|
||||
@@ -0,0 +1,15 @@
|
||||
import sys
|
||||
from audit_page_boundary_overlaps import boundary_cases
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
by_file = {}
|
||||
for b in boundary_cases:
|
||||
by_file.setdefault(b['file'], []).append(b)
|
||||
|
||||
for f, cases in sorted(by_file.items()):
|
||||
data_cases = [c for c in cases if not any(w in c['before_row'] for w in ['구 분', '직 종', '품 명', '공 정 별', '장 비 명', '작 업 구 분', '단 위', '수 량'])]
|
||||
print(f"=== {f}: {len(cases)} boundary cases (Data row overlaps: {len(data_cases)}) ===")
|
||||
for dc in data_cases:
|
||||
print(f" L{dc['boundary_line']} [{dc['type']}]:")
|
||||
print(f" Before: {dc['before_row']}")
|
||||
print(f" After: {dc['after_row']}")
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,13 @@
|
||||
import pymupdf
|
||||
import sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
doc = pymupdf.open("resources/knowledge/original/행정규칙/임도 품셈 적용기준 (현 산림사업 표준품셈)/첨부/(산림청고시 제2025-82호) 산림사업 표준품셈.pdf")
|
||||
|
||||
# Print entire page 52 (0-indexed 51) and 53 (0-indexed 52)
|
||||
print("==================== PDF PAGE 52 (1-indexed) ====================")
|
||||
print(doc[51].get_text())
|
||||
|
||||
print("==================== PDF PAGE 53 (1-indexed) ====================")
|
||||
print(doc[52].get_text())
|
||||
@@ -0,0 +1,117 @@
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
md_path = Path("resources/knowledge/original/행정규칙/임도 품셈 적용기준 (현 산림사업 표준품셈)/첨부/(산림청고시 제2025-82호) 산림사업 표준품셈.md")
|
||||
with open(md_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# 1. Parse Chapters and Sections
|
||||
# Track current chapter, section, and text before tables
|
||||
chapters = []
|
||||
for idx, line in enumerate(lines):
|
||||
m = re.match(r'^(#{1,3})\s*(제\s*\d+\s*장(?:\s+[^\,\n]+)?)$', line.strip())
|
||||
if m and not any(k in line for k in ['참조', '적용기준,', '토질,']):
|
||||
chapters.append((idx + 1, m.group(2).strip()))
|
||||
|
||||
def get_chapter(line_no):
|
||||
cur_ch = "제0장 머리말"
|
||||
for lno, ch in chapters:
|
||||
if line_no >= lno:
|
||||
cur_ch = ch
|
||||
else:
|
||||
break
|
||||
return cur_ch
|
||||
|
||||
# 2. Extract tables and their contexts
|
||||
tables = []
|
||||
current_table = []
|
||||
start_line = -1
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
if not current_table:
|
||||
start_line = idx + 1
|
||||
current_table.append((idx + 1, line))
|
||||
else:
|
||||
if current_table:
|
||||
has_sep = any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', l.strip()) for _, l in current_table)
|
||||
if has_sep:
|
||||
tables.append({
|
||||
'start_line': start_line,
|
||||
'end_line': current_table[-1][0],
|
||||
'lines': [l for _, l in current_table]
|
||||
})
|
||||
current_table = []
|
||||
|
||||
if current_table:
|
||||
has_sep = any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', l.strip()) for _, l in current_table)
|
||||
if has_sep:
|
||||
tables.append({
|
||||
'start_line': start_line,
|
||||
'end_line': current_table[-1][0],
|
||||
'lines': [l for _, l in current_table]
|
||||
})
|
||||
|
||||
print(f"Extracted {len(tables)} tables.")
|
||||
|
||||
# Enrich each table with pre-context (up to 15 lines before) and post-context ([주] up to next table/heading)
|
||||
for i, t in enumerate(tables):
|
||||
t_id = f"F{i+1:04d}"
|
||||
t['table_id'] = t_id
|
||||
t['chapter'] = get_chapter(t['start_line'])
|
||||
|
||||
# Pre-context: lines before start_line
|
||||
pre_start = max(0, t['start_line'] - 15)
|
||||
pre_lines = [lines[j].strip() for j in range(pre_start, t['start_line'] - 1) if lines[j].strip()]
|
||||
t['pre_context'] = pre_lines
|
||||
|
||||
# Find section title from pre-lines (looking backwards)
|
||||
section = "미식별"
|
||||
for pl in reversed(pre_lines):
|
||||
if re.match(r'^(?:#{1,4}\s*)?(?:\d+[-\.]\d+|\d+\.)', pl):
|
||||
section = pl
|
||||
break
|
||||
t['section'] = section
|
||||
|
||||
# Post-context: lines after end_line until next table start or heading
|
||||
post_end = len(lines)
|
||||
if i + 1 < len(tables):
|
||||
post_end = min(tables[i+1]['start_line'] - 1, t['end_line'] + 30)
|
||||
else:
|
||||
post_end = min(len(lines), t['end_line'] + 30)
|
||||
|
||||
post_lines = []
|
||||
notes = []
|
||||
in_note = False
|
||||
for j in range(t['end_line'], post_end):
|
||||
lj = lines[j].strip()
|
||||
if not lj:
|
||||
if in_note and len(notes) > 5 and not lj: # allow blank lines inside note
|
||||
pass
|
||||
continue
|
||||
if lj.startswith('#') or (lj.startswith('|') and lj.endswith('|')):
|
||||
break
|
||||
post_lines.append(lj)
|
||||
if '[주]' in lj or '【주】' in lj or re.match(r'^\s*주\s*[:\)]', lj):
|
||||
in_note = True
|
||||
if in_note:
|
||||
notes.append(lj)
|
||||
# if line looks like next section heading, stop
|
||||
if re.match(r'^\d+\s*[\.-]\s*\d+', lj) and not re.match(r'^\s*[\(\[\d]+\s*[\.\)]', lj):
|
||||
break
|
||||
t['post_lines'] = post_lines
|
||||
t['notes'] = notes
|
||||
|
||||
print(f"Sample table 10 (F0010):")
|
||||
t10 = tables[9]
|
||||
print(f" ID: {t10['table_id']}, Chapter: {t10['chapter']}, Section: {t10['section']}")
|
||||
print(f" Start line: {t10['start_line']}, End line: {t10['end_line']}")
|
||||
print(f" Pre-context (last 3): {t10['pre_context'][-3:]}")
|
||||
print(f" Notes count: {len(t10['notes'])}")
|
||||
if t10['notes']:
|
||||
print(f" Notes sample: {t10['notes'][:3]}")
|
||||
@@ -0,0 +1,72 @@
|
||||
import glob, re, os, sys, fitz
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
pdf_path = 'resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.pdf'
|
||||
doc = fitz.open(pdf_path)
|
||||
|
||||
target_files = []
|
||||
target_files.extend(glob.glob('resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/*.md'))
|
||||
target_files.extend(glob.glob('resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/*.md'))
|
||||
target_files.append('resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md')
|
||||
|
||||
active_remarks_tables = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
current_sec = 'UNKNOWN'
|
||||
in_table = False
|
||||
headers = []
|
||||
table_rows = []
|
||||
start_line = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_s = line.strip()
|
||||
sec_m = re.search(r'(\d+-\d+(?:-\d+)?(?:\s+[^\n(]+)?)', line_s)
|
||||
if sec_m and not line_s.startswith('|'):
|
||||
current_sec = sec_m.group(1).strip()
|
||||
|
||||
if line_s.startswith('|') and not line_s.startswith('|---'):
|
||||
cols = [c.strip() for c in line_s.split('|')[1:-1]]
|
||||
if not in_table:
|
||||
if any('비고' in c or '비 고' in c for c in cols):
|
||||
headers = cols
|
||||
in_table = True
|
||||
start_line = idx + 1
|
||||
table_rows = []
|
||||
continue
|
||||
if in_table:
|
||||
table_rows.append((idx + 1, cols))
|
||||
else:
|
||||
if in_table and (line_s == '' or not line_s.startswith('|')):
|
||||
# Check if remarks column has non-empty text
|
||||
rem_idx = -1
|
||||
for ci, h in enumerate(headers):
|
||||
if '비고' in h or '비 고' in h:
|
||||
rem_idx = ci
|
||||
break
|
||||
if rem_idx != -1:
|
||||
non_empty_remarks = [r[1][rem_idx] for r in table_rows if rem_idx < len(r[1]) and r[1][rem_idx]]
|
||||
if len(non_empty_remarks) > 0:
|
||||
active_remarks_tables.append({
|
||||
'file': fname,
|
||||
'sec': current_sec,
|
||||
'line': start_line,
|
||||
'row_count': len(table_rows),
|
||||
'headers': headers,
|
||||
'non_empty_cnt': len(non_empty_remarks),
|
||||
'remarks_sample': non_empty_remarks[:5],
|
||||
'all_remarks': non_empty_remarks
|
||||
})
|
||||
in_table = False
|
||||
headers = []
|
||||
table_rows = []
|
||||
|
||||
print(f"Total active remarks tables found: {len(active_remarks_tables)}")
|
||||
for art in active_remarks_tables:
|
||||
print(f"\n[{art['file']}] {art['sec']} (L{art['line']}) -> {art['row_count']} rows, {art['non_empty_cnt']} non-empty remarks")
|
||||
print(f" Headers: {art['headers']}")
|
||||
print(f" Remarks: {art['remarks_sample']}")
|
||||
@@ -0,0 +1,64 @@
|
||||
import sys, os, re, json
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
|
||||
all_md_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
all_md_files.append(os.path.join(root, f))
|
||||
|
||||
total_candidates = []
|
||||
|
||||
# Section pattern: digit-digit(-digit)? followed by Korean letters (section title)
|
||||
# We want to detect when it is NOT preceded by newline/start of line
|
||||
# e.g. "한다.1-2-2 단위표준" or "계산한다.1-2-3 토질"
|
||||
sec_regex = re.compile(r'([^\n#\-\*\|\s])(\d+-\d+(?:-\d+)?\s+[가-힣\w\(\)·\,\s]+?(?:\(\'\d+[^)]*년\s*(?:보완|제정|신설)[^)]*\)|(?=\n|\([일|㎥|㎡|m|인|개|km|ton|hr|대|본|개소|소|개련|조|ha|회]))?)')
|
||||
|
||||
for fpath in sorted(all_md_files):
|
||||
rel_path = os.path.relpath(fpath, base_dir)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
lines = text.split('\n')
|
||||
file_matches = []
|
||||
for line_idx, line in enumerate(lines):
|
||||
line_num = line_idx + 1
|
||||
# find patterns in line
|
||||
# check if line contains a section header that didn't start at beginning
|
||||
# skip markdown table rows (| ... |)
|
||||
if line.strip().startswith('|') and line.strip().endswith('|'):
|
||||
continue
|
||||
|
||||
# Regex to find attached headers
|
||||
# Look for e.g. text followed immediately by \d+-\d+
|
||||
for m in re.finditer(r'([^\s#\-\*\>\|])(\d+-\d+(?:-\d+)?)\s+([가-힣][가-힣\s\w\(\)\/·]+)', line):
|
||||
prev_char = m.group(1)
|
||||
sec_num = m.group(2)
|
||||
sec_title = m.group(3).strip()
|
||||
|
||||
# Filter out citations: e.g. '[공통부문] 5-3-1' or '‘5-3-1'
|
||||
if prev_char in ["'", '"', '‘', '’', '[', '(', '제', '표', '·', ':', '―', '-', '/']:
|
||||
continue
|
||||
if '참고' in sec_title[:10] or '따른다' in sec_title[:10]:
|
||||
continue
|
||||
|
||||
file_matches.append({
|
||||
"file": rel_path,
|
||||
"line": line_num,
|
||||
"prev_char": prev_char,
|
||||
"sec_num": sec_num,
|
||||
"sec_title": sec_title[:30],
|
||||
"full_match": m.group(0)[:60],
|
||||
"line_snippet": line[:120]
|
||||
})
|
||||
|
||||
if file_matches:
|
||||
print(f"{rel_path}: {len(file_matches)} candidates")
|
||||
total_candidates.extend(file_matches)
|
||||
|
||||
print(f"\nTotal attached section header candidates across all 45 files: {len(total_candidates)}")
|
||||
|
||||
with open('scratch/all_attached_headers_candidates.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(total_candidates, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,70 @@
|
||||
import os, sys, re, json
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
|
||||
# Exclude 8장 (건설기계) and 합본
|
||||
all_md_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
if '01_공통부문\\제8장' in os.path.join(root, f) or '01_공통부문/제8장' in os.path.join(root, f):
|
||||
continue
|
||||
all_md_files.append(os.path.join(root, f))
|
||||
|
||||
print(f"Total target files (excluding 8장 and merged): {len(all_md_files)}")
|
||||
|
||||
results = []
|
||||
|
||||
for fpath in sorted(all_md_files):
|
||||
rel_path = os.path.relpath(fpath, base_dir)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_num = idx + 1
|
||||
line_str = line.strip()
|
||||
if not (line_str.startswith('|') and line_str.endswith('|')):
|
||||
continue
|
||||
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
# Skip header separators |---|---|
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
|
||||
# Check for multi-token collapsed cells
|
||||
# Conditions:
|
||||
# A) cell has >= 8 tokens and contains numbers or spec symbols
|
||||
# B) cell contains embedded table headers like '구 분', '규 격', '단 위' etc
|
||||
# C) multiple numeric tokens that look like a row data squashed together
|
||||
for c_idx, c in enumerate(cells):
|
||||
tokens = c.split()
|
||||
# If cell has embedded table structure
|
||||
is_embedded_table = ('구 분' in c or '구분' in c) and ('단위' in c or '단 위' in c or '수량' in c or '수 량' in c)
|
||||
# If cell has many tokens
|
||||
is_many_tokens = len(tokens) >= 8 and sum(1 for tok in tokens if any(char.isdigit() for char in tok)) >= 4
|
||||
|
||||
if is_embedded_table or is_many_tokens:
|
||||
results.append({
|
||||
"file": rel_path,
|
||||
"line": line_num,
|
||||
"col_idx": c_idx,
|
||||
"cell_snippet": c[:120],
|
||||
"token_count": len(tokens),
|
||||
"is_embedded_table": is_embedded_table
|
||||
})
|
||||
break
|
||||
|
||||
print(f"Total candidate collapsed rows found: {len(results)}")
|
||||
|
||||
by_file = {}
|
||||
for r in results:
|
||||
f = r['file']
|
||||
by_file[f] = by_file.get(f, 0) + 1
|
||||
|
||||
for f, cnt in sorted(by_file.items(), key=lambda x: x[1], reverse=True):
|
||||
print(f" {cnt:3d} rows: {f}")
|
||||
|
||||
with open('scratch/all_candidates_collapsed_ex8.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,57 @@
|
||||
import os, sys, re, json
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
|
||||
all_md_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
if '01_공통부문\\제8장' in os.path.join(root, f) or '01_공통부문/제8장' in os.path.join(root, f):
|
||||
continue
|
||||
all_md_files.append(os.path.join(root, f))
|
||||
|
||||
squashed_tables = []
|
||||
|
||||
for fpath in sorted(all_md_files):
|
||||
rel_path = os.path.relpath(fpath, base_dir)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_num = idx + 1
|
||||
line_str = line.strip()
|
||||
if line_str.startswith('|'):
|
||||
continue
|
||||
|
||||
# Check if line contains a squashed table
|
||||
# Sign 1: contains both '구 분' (or '구분') and ('단위' or '단 위') and ('수량' or '수 량')
|
||||
# Sign 2: contains sec header followed immediately by table elements
|
||||
has_table_headers = ('구 분' in line_str or '구분' in line_str) and \
|
||||
('단 위' in line_str or '단위' in line_str) and \
|
||||
('수 량' in line_str or '수량' in line_str or '인' in line_str)
|
||||
|
||||
# Also check for tables without '수량' like '규격' '단위'
|
||||
has_spec_unit = ('규 격' in line_str or '규격' in line_str) and \
|
||||
('단 위' in line_str or '단위' in line_str) and \
|
||||
any(term in line_str for term in ['공(인)', '인부', '비고', '시공량'])
|
||||
|
||||
if has_table_headers or has_spec_unit:
|
||||
# Check length to avoid simple sentences
|
||||
if len(line_str) > 40:
|
||||
squashed_tables.append({
|
||||
"file": rel_path,
|
||||
"line": line_num,
|
||||
"length": len(line_str),
|
||||
"line_text": line_str
|
||||
})
|
||||
|
||||
print(f"Total squashed table lines in non-table text: {len(squashed_tables)}")
|
||||
|
||||
for st in squashed_tables:
|
||||
print(f"\n{st['file']} L{st['line']} (len: {st['length']}):")
|
||||
print(f" {st['line_text'][:120]}")
|
||||
|
||||
with open('scratch/squashed_tables_in_text.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(squashed_tables, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,72 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
# Suspect sentence continuation words in Col 0
|
||||
CONTINUATION_PATTERNS = [
|
||||
r'^구\s*간$', r'^설\s*치$', r'^후\s*설치$', r'^조립\s*설치$', r'^취급함', r'^정돈함',
|
||||
r'^[가-힣\w\s]+(하고|하며|하여|혹은|및|등|까지|위해|위하여|취부함|포함|제외|운반|조양함)\b',
|
||||
r'^→', r'^[A-Za-z0-9#]+\s*$', r'^분기기$', r'^교\s*환$', r'^매\s*립\s*식$', r'^\( 직 결 형 \)$'
|
||||
]
|
||||
|
||||
print("=== Scanning All Target Files for Broken Groupings / Split Col 0 Sentences ===")
|
||||
|
||||
results = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
if not line.startswith('|'):
|
||||
continue
|
||||
cells = [c.strip() for c in line.split('|')[1:-1]]
|
||||
if not cells or all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
|
||||
c0 = cells[0]
|
||||
# check if c0 matches any continuation patterns
|
||||
for pat in CONTINUATION_PATTERNS:
|
||||
if re.search(pat, c0):
|
||||
# Also check surrounding lines to confirm it's part of a split
|
||||
prev_line = lines[idx-1].strip() if idx > 0 else ""
|
||||
next_line = lines[idx+1].strip() if idx + 1 < len(lines) else ""
|
||||
results.append({
|
||||
'file': fname,
|
||||
'line': idx + 1,
|
||||
'c0': c0,
|
||||
'full_line': line.strip()[:100],
|
||||
'matched_pat': pat
|
||||
})
|
||||
break
|
||||
|
||||
print(f"Total Suspect Split Col 0 Cells Found: {len(results)}")
|
||||
by_file = {}
|
||||
for r in results:
|
||||
by_file.setdefault(r['file'], []).append(r)
|
||||
|
||||
for f, items in sorted(by_file.items()):
|
||||
print(f"\n[{f}] ({len(items)}건)")
|
||||
for it in items:
|
||||
print(f" L{it['line']:<5} | Col0: '{it['c0']}' | {it['full_line']}")
|
||||
@@ -0,0 +1,100 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문 (전체 9개 파일)
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문 (전체 4개 파일)
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== 1. Checking for duplicated or overlapped table rows ===")
|
||||
|
||||
candidates = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
tables = []
|
||||
current_table = []
|
||||
current_start = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
if not current_table:
|
||||
current_start = idx + 1
|
||||
current_table.append((idx + 1, line_str))
|
||||
else:
|
||||
if current_table:
|
||||
tables.append((current_start, current_table))
|
||||
current_table = []
|
||||
if current_table:
|
||||
tables.append((current_start, current_table))
|
||||
|
||||
# Check each table
|
||||
for start_line, t_rows in tables:
|
||||
# Separate header and data rows
|
||||
data_rows = []
|
||||
for r_idx, (l_no, r_str) in enumerate(t_rows):
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if r_idx == 0:
|
||||
header_cells = cells
|
||||
continue
|
||||
data_rows.append((l_no, cells, r_str))
|
||||
|
||||
# Check if later data rows duplicate earlier data rows or contain words of earlier rows
|
||||
earlier_first_cells = []
|
||||
earlier_words = set()
|
||||
|
||||
for l_no, cells, r_str in data_rows:
|
||||
if not cells:
|
||||
continue
|
||||
c0 = cells[0]
|
||||
# Check if this row is a collapsed multi-item row
|
||||
# Split words in c0
|
||||
c0_words = [w for w in c0.split() if len(w) > 1 and w not in ["-", "·", "ㆍ"]]
|
||||
|
||||
# Check overlap
|
||||
overlap_words = [w for w in c0_words if w in earlier_words]
|
||||
if len(overlap_words) >= 2:
|
||||
candidates.append({
|
||||
'file': fname,
|
||||
'line': l_no,
|
||||
'type': 'word_overlap_in_table',
|
||||
'overlap': overlap_words,
|
||||
'row': r_str[:80],
|
||||
'earlier_first_cells': earlier_first_cells[-5:]
|
||||
})
|
||||
elif len(earlier_first_cells) > 0 and c0 in earlier_first_cells:
|
||||
# Same first cell appears again - could be normal (e.g. different spec) or duplicate
|
||||
# Check if entire row or multiple cells are identical
|
||||
pass
|
||||
|
||||
earlier_first_cells.append(c0)
|
||||
for w in c0_words:
|
||||
earlier_words.add(w)
|
||||
|
||||
print(f"Total word overlap candidates in tables: {len(candidates)}")
|
||||
for c in candidates:
|
||||
print(f"[{c['file']}:L{c['line']}] {c['type']} - overlap: {c['overlap']}")
|
||||
print(f" row: {c['row']}")
|
||||
print(f" earlier: {c['earlier_first_cells']}")
|
||||
@@ -0,0 +1,112 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문 (전체 9개 파일)
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문 (전체 4개 파일)
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Scanning Tables for Trailing Duplicate/Overlapped Rows ===")
|
||||
|
||||
tail_duplicates = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
tables = []
|
||||
cur_t = []
|
||||
cur_start = 0
|
||||
for idx, l in enumerate(lines):
|
||||
line_str = l.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
if not cur_t:
|
||||
cur_start = idx + 1
|
||||
cur_t.append((idx + 1, line_str))
|
||||
else:
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
cur_t = []
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
|
||||
for t_start, t_rows in tables:
|
||||
# Separate divider
|
||||
data_rows = []
|
||||
for lno, r_str in t_rows:
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
data_rows.append((lno, r_str, cells))
|
||||
|
||||
if len(data_rows) < 4:
|
||||
continue
|
||||
|
||||
# Check every row against all PRECEDING rows in the table
|
||||
# We look for:
|
||||
# 1. Exact duplicate rows (not consecutive 〃 or standard repeats)
|
||||
# 2. Rows whose cells contain multiple tokens from distinct preceding rows
|
||||
|
||||
seen_cells_set = [] # list of (lno, cells, text_tokens, num_tokens)
|
||||
|
||||
for r_idx, (lno, r_str, cells) in enumerate(data_rows):
|
||||
# Tokens
|
||||
text_tokens = set(re.findall(r'[가-힣A-Za-z]+', r_str))
|
||||
text_tokens = {t for t in text_tokens if len(t) > 1 and t not in ["인부", "보통인부", "특별인부", "플랜트", "설비공", "용접공", "배관공"]}
|
||||
num_tokens = set(re.findall(r'\b\d+(?:\.\d+)?\b', r_str))
|
||||
|
||||
# Check 1: Exact duplicate of an earlier row that is NOT adjacent
|
||||
for prev_lno, prev_cells, prev_tt, prev_nt in seen_cells_set[:-1]:
|
||||
non_empty = [c for c in cells if c not in ["", "-", "〃"]]
|
||||
if len(non_empty) >= 2 and cells == prev_cells:
|
||||
tail_duplicates.append({
|
||||
'file': fname,
|
||||
'line': lno,
|
||||
'type': 'exact_duplicate_of_earlier_row',
|
||||
'dup_of_line': prev_lno,
|
||||
'row': r_str[:80]
|
||||
})
|
||||
break
|
||||
|
||||
# Check 2: Squashed composite row that combines tokens of 2+ distinct preceding rows
|
||||
matching_earlier = []
|
||||
for prev_lno, prev_cells, prev_tt, prev_nt in seen_cells_set:
|
||||
if len(prev_tt) >= 2 and prev_tt.issubset(text_tokens) and len(prev_nt.intersection(num_tokens)) >= 1:
|
||||
matching_earlier.append(prev_lno)
|
||||
|
||||
if len(matching_earlier) >= 2:
|
||||
tail_duplicates.append({
|
||||
'file': fname,
|
||||
'line': lno,
|
||||
'type': 'composite_squashed_duplicate',
|
||||
'dup_of_lines': matching_earlier,
|
||||
'row': r_str[:80]
|
||||
})
|
||||
|
||||
seen_cells_set.append((lno, cells, text_tokens, num_tokens))
|
||||
|
||||
print(f"Total potential trailing duplicates found: {len(tail_duplicates)}")
|
||||
for td in tail_duplicates:
|
||||
if td['type'] == 'exact_duplicate_of_earlier_row':
|
||||
print(f"[{td['file']}:L{td['line']}] EXACT DUP of L{td['dup_of_line']}: {td['row']}")
|
||||
else:
|
||||
print(f"[{td['file']}:L{td['line']}] COMPOSITE DUP of {td['dup_of_lines']}: {td['row']}")
|
||||
@@ -0,0 +1,119 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Scanning Tables for Exact Numeric/Text Subsets or Duplicates ===")
|
||||
|
||||
true_overlaps = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
tables = []
|
||||
cur_t = []
|
||||
cur_start = 0
|
||||
for idx, l in enumerate(lines):
|
||||
line_str = l.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
if not cur_t:
|
||||
cur_start = idx + 1
|
||||
cur_t.append((idx + 1, line_str))
|
||||
else:
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
cur_t = []
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
|
||||
for t_start, t_rows in tables:
|
||||
data_rows = []
|
||||
for lno, r_str in t_rows:
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
data_rows.append((lno, r_str, cells))
|
||||
|
||||
if len(data_rows) < 3:
|
||||
continue
|
||||
|
||||
# For each row, extract list of numbers (excluding trivial 0, 1, 2)
|
||||
row_num_tuples = []
|
||||
for lno, r_str, cells in data_rows:
|
||||
nums = re.findall(r'\b\d+(?:\.\d+)?\b', r_str)
|
||||
# filter out non-informative
|
||||
substantive_nums = [n for n in nums if n not in ["1", "2", "3", "4", "5", "1.0", "2.0"]]
|
||||
row_num_tuples.append((lno, r_str, cells, substantive_nums))
|
||||
|
||||
for i in range(len(row_num_tuples)):
|
||||
lno_i, str_i, cells_i, nums_i = row_num_tuples[i]
|
||||
if len(nums_i) < 2:
|
||||
continue
|
||||
|
||||
for j in range(i + 1, len(row_num_tuples)):
|
||||
lno_j, str_j, cells_j, nums_j = row_num_tuples[j]
|
||||
if len(nums_j) < 2:
|
||||
continue
|
||||
|
||||
# Check if nums_i is identical to nums_j
|
||||
if nums_i == nums_j and len(nums_i) >= 3:
|
||||
# Same 3+ substantive numbers!
|
||||
# Check text
|
||||
t_i = set(re.findall(r'[가-힣A-Za-z]+', str_i))
|
||||
t_j = set(re.findall(r'[가-힣A-Za-z]+', str_j))
|
||||
common_t = t_i.intersection(t_j)
|
||||
if len(common_t) >= 1:
|
||||
true_overlaps.append({
|
||||
'file': fname,
|
||||
'line_1': lno_i,
|
||||
'line_2': lno_j,
|
||||
'type': 'identical_substantive_numbers',
|
||||
'nums': nums_i,
|
||||
'common_text': list(common_t),
|
||||
'row_1': str_i[:80],
|
||||
'row_2': str_j[:80]
|
||||
})
|
||||
# Check if nums_j contains nums_i and more (squashed combination)
|
||||
elif set(nums_i).issubset(set(nums_j)) and len(nums_i) >= 3 and len(nums_j) > len(nums_i):
|
||||
t_i = set(re.findall(r'[가-힣A-Za-z]+', str_i))
|
||||
t_j = set(re.findall(r'[가-힣A-Za-z]+', str_j))
|
||||
if t_i.intersection(t_j):
|
||||
true_overlaps.append({
|
||||
'file': fname,
|
||||
'line_1': lno_i,
|
||||
'line_2': lno_j,
|
||||
'type': 'subset_squashed_numbers',
|
||||
'nums_subset': nums_i,
|
||||
'nums_superset': nums_j,
|
||||
'row_1': str_i[:80],
|
||||
'row_2': str_j[:80]
|
||||
})
|
||||
|
||||
print(f"Total True Overlaps Found: {len(true_overlaps)}")
|
||||
for to in true_overlaps:
|
||||
print(f"\n[{to['file']}] Lines L{to['line_1']} & L{to['line_2']} - {to['type']}")
|
||||
print(f" Row 1: {to['row_1']}")
|
||||
print(f" Row 2: {to['row_2']}")
|
||||
@@ -0,0 +1,121 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Scanning by Section for Overlapping / Redundant Table Rows ===")
|
||||
|
||||
findings = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Split content by section pattern: e.g. \n\d+-\d+(?:-\d+)?
|
||||
# Or line by line tracking current section
|
||||
lines = content.split('\n')
|
||||
current_sec = "TOP"
|
||||
sec_lines = []
|
||||
|
||||
sections = [] # (sec_name, start_lno, lines)
|
||||
for idx, line in enumerate(lines):
|
||||
# Section header match e.g. "1-2-4 ", "## 1-2-4", etc.
|
||||
m = re.match(r'^(?:#+\s*)?(\d+-\d+(?:-\d+)?)\s+', line.strip())
|
||||
if m:
|
||||
if sec_lines:
|
||||
sections.append((current_sec, idx - len(sec_lines) + 1, sec_lines))
|
||||
current_sec = m.group(1)
|
||||
sec_lines = [line]
|
||||
else:
|
||||
sec_lines.append(line)
|
||||
if sec_lines:
|
||||
sections.append((current_sec, len(lines) - len(sec_lines) + 1, sec_lines))
|
||||
|
||||
for sec_name, start_lno, s_lines in sections:
|
||||
# Extract tables in this section
|
||||
tables = []
|
||||
cur_t = []
|
||||
cur_t_start = 0
|
||||
for s_idx, sl in enumerate(s_lines):
|
||||
l_str = sl.strip()
|
||||
if l_str.startswith('|') and l_str.endswith('|'):
|
||||
if not cur_t:
|
||||
cur_t_start = start_lno + s_idx
|
||||
cur_t.append((start_lno + s_idx, l_str))
|
||||
else:
|
||||
if cur_t:
|
||||
tables.append((cur_t_start, cur_t))
|
||||
cur_t = []
|
||||
if cur_t:
|
||||
tables.append((cur_t_start, cur_t))
|
||||
|
||||
# Check within each table or across tables in the same section
|
||||
# Collect all rows in this section
|
||||
all_rows = []
|
||||
for t_start, t_rows in tables:
|
||||
for r_lno, r_str in t_rows:
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
# Skip header rows
|
||||
if any(c in ["구 분", "구분", "품 명", "품명", "규 격", "규격", "단 위", "단위", "수 량", "수량", "직 종", "직종"] for c in cells):
|
||||
continue
|
||||
all_rows.append((r_lno, r_str, cells))
|
||||
|
||||
# Now check if later rows duplicate earlier rows in the same section!
|
||||
# Two types:
|
||||
# Type A: Exact same data in 3 or more consecutive rows
|
||||
# Type B: A single row that squashes 2 or more preceding rows from the same section
|
||||
for i in range(len(all_rows)):
|
||||
r_lno, r_str, cells = all_rows[i]
|
||||
|
||||
# Check if this row is a squashed version of preceding rows
|
||||
# Extract distinct tokens from this row
|
||||
row_tokens = set(re.findall(r'[가-힣A-Za-z0-9.]+', r_str))
|
||||
row_tokens = {t for t in row_tokens if len(t) > 1 and t not in ["인부", "보통인부", "특별인부", "0.0", "1.0"]}
|
||||
|
||||
matching_preceding = []
|
||||
for j in range(0, i):
|
||||
prev_lno, prev_str, prev_cells = all_rows[j]
|
||||
prev_tokens = set(re.findall(r'[가-힣A-Za-z0-9.]+', prev_str))
|
||||
prev_tokens = {t for t in prev_tokens if len(t) > 1 and t not in ["인부", "보통인부", "특별인부", "0.0", "1.0"]}
|
||||
|
||||
# Check how much prev_tokens is contained in row_tokens
|
||||
if len(prev_tokens) >= 3 and prev_tokens.issubset(row_tokens):
|
||||
matching_preceding.append(prev_lno)
|
||||
|
||||
if len(matching_preceding) >= 2:
|
||||
findings.append({
|
||||
'file': fname,
|
||||
'section': sec_name,
|
||||
'line': r_lno,
|
||||
'squashed_row': r_str[:80],
|
||||
'duplicates_of_lines': matching_preceding
|
||||
})
|
||||
|
||||
print(f"Total findings of squashed rows duplicating preceding rows in same section: {len(findings)}")
|
||||
for f in findings:
|
||||
print(f"[{f['file']}:{f['section']}:L{f['line']}] duplicates lines {f['duplicates_of_lines']}")
|
||||
print(f" Squashed row: {f['squashed_row']}")
|
||||
@@ -0,0 +1,94 @@
|
||||
import sys, os, re, json
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
merged_file = os.path.join(base_dir, "2026년_건설공사_표준품셈.md")
|
||||
|
||||
with open(merged_file, 'r', encoding='utf-8') as f:
|
||||
merged_text = f.read()
|
||||
|
||||
all_split_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
all_split_files.append(os.path.join(root, f))
|
||||
|
||||
# Function to extract all tables with their unique text fingerprints
|
||||
def extract_table_blocks(text, label):
|
||||
lines = text.split('\n')
|
||||
tables = []
|
||||
curr_lines = []
|
||||
start_line = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_num = idx + 1
|
||||
l_strip = line.strip()
|
||||
if l_strip.startswith('|') and l_strip.endswith('|'):
|
||||
if not curr_lines:
|
||||
start_line = line_num
|
||||
curr_lines.append(l_strip)
|
||||
else:
|
||||
if curr_lines:
|
||||
# check divider
|
||||
if any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', r) for r in curr_lines):
|
||||
# take first 2 rows as key
|
||||
header_str = " // ".join(curr_lines[:2])
|
||||
tables.append({
|
||||
"file": label,
|
||||
"start_line": start_line,
|
||||
"line_count": len(curr_lines),
|
||||
"header_key": header_str,
|
||||
"first_row": curr_lines[0],
|
||||
"sample_cell": curr_lines[min(2, len(curr_lines)-1)]
|
||||
})
|
||||
curr_lines = []
|
||||
if curr_lines and any(re.match(r'^\|(?:\s*:?-+:?\s*\|)+$', r) for r in curr_lines):
|
||||
tables.append({
|
||||
"file": label,
|
||||
"start_line": start_line,
|
||||
"line_count": len(curr_lines),
|
||||
"header_key": " // ".join(curr_lines[:2]),
|
||||
"first_row": curr_lines[0],
|
||||
"sample_cell": curr_lines[min(2, len(curr_lines)-1)]
|
||||
})
|
||||
return tables
|
||||
|
||||
merged_tbls = extract_table_blocks(merged_text, "합본")
|
||||
split_tbls = []
|
||||
for p in sorted(all_split_files):
|
||||
rel = os.path.relpath(p, base_dir)
|
||||
with open(p, 'r', encoding='utf-8') as f:
|
||||
t = f.read()
|
||||
split_tbls.extend(extract_table_blocks(t, rel))
|
||||
|
||||
print(f"Merged tables: {len(merged_tbls)}")
|
||||
print(f"Split tables: {len(split_tbls)}")
|
||||
|
||||
# Match each split table in merged_text
|
||||
# If first_row and sample_cell not found together in merged_text
|
||||
truly_missing_in_merged = []
|
||||
for st in split_tbls:
|
||||
# check if first_row exists in merged_text
|
||||
r0 = st['first_row']
|
||||
# remove spacing differences
|
||||
norm_r0 = re.sub(r'\s+', '', r0)
|
||||
# Check if this row exists in any merged table
|
||||
found = False
|
||||
for mt in merged_tbls:
|
||||
norm_mt = re.sub(r'\s+', '', mt['first_row'])
|
||||
if norm_r0 == norm_mt:
|
||||
# check sample_cell too
|
||||
norm_sc = re.sub(r'\s+', '', st['sample_cell'])
|
||||
norm_msc = re.sub(r'\s+', '', mt['sample_cell'])
|
||||
if norm_sc == norm_msc or norm_sc[:15] in norm_msc or norm_msc[:15] in norm_sc:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
truly_missing_in_merged.append(st)
|
||||
|
||||
print(f"\nTruly missing tables in 합본: {len(truly_missing_in_merged)}")
|
||||
for t in truly_missing_in_merged:
|
||||
print(f" - {t['file']} (L{t['start_line']}): {t['first_row'][:60]} | sample={t['sample_cell'][:40]}")
|
||||
|
||||
with open('scratch/truly_missing_in_merged.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(truly_missing_in_merged, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,92 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Scanning for Non-Adjacent Duplicate Rows within the EXACT SAME Table ===")
|
||||
|
||||
duplicates_in_table = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
tables = []
|
||||
cur_t = []
|
||||
cur_start = 0
|
||||
for idx, l in enumerate(lines):
|
||||
line_str = l.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
if not cur_t:
|
||||
cur_start = idx + 1
|
||||
cur_t.append((idx + 1, line_str))
|
||||
else:
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
cur_t = []
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
|
||||
for t_start, t_rows in tables:
|
||||
data_rows = []
|
||||
for lno, r_str in t_rows:
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
if any(c in ["구 분", "구분", "품 명", "품명", "규 격", "규격", "단 위", "단위", "수 량", "수량", "직 종", "직종"] for c in cells):
|
||||
continue
|
||||
data_rows.append((lno, r_str, cells))
|
||||
|
||||
if len(data_rows) < 4:
|
||||
continue
|
||||
|
||||
# Check if any non-trivial row appears multiple times in THIS SAME TABLE
|
||||
for i in range(len(data_rows)):
|
||||
lno_i, str_i, cells_i = data_rows[i]
|
||||
# Filter trivial (like empty cells, or only generic worker names)
|
||||
non_empty = [c for c in cells_i if c not in ["", "-", "〃"]]
|
||||
if len(non_empty) <= 1:
|
||||
continue
|
||||
# Generic worker row check
|
||||
if len(non_empty) <= 3 and any(w in str_i for w in ["보통인부", "특별인부", "인부", "기계설비공", "용접공"]) and not any(re.search(r'\d+\.\d{2,}', c) for c in cells_i):
|
||||
nums = re.findall(r'\b\d+(?:\.\d+)?\b', str_i)
|
||||
if all(n in ["1", "2", "3", "4", "5", "10", "20"] for n in nums):
|
||||
continue
|
||||
|
||||
for j in range(i + 2, len(data_rows)): # Non-adjacent (distance >= 2)
|
||||
lno_j, str_j, cells_j = data_rows[j]
|
||||
if cells_i == cells_j:
|
||||
duplicates_in_table.append({
|
||||
'file': fname,
|
||||
'table_start': t_start,
|
||||
'line_1': lno_i,
|
||||
'line_2': lno_j,
|
||||
'row': str_i[:85]
|
||||
})
|
||||
|
||||
print(f"Total Non-Adjacent Duplicates in Same Table: {len(duplicates_in_table)}")
|
||||
for d in duplicates_in_table:
|
||||
print(f"\n[{d['file']}] Table starting at L{d['table_start']}: L{d['line_1']} & L{d['line_2']}")
|
||||
print(f" Row: {d['row']}")
|
||||
@@ -0,0 +1,108 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Scanning for Overlapped / Duplicated Squashed Rows in Same Table ===")
|
||||
|
||||
overlap_cases = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
tables = []
|
||||
cur_t = []
|
||||
cur_start = 0
|
||||
for idx, l in enumerate(lines):
|
||||
line_str = l.strip()
|
||||
if line_str.startswith('|') and line_str.endswith('|'):
|
||||
if not cur_t:
|
||||
cur_start = idx + 1
|
||||
cur_t.append((idx + 1, line_str))
|
||||
else:
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
cur_t = []
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_t))
|
||||
|
||||
for t_start, t_rows in tables:
|
||||
# Separate header and data rows
|
||||
data_rows = []
|
||||
for lno, r_str in t_rows:
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
data_rows.append((lno, r_str, cells))
|
||||
|
||||
if len(data_rows) < 3:
|
||||
continue
|
||||
|
||||
# For each row in the table, check if it is a "squashed row"
|
||||
# (contains multiple words or numbers in a single cell)
|
||||
for r_idx, (lno, r_str, cells) in enumerate(data_rows):
|
||||
# Check if any cell has multi-values
|
||||
is_squashed = False
|
||||
multi_words = []
|
||||
for c in cells:
|
||||
# E.g. 2 or more distinct words (length >= 2) in the cell
|
||||
w_list = [w for w in c.split() if len(w) >= 2 and w not in ["-", "·", "ㆍ"]]
|
||||
if len(w_list) >= 2:
|
||||
is_squashed = True
|
||||
multi_words.extend(w_list)
|
||||
|
||||
if not is_squashed:
|
||||
continue
|
||||
|
||||
# Now, check if these multi_words appear in PRECEDING rows of the SAME table as separate entries!
|
||||
matched_preceding_rows = []
|
||||
for prev_idx in range(r_idx):
|
||||
prev_lno, prev_str, prev_cells = data_rows[prev_idx]
|
||||
prev_words = set(w for c in prev_cells for w in c.split() if len(w) >= 2)
|
||||
# Does prev_words share words with multi_words?
|
||||
overlap = prev_words.intersection(set(multi_words))
|
||||
# Filter out generic words like '인부', '보통인부'
|
||||
overlap = {w for w in overlap if w not in ["인부", "보통인부", "특별인부", "플랜트", "설비공", "용접공", "배관공"]}
|
||||
if overlap:
|
||||
matched_preceding_rows.append((prev_lno, list(overlap), prev_str[:60]))
|
||||
|
||||
if len(matched_preceding_rows) >= 2:
|
||||
# This squashed row matches 2 or more preceding distinct rows in the same table!
|
||||
overlap_cases.append({
|
||||
'file': fname,
|
||||
'line': lno,
|
||||
'table_start': t_start,
|
||||
'squashed_row': r_str[:90],
|
||||
'matched_preceding': matched_preceding_rows
|
||||
})
|
||||
|
||||
print(f"Total squashed rows overlapping with preceding rows in same table: {len(overlap_cases)}")
|
||||
for oc in overlap_cases:
|
||||
print(f"\n[{oc['file']}:L{oc['line']}] (Table starting at L{oc['table_start']})")
|
||||
print(f" Squashed row: {oc['squashed_row']}")
|
||||
print(f" Matches {len(oc['matched_preceding'])} preceding rows:")
|
||||
for plno, ow, pstr in oc['matched_preceding'][:5]:
|
||||
print(f" L{plno} (overlap: {ow}): {pstr}")
|
||||
@@ -0,0 +1,24 @@
|
||||
import json, sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ch13_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md"
|
||||
with open(ch13_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
remaining = []
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if not (line_str.startswith('|') and line_str.endswith('|')):
|
||||
continue
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
# Check if first cell has multiple numbers or multiple specs
|
||||
for c_idx, c in enumerate(cells):
|
||||
toks = c.split()
|
||||
if len(toks) >= 6 and any(t[0].isdigit() for t in toks if t):
|
||||
remaining.append((idx + 1, c_idx, len(toks), c[:80]))
|
||||
break
|
||||
|
||||
print(f"Total multi-token rows in 제13장: {len(remaining)}")
|
||||
for l_num, c_idx, cnt, snip in remaining[:20]:
|
||||
print(f"L{l_num} col{c_idx} (tokens: {cnt}): {snip}")
|
||||
@@ -0,0 +1,119 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
# Common worker / equipment names that do NOT represent condition categories
|
||||
WORKER_EQUIP_KEYWORDS = {
|
||||
'특별인부', '보통인부', '인부', '조력공', '포장공', '철근공', '형틀목공', '목공', '비계공', '석공',
|
||||
'용접공', '배관공', '덕트공', '도장공', '미장공', '방수공', '잠수부', '보링공', '갱부', '착암공',
|
||||
'궤도공', '전공', '통신공', '플랜트배관공', '기계설치공', '제관공', '용접기', '크레인', '트럭',
|
||||
'굴착기', '로더', '덤프트럭', '진동롤러', '머캐덤롤러', '탠덤롤러', '타이어롤러', '살수차', '지게차',
|
||||
'발전기', '공기압축기', '모터그레이더', '불도저', '아스팔트피니셔', '콘크리트페이버', '플레이트콤팩터',
|
||||
'중급기술자', '초급기술자', '고급기술자', '특급기술자', '측량보조', '측량기사', '일반기계운전사'
|
||||
}
|
||||
|
||||
def is_worker_or_equip(text):
|
||||
clean = re.sub(r'[\s\(\)\d\+]+', '', text)
|
||||
return any(w in clean for w in WORKER_EQUIP_KEYWORDS)
|
||||
|
||||
true_grouped_tables = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
tables = []
|
||||
cur_t = []
|
||||
cur_start = 0
|
||||
cur_title = ""
|
||||
|
||||
for idx, l in enumerate(lines):
|
||||
line_str = l.strip()
|
||||
if not line_str.startswith('|'):
|
||||
if line_str and not line_str.startswith('>'):
|
||||
cur_title = line_str
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_title, cur_t))
|
||||
cur_t = []
|
||||
else:
|
||||
if not cur_t:
|
||||
cur_start = idx + 1
|
||||
cur_t.append((idx + 1, line_str))
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_title, cur_t))
|
||||
|
||||
for t_start, t_title, t_rows in tables:
|
||||
data_rows = []
|
||||
for lno, r_str in t_rows:
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
data_rows.append((lno, r_str, cells))
|
||||
|
||||
if len(data_rows) < 3:
|
||||
continue
|
||||
|
||||
# Check classification number in rows
|
||||
has_item_no = False
|
||||
for lno, r_str, cells in data_rows[1:]:
|
||||
c0 = cells[0] if cells else ""
|
||||
if re.match(r'^\d+-\d+(?:-\d+)?', c0) or re.match(r'^[A-Z]\d{4}', c0):
|
||||
has_item_no = True
|
||||
break
|
||||
if has_item_no:
|
||||
continue
|
||||
|
||||
# Analyze first column (and second column) to see if there are multi-row condition groupings
|
||||
groups = {}
|
||||
cur_group = None
|
||||
for lno, r_str, cells in data_rows[1:]:
|
||||
c0 = cells[0] if cells else ""
|
||||
if c0 and not is_worker_or_equip(c0):
|
||||
cur_group = c0
|
||||
if cur_group:
|
||||
groups.setdefault(cur_group, []).append((lno, r_str))
|
||||
|
||||
# If there are 2 or more true non-worker groups, this is a grouped table!
|
||||
if len(groups) >= 2 and any(len(rows) >= 1 for rows in groups.values()):
|
||||
true_grouped_tables.append({
|
||||
'file': fname,
|
||||
'line': t_start,
|
||||
'title': t_title[:60],
|
||||
'groups': list(groups.keys()),
|
||||
'group_details': {k: len(v) for k, v in groups.items()},
|
||||
'total_rows': len(data_rows)
|
||||
})
|
||||
|
||||
print(f"=== Truly Grouped / Condition Branch Tables: {len(true_grouped_tables)} found ===")
|
||||
by_f = {}
|
||||
for tgt in true_grouped_tables:
|
||||
by_f.setdefault(tgt['file'], []).append(tgt)
|
||||
|
||||
for f, tbls in sorted(by_f.items()):
|
||||
print(f"\n[{f}] ({len(tbls)}개)")
|
||||
for t in tbls:
|
||||
g_str = ", ".join([f"{k}({v}행)" for k, v in t['group_details'].items()][:4])
|
||||
print(f" L{t['line']:<5} | {t['title'][:35]:<35} | {g_str}")
|
||||
@@ -0,0 +1,41 @@
|
||||
import os, sys, re, json
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈"
|
||||
|
||||
all_md_files = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for f in files:
|
||||
if f.endswith('.md') and not f.startswith('_') and '개정사항' not in f and '2026년_건설공사_표준품셈.md' not in f:
|
||||
if '01_공통부문\\제8장' in os.path.join(root, f) or '01_공통부문/제8장' in os.path.join(root, f):
|
||||
continue
|
||||
all_md_files.append(os.path.join(root, f))
|
||||
|
||||
text_tables = []
|
||||
|
||||
for fpath in sorted(all_md_files):
|
||||
rel_path = os.path.relpath(fpath, base_dir)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_num = idx + 1
|
||||
line_str = line.strip()
|
||||
# Non-table line containing table keywords
|
||||
if not line_str.startswith('|'):
|
||||
if ('구 분' in line_str or '구분' in line_str) and ('단위' in line_str or '단 위' in line_str or '수량' in line_str or '수 량' in line_str):
|
||||
# Filter out obvious false positives
|
||||
if len(line_str) > 30:
|
||||
text_tables.append({
|
||||
"file": rel_path,
|
||||
"line": line_num,
|
||||
"snippet": line_str[:120]
|
||||
})
|
||||
|
||||
print(f"Total non-table lines with embedded tables: {len(text_tables)}")
|
||||
for tt in text_tables:
|
||||
print(f"{tt['file']} L{tt['line']}: {tt['snippet']}")
|
||||
|
||||
with open('scratch/text_inline_tables.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(text_tables, f, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,105 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_files = [
|
||||
# 02_토목부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제3장_터널공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제8장_지반조사.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md',
|
||||
# 05_유지관리부문
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md',
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md',
|
||||
# 04_기계설비부문 제13장
|
||||
'resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md',
|
||||
]
|
||||
|
||||
print("=== Scanning for Condition/Grouped Tables without Item Classification Numbers ===")
|
||||
|
||||
grouped_tables = []
|
||||
|
||||
for fpath in target_files:
|
||||
fname = os.path.basename(fpath)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
tables = []
|
||||
cur_t = []
|
||||
cur_start = 0
|
||||
cur_title = ""
|
||||
|
||||
for idx, l in enumerate(lines):
|
||||
line_str = l.strip()
|
||||
if not line_str.startswith('|'):
|
||||
if line_str and not line_str.startswith('>'):
|
||||
cur_title = line_str
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_title, cur_t))
|
||||
cur_t = []
|
||||
else:
|
||||
if not cur_t:
|
||||
cur_start = idx + 1
|
||||
cur_t.append((idx + 1, line_str))
|
||||
if cur_t:
|
||||
tables.append((cur_start, cur_title, cur_t))
|
||||
|
||||
for t_start, t_title, t_rows in tables:
|
||||
data_rows = []
|
||||
for lno, r_str in t_rows:
|
||||
cells = [c.strip() for c in r_str.split('|')[1:-1]]
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c):
|
||||
continue
|
||||
if cells and cells[0] in ["비고", "비 고", "주", "[주]"]:
|
||||
continue
|
||||
data_rows.append((lno, r_str, cells))
|
||||
|
||||
if len(data_rows) < 3:
|
||||
continue
|
||||
|
||||
# Check if rows have item numbers like 1-1, 1-2, 1-1-1 or 4-3-1 etc.
|
||||
has_item_numbers = False
|
||||
for lno, r_str, cells in data_rows[1:]:
|
||||
c0 = cells[0] if cells else ""
|
||||
if re.match(r'^\d+-\d+(?:-\d+)?', c0) or re.match(r'^[A-Z]\d{4}', c0):
|
||||
has_item_numbers = True
|
||||
break
|
||||
|
||||
if has_item_numbers:
|
||||
continue # Safe, has explicit classification numbers
|
||||
|
||||
# Check if this table has condition/branch groups
|
||||
# (e.g. multi-level condition headers or blank cells inheriting above groups)
|
||||
has_grouping = False
|
||||
group_col_values = []
|
||||
for lno, r_str, cells in data_rows[1:]:
|
||||
c0 = cells[0] if cells else ""
|
||||
c1 = cells[1] if len(cells) > 1 else ""
|
||||
# Inherited blank cell or condition keyword
|
||||
if c0 == "" or c0 == "〃" or any(k in c0 for k in ["기층", "보조기층", "포장", "Type", "식", "식재", "토사", "암반", "외업", "내업", "직선", "곡선"]):
|
||||
has_grouping = True
|
||||
if c0:
|
||||
group_col_values.append(c0)
|
||||
|
||||
if has_grouping and len(set(group_col_values)) >= 2:
|
||||
grouped_tables.append({
|
||||
'file': fname,
|
||||
'line': t_start,
|
||||
'title': t_title[:60],
|
||||
'rows_count': len(data_rows),
|
||||
'groups': list(set(group_col_values))[:6],
|
||||
'sample_header': data_rows[0][1][:70]
|
||||
})
|
||||
|
||||
print(f"Total Condition/Grouped Tables found in our scope: {len(grouped_tables)}")
|
||||
for gt in grouped_tables:
|
||||
print(f"\n[{gt['file']}:L{gt['line']}] {gt['title']}")
|
||||
print(f" Header: {gt['sample_header']}")
|
||||
print(f" Groups: {gt['groups']} (Rows: {gt['rows_count']})")
|
||||
@@ -0,0 +1,316 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# 1. 03_건축부문/제1장_철골공사.md
|
||||
ch1_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제1장_철골공사.md"
|
||||
with open(ch1_path, 'r', encoding='utf-8') as f:
|
||||
text1 = f.read()
|
||||
|
||||
target_1_4_4 = "1-4-4 경량형강철골조 조립설치(ton당)수 량구 분단 위비 고내 력 식비 내 력 식철공인15.9312.54[주] ① 본 품은 건축구조용 표면처리 경량형강을 기준한 것이다."
|
||||
repl_1_4_4 = """1-4-4 경량형강철골조 조립설치
|
||||
|
||||
(ton당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | | 비 고 |
|
||||
|---|---|---|---|---|
|
||||
| | | 내 력 식 | 비 내 력 식 | |
|
||||
| 철공 | 인 | 15.93 | 12.54 | |
|
||||
|
||||
[주] ① 본 품은 건축구조용 표면처리 경량형강을 기준한 것이다."""
|
||||
text1 = text1.replace(target_1_4_4, repl_1_4_4)
|
||||
with open(ch1_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text1)
|
||||
print("Updated 제1장_철골공사.md successfully.")
|
||||
|
||||
# 2. 03_건축부문/제2장_조적공사.md
|
||||
ch2_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제2장_조적공사.md"
|
||||
with open(ch2_path, 'r', encoding='utf-8') as f:
|
||||
text2 = f.read()
|
||||
|
||||
target_2_1_4 = "2-1-4 아치쌓기 치장줄눈 설치('13년 보완)(1,000매당)수 량 (벽두께)구 분단 위1.0B1.5B줄눈공인0.40.3[주] ① 본 품은 아치쌓기 구간에 치장줄눈을 채우는 기준이다."
|
||||
repl_2_1_4 = """2-1-4 아치쌓기 치장줄눈 설치('13년 보완)
|
||||
|
||||
(1,000매당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (벽두께) | |
|
||||
|---|---|---|---|
|
||||
| | | 1.0B | 1.5B |
|
||||
| 줄눈공 | 인 | 0.4 | 0.3 |
|
||||
|
||||
[주] ① 본 품은 아치쌓기 구간에 치장줄눈을 채우는 기준이다."""
|
||||
|
||||
target_2_3_2 = "2-3-2 ALC패널 설치('13, '25년 보완)(일당)구 분단 위수 량패널두께(mm)시공량(㎡)7522조적공인3100191251615014보통인부인11751320011[주] ① 본 품은 경량콘크리트 패널의 내벽설치 기준이다."
|
||||
repl_2_3_2 = """2-3-2 ALC패널 설치('13, '25년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 패널두께(mm) | 시공량(㎡) |
|
||||
|---|---|---|---|---|
|
||||
| 조적공 | 인 | 3 | 75 | 22 |
|
||||
| 보통인부 | 인 | 1 | 100 | 19 |
|
||||
| | | | 125 | 16 |
|
||||
| | | | 150 | 14 |
|
||||
| | | | 175 | 13 |
|
||||
| | | | 200 | 11 |
|
||||
|
||||
[주] ① 본 품은 경량콘크리트 패널의 내벽설치 기준이다."""
|
||||
|
||||
text2 = text2.replace(target_2_1_4, repl_2_1_4)
|
||||
text2 = text2.replace(target_2_3_2, repl_2_3_2)
|
||||
with open(ch2_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text2)
|
||||
print("Updated 제2장_조적공사.md successfully.")
|
||||
|
||||
# 3. 03_건축부문/제3장_타일공사.md
|
||||
ch3_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제3장_타일공사.md"
|
||||
with open(ch3_path, 'r', encoding='utf-8') as f:
|
||||
text3 = f.read()
|
||||
|
||||
target_3_2_3 = "3-2-3 접착 붙이기('98년 신설, '13, '16, '20, '25년 보완)(일당)구 분단 위수 량타일규격(㎡)시공량(㎡)0.04~0.10이하25타일공인20.11~0.20이하26보통인부인10.21~0.40이하28"
|
||||
repl_3_2_3 = """3-2-3 접착 붙이기('98년 신설, '13, '16, '20, '25년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 타일규격(㎡) | 시공량(㎡) |
|
||||
|---|---|---|---|---|
|
||||
| 타일공 | 인 | 2 | 0.04~0.10이하 | 25 |
|
||||
| 보통인부 | 인 | 1 | 0.11~0.20이하 | 26 |
|
||||
| | | | 0.21~0.40이하 | 28 |"""
|
||||
|
||||
text3 = text3.replace(target_3_2_3, repl_3_2_3)
|
||||
with open(ch3_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text3)
|
||||
print("Updated 제3장_타일공사.md successfully.")
|
||||
|
||||
# 4. 03_건축부문/제4장_목공사.md
|
||||
ch4_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제4장_목공사.md"
|
||||
with open(ch4_path, 'r', encoding='utf-8') as f:
|
||||
text4 = f.read()
|
||||
|
||||
target_4_3_3 = "4-3-3 목재데크틀 설치('24년 보완)(일당)구 분단 위수 량시공량(ton)철공인3평구조용접공인10.40보통인부인1철공인4계단구조용접공인10.32보통인부인2[주] ① 본 품은 철물(각관 및 형강)을 사용하여 데크틀(H-Beam 등 철골류 제외)을 설치하는 기준이다."
|
||||
repl_4_3_3 = """4-3-3 목재데크틀 설치('24년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(ton) |
|
||||
|---|---|---|---|
|
||||
| **평구조** | | | |
|
||||
| 철공 | 인 | 3 | 0.40 |
|
||||
| 용접공 | 인 | 1 | |
|
||||
| 보통인부 | 인 | 1 | |
|
||||
| **계단구조** | | | |
|
||||
| 철공 | 인 | 4 | 0.32 |
|
||||
| 용접공 | 인 | 1 | |
|
||||
| 보통인부 | 인 | 2 | |
|
||||
|
||||
[주] ① 본 품은 철물(각관 및 형강)을 사용하여 데크틀(H-Beam 등 철골류 제외)을 설치하는 기준이다."""
|
||||
text4 = text4.replace(target_4_3_3, repl_4_3_3)
|
||||
with open(ch4_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text4)
|
||||
print("Updated 제4장_목공사.md successfully.")
|
||||
|
||||
# 5. 03_건축부문/제5장_수장공사.md
|
||||
ch5_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제5장_수장공사.md"
|
||||
with open(ch5_path, 'r', encoding='utf-8') as f:
|
||||
text5 = f.read()
|
||||
|
||||
target_5_4_2 = "5-4-2 단열재 접착제 붙이기('22, '24년 보완)(일당)시공량(㎡)구 분단 위수 량단열두께(mm)벽천장50이하4740내 장 공인2100이하4235200이하4032보통인부인1300이하3830[주] ① 본 품은 바탕면에 접착제를 사용하여 단열재를 설치하는 기준이다."
|
||||
repl_5_4_2 = """5-4-2 단열재 접착제 붙이기('22, '24년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 단열두께(mm) | 시공량(㎡) | |
|
||||
|---|---|---|---|---|---|
|
||||
| | | | | 벽 | 천장 |
|
||||
| 내장공 | 인 | 2 | 50이하 | 47 | 40 |
|
||||
| 보통인부 | 인 | 1 | 100이하 | 42 | 35 |
|
||||
| | | | 200이하 | 40 | 32 |
|
||||
| | | | 300이하 | 38 | 30 |
|
||||
|
||||
[주] ① 본 품은 바탕면에 접착제를 사용하여 단열재를 설치하는 기준이다."""
|
||||
|
||||
target_5_4_9 = "5-4-9 외벽단열공법('99년 신설, '15, '22년 보완)(㎡당)단열두께(mm)구 분단 위60㎜이하100㎜이하200㎜이하내장공인0.0600.0630.081미장공인0.0380.0400.052보통인부인0.0310.0330.042"
|
||||
repl_5_4_9 = """5-4-9 외벽단열공법('99년 신설, '15, '22년 보완)
|
||||
|
||||
(㎡당)
|
||||
|
||||
| 구 분 | 단 위 | 단열두께(mm) | | |
|
||||
|---|---|---|---|---|
|
||||
| | | 60㎜이하 | 100㎜이하 | 200㎜이하 |
|
||||
| 내장공 | 인 | 0.060 | 0.063 | 0.081 |
|
||||
| 미장공 | 인 | 0.038 | 0.040 | 0.052 |
|
||||
| 보통인부 | 인 | 0.031 | 0.033 | 0.042 |"""
|
||||
|
||||
# L149 주석 안 줄눈재 설치
|
||||
target_5_joint = "| 비 고 | - 줄눈재 설치가 필요한 경우 다음을 적용한다. (일당) 구 분 단 위 수 량 시공량(m) 내 장 공 인 1 37"
|
||||
repl_5_joint = """| 비 고 | - 줄눈재 설치가 필요한 경우 다음을 적용한다. (일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(m) |
|
||||
|---|---|---|---|
|
||||
| 내장공 | 인 | 1 | 37 |"""
|
||||
|
||||
text5 = text5.replace(target_5_4_2, repl_5_4_2)
|
||||
text5 = text5.replace(target_5_4_9, repl_5_4_9)
|
||||
text5 = text5.replace(target_5_joint, repl_5_joint)
|
||||
with open(ch5_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text5)
|
||||
print("Updated 제5장_수장공사.md successfully.")
|
||||
|
||||
# 6. 03_건축부문/제6장_방수공사.md
|
||||
ch6_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제6장_방수공사.md"
|
||||
with open(ch6_path, 'r', encoding='utf-8') as f:
|
||||
text6 = f.read()
|
||||
|
||||
target_6_1_4 = "6-1-4 방수층 누름철물 설치('18년 신설)(m당)구 분단 위수 량방수공인0.011보통인부인0.011[주] 본 품은 시트 및 보호재 상부의 누름철물 마감 작업을 기준한 것이다."
|
||||
repl_6_1_4 = """6-1-4 방수층 누름철물 설치('18년 신설)
|
||||
|
||||
(m당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 방수공 | 인 | 0.011 |
|
||||
| 보통인부 | 인 | 0.011 |
|
||||
|
||||
[주] 본 품은 시트 및 보호재 상부의 누름철물 마감 작업을 기준한 것이다."""
|
||||
|
||||
target_6_6_2 = "6-6-2 줄눈 절단('18년 신설)(m당)구 분규 격단 위수 량방수공인0.005보통인부인0.001커터320~400㎜hr0.017[주] ① 본 품은 옥상 보호콘크리트의 절단을 기준한 것이다."
|
||||
repl_6_6_2 = """6-6-2 줄눈 절단('18년 신설)
|
||||
|
||||
(m당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 |
|
||||
|---|---|---|---|
|
||||
| 방수공 | | 인 | 0.005 |
|
||||
| 보통인부 | | 인 | 0.001 |
|
||||
| 커터 | 320~400㎜ | hr | 0.017 |
|
||||
|
||||
[주] ① 본 품은 옥상 보호콘크리트의 절단을 기준한 것이다."""
|
||||
|
||||
target_6_6_3 = "6-6-3 줄눈 설치('18년 신설)(m당)구 분단 위수 량방수공인0.005보통인부인0.001[주] ① 본 품은 옥상 보호콘크리트의 줄눈 설치를 기준한 것이다."
|
||||
repl_6_6_3 = """6-6-3 줄눈 설치('18년 신설)
|
||||
|
||||
(m당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 방수공 | 인 | 0.005 |
|
||||
| 보통인부 | 인 | 0.001 |
|
||||
|
||||
[주] ① 본 품은 옥상 보호콘크리트의 줄눈 설치를 기준한 것이다."""
|
||||
|
||||
text6 = text6.replace(target_6_1_4, repl_6_1_4)
|
||||
text6 = text6.replace(target_6_6_2, repl_6_6_2)
|
||||
text6 = text6.replace(target_6_6_3, repl_6_6_3)
|
||||
with open(ch6_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text6)
|
||||
print("Updated 제6장_방수공사.md successfully.")
|
||||
|
||||
# 7. 03_건축부문/제7장_지붕및홈통공사.md
|
||||
ch7_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제7장_지붕및홈통공사.md"
|
||||
with open(ch7_path, 'r', encoding='utf-8') as f:
|
||||
text7 = f.read()
|
||||
|
||||
target_7_3_1 = "7-3 드레인7-3-1 루프드레인 설치('16년 보완)(개소당)구 분단 위수 량배관공인0.17보통인부인0.04[주] ① 본 품은 루프드레인 규격 ø100㎜∼150㎜의 설치 기준이다."
|
||||
repl_7_3_1 = """7-3 드레인
|
||||
|
||||
7-3-1 루프드레인 설치('16년 보완)
|
||||
|
||||
(개소당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 배관공 | 인 | 0.17 |
|
||||
| 보통인부 | 인 | 0.04 |
|
||||
|
||||
[주] ① 본 품은 루프드레인 규격 ø100㎜∼150㎜의 설치 기준이다."""
|
||||
text7 = text7.replace(target_7_3_1, repl_7_3_1)
|
||||
with open(ch7_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text7)
|
||||
print("Updated 제7장_지붕및홈통공사.md successfully.")
|
||||
|
||||
# 8. 03_건축부문/제9장_미장공사.md
|
||||
ch9_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제9장_미장공사.md"
|
||||
with open(ch9_path, 'r', encoding='utf-8') as f:
|
||||
text9 = f.read()
|
||||
|
||||
target_9_1_5 = "9-1-5 라스 붙임('17년 신설)(10㎡당)구 분단 위수 량미장공인0.14[주] 본 품은 미장면 보강을 위해 미장 시 메탈라스 또는 유리섬유메쉬를 붙이는 작업을 기준한 것이다."
|
||||
repl_9_1_5 = """9-1-5 라스 붙임('17년 신설)
|
||||
|
||||
(10㎡당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 미장공 | 인 | 0.14 |
|
||||
|
||||
[주] 본 품은 미장면 보강을 위해 미장 시 메탈라스 또는 유리섬유메쉬를 붙이는 작업을 기준한 것이다."""
|
||||
|
||||
target_9_3_2 = "9-3-2 창호주위 발포우레탄 충전('14년 신설, '20년 보완)(10m당)구 분단 위수 량미장공인0.08보통인부인0.03[주] ① 본 품은 창호틀 주위에 발포우레탄을 사용하여 충전하는 기준이다."
|
||||
repl_9_3_2 = """9-3-2 창호주위 발포우레탄 충전('14년 신설, '20년 보완)
|
||||
|
||||
(10m당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 미장공 | 인 | 0.08 |
|
||||
| 보통인부 | 인 | 0.03 |
|
||||
|
||||
[주] ① 본 품은 창호틀 주위에 발포우레탄을 사용하여 충전하는 기준이다."""
|
||||
|
||||
target_9_door = "| 비 고 | - 문선을 설치하는 경우 다음 품을 추가 계상한다. (m당) 구 분 단 위 수 량 창 호 공 인 0.010"
|
||||
repl_9_door = """| 비 고 | - 문선을 설치하는 경우 다음 품을 추가 계상한다. (m당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 창호공 | 인 | 0.010 |"""
|
||||
|
||||
text9 = text9.replace(target_9_1_5, repl_9_1_5)
|
||||
text9 = text9.replace(target_9_3_2, repl_9_3_2)
|
||||
text9 = text9.replace(target_9_door, repl_9_door)
|
||||
with open(ch9_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text9)
|
||||
print("Updated 제9장_미장공사.md successfully.")
|
||||
|
||||
# 9. 03_건축부문/제10장_창호및유리공사.md
|
||||
ch10_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제10장_창호및유리공사.md"
|
||||
with open(ch10_path, 'r', encoding='utf-8') as f:
|
||||
text10 = f.read()
|
||||
|
||||
target_10_3_2 = "10-3-2 커튼월유리 설치('14, '20, '26년 보완)(㎡당)수 량구 분단 위12㎜이하16㎜이하18㎜이하22㎜이하24㎜이하28㎜이하유리공인0.1200.1310.1370.1390.1450.155보통인부인0.0200.0210.0220.0230.0240.025[주] ①본 품은 커튼월 프레임에 구조용실란트를 사용하여 복층유리를 부착하는 기준이다."
|
||||
repl_10_3_2 = """10-3-2 커튼월유리 설치('14, '20, '26년 보완)
|
||||
|
||||
(㎡당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | | | | | |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| | | 12㎜이하 | 16㎜이하 | 18㎜이하 | 22㎜이하 | 24㎜이하 | 28㎜이하 |
|
||||
| 유리공 | 인 | 0.120 | 0.131 | 0.137 | 0.139 | 0.145 | 0.155 |
|
||||
| 보통인부 | 인 | 0.020 | 0.021 | 0.022 | 0.023 | 0.024 | 0.025 |
|
||||
|
||||
[주] ①본 품은 커튼월 프레임에 구조용실란트를 사용하여 복층유리를 부착하는 기준이다."""
|
||||
text10 = text10.replace(target_10_3_2, repl_10_3_2)
|
||||
with open(ch10_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text10)
|
||||
print("Updated 제10장_창호및유리공사.md successfully.")
|
||||
|
||||
# 10. 03_건축부문/제11장_칠공사.md
|
||||
ch11_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/03_건축부문/제11장_칠공사.md"
|
||||
with open(ch11_path, 'r', encoding='utf-8') as f:
|
||||
text11 = f.read()
|
||||
|
||||
target_11_3_3 = "11-3-3 석재도료칠('14년 신설)(100㎡당)구 분규 격단 위줄눈무늬(無)줄눈무늬(有)도장공인0.6200.810보통인부인0.1000.130고소작업차3tonhr3.2704.280[주] ① 본 품은 석재가 포함된 도료를 1회 뿜칠하는 기준이다."
|
||||
repl_11_3_3 = """11-3-3 석재도료칠('14년 신설)
|
||||
|
||||
(100㎡당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 줄눈무늬(無) | 줄눈무늬(有) |
|
||||
|---|---|---|---|---|
|
||||
| 도장공 | | 인 | 0.620 | 0.810 |
|
||||
| 보통인부 | | 인 | 0.100 | 0.130 |
|
||||
| 고소작업차 | 3ton | hr | 3.270 | 4.280 |
|
||||
|
||||
[주] ① 본 품은 석재가 포함된 도료를 1회 뿜칠하는 기준이다."""
|
||||
text11 = text11.replace(target_11_3_3, repl_11_3_3)
|
||||
with open(ch11_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text11)
|
||||
print("Updated 제11장_칠공사.md successfully.")
|
||||
@@ -0,0 +1,89 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ch13_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md"
|
||||
with open(ch13_path, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
# 1. L36: 배관용 탄소강관 KS D3507 (350 ~ 500)
|
||||
old_l36 = "| 배 관 용 탄 소 강 관 KS D3507 | 350 〃 〃 400 〃 〃 450 〃 〃 500 〃 〃 〃 〃 | 355.6 〃 〃 406.4 〃 〃 457.2 〃 〃 508.0 〃 〃 〃 〃 | 6.0 6.4 7.9 6.0 6.4 7.9 6.0 6.4 7.9 6.0 6.4 7.9 8.7 9.5 | 51.7 55.1 67.7 59.2 63.1 77.6 66.8 71.1 87.5 74.3 79.2 97.4 107 117 | 19.3 18.7 16.8 19.5 19.5 16.7 19.4 19.5 16.7 19.5 19.4 16.6 16.2 13.3 | 9.7 9.3 8.4 9.3 8.4 8.4 9.3 8.3 8.3 9.2 8.3 8.3 7.6 9.5 | 9.7 9.3 8.4 9.3 8.4 8.4 9.3 8.3 8.3 9.2 8.3 8.3 7.6 9.5 | 19.3 18.7 16.8 19.5 19.5 16.7 19.4 19.5 16.7 19.5 19.4 16.6 16.2 13.3 |"
|
||||
s36 = "350 〃 〃 400 〃 〃 450 〃 〃 500 〃 〃 〃 〃".split()
|
||||
o36 = "355.6 〃 〃 406.4 〃 〃 457.2 〃 〃 508.0 〃 〃 〃 〃".split()
|
||||
t36 = "6.0 6.4 7.9 6.0 6.4 7.9 6.0 6.4 7.9 6.0 6.4 7.9 8.7 9.5".split()
|
||||
w36 = "51.7 55.1 67.7 59.2 63.1 77.6 66.8 71.1 87.5 74.3 79.2 97.4 107 117".split()
|
||||
yw36 = "19.3 18.7 16.8 19.5 19.5 16.7 19.4 19.5 16.7 19.5 19.4 16.6 16.2 13.3".split()
|
||||
yb36 = "9.7 9.3 8.4 9.3 8.4 8.4 9.3 8.3 8.3 9.2 8.3 8.3 7.6 9.5".split()
|
||||
yi36 = "9.7 9.3 8.4 9.3 8.4 8.4 9.3 8.3 8.3 9.2 8.3 8.3 7.6 9.5".split()
|
||||
nb36 = "19.3 18.7 16.8 19.5 19.5 16.7 19.4 19.5 16.7 19.5 19.4 16.6 16.2 13.3".split()
|
||||
|
||||
rows_36 = []
|
||||
for i in range(len(s36)):
|
||||
div_name = "배관용 탄소강관 KS D3507" if i == 0 else ""
|
||||
row = f"| {div_name} | {s36[i]} | {o36[i]} | {t36[i]} | {w36[i]} | {yw36[i]} | {yb36[i]} | {yi36[i]} | {nb36[i]} |"
|
||||
rows_36.append(row)
|
||||
new_l36 = "\n".join(rows_36)
|
||||
text = text.replace(old_l36, new_l36)
|
||||
|
||||
# 2. L45: 배관용 탄소강관 KS D3507 (550 ~ 600)
|
||||
old_l45 = "| 배 관 용 탄 소 강 관 KS D3507 | 550 〃 〃 〃 600 〃 〃 〃 | 558.8 〃 〃 〃 609.6 〃 〃 〃 | 6.0 6.4 7.9 9.5 6.0 6.4 7.1 7.9 | 81.8 87.2 107 129 89.0 95.2 106 117 | 19.1 18.5 16.7 15.1 19.1 18.4 17.5 16.6 | 9.5 9.2 8.3 7.6 9.5 9.2 8.7 8.3 | 9.5 9.2 8.3 7.6 9.5 9.2 8.7 8.3 | 19.1 18.5 16.7 15.1 19.1 18.4 17.5 16.6 |"
|
||||
s45 = "550 〃 〃 〃 600 〃 〃 〃".split()
|
||||
o45 = "558.8 〃 〃 〃 609.6 〃 〃 〃".split()
|
||||
t45 = "6.0 6.4 7.9 9.5 6.0 6.4 7.1 7.9".split()
|
||||
w45 = "81.8 87.2 107 129 89.0 95.2 106 117".split()
|
||||
yw45 = "19.1 18.5 16.7 15.1 19.1 18.4 17.5 16.6".split()
|
||||
yb45 = "9.5 9.2 8.3 7.6 9.5 9.2 8.7 8.3".split()
|
||||
yi45 = "9.5 9.2 8.3 7.6 9.5 9.2 8.7 8.3".split()
|
||||
nb45 = "19.1 18.5 16.7 15.1 19.1 18.4 17.5 16.6".split()
|
||||
|
||||
rows_45 = []
|
||||
for i in range(len(s45)):
|
||||
div_name = "배관용 탄소강관 KS D3507" if i == 0 else ""
|
||||
row = f"| {div_name} | {s45[i]} | {o45[i]} | {t45[i]} | {w45[i]} | {yw45[i]} | {yb45[i]} | {yi45[i]} | {nb45[i]} |"
|
||||
rows_45.append(row)
|
||||
new_l45 = "\n".join(rows_45)
|
||||
text = text.replace(old_l45, new_l45)
|
||||
|
||||
# 3. L308: 13-1-2 관만곡 설치 (PDF 732쪽 대조 18행 전개)
|
||||
old_l308 = "| ø25 32 40 50 65 80 90 100 125 150 200 250 300 350 400 450 500 600 | | 0.035 0.040 0.045 0.050 0.060 0.070 0.085 0.100 0.130 0.160 0.20 0.28 0.38 0.48 0.63 0.81 1.00 1.50 | 0.015 0.015 0.020 0.020 0.025 0.030 0.035 0.045 0.055 0.070 0.09 0.12 0.16 0.20 0.27 0.35 0.45 0.75 | 0.040 0.045 0.055 0.065 0.075 0.085 0.110 0.120 0.130 0.170 0.25 0.32 0.45 0.57 0.76 0.96 1.19 1.70 | 0.020 0.020 0.020 0.025 0.030 0.035 0.045 0.050 0.060 0.075 0.11 0.14 0.19 0.24 0.32 0.42 0.52 0.75 | 0.040 0.050 0.060 0.075 0.090 0.100 0.110 0.140 0.170 0.200 0.28 0.38 0.53 0.77 1.10 1.55 | 0.020 0.020 0.025 0.030 0.035 0.045 0.050 0.060 0.075 0.085 0.12 0.17 0.23 0.33 0.51 0.73 | 0.050 0.055 0.065 0.075 0.100 0.120 0.135 0.160 0.200 0.240 0.32 0.46 0.63 1.00 1.40 1.75 | 0.020 0.025 0.030 0.035 0.045 0.050 0.060 0.070 0.085 0.110 0.14 0.20 0.27 0.43 0.60 0.75 | 0.055 0.060 0.065 0.080 0.100 0.115 0.130 0.150 0.200 0.270 0.28 0.38 0.52 0.68 0.90 1.15 1.46 2.30 | 0.020 0.025 0.030 0.035 0.040 0.045 0.055 0.065 0.080 0.095 0.12 0.16 0.22 0.29 0.38 0.49 0.62 0.90 |"
|
||||
cols_308 = [c.strip().split() for c in old_l308.split('|')[1:-1]]
|
||||
specs_308 = cols_308[0] # 18 specs
|
||||
rows_308 = []
|
||||
for i in range(len(specs_308)):
|
||||
r = [specs_308[i], ""]
|
||||
# cols 2..5: 18 values
|
||||
for c_idx in [2, 3, 4, 5]:
|
||||
r.append(cols_308[c_idx][i])
|
||||
# cols 6..9: 16 values (500, 600 have no U-곡관)
|
||||
for c_idx in [6, 7, 8, 9]:
|
||||
r.append(cols_308[c_idx][i] if i < 16 else "-")
|
||||
# cols 10..11: 18 values
|
||||
for c_idx in [10, 11]:
|
||||
r.append(cols_308[c_idx][i])
|
||||
rows_308.append("| " + " | ".join(r) + " |")
|
||||
new_l308 = "\n".join(rows_308)
|
||||
text = text.replace(old_l308, new_l308)
|
||||
|
||||
# 4. L345: 13-1-2 관만곡 U곡관 (PDF 733쪽 대조 18행 전개)
|
||||
old_l345 = "| ø25 32 40 50 65 80 90 100 125 150 200 250 300 350 400 450 500 600 | | 0.075 0.090 0.110 0.130 0.160 0.190 0.230 0.260 0.320 0.380 0.540 0.740 1.000 1.450 2.170 | 0.035 0.040 0.045 0.055 0.070 0.080 0.095 0.110 0.130 0.160 0.230 0.310 0.420 0.620 0.930 | 0.100 0.120 0.140 0.170 0.200 0.230 0.270 0.310 0.380 0.440 0.560 0.860 1.200 1.660 2.200 | 0.040 0.050 0.060 0.070 0.080 0.095 0.110 0.130 0.160 0.190 0.240 0.360 0.510 0.710 0.940 | 0.100 0.110 0.130 0.150 0.180 0.220 0.270 0.320 0.380 0.480 0.590 0.840 1.330 1.830 - | 0.040 0.050 0.060 0.070 0.080 0.095 0.110 0.125 0.160 0.200 0.250 0.360 0.570 0.830 - | 0.120 0.140 0.160 0.190 0.220 0.250 0.290 0.330 0.430 0.540 0.700 0.990 1.400 - - | 0.050 0.060 0.070 0.080 0.095 0.110 0.125 0.145 0.190 0.230 0.300 0.420 0.510 - - |"
|
||||
cols_345 = [c.strip().split() for c in old_l345.split('|')[1:-1]]
|
||||
specs_345 = cols_345[0] # 18 specs
|
||||
rows_345 = []
|
||||
for i in range(len(specs_345)):
|
||||
r = [specs_345[i], ""]
|
||||
# cols 2..5: 15 values (25..400), 450..600 are "-"
|
||||
for c_idx in [2, 3, 4, 5]:
|
||||
r.append(cols_345[c_idx][i] if i < 15 else "-")
|
||||
# cols 6..7: 14 values (25..350), 400..600 are "-"
|
||||
for c_idx in [6, 7]:
|
||||
r.append(cols_345[c_idx][i] if i < 14 else "-")
|
||||
# cols 8..9: 13 values (25..300), 350..600 are "-"
|
||||
for c_idx in [8, 9]:
|
||||
r.append(cols_345[c_idx][i] if i < 13 else "-")
|
||||
rows_345.append("| " + " | ".join(r) + " |")
|
||||
new_l345 = "\n".join(rows_345)
|
||||
text = text.replace(old_l345, new_l345)
|
||||
|
||||
with open(ch13_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
print("Updated L36, L45, L308, L345 in 제13장_플랜트설비공사.md successfully.")
|
||||
@@ -0,0 +1,53 @@
|
||||
import os, sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ch13_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md"
|
||||
with open(ch13_path, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
# 1. Line 83
|
||||
old_l83 = "| 배 관 용 탄 소 강 관 KS D3507 | 600 〃 650 〃 〃 〃 〃 700 〃 〃 〃 〃 750 〃 〃 〃 800 〃 〃 〃 850 〃 | 609.6 〃 660.4 〃 〃 〃 〃 711.2 〃 〃 〃 〃 762.0 〃 〃 〃 812.8 〃 〃 〃 863.6 〃 | 9.5 10.3 6.0 6.4 7.1 7.9 11.1 6.0 6.4 7.1 7.9 11.9 6.4 7.1 7.9 11.9 6.4 7.1 7.9 11.9 6.4 7.1 | 141 152 96.8 103 114 127 178 104 111 123 137 205 119 132 147 220 127 141 157 235 135 150 | 15.1 14.5 19.0 18.4 17.5 16.6 14.0 19.0 18.4 17.5 16.5 13.5 18.4 17.5 16.5 13.5 18.3 17.4 16.5 13.5 18.3 17.4 | 7.6 7.3 9.5 9.2 8.8 8.3 7.0 9.5 9.2 8.7 8.3 6.7 9.2 8.7 8.3 6.7 9.2 8.7 8.2 6.7 9.2 8.7 | 7.6 7.3 9.5 9.2 8.8 8.3 7.0 9.5 9.2 8.7 8.3 6.7 9.2 8.7 8.3 6.7 9.2 8.7 8.2 6.7 9.2 8.7 | 15.1 14.5 19.0 18.4 17.5 16.6 14.0 19.0 18.4 17.5 16.5 13.5 18.4 17.5 16.5 13.5 18.3 17.4 16.5 13.5 18.3 17.4 |"
|
||||
|
||||
c_specs = "600 〃 650 〃 〃 〃 〃 700 〃 〃 〃 〃 750 〃 〃 〃 800 〃 〃 〃 850 〃".split()
|
||||
c_out = "609.6 〃 660.4 〃 〃 〃 〃 711.2 〃 〃 〃 〃 762.0 〃 〃 〃 812.8 〃 〃 〃 863.6 〃".split()
|
||||
c_th = "9.5 10.3 6.0 6.4 7.1 7.9 11.1 6.0 6.4 7.1 7.9 11.9 6.4 7.1 7.9 11.9 6.4 7.1 7.9 11.9 6.4 7.1".split()
|
||||
c_wt = "141 152 96.8 103 114 127 178 104 111 123 137 205 119 132 147 220 127 141 157 235 135 150".split()
|
||||
c_yw = "15.1 14.5 19.0 18.4 17.5 16.6 14.0 19.0 18.4 17.5 16.5 13.5 18.4 17.5 16.5 13.5 18.3 17.4 16.5 13.5 18.3 17.4".split()
|
||||
c_yb = "7.6 7.3 9.5 9.2 8.8 8.3 7.0 9.5 9.2 8.7 8.3 6.7 9.2 8.7 8.3 6.7 9.2 8.7 8.2 6.7 9.2 8.7".split()
|
||||
c_yi = "7.6 7.3 9.5 9.2 8.8 8.3 7.0 9.5 9.2 8.7 8.3 6.7 9.2 8.7 8.3 6.7 9.2 8.7 8.2 6.7 9.2 8.7".split()
|
||||
c_nb = "15.1 14.5 19.0 18.4 17.5 16.6 14.0 19.0 18.4 17.5 16.5 13.5 18.4 17.5 16.5 13.5 18.3 17.4 16.5 13.5 18.3 17.4".split()
|
||||
|
||||
rows_l83 = []
|
||||
for i in range(len(c_specs)):
|
||||
div_name = "배관용 탄소강관 KS D3507" if i == 0 else ""
|
||||
row = f"| {div_name} | {c_specs[i]} | {c_out[i]} | {c_th[i]} | {c_wt[i]} | {c_yw[i]} | {c_yb[i]} | {c_yi[i]} | {c_nb[i]} |"
|
||||
rows_l83.append(row)
|
||||
new_l83 = "\n".join(rows_l83)
|
||||
|
||||
text = text.replace(old_l83, new_l83)
|
||||
|
||||
# 2. Line 176
|
||||
old_l176 = "| 압력배관용 탄 소 강 관 KSD3562 SCH#40 | 15 20 25 32 40 50 65 80 90 100 125 150 200 250 300 350 400 450 500 | 21.7 27.2 34.0 42.7 48.6 60.5 76.3 89.1 101.6 114.3 139.8 165.2 216.3 267.4 318.5 355.6 406.4 457.2 508.0 | 2.8 2.9 3.4 3.6 3.7 3.9 5.2 5.5 5.7 6.0 6.6 7.1 8.2 9.3 10.3 11.1 12.7 14.3 15.1 | 1.31 1.74 2.57 3.47 4.10 5.44 9.12 11.3 13.5 16.0 21.7 27.7 42.1 59.2 78.3 94.3 123 156 184 | 47.0 41.8 35.2 32.0 30.4 28.2 23.4 22.2 21.5 20.7 19.3 18.4 16.0 15.7 14.8 14.2 13.3 12.5 12.1 | 23.5 20.9 17.6 16.0 15.2 14.1 11.7 11.1 10.7 10.3 9.7 9.2 8.0 7.8 7.4 7.1 6.6 6.2 6.0 | 23.5 20.9 17.6 16.0 15.2 14.1 11.7 11.1 10.7 10.3 9.7 9.2 8.0 7.8 7.4 7.1 6.6 6.2 6.0 | 47.0 41.8 35.2 32.0 30.4 28.2 23.4 22.2 21.5 20.7 19.3 18.4 16.0 15.7 14.8 14.2 13.3 12.5 12.1 |"
|
||||
|
||||
p_specs = "15 20 25 32 40 50 65 80 90 100 125 150 200 250 300 350 400 450 500".split()
|
||||
p_out = "21.7 27.2 34.0 42.7 48.6 60.5 76.3 89.1 101.6 114.3 139.8 165.2 216.3 267.4 318.5 355.6 406.4 457.2 508.0".split()
|
||||
p_th = "2.8 2.9 3.4 3.6 3.7 3.9 5.2 5.5 5.7 6.0 6.6 7.1 8.2 9.3 10.3 11.1 12.7 14.3 15.1".split()
|
||||
p_wt = "1.31 1.74 2.57 3.47 4.10 5.44 9.12 11.3 13.5 16.0 21.7 27.7 42.1 59.2 78.3 94.3 123 156 184".split()
|
||||
p_yw = "47.0 41.8 35.2 32.0 30.4 28.2 23.4 22.2 21.5 20.7 19.3 18.4 16.0 15.7 14.8 14.2 13.3 12.5 12.1".split()
|
||||
p_yb = "23.5 20.9 17.6 16.0 15.2 14.1 11.7 11.1 10.7 10.3 9.7 9.2 8.0 7.8 7.4 7.1 6.6 6.2 6.0".split()
|
||||
p_yi = "23.5 20.9 17.6 16.0 15.2 14.1 11.7 11.1 10.7 10.3 9.7 9.2 8.0 7.8 7.4 7.1 6.6 6.2 6.0".split()
|
||||
p_nb = "47.0 41.8 35.2 32.0 30.4 28.2 23.4 22.2 21.5 20.7 19.3 18.4 16.0 15.7 14.8 14.2 13.3 12.5 12.1".split()
|
||||
|
||||
rows_l176 = []
|
||||
for i in range(len(p_specs)):
|
||||
div_name = "압력배관용 탄소강관 KSD3562 SCH#40" if i == 0 else ""
|
||||
row = f"| {div_name} | {p_specs[i]} | {p_out[i]} | {p_th[i]} | {p_wt[i]} | {p_yw[i]} | {p_yb[i]} | {p_yi[i]} | {p_nb[i]} |"
|
||||
rows_l176.append(row)
|
||||
new_l176 = "\n".join(rows_l176)
|
||||
|
||||
text = text.replace(old_l176, new_l176)
|
||||
|
||||
with open(ch13_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
print("Updated L83 and L176 in 제13장_플랜트설비공사.md successfully.")
|
||||
@@ -0,0 +1,69 @@
|
||||
import os, sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ch13_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md"
|
||||
with open(ch13_path, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
# 1. L149
|
||||
old_l149 = "| 배 관 용 탄 소 강 관 KS D3 50 7 | 850 〃 〃 900 〃 〃 〃 1000 〃 1100 〃 1200 〃 1350 〃 | 863.6 〃 〃 914.4 〃 〃 〃 1016.0 〃 1117.6 〃 1219.2 〃 1371.6 〃 | 7.9 9.5 12.7 6.4 7.9 8.7 12.7 8.7 10.3 10.3 11.1 11.1 11.9 11.9 12.7 | 167 200 266 143 177 194 282 216 255 281 303 331 354 399 426 | 16.5 15.1 13.1 18.3 16.5 15.7 13.0 15.7 14.5 14.4 13.8 13.9 13.4 13.4 12.9 | 8.2 7.5 6.5 9.2 8.2 7.9 6.5 7.8 7.2 7.2 6.9 6.9 6.7 6.7 6.5 | 8.2 7.5 6.5 9.2 8.2 7.9 6.5 7.8 7.2 7.2 6.9 6.9 6.7 6.7 6.5 | 16.5 15.1 13.1 18.3 16.5 15.7 13.0 15.7 14.5 14.4 13.8 13.9 13.4 13.4 12.9 |"
|
||||
|
||||
s149 = "850 〃 〃 900 〃 〃 〃 1000 〃 1100 〃 1200 〃 1350 〃".split()
|
||||
o149 = "863.6 〃 〃 914.4 〃 〃 〃 1016.0 〃 1117.6 〃 1219.2 〃 1371.6 〃".split()
|
||||
t149 = "7.9 9.5 12.7 6.4 7.9 8.7 12.7 8.7 10.3 10.3 11.1 11.1 11.9 11.9 12.7".split()
|
||||
w149 = "167 200 266 143 177 194 282 216 255 281 303 331 354 399 426".split()
|
||||
yw149 = "16.5 15.1 13.1 18.3 16.5 15.7 13.0 15.7 14.5 14.4 13.8 13.9 13.4 13.4 12.9".split()
|
||||
yb149 = "8.2 7.5 6.5 9.2 8.2 7.9 6.5 7.8 7.2 7.2 6.9 6.9 6.7 6.7 6.5".split()
|
||||
yi149 = "8.2 7.5 6.5 9.2 8.2 7.9 6.5 7.8 7.2 7.2 6.9 6.9 6.7 6.7 6.5".split()
|
||||
nb149 = "16.5 15.1 13.1 18.3 16.5 15.7 13.0 15.7 14.5 14.4 13.8 13.9 13.4 13.4 12.9".split()
|
||||
|
||||
rows_149 = []
|
||||
for i in range(len(s149)):
|
||||
div_name = "배관용 탄소강관 KS D3507" if i == 0 else ""
|
||||
row = f"| {div_name} | {s149[i]} | {o149[i]} | {t149[i]} | {w149[i]} | {yw149[i]} | {yb149[i]} | {yi149[i]} | {nb149[i]} |"
|
||||
rows_149.append(row)
|
||||
new_l149 = "\n".join(rows_149)
|
||||
text = text.replace(old_l149, new_l149)
|
||||
|
||||
# 2. L158
|
||||
old_l158 = "| 배 관 용 탄 소 강 관 KS D3 50 7 | 1350 1500 〃 〃 | 1371.6 1574 〃 〃 | 13.1 12.7 13.1 15.1 | 439 473 488 562 | 12.7 13.1 12.9 12.1 | 6.4 6.6 6.5 6.0 | 6.4 6.6 6.5 6.0 | 12.7 13.1 12.9 12.1 |"
|
||||
s158 = "1350 1500 〃 〃".split()
|
||||
o158 = "1371.6 1574 〃 〃".split()
|
||||
t158 = "13.1 12.7 13.1 15.1".split()
|
||||
w158 = "439 473 488 562".split()
|
||||
yw158 = "12.7 13.1 12.9 12.1".split()
|
||||
yb158 = "6.4 6.6 6.5 6.0".split()
|
||||
yi158 = "6.4 6.6 6.5 6.0".split()
|
||||
nb158 = "12.7 13.1 12.9 12.1".split()
|
||||
|
||||
rows_158 = []
|
||||
for i in range(len(s158)):
|
||||
div_name = "배관용 탄소강관 KS D3507" if i == 0 else ""
|
||||
row = f"| {div_name} | {s158[i]} | {o158[i]} | {t158[i]} | {w158[i]} | {yw158[i]} | {yb158[i]} | {yi158[i]} | {nb158[i]} |"
|
||||
rows_158.append(row)
|
||||
new_l158 = "\n".join(rows_158)
|
||||
text = text.replace(old_l158, new_l158)
|
||||
|
||||
# 3. L159
|
||||
old_l159 = "| 압력배관용 탄 소 강 관 KSD3562 SCH#40 | 6 8 10 | 10.5 13.8 17.3 | 1.7 2.2 2.3 | 0.369 0.629 0.851 | 101.3 70.7 59.9 | 50.7 35.3 29.9 | 50.7 35.3 29.9 | 101.3 70.7 59.9 |"
|
||||
s159 = "6 8 10".split()
|
||||
o159 = "10.5 13.8 17.3".split()
|
||||
t159 = "1.7 2.2 2.3".split()
|
||||
w159 = "0.369 0.629 0.851".split()
|
||||
yw159 = "101.3 70.7 59.9".split()
|
||||
yb159 = "50.7 35.3 29.9".split()
|
||||
yi159 = "50.7 35.3 29.9".split()
|
||||
nb159 = "101.3 70.7 59.9".split()
|
||||
|
||||
rows_159 = []
|
||||
for i in range(len(s159)):
|
||||
div_name = "압력배관용 탄소강관 KSD3562 SCH#40" if i == 0 else ""
|
||||
row = f"| {div_name} | {s159[i]} | {o159[i]} | {t159[i]} | {w159[i]} | {yw159[i]} | {yb159[i]} | {yi159[i]} | {nb159[i]} |"
|
||||
rows_159.append(row)
|
||||
new_l159 = "\n".join(rows_159)
|
||||
text = text.replace(old_l159, new_l159)
|
||||
|
||||
with open(ch13_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
print("Updated L149, L158, L159 in 제13장_플랜트설비공사.md successfully.")
|
||||
@@ -0,0 +1,107 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ch13_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md"
|
||||
with open(ch13_path, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
# 1. L459 (Flange Type Valve)
|
||||
# Notice: old_l459
|
||||
lines = text.split('\n')
|
||||
target_idx = -1
|
||||
for i, l in enumerate(lines):
|
||||
if l.startswith("| ø50 65 80 90 100 125 150 200 250 300 350 400 450 500 600 | 0.100 0.133"):
|
||||
target_idx = i
|
||||
break
|
||||
|
||||
if target_idx != -1:
|
||||
l_str = lines[target_idx]
|
||||
cells = [c.strip() for c in l_str.split('|')[1:-1]]
|
||||
specs = cells[0].split() # 15 specs
|
||||
# cells 1..10 each have 15 or 10 values (col 11, 12 have 10 values)
|
||||
cols = [c.split() for c in cells]
|
||||
rows = []
|
||||
for r_i in range(len(specs)):
|
||||
r = [specs[r_i]]
|
||||
for c_i in range(1, len(cols)):
|
||||
vals = cols[c_i]
|
||||
val = vals[r_i] if r_i < len(vals) else "-"
|
||||
r.append(val)
|
||||
rows.append("| " + " | ".join(r) + " |")
|
||||
lines[target_idx] = "\n".join(rows)
|
||||
print("Fixed Flange Type Valve table (15 rows)")
|
||||
|
||||
# 2. Check 13-1-4 Fitting Screwed / Welded (around L492)
|
||||
for i, l in enumerate(lines):
|
||||
if l.startswith("| ø50 65 80 90 100 150 200 250 300 350 400 450 500 600 |"):
|
||||
cells = [c.strip() for c in l.split('|')[1:-1]]
|
||||
specs = cells[0].split()
|
||||
cols = [c.split() for c in cells]
|
||||
rows = []
|
||||
for r_i in range(len(specs)):
|
||||
r = [specs[r_i]]
|
||||
for c_i in range(1, len(cols)):
|
||||
vals = cols[c_i]
|
||||
val = vals[r_i] if r_i < len(vals) else "-"
|
||||
r.append(val)
|
||||
rows.append("| " + " | ".join(r) + " |")
|
||||
lines[i] = "\n".join(rows)
|
||||
print(f"Fixed Fitting table at line {i+1} ({len(specs)} rows)")
|
||||
|
||||
# 3. Check phi 50..600 table (around L542)
|
||||
for i, l in enumerate(lines):
|
||||
if l.startswith("| φ 50 65 80 90 100 125 150 200 250 300 350 400 450 500 600 |"):
|
||||
cells = [c.strip() for c in l.split('|')[1:-1]]
|
||||
specs = cells[0].split()
|
||||
cols = [c.split() for c in cells]
|
||||
rows = []
|
||||
for r_i in range(len(specs)):
|
||||
r = [specs[r_i]]
|
||||
for c_i in range(1, len(cols)):
|
||||
vals = cols[c_i]
|
||||
val = vals[r_i] if r_i < len(vals) else "-"
|
||||
r.append(val)
|
||||
rows.append("| " + " | ".join(r) + " |")
|
||||
lines[i] = "\n".join(rows)
|
||||
print(f"Fixed phi table at line {i+1} ({len(specs)} rows)")
|
||||
|
||||
# 4. Check phi 15..350 table (around L852)
|
||||
for i, l in enumerate(lines):
|
||||
if l.startswith("| φ 15 20 25 40 50 65 80 90 100 125 150 200 250 300 350 |"):
|
||||
cells = [c.strip() for c in l.split('|')[1:-1]]
|
||||
specs = cells[0].split()
|
||||
cols = [c.split() for c in cells]
|
||||
rows = []
|
||||
for r_i in range(len(specs)):
|
||||
r = [specs[r_i]]
|
||||
for c_i in range(1, len(cols)):
|
||||
vals = cols[c_i]
|
||||
val = vals[r_i] if r_i < len(vals) else "-"
|
||||
r.append(val)
|
||||
rows.append("| " + " | ".join(r) + " |")
|
||||
lines[i] = "\n".join(rows)
|
||||
print(f"Fixed phi 15..350 table at line {i+1} ({len(specs)} rows)")
|
||||
|
||||
# 5. Check phi 15..150 table (around L882)
|
||||
for i, l in enumerate(lines):
|
||||
if l.startswith("| φ 15 20 25 40 50 65 80 90 100 125 150 |"):
|
||||
cells = [c.strip() for c in l.split('|')[1:-1]]
|
||||
specs = cells[0].split()
|
||||
cols = [c.split() for c in cells]
|
||||
rows = []
|
||||
for r_i in range(len(specs)):
|
||||
r = [specs[r_i]]
|
||||
for c_i in range(1, len(cols)):
|
||||
vals = cols[c_i]
|
||||
val = vals[r_i] if r_i < len(vals) else "-"
|
||||
r.append(val)
|
||||
rows.append("| " + " | ".join(r) + " |")
|
||||
lines[i] = "\n".join(rows)
|
||||
print(f"Fixed phi 15..150 table at line {i+1} ({len(specs)} rows)")
|
||||
|
||||
text = "\n".join(lines)
|
||||
with open(ch13_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
|
||||
print("Saved updated 제13장_플랜트설비공사.md successfully.")
|
||||
@@ -0,0 +1,246 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# 1. 02_토목부문/제1장_도로포장공사.md
|
||||
ch1_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제1장_도로포장공사.md"
|
||||
with open(ch1_path, 'r', encoding='utf-8') as f:
|
||||
text1 = f.read()
|
||||
|
||||
target_1_5_2 = "1-5-2 아스팔트 기층 소규모포설(일당)배치인원(인)규 격단 위수 량시공량(㎡)포장공인2보통인부인1플레이트콤팩터1.5ton대1320진동롤러( 핸드가이드식)0.7ton대1로더(타이어)0.57㎥대1살수차5,500L대0.5[주] ① 본 품은 소로, 주택가내 도로 등 피니셔를 사용하지 못하는 소규모 아스팔트 기층 포설 기준이다."
|
||||
repl_1_5_2 = """1-5-2 아스팔트 기층 소규모포설
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |
|
||||
|---|---|---|---|---|
|
||||
| 포장공 | | 인 | 2 | 320 |
|
||||
| 보통인부 | | 인 | 1 | |
|
||||
| 플레이트콤팩터 | 1.5ton | 대 | 1 | |
|
||||
| 진동롤러(핸드가이드식) | 0.7ton | 대 | 1 | |
|
||||
| 로더(타이어) | 0.57㎥ | 대 | 1 | |
|
||||
| 살수차 | 5,500L | 대 | 0.5 | |
|
||||
|
||||
[주] ① 본 품은 소로, 주택가내 도로 등 피니셔를 사용하지 못하는 소규모 아스팔트 기층 포설 기준이다."""
|
||||
|
||||
target_1_6_1 = "1-6-1 린 콘크리트 기층 포설(일당)시공량(㎥)구 분규 격단 위수 량일반포장터널포장포장공인2보통인부인2아스팔트피니셔3m대1550500타이어롤러8∼15ton대1진동롤러10ton대1[주] ① 본 품은 피니셔를 사용한 린 콘크리트의 기층 포설 기준이다."
|
||||
repl_1_6_1 = """1-6-1 린 콘크리트 기층 포설
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) | |
|
||||
|---|---|---|---|---|---|
|
||||
| | | | | 일반포장 | 터널포장 |
|
||||
| 포장공 | | 인 | 2 | 550 | 500 |
|
||||
| 보통인부 | | 인 | 2 | | |
|
||||
| 아스팔트피니셔 | 3m | 대 | 1 | | |
|
||||
| 타이어롤러 | 8∼15ton | 대 | 1 | | |
|
||||
| 진동롤러 | 10ton | 대 | 1 | | |
|
||||
|
||||
[주] ① 본 품은 피니셔를 사용한 린 콘크리트의 기층 포설 기준이다."""
|
||||
|
||||
target_wire1 = "- 2. 와이어 설치(일당)구 분단 위수 량시공량(m)특별인부인4200보통인부인2[주] ① 본 품은 높이 3m이하 낙석방지책의 와이어를 설치하는 기준이다."
|
||||
repl_wire1 = """- 2. 와이어 설치(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(m) |
|
||||
|---|---|---|---|
|
||||
| 특별인부 | 인 | 4 | 200 |
|
||||
| 보통인부 | 인 | 2 | |
|
||||
|
||||
[주] ① 본 품은 높이 3m이하 낙석방지책의 와이어를 설치하는 기준이다."""
|
||||
|
||||
target_wire2 = "- 2. 철망 및 와이어 설치(일당)구 분규 격단 위수 량시공량(㎡)특별인부인2기계식보통인부인3400크레인50ton대1특별인부인2인력식100보통인부인3[주] ① 본 품은 낙석방지망(포켓식, 비포켓식)의 철망 및 와이어로프를 설치하는 기준이다."
|
||||
repl_wire2 = """- 2. 철망 및 와이어 설치(일당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) |
|
||||
|---|---|---|---|---|
|
||||
| **기계식** | | | | |
|
||||
| 특별인부 | | 인 | 2 | 400 |
|
||||
| 보통인부 | | 인 | 3 | |
|
||||
| 크레인 | 50ton | 대 | 1 | |
|
||||
| **인력식** | | | | |
|
||||
| 특별인부 | | 인 | 2 | 100 |
|
||||
| 보통인부 | | 인 | 3 | |
|
||||
|
||||
[주] ① 본 품은 낙석방지망(포켓식, 비포켓식)의 철망 및 와이어로프를 설치하는 기준이다."""
|
||||
|
||||
text1 = text1.replace(target_1_5_2, repl_1_5_2)
|
||||
text1 = text1.replace(target_1_6_1, repl_1_6_1)
|
||||
text1 = text1.replace(target_wire1, repl_wire1)
|
||||
text1 = text1.replace(target_wire2, repl_wire2)
|
||||
with open(ch1_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text1)
|
||||
print("Updated 제1장_도로포장공사.md successfully.")
|
||||
|
||||
# 2. 02_토목부문/제2장_하천공사.md
|
||||
ch2_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제2장_하천공사.md"
|
||||
with open(ch2_path, 'r', encoding='utf-8') as f:
|
||||
text2 = f.read()
|
||||
|
||||
target_2_2_2 = "2-2-2 매트리스형 돌망태 설치('07, '12년 보완)(㎡당)구 분규 격단 위수 량석공-인0.027특별인부-인0.010보통인부-인0.010굴착기1.0㎥hr0.025[주] ① 본 품은 매트리스형 돌망태(폭 200㎝, 높이 30㎝)를 설치하는 기준이다."
|
||||
repl_2_2_2 = """2-2-2 매트리스형 돌망태 설치('07, '12년 보완)
|
||||
|
||||
(㎡당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 |
|
||||
|---|---|---|---|
|
||||
| 석공 | - | 인 | 0.027 |
|
||||
| 특별인부 | - | 인 | 0.010 |
|
||||
| 보통인부 | - | 인 | 0.010 |
|
||||
| 굴착기 | 1.0㎥ | hr | 0.025 |
|
||||
|
||||
[주] ① 본 품은 매트리스형 돌망태(폭 200㎝, 높이 30㎝)를 설치하는 기준이다."""
|
||||
|
||||
target_2_3_2 = "2-3-2 블록 붙이기(인력)('12년 보완)(㎡당)구 분규 격단 위수 량인0.076특별인부보통인부인0.066[주] ① 본 품은 하천제방에 인력으로 호안블록을 설치하는 기준이다."
|
||||
repl_2_3_2 = """2-3-2 블록 붙이기(인력)('12년 보완)
|
||||
|
||||
(㎡당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 |
|
||||
|---|---|---|---|
|
||||
| 특별인부 | - | 인 | 0.076 |
|
||||
| 보통인부 | - | 인 | 0.066 |
|
||||
|
||||
[주] ① 본 품은 하천제방에 인력으로 호안블록을 설치하는 기준이다."""
|
||||
|
||||
target_2_3_3 = "2-3-3 블록 붙이기(기계)('12년 보완)(㎡당)구 분규 격단 위수 량특별인부인0.017보통인부인0.007크레인10 톤시간0.048[주] ① 본 품은 하천제방에 장비를 사용하여 호안블록을 설치하는 기준이다."
|
||||
repl_2_3_3 = """2-3-3 블록 붙이기(기계)('12년 보완)
|
||||
|
||||
(㎡당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 |
|
||||
|---|---|---|---|
|
||||
| 특별인부 | - | 인 | 0.017 |
|
||||
| 보통인부 | - | 인 | 0.007 |
|
||||
| 크레인 | 10 톤 | 시간 | 0.048 |
|
||||
|
||||
[주] ① 본 품은 하천제방에 장비를 사용하여 호안블록을 설치하는 기준이다."""
|
||||
|
||||
text2 = text2.replace(target_2_2_2, repl_2_2_2)
|
||||
text2 = text2.replace(target_2_3_2, repl_2_3_2)
|
||||
text2 = text2.replace(target_2_3_3, repl_2_3_3)
|
||||
with open(ch2_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text2)
|
||||
print("Updated 제2장_하천공사.md successfully.")
|
||||
|
||||
# 3. 02_토목부문/제4장_궤도공사.md
|
||||
ch4_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제4장_궤도공사.md"
|
||||
with open(ch4_path, 'r', encoding='utf-8') as f:
|
||||
text4 = f.read()
|
||||
|
||||
target_4_6_10 = "4-6-10 목침목 탄성체결장치 설치('12년 보완)(침목 개소당)구 분단 위수 량궤도공인0.028보통인부인0.022[주] ① 본 품은 목침목에 탄성체결장치를 설치하는 기준이다."
|
||||
repl_4_6_10 = """4-6-10 목침목 탄성체결장치 설치('12년 보완)
|
||||
|
||||
(침목 개소당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 궤도공 | 인 | 0.028 |
|
||||
| 보통인부 | 인 | 0.022 |
|
||||
|
||||
[주] ① 본 품은 목침목에 탄성체결장치를 설치하는 기준이다."""
|
||||
text4 = text4.replace(target_4_6_10, repl_4_6_10)
|
||||
with open(ch4_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text4)
|
||||
print("Updated 제4장_궤도공사.md successfully.")
|
||||
|
||||
# 4. 02_토목부문/제5장_강구조공사.md
|
||||
ch5_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제5장_강구조공사.md"
|
||||
with open(ch5_path, 'r', encoding='utf-8') as f:
|
||||
text5 = f.read()
|
||||
|
||||
target_5_p1 = "- 인력에 의한 부설 및 접합을 수행하는 경우 다음 품을 적용한다. 구 분 단 위 수 량 관경(㎜) 시공량(본) 배관공(수도) 인 3 80 15 보 통 인 부 인 1 100 13 배관공(수도) 인 3 150 11 보 통 인 부 인 1.5 200 9"
|
||||
repl_5_p1 = """- 인력에 의한 부설 및 접합을 수행하는 경우 다음 품을 적용한다.
|
||||
|
||||
| 관경(㎜) | 시공량(본) | 배관공(수도) (인) | 보통인부 (인) |
|
||||
|---|---|---|---|
|
||||
| 80 | 15 | 3 | 1 |
|
||||
| 100 | 13 | 3 | 1 |
|
||||
| 150 | 11 | 3 | 1.5 |
|
||||
| 200 | 9 | 3 | 1.5 |"""
|
||||
|
||||
target_5_p2 = "- 인력에 의한 부설 및 접합을 수행하는 경우 다음 품을 적용한다. 구 분 단 위 수 량 관경(㎜) 시공량(본) 배관공(수도) 인 3 80 13 보 통 인 부 인 1 100 12 배관공(수도) 인 3 150 10 보 통 인 부 인 1.5 200 8"
|
||||
repl_5_p2 = """- 인력에 의한 부설 및 접합을 수행하는 경우 다음 품을 적용한다.
|
||||
|
||||
| 관경(㎜) | 시공량(본) | 배관공(수도) (인) | 보통인부 (인) |
|
||||
|---|---|---|---|
|
||||
| 80 | 13 | 3 | 1 |
|
||||
| 100 | 12 | 3 | 1 |
|
||||
| 150 | 10 | 3 | 1.5 |
|
||||
| 200 | 8 | 3 | 1.5 |"""
|
||||
|
||||
text5 = text5.replace(target_5_p1, repl_5_p1)
|
||||
text5 = text5.replace(target_5_p2, repl_5_p2)
|
||||
with open(ch5_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text5)
|
||||
print("Updated 제5장_강구조공사.md successfully.")
|
||||
|
||||
# 5. 02_토목부문/제6장_관부설및접합공사.md
|
||||
ch6_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제6장_관부설및접합공사.md"
|
||||
with open(ch6_path, 'r', encoding='utf-8') as f:
|
||||
text6 = f.read()
|
||||
|
||||
target_6_p = "- 인력에 의한 부설을 수행하는 경우 다음 품을 적용한다. 구 분 단 위 수 량 관경(㎜) 시공량(본) 배관공(수도) 인 2 80 8 보 통 인 부 인 1 100 6 배관공(수도) 인 3 150 4 보 통 인 부 인 1 200 3"
|
||||
repl_6_p = """- 인력에 의한 부설을 수행하는 경우 다음 품을 적용한다.
|
||||
|
||||
| 관경(㎜) | 시공량(본) | 배관공(수도) (인) | 보통인부 (인) |
|
||||
|---|---|---|---|
|
||||
| 80 | 8 | 2 | 1 |
|
||||
| 100 | 6 | 2 | 1 |
|
||||
| 150 | 4 | 3 | 1 |
|
||||
| 200 | 3 | 3 | 1 |"""
|
||||
|
||||
text6 = text6.replace(target_6_p, repl_6_p)
|
||||
with open(ch6_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text6)
|
||||
print("Updated 제6장_관부설및접합공사.md successfully.")
|
||||
|
||||
# 6. 02_토목부문/제7장_항만공사.md
|
||||
ch7_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제7장_항만공사.md"
|
||||
with open(ch7_path, 'r', encoding='utf-8') as f:
|
||||
text7 = f.read()
|
||||
|
||||
target_7_2_2 = "7-2-2 해상투하('19년 보완)(10㎥당)수 량구 분단 위0.03㎥ 이하 굴착기 투하0.1㎥ 이상 크레인 투하잠수부조0.070.09특별인부인0.040.20보통인부인0.120.22[주] ① 본 품은 해상 투하장소에 도착하여 대선위에서 투하하는 것이다."
|
||||
repl_7_2_2 = """7-2-2 해상투하('19년 보완)
|
||||
|
||||
(10㎥당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | |
|
||||
|---|---|---|---|
|
||||
| | | 0.03㎥ 이하 굴착기 투하 | 0.1㎥ 이상 크레인 투하 |
|
||||
| 잠수부 | 조 | 0.07 | 0.09 |
|
||||
| 특별인부 | 인 | 0.04 | 0.20 |
|
||||
| 보통인부 | 인 | 0.12 | 0.22 |
|
||||
|
||||
[주] ① 본 품은 해상 투하장소에 도착하여 대선위에서 투하하는 것이다."""
|
||||
text7 = text7.replace(target_7_2_2, repl_7_2_2)
|
||||
with open(ch7_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text7)
|
||||
print("Updated 제7장_항만공사.md successfully.")
|
||||
|
||||
# 7. 02_토목부문/제9장_측량.md
|
||||
ch9_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md"
|
||||
with open(ch9_path, 'r', encoding='utf-8') as f:
|
||||
text9 = f.read()
|
||||
|
||||
target_9_cad = "- 3. 기존 도로대장(종이, PDF, CAD 등)을 이용하여 작성하는 경우(단위 : 10㎞)투입인원작업구분비 고특급기술자고급기술자중급기술자초급기술자작업계획 및 준비0.30.7정위치편집0.81.22.0구조화편집1.64.02.4정리 및 점검0.10.50.4계0.43.65.64.4[주] ① 기존 도로대장(종이, PDF, CAD 등)을 활용하여 도로대장을 작성하는 경우라 함은 현행화된 기존 도로대장을 디지털화하여 작성하는 일련의 작업과정을 의미하며, 본 품은 CAD 형태의 기존 도로대장을 이용한 작성을 기준으로 한 것이다.㉮ 본 품에서 기존 도로대장 형태가 종이일 경우 ‘[토목부문] 9-5-4/3. 자동입력’의 자동독취, 벡터편집 작업 품을 별도 계상한다.㉯ 본 품에서 기존 도로대장 형태가 PDF일 경우 ‘[토목부문] 9-5-4/3. 자동입력’의 벡터편집 작업 품을 별도 계상한다."
|
||||
repl_9_cad = """- 3. 기존 도로대장(종이, PDF, CAD 등)을 이용하여 작성하는 경우
|
||||
|
||||
(단위 : 10㎞)
|
||||
|
||||
| 작업구분 | 투입인원 | | | | 비 고 |
|
||||
|---|---|---|---|---|---|
|
||||
| | 특급기술자 | 고급기술자 | 중급기술자 | 초급기술자 | |
|
||||
| 작업계획 및 준비 | 0.3 | 0.7 | - | - | |
|
||||
| 정위치편집 | - | 0.8 | 1.2 | 2.0 | |
|
||||
| 구조화편집 | - | 1.6 | 4.0 | 2.4 | |
|
||||
| 정리 및 점검 | 0.1 | 0.5 | 0.4 | - | |
|
||||
| 계 | 0.4 | 3.6 | 5.6 | 4.4 | |
|
||||
|
||||
[주] ① 기존 도로대장(종이, PDF, CAD 등)을 활용하여 도로대장을 작성하는 경우라 함은 현행화된 기존 도로대장을 디지털화하여 작성하는 일련의 작업과정을 의미하며, 본 품은 CAD 형태의 기존 도로대장을 이용한 작성을 기준으로 한 것이다.
|
||||
㉮ 본 품에서 기존 도로대장 형태가 종이일 경우 ‘[토목부문] 9-5-4/3. 자동입력’의 자동독취, 벡터편집 작업 품을 별도 계상한다.
|
||||
㉯ 본 품에서 기존 도로대장 형태가 PDF일 경우 ‘[토목부문] 9-5-4/3. 자동입력’의 벡터편집 작업 품을 별도 계상한다."""
|
||||
text9 = text9.replace(target_9_cad, repl_9_cad)
|
||||
with open(ch9_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text9)
|
||||
print("Updated 제9장_측량.md successfully.")
|
||||
@@ -0,0 +1,125 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문"
|
||||
|
||||
job_words = [
|
||||
"포장공", "보통인부", "특별인부", "철근공", "보링공", "기계설비공", "철공", "조경공",
|
||||
"착암공", "용접공", "배관공(수도)", "배관공", "할석공", "석공", "줄눈공", "미장공", "도장공", "방수공",
|
||||
"타일공", "내장공", "석면해체공", "궤도공", "중급기술자", "초급기술자", "고급기술자",
|
||||
"형틀목공", "목도", "잠수부", "비계공", "조적공"
|
||||
]
|
||||
|
||||
all_md_files = [os.path.join(base_dir, f) for f in os.listdir(base_dir) if f.endswith('.md')]
|
||||
|
||||
total_fixed = 0
|
||||
|
||||
for fpath in sorted(all_md_files):
|
||||
fname = os.path.basename(fpath)
|
||||
# Exclude 제9장_측량.md for separate specialized handling
|
||||
if "제9장" in fname:
|
||||
continue
|
||||
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
file_fixes = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if not (line_str.startswith('|') and line_str.endswith('|')):
|
||||
new_lines.append(line)
|
||||
continue
|
||||
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
|
||||
# Skip divider or note
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c) or (cells and cells[0] in ["비고", "비 고", "주", "[주]"]):
|
||||
new_lines.append(line)
|
||||
continue
|
||||
|
||||
# Find which cell has multiple job titles
|
||||
job_cell_idx = -1
|
||||
detected_jobs = []
|
||||
for c_idx, c in enumerate(cells):
|
||||
toks = c.split()
|
||||
if len(toks) >= 2 and all(any(tok.endswith(end) for end in ["공", "부", "사", "자"]) for tok in toks):
|
||||
job_cell_idx = c_idx
|
||||
detected_jobs = toks
|
||||
break
|
||||
|
||||
c_nospace = c.replace(" ", "")
|
||||
cur_str = c_nospace
|
||||
matched_jobs = []
|
||||
for jw in sorted(job_words, key=len, reverse=True):
|
||||
while jw in cur_str:
|
||||
matched_jobs.append(jw)
|
||||
cur_str = cur_str.replace(jw, "", 1)
|
||||
if len(matched_jobs) >= 2 and cur_str == "":
|
||||
ordered_jobs = []
|
||||
pos = 0
|
||||
while pos < len(c_nospace):
|
||||
found = False
|
||||
for jw in sorted(job_words, key=len, reverse=True):
|
||||
if c_nospace[pos:].startswith(jw):
|
||||
ordered_jobs.append(jw)
|
||||
pos += len(jw)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
break
|
||||
if len(ordered_jobs) >= 2:
|
||||
job_cell_idx = c_idx
|
||||
detected_jobs = ordered_jobs
|
||||
break
|
||||
|
||||
if job_cell_idx != -1 and len(detected_jobs) >= 2:
|
||||
N = len(detected_jobs)
|
||||
unit_cell_idx = -1
|
||||
for c_idx in range(job_cell_idx + 1, len(cells)):
|
||||
toks = cells[c_idx].split()
|
||||
if len(toks) == N and all(t in ["인", "대", "hr", "시간", "식", "-", "조"] for t in toks):
|
||||
unit_cell_idx = c_idx
|
||||
break
|
||||
|
||||
if unit_cell_idx != -1:
|
||||
can_split = False
|
||||
col_splits = {}
|
||||
for c_idx in range(unit_cell_idx + 1, len(cells)):
|
||||
toks = cells[c_idx].split()
|
||||
if len(toks) == N and all(re.match(r'^-?\d+(?:,\d+)?(?:\.\d+)?$', t) for t in toks):
|
||||
col_splits[c_idx] = toks
|
||||
can_split = True
|
||||
|
||||
if can_split:
|
||||
unit_toks = cells[unit_cell_idx].split()
|
||||
for r_idx in range(N):
|
||||
new_row_cells = []
|
||||
for c_idx in range(len(cells)):
|
||||
if c_idx == job_cell_idx:
|
||||
new_row_cells.append(detected_jobs[r_idx])
|
||||
elif c_idx == unit_cell_idx:
|
||||
new_row_cells.append(unit_toks[r_idx])
|
||||
elif c_idx in col_splits:
|
||||
new_row_cells.append(col_splits[c_idx][r_idx])
|
||||
else:
|
||||
if r_idx == 0:
|
||||
new_row_cells.append(cells[c_idx])
|
||||
else:
|
||||
new_row_cells.append("")
|
||||
new_line_str = "| " + " | ".join(new_row_cells) + " |\n"
|
||||
new_lines.append(new_line_str)
|
||||
file_fixes += 1
|
||||
total_fixed += 1
|
||||
continue
|
||||
|
||||
new_lines.append(line)
|
||||
|
||||
if file_fixes > 0:
|
||||
print(f"{fname}: fixed {file_fixes} multi-worker squashed rows")
|
||||
with open(fpath, 'w', encoding='utf-8') as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
print(f"Total multi-worker rows fixed in 토목부문 (excluding 9장): {total_fixed}")
|
||||
@@ -0,0 +1,295 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# 1. 01_공통부문/제2장_가설공사.md
|
||||
ch2_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제2장_가설공사.md"
|
||||
with open(ch2_path, 'r', encoding='utf-8') as f:
|
||||
text2 = f.read()
|
||||
|
||||
# Fix 2-8-7
|
||||
target_2_8_7 = "2-8-7 철골 안전망 설치 및 해체('18년 보완)(10㎡당)구 분단위수 량비계공인0.17보통인부인0.05[주] ① 본 품은 철골공사 시공 중 철골사이에 설치되는 안전망의 설치 및 해체 작업 기준이다."
|
||||
repl_2_8_7 = """2-8-7 철골 안전망 설치 및 해체('18년 보완)
|
||||
|
||||
(10㎡당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 비계공 | 인 | 0.17 |
|
||||
| 보통인부 | 인 | 0.05 |
|
||||
|
||||
[주] ① 본 품은 철골공사 시공 중 철골사이에 설치되는 안전망의 설치 및 해체 작업 기준이다."""
|
||||
|
||||
# Fix 2-8-10
|
||||
target_2_8_10 = "2-8-10 수직형 추락방망 설치 및 해체('20년 신설)(10개소당)개구부 면적구 분단 위1.0㎡이하1.0~3.0㎡이하3.0~6.0㎡이하6.0~9.0㎡이하9.0~12.0㎡이하비계공인0.490.631.011.301.60[주] ① 본 품은 창호, 발코니 등 개구부에 추락의 위험을 방지하기 위한 수직형 방망을 설치 및 해체하는 기준이다."
|
||||
repl_2_8_10 = """2-8-10 수직형 추락방망 설치 및 해체('20년 신설)
|
||||
|
||||
(10개소당)
|
||||
|
||||
| 구 분 | 단 위 | 개구부 면적 | | | | |
|
||||
|---|---|---|---|---|---|---|
|
||||
| | | 1.0㎡이하 | 1.0~3.0㎡이하 | 3.0~6.0㎡이하 | 6.0~9.0㎡이하 | 9.0~12.0㎡이하 |
|
||||
| 비계공 | 인 | 0.49 | 0.63 | 1.01 | 1.30 | 1.60 |
|
||||
|
||||
[주] ① 본 품은 창호, 발코니 등 개구부에 추락의 위험을 방지하기 위한 수직형 방망을 설치 및 해체하는 기준이다."""
|
||||
|
||||
# Fix 2-11-5
|
||||
target_2_11_5 = "2-11-5 비산먼지 발생 억제를 위한 살수('02년 신설, '09년 보완)(100㎡당)구 분규 격단 위수 량물탱크(살수차)16,000ℓ시간0.008[주] ① 본 품은 공사현장의 비산먼지 발생억제를 위하여 물탱크(살수차)로 살수하는 품이다."
|
||||
repl_2_11_5 = """2-11-5 비산먼지 발생 억제를 위한 살수('02년 신설, '09년 보완)
|
||||
|
||||
(100㎡당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 |
|
||||
|---|---|---|---|
|
||||
| 물탱크(살수차) | 16,000ℓ | 시간 | 0.008 |
|
||||
|
||||
[주] ① 본 품은 공사현장의 비산먼지 발생억제를 위하여 물탱크(살수차)로 살수하는 품이다."""
|
||||
|
||||
# Fix 2-11-7
|
||||
target_2_11_7 = "2-11-7 슬러지 제거('19년 신설)(회당)구 분규 격단 위수 량특별인부인0.63굴착기0.2㎥hr1.00[주] ① 본 품은 자동세륜기(슬러지함 2.0×1.2×1.2m) 슬러지를 제거하는 기준이다."
|
||||
repl_2_11_7 = """2-11-7 슬러지 제거('19년 신설)
|
||||
|
||||
(회당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 |
|
||||
|---|---|---|---|
|
||||
| 특별인부 | | 인 | 0.63 |
|
||||
| 굴착기 | 0.2㎥ | hr | 1.00 |
|
||||
|
||||
[주] ① 본 품은 자동세륜기(슬러지함 2.0×1.2×1.2m) 슬러지를 제거하는 기준이다."""
|
||||
|
||||
# Replace page header attachments
|
||||
text2 = text2.replace("인력품의 2%로 계상한다.52공통부문\n" + target_2_8_7, "인력품의 2%로 계상한다.\n\n52 공통부문\n\n" + repl_2_8_7)
|
||||
text2 = text2.replace(target_2_8_7, repl_2_8_7)
|
||||
text2 = text2.replace("1회사용 후 100%로 한다.53제2장 가설공사\n" + target_2_8_10, "1회사용 후 100%로 한다.\n\n53 제2장 가설공사\n\n" + repl_2_8_10)
|
||||
text2 = text2.replace(target_2_8_10, repl_2_8_10)
|
||||
text2 = text2.replace(target_2_11_5, repl_2_11_5)
|
||||
text2 = text2.replace("인력품의 2%로 계상한다.62공통부문\n" + target_2_11_7, "인력품의 2%로 계상한다.\n\n62 공통부문\n\n" + repl_2_11_7)
|
||||
text2 = text2.replace(target_2_11_7, repl_2_11_7)
|
||||
|
||||
# Clean up L348 & L371 embedded table residue in 비고
|
||||
target_bg1 = "비 고 | - 수평연결재가 필요한 경우는 다음과 같이 계상한다. (1단 설치일 때, ㎡당 구 분 규 격 단 위 수 량 형 틀 목 공 설치, 해체 인 0.02 보 통 인 부 설치, 해체 인 0.01 ※ 전체동바리 연결을 기준으로 산정된 것이다. - 설치간격에 따라 시공량에 다음 요율을 적용한다. 설치간격 0.6m이하 0.6m초과∼0.8m이하 0.8m초과 요 율 - 17% - + 11% ※ 설치간격은 멍에간격을 기준한 것이다."
|
||||
repl_bg1 = "비 고 | - 수평연결재가 필요한 경우는 다음과 같이 계상한다. (1단 설치일 때, ㎡당, 아래 표 참조) ※ 전체동바리 연결을 기준으로 산정된 것이다. - 설치간격에 따라 시공량에 다음 요율을 적용한다. (아래 표 참조) ※ 설치간격은 멍에간격을 기준한 것이다."
|
||||
text2 = text2.replace(target_bg1, repl_bg1)
|
||||
|
||||
target_bg2 = "비 고 | - 수평연결재가 필요한 경우는 다음과 같이 계상한다. (1단설치일 때, ㎡당 구 분 규 격 단 위 수 량 형 틀 목 공 설치, 해체 인 0.02 보 통 인 부 설치, 해체 인 0.01 ※ 전체동바리 연결을 기준으로 산정된 것이다. - 설치간격에 따라 시공량에 다음 요율을 적용한다. 설치간격 0.6m이하 0.6m초과∼0.8m이하 0.8m초과 요 율 - 17% - + 11% ※ 설치간격은 멍에간격을 기준한다."
|
||||
repl_bg2 = "비 고 | - 수평연결재가 필요한 경우는 다음과 같이 계상한다. (1단설치일 때, ㎡당, 아래 표 참조) ※ 전체동바리 연결을 기준으로 산정된 것이다. - 설치간격에 따라 시공량에 다음 요율을 적용한다. (아래 표 참조) ※ 설치간격은 멍에간격을 기준한다."
|
||||
text2 = text2.replace(target_bg2, repl_bg2)
|
||||
|
||||
with open(ch2_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text2)
|
||||
print("Updated 제2장_가설공사.md successfully.")
|
||||
|
||||
# 2. 01_공통부문/제4장_조경공사.md
|
||||
ch4_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제4장_조경공사.md"
|
||||
with open(ch4_path, 'r', encoding='utf-8') as f:
|
||||
text4 = f.read()
|
||||
|
||||
target_4_4_4 = "4-4-4 야자섬유매트포장('22년 신설, '24년 보완)(일당)시공량(㎡)구 분단 위수 량폭 1.5m이하폭 2.0m이하조경공인290130보통인부인1[주] ① 본 품은 설치위치의 토공사가 완료된 상태에서 야자섬유매트로 포장하는 기준이다."
|
||||
repl_4_4_4 = """4-4-4 야자섬유매트포장('22년 신설, '24년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(㎡) | |
|
||||
|---|---|---|---|---|
|
||||
| | | | 폭 1.5m이하 | 폭 2.0m이하 |
|
||||
| 조경공 | 인 | 2 | 90 | 130 |
|
||||
| 보통인부 | 인 | 1 | | |
|
||||
|
||||
[주] ① 본 품은 설치위치의 토공사가 완료된 상태에서 야자섬유매트로 포장하는 기준이다."""
|
||||
text4 = text4.replace(target_4_4_4, repl_4_4_4)
|
||||
with open(ch4_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text4)
|
||||
print("Updated 제4장_조경공사.md successfully.")
|
||||
|
||||
# 3. 01_공통부문/제5장_기초공사.md
|
||||
ch5_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제5장_기초공사.md"
|
||||
with open(ch5_path, 'r', encoding='utf-8') as f:
|
||||
text5 = f.read()
|
||||
|
||||
target_5_1_3 = "- ⑥ 장비(크레인)의 규격은 작업여건(시공높이, 시공위치 등) 및 안전율(적정하중, 작업반경 등)을 고려하여적합한 규격을 적용한다.99제5장 기초공사5-1-3 H-Beam 설치(버팀보)('26년 보완)(일당)시공량(본)구 분단 위수 량규격(m)H=250∼500H=600∼8005m이하1210철골공인36~8m119용접공인29~11m108보통인부인112~14m97크레인대115~18m86[주] ① 본 품은 수평지보공(H-Beam)의 버팀보 설치 품이다."
|
||||
repl_5_1_3 = """- ⑥ 장비(크레인)의 규격은 작업여건(시공높이, 시공위치 등) 및 안전율(적정하중, 작업반경 등)을 고려하여적합한 규격을 적용한다.
|
||||
|
||||
99 제5장 기초공사
|
||||
|
||||
5-1-3 H-Beam 설치(버팀보)('26년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 규격(m) | 시공량(본) | |
|
||||
|---|---|---|---|---|---|
|
||||
| | | | | H=250∼500 | H=600∼800 |
|
||||
| 철골공 | 인 | 3 | 5m이하 | 12 | 10 |
|
||||
| 용접공 | 인 | 2 | 6~8m | 11 | 9 |
|
||||
| 보통인부 | 인 | 1 | 9~11m | 10 | 8 |
|
||||
| 크레인 | 대 | 1 | 12~14m | 9 | 7 |
|
||||
| | | | 15~18m | 8 | 6 |
|
||||
|
||||
[주] ① 본 품은 수평지보공(H-Beam)의 버팀보 설치 품이다."""
|
||||
|
||||
target_5_1_8 = "5-1-8 복공판 설치·철거('25년 신설)(일당)시공량(㎡)구 분단 위수 량설치철거철골공인2용접공인2120170보통인부인1크레인대1[주] ① 본 품은 주형보 상부에 복공판을 설치 및 철거하는 기준이다."
|
||||
repl_5_1_8 = """5-1-8 복공판 설치·철거('25년 신설)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(㎡) | |
|
||||
|---|---|---|---|---|
|
||||
| | | | 설치 | 철거 |
|
||||
| 철골공 | 인 | 2 | 120 | 170 |
|
||||
| 용접공 | 인 | 2 | | |
|
||||
| 보통인부 | 인 | 1 | | |
|
||||
| 크레인 | 대 | 1 | | |
|
||||
|
||||
[주] ① 본 품은 주형보 상부에 복공판을 설치 및 철거하는 기준이다."""
|
||||
|
||||
target_5_pbd1 = "- 2. 장비조립 및 해체(회당)구 분단 위수 량기계설비공인1인력철공〃2특별인부〃1장비크레인대1조립일2소요일수해체일1[주] ① 본 품은 PBD천공기를 1회 조립 및 해체하는 기준이며, 시공조건(외부 반입/반출)에 따라 조립․해체를 반복 적용한다."
|
||||
repl_5_pbd1 = """- 2. 장비조립 및 해체(회당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 기계설비공 | 인 | 1 |
|
||||
| 철공 | 인 | 2 |
|
||||
| 특별인부 | 인 | 1 |
|
||||
| 크레인 | 대 | 1 |
|
||||
| 조립 | 일 | 2 |
|
||||
| 해체 | 일 | 1 |
|
||||
|
||||
[주] ① 본 품은 PBD천공기를 1회 조립 및 해체하는 기준이며, 시공조건(외부 반입/반출)에 따라 조립․해체를 반복 적용한다."""
|
||||
|
||||
target_5_pbd2 = "- 3. 장비 및 인력편성구 분규 격단 위수 량특별인부인2보통인부〃1PBD천공기147㎾, 38m(리더길이)대1[주] ① 부속장비(자동기록기, 계측기, 맨드릴 등)의 경비는 인력품에 다음 요율을 적용한다."
|
||||
repl_5_pbd2 = """- 3. 장비 및 인력편성
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 |
|
||||
|---|---|---|---|
|
||||
| 특별인부 | | 인 | 2 |
|
||||
| 보통인부 | | 인 | 1 |
|
||||
| PBD천공기 | 147㎾, 38m(리더길이) | 대 | 1 |
|
||||
|
||||
[주] ① 부속장비(자동기록기, 계측기, 맨드릴 등)의 경비는 인력품에 다음 요율을 적용한다."""
|
||||
|
||||
target_5_pbd3 = "- 2. 장비조립・해체(회당)구 분단 위외부 반출/반입작업구간내 이동기계설비공인11철공〃22특별인부〃11크레인대11조립일32소요일수해체〃1.51"
|
||||
repl_5_pbd3 = """- 2. 장비조립・해체(회당)
|
||||
|
||||
| 구 분 | 단 위 | 외부 반출/반입 | 작업구간내 이동 |
|
||||
|---|---|---|---|
|
||||
| 기계설비공 | 인 | 1 | 1 |
|
||||
| 철공 | 인 | 2 | 2 |
|
||||
| 특별인부 | 인 | 1 | 1 |
|
||||
| 크레인 | 대 | 1 | 1 |
|
||||
| 조립 | 일 | 3 | 2 |
|
||||
| 해체 | 일 | 1.5 | 1 |"""
|
||||
|
||||
text5 = text5.replace(target_5_1_3, repl_5_1_3)
|
||||
text5 = text5.replace(target_5_1_8, repl_5_1_8)
|
||||
text5 = text5.replace(target_5_pbd1, repl_5_pbd1)
|
||||
text5 = text5.replace(target_5_pbd2, repl_5_pbd2)
|
||||
text5 = text5.replace(target_5_pbd3, repl_5_pbd3)
|
||||
|
||||
# L150 주석 안 좌대 설치 및 해체
|
||||
target_5_base = "- 좌대 설치 및 해체가 필요한 경우 다음 품을 적용한다. (일당) 시공량(개소) 구 분 단 위 수 량 설치 해체 철 공 인 1 18 35 보 통 인 부 인 1"
|
||||
repl_5_base = """- 좌대 설치 및 해체가 필요한 경우 다음 품을 적용한다. (일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(개소) | |
|
||||
|---|---|---|---|---|
|
||||
| | | | 설치 | 해체 |
|
||||
| 철공 | 인 | 1 | 18 | 35 |
|
||||
| 보통인부 | 인 | 1 | | |"""
|
||||
text5 = text5.replace(target_5_base, repl_5_base)
|
||||
|
||||
with open(ch5_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text5)
|
||||
print("Updated 제5장_기초공사.md successfully.")
|
||||
|
||||
# 4. 01_공통부문/제6장_철근콘크리트공사.md
|
||||
ch6_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제6장_철근콘크리트공사.md"
|
||||
with open(ch6_path, 'r', encoding='utf-8') as f:
|
||||
text6 = f.read()
|
||||
|
||||
target_6_slip = "- 1. 설치 및 해체(㎡당)설 치해 체구 분단 위수 량구 분단 위수 량비계공인0.199특수비계공인0.154보통인부인0.091보통인부인0.064크레인hr0.132 크레인hr0.170[주] ① 슬립폼 제작비용은 별도계상하되, 단면형상은 고정단면을 기준으로 한 것이다."
|
||||
repl_6_slip = """- 1. 설치 및 해체(㎡당)
|
||||
|
||||
| 설 치 | | | 해 체 | | |
|
||||
|---|---|---|---|---|---|
|
||||
| 구 분 | 단 위 | 수 량 | 구 분 | 단 위 | 수 량 |
|
||||
| 비계공 | 인 | 0.199 | 특수비계공 | 인 | 0.154 |
|
||||
| 보통인부 | 인 | 0.091 | 보통인부 | 인 | 0.064 |
|
||||
| 크레인 | hr | 0.132 | 크레인 | hr | 0.170 |
|
||||
|
||||
[주] ① 슬립폼 제작비용은 별도계상하되, 단면형상은 고정단면을 기준으로 한 것이다."""
|
||||
|
||||
target_6_socket = "- 2. 소켓 연결(m당)구 분단 위수 량특별인부인0.085보통인부인0.029[주] ① 본 품은 지수판 연결재(소켓)를 사용한 지수판 설치를 기준한 것이다."
|
||||
repl_6_socket = """- 2. 소켓 연결(m당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 특별인부 | 인 | 0.085 |
|
||||
| 보통인부 | 인 | 0.029 |
|
||||
|
||||
[주] ① 본 품은 지수판 연결재(소켓)를 사용한 지수판 설치를 기준한 것이다."""
|
||||
|
||||
target_6_grout = "- 7. 그라우팅(일당)구 분규 격단 위수 량시공량(㎥)기계설비공인1인력특별인부인3보통인부인11.50그라우팅 믹서190×2ℓ대1장비그라우팅 펌프30~60ℓ/min대1[주] ① 본 품은 쉬즈관 내부 그라우팅 작업 기준이다."
|
||||
repl_6_grout = """- 7. 그라우팅(일당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎥) |
|
||||
|---|---|---|---|---|
|
||||
| 기계설비공 | | 인 | 1 | 1.50 |
|
||||
| 특별인부 | | 인 | 3 | |
|
||||
| 보통인부 | | 인 | 1 | |
|
||||
| 그라우팅 믹서 | 190×2ℓ | 대 | 1 | |
|
||||
| 그라우팅 펌프 | 30~60ℓ/min | 대 | 1 | |
|
||||
|
||||
[주] ① 본 품은 쉬즈관 내부 그라우팅 작업 기준이다."""
|
||||
|
||||
target_6_anchor = "- 2. 정착구 설치(개당)수 량(쉬즈관 규격)구 분단 위 ø75㎜ 이하ø100㎜ 이하ø130㎜ 이하형틀목공인0.380.480.61보통인부인0.180.230.29비고"
|
||||
repl_6_anchor = """- 2. 정착구 설치(개당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (쉬즈관 규격) | | | 비 고 |
|
||||
|---|---|---|---|---|---|
|
||||
| | | ø75㎜ 이하 | ø100㎜ 이하 | ø130㎜ 이하 | |
|
||||
| 형틀목공 | 인 | 0.38 | 0.48 | 0.61 | |
|
||||
| 보통인부 | 인 | 0.18 | 0.23 | 0.29 | |"""
|
||||
|
||||
target_6_sheath = "- 3. 쉬즈관 설치(m당)수 량(쉬즈관 규격)구 분단위ø75㎜ 이하ø100㎜이하ø130㎜이하철근공인0.030.050.07보통인부인0.020.020.03[주] ① 본 품은 쉬즈관을 철근에 연결하여 설치하는 기준이다."
|
||||
repl_6_sheath = """- 3. 쉬즈관 설치(m당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (쉬즈관 규격) | | |
|
||||
|---|---|---|---|---|
|
||||
| | | ø75㎜ 이하 | ø100㎜ 이하 | ø130㎜ 이하 |
|
||||
| 철근공 | 인 | 0.03 | 0.05 | 0.07 |
|
||||
| 보통인부 | 인 | 0.02 | 0.02 | 0.03 |
|
||||
|
||||
[주] ① 본 품은 쉬즈관을 철근에 연결하여 설치하는 기준이다."""
|
||||
|
||||
text6 = text6.replace(target_6_slip, repl_6_slip)
|
||||
text6 = text6.replace(target_6_socket, repl_6_socket)
|
||||
text6 = text6.replace(target_6_grout, repl_6_grout)
|
||||
text6 = text6.replace(target_6_anchor, repl_6_anchor)
|
||||
text6 = text6.replace(target_6_sheath, repl_6_sheath)
|
||||
|
||||
with open(ch6_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text6)
|
||||
print("Updated 제6장_철근콘크리트공사.md successfully.")
|
||||
|
||||
# 5. 01_공통부문/제7장_돌공사.md
|
||||
ch7_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제7장_돌공사.md"
|
||||
with open(ch7_path, 'r', encoding='utf-8') as f:
|
||||
text7 = f.read()
|
||||
|
||||
target_7_4_2 = "7-4-2 앵커 긴결공법('19, '26년 보완)(일당)시공량(㎡)구 분단 위수 량0.3㎡이하0.3㎡초과~0.8㎡이하석공인3줄눈공인11011보통인부인2[주] ① 본 품은 구조물 벽체에 앵커로 고정하여 석재판(비구조요소)을 설치하는 기준이다."
|
||||
repl_7_4_2 = """7-4-2 앵커 긴결공법('19, '26년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(㎡) | |
|
||||
|---|---|---|---|---|
|
||||
| | | | 0.3㎡이하 | 0.3㎡초과~0.8㎡이하 |
|
||||
| 석공 | 인 | 3 | 10 | 11 |
|
||||
| 줄눈공 | 인 | 1 | | |
|
||||
| 보통인부 | 인 | 2 | | |
|
||||
|
||||
[주] ① 본 품은 구조물 벽체에 앵커로 고정하여 석재판(비구조요소)을 설치하는 기준이다."""
|
||||
|
||||
text7 = text7.replace(target_7_4_2, repl_7_4_2)
|
||||
with open(ch7_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text7)
|
||||
print("Updated 제7장_돌공사.md successfully.")
|
||||
@@ -0,0 +1,220 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# 1. 제1장 공통
|
||||
ch1_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제1장_공통.md"
|
||||
with open(ch1_path, 'r', encoding='utf-8') as f:
|
||||
text1 = f.read()
|
||||
|
||||
target_1_2_10 = "[주] 살수차의 운전시간에는 급수시간 및 1회당 5㎞까지의 이동시간을 포함한다.유지관리부문8621-2-10 제초('14, '19, '22, '26년 보완)(일당)시공량(㎡) 구 분단 위수 량일반 잔디지역지장물 지역조경공인11,400 1,000 보통인부인5[주] ① 본 품은 인력으로 잡초를 제거하는 기준이다."
|
||||
repl_1_2_10 = """[주] 살수차의 운전시간에는 급수시간 및 1회당 5㎞까지의 이동시간을 포함한다.
|
||||
|
||||
유지관리부문 862
|
||||
|
||||
1-2-10 제초('14, '19, '22, '26년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(㎡) | |
|
||||
|---|---|---|---|---|
|
||||
| | | | 일반 잔디지역 | 지장물 지역 |
|
||||
| 조경공 | 인 | 1 | 1,400 | 1,000 |
|
||||
| 보통인부 | 인 | 5 | | |
|
||||
|
||||
[주] ① 본 품은 인력으로 잡초를 제거하는 기준이다."""
|
||||
text1 = text1.replace(target_1_2_10, repl_1_2_10)
|
||||
with open(ch1_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text1)
|
||||
print("Updated 제1장_공통.md successfully.")
|
||||
|
||||
# 2. 제2장 토목
|
||||
ch2_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제2장_토목.md"
|
||||
with open(ch2_path, 'r', encoding='utf-8') as f:
|
||||
text2 = f.read()
|
||||
|
||||
target_2_1_5 = "2-1-5 절삭 후 콘크리트 덧씌우기('20년 보완)(일당)시공량(㎡)구 분 규 격단 위수 량밀링깊이100㎜밀링깊이150㎜포장공인4특별인부인1보통인부(절삭)인1보통인부(청소)인1보통인부(포설)인42,5001,600콘크리트 페이버75㎾대1조면마무리기7.95m대1노면파쇄기2m대1로더(타이어)0.57㎥대1[주] ① 본 품은 아스팔트 포장 절삭 후 콘크리트 덧씌우기의 포장면 절삭 및 청소, 포설, 양생, 조면마무리에 대한 품이다."
|
||||
repl_2_1_5 = """2-1-5 절삭 후 콘크리트 덧씌우기('20년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 | 시공량(㎡) | |
|
||||
|---|---|---|---|---|---|
|
||||
| | | | | 밀링깊이 100㎜ | 밀링깊이 150㎜ |
|
||||
| 포장공 | | 인 | 4 | 2,500 | 1,600 |
|
||||
| 특별인부 | | 인 | 1 | | |
|
||||
| 보통인부(절삭) | | 인 | 1 | | |
|
||||
| 보통인부(청소) | | 인 | 1 | | |
|
||||
| 보통인부(포설) | | 인 | 4 | | |
|
||||
| 콘크리트 페이버 | 75㎾ | 대 | 1 | | |
|
||||
| 조면마무리기 | 7.95m | 대 | 1 | | |
|
||||
| 노면파쇄기 | 2m | 대 | 1 | | |
|
||||
| 로더(타이어) | 0.57㎥ | 대 | 1 | | |
|
||||
|
||||
[주] ① 본 품은 아스팔트 포장 절삭 후 콘크리트 덧씌우기의 포장면 절삭 및 청소, 포설, 양생, 조면마무리에 대한 품이다."""
|
||||
|
||||
target_2_1_27 = "- ④ 공구손료 및 경장비(전동드릴 등)의 기계경비는 인력품의 3%로 계상한다.유지관리부문8882-1-27 보도용 블록 인력철거('21, '24년, ‘25년 보완)(일당)A-TypeB-Type구 분규격단 위수 량시공량(㎡)수 량시공량(㎡)포장공인22보통인부인23601260트럭2.5ton대11[주] ① 본 품은 유용할 목적으로 철거하거나 또는 장비를 사용하지 못하는 구간의 철거 작업 기준이다."
|
||||
repl_2_1_27 = """- ④ 공구손료 및 경장비(전동드릴 등)의 기계경비는 인력품의 3%로 계상한다.
|
||||
|
||||
유지관리부문 888
|
||||
|
||||
2-1-27 보도용 블록 인력철거('21, '24년, ‘25년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | A-Type | | B-Type | |
|
||||
|---|---|---|---|---|---|---|
|
||||
| | | | 수 량 | 시공량(㎡) | 수 량 | 시공량(㎡) |
|
||||
| 포장공 | | 인 | 2 | 360 | 2 | 260 |
|
||||
| 보통인부 | | 인 | 2 | | 1 | |
|
||||
| 트럭 | 2.5ton | 대 | 1 | | 1 | |
|
||||
|
||||
[주] ① 본 품은 유용할 목적으로 철거하거나 또는 장비를 사용하지 못하는 구간의 철거 작업 기준이다."""
|
||||
|
||||
target_2_2_15 = "- ③ 자갈다지기는 별도 계상한다.유지관리부문9002-2-15 교상가드레일 철거('12, '19년 보완)(km당)구 분규 격단 위수 량궤도공-인30보통인부-인11굴착기+ 부착용집게0.2㎥hr34.8[주] ① 본 품은 교상에 가드레일을 철거하는 기준이다."
|
||||
repl_2_2_15 = """- ③ 자갈다지기는 별도 계상한다.
|
||||
|
||||
유지관리부문 900
|
||||
|
||||
2-2-15 교상가드레일 철거('12, '19년 보완)
|
||||
|
||||
(km당)
|
||||
|
||||
| 구 분 | 규 격 | 단 위 | 수 량 |
|
||||
|---|---|---|---|
|
||||
| 궤도공 | - | 인 | 30 |
|
||||
| 보통인부 | - | 인 | 11 |
|
||||
| 굴착기+부착용집게 | 0.2㎥ | hr | 34.8 |
|
||||
|
||||
[주] ① 본 품은 교상에 가드레일을 철거하는 기준이다."""
|
||||
|
||||
target_2_2_16 = "2-2-16 목침목 탄성체결장치 철거('12, '19년 보완)(침목 개소당)구 분단 위수 량궤도공인0.028보통인부인0.022[주] ① 본 품은 목침목에 탄성체결장치를 설치 또는 해체하는 기준이다."
|
||||
repl_2_2_16 = """2-2-16 목침목 탄성체결장치 철거('12, '19년 보완)
|
||||
|
||||
(침목 개소당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 궤도공 | 인 | 0.028 |
|
||||
| 보통인부 | 인 | 0.022 |
|
||||
|
||||
[주] ① 본 품은 목침목에 탄성체결장치를 설치 또는 해체하는 기준이다."""
|
||||
|
||||
target_2_4_1 = "- ⑥ 공구손료 및 경장비의 기계경비는 인력품의 3%로 계상한다.유지관리부문9022-4 관부설 및 접합2-4-1 상수관 세척('18년 신설)(일당)구 분단 위수 량시공량(구간)배관공(수도)인1보통인부인32시험기구식1[주] ① 본 품은 양측의 제수밸브와 소화전을 이용한 상수관(300㎜이하)의 물세척(플러싱) 작업 기준이다."
|
||||
repl_2_4_1 = """- ⑥ 공구손료 및 경장비의 기계경비는 인력품의 3%로 계상한다.
|
||||
|
||||
유지관리부문 902
|
||||
|
||||
2-4 관부설 및 접합
|
||||
|
||||
2-4-1 상수관 세척('18년 신설)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(구간) |
|
||||
|---|---|---|---|
|
||||
| 배관공(수도) | 인 | 1 | 2 |
|
||||
| 보통인부 | 인 | 3 | |
|
||||
| 시험기구 | 식 | 1 | |
|
||||
|
||||
[주] ① 본 품은 양측의 제수밸브와 소화전을 이용한 상수관(300㎜이하)의 물세척(플러싱) 작업 기준이다."""
|
||||
|
||||
text2 = text2.replace(target_2_1_5, repl_2_1_5)
|
||||
text2 = text2.replace(target_2_1_27, repl_2_1_27)
|
||||
text2 = text2.replace(target_2_2_15, repl_2_2_15)
|
||||
text2 = text2.replace(target_2_2_16, repl_2_2_16)
|
||||
text2 = text2.replace(target_2_4_1, repl_2_4_1)
|
||||
with open(ch2_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text2)
|
||||
print("Updated 제2장_토목.md successfully.")
|
||||
|
||||
# 3. 제3장 건축
|
||||
ch3_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제3장_건축.md"
|
||||
with open(ch3_path, 'r', encoding='utf-8') as f:
|
||||
text3 = f.read()
|
||||
|
||||
target_3_1_5 = "- ⑦ 공구손료 및 경장비(살수장비 등)의 기계경비는 인력품의 4%로 계상한다.유지관리부문9123-1-5 석축 헐기(인력)('22년 보완)구 분단 위할석공(인)보통인부(인)메쌓기 뒷길이 45∼60㎝㎡당-0.2메쌓기 뒷길이 60∼90㎝㎡당-0.3찰쌓기㎡당-0.6절석 ( 마름돌) 쌓기㎥당0.11.1[주] ① 본 품은 기준높이 3.6m일 때의 인력헐기를 기준한 것이며, 그 이상일 때의 작업 안전설비 및 특수 조건에 대한 품은 별도 계상한다."
|
||||
repl_3_1_5 = """- ⑦ 공구손료 및 경장비(살수장비 등)의 기계경비는 인력품의 4%로 계상한다.
|
||||
|
||||
유지관리부문 912
|
||||
|
||||
3-1-5 석축 헐기(인력)('22년 보완)
|
||||
|
||||
| 구 분 | 단 위 | 할석공(인) | 보통인부(인) |
|
||||
|---|---|---|---|
|
||||
| 메쌓기 뒷길이 45∼60㎝ | ㎡당 | - | 0.2 |
|
||||
| 메쌓기 뒷길이 60∼90㎝ | ㎡당 | - | 0.3 |
|
||||
| 찰쌓기 | ㎡당 | - | 0.6 |
|
||||
| 절석(마름돌) 쌓기 | ㎥당 | 0.1 | 1.1 |
|
||||
|
||||
[주] ① 본 품은 기준높이 3.6m일 때의 인력헐기를 기준한 것이며, 그 이상일 때의 작업 안전설비 및 특수 조건에 대한 품은 별도 계상한다."""
|
||||
|
||||
target_3_2_12 = "- ④ 바탕처리에 사용되는 재료(퍼티, 방수테이프 등)는 별도 계상한다.유지관리부문9163-2-12 석면건축자재 해체('09년 신설, '11년 보완)(㎡당)구 분단 위내장재외장재뿜칠재석면해체공인0.1200.0450.5보통인부인0.0170.011-[주] ① 본 품은 석면이 함유된 자재를 해체하는 품으로 적용기준은 다음과 같다.㉮ 내장재는 건축물의 내부 천장재, 내벽체, 간막이재 철거를 기준한 것이다.㉯ 외장재는 슬레이트 지붕재 해체를 기준한 것이다.㉰ 뿜칠재는 철골내화피복재를 기준으로 한 것으로 철골면의 하부면, 측면부, 상부면 등의 해체공사와 철재로 시공된 천장면에 부착되어 있는 뿜칠재의 해체를 기준한 것이다."
|
||||
repl_3_2_12 = """- ④ 바탕처리에 사용되는 재료(퍼티, 방수테이프 등)는 별도 계상한다.
|
||||
|
||||
유지관리부문 916
|
||||
|
||||
3-2-12 석면건축자재 해체('09년 신설, '11년 보완)
|
||||
|
||||
(㎡당)
|
||||
|
||||
| 구 분 | 단 위 | 내장재 | 외장재 | 뿜칠재 |
|
||||
|---|---|---|---|---|
|
||||
| 석면해체공 | 인 | 0.120 | 0.045 | 0.5 |
|
||||
| 보통인부 | 인 | 0.017 | 0.011 | - |
|
||||
|
||||
[주] ① 본 품은 석면이 함유된 자재를 해체하는 품으로 적용기준은 다음과 같다.
|
||||
㉮ 내장재는 건축물의 내부 천장재, 내벽체, 간막이재 철거를 기준한 것이다.
|
||||
㉯ 외장재는 슬레이트 지붕재 해체를 기준한 것이다.
|
||||
㉰ 뿜칠재는 철골내화피복재를 기준으로 한 것으로 철골면의 하부면, 측면부, 상부면 등의 해체공사와 철재로 시공된 천장면에 부착되어 있는 뿜칠재의 해체를 기준한 것이다."""
|
||||
|
||||
target_3_4_4 = "- ③ 가구 등 지장물의 운반은 별도 계상한다.제3장 건축9193-4-4 PVC계바닥재 교체('22년 신설)(일당)구 분단 위수 량시공량(㎡)내장공인261비고"
|
||||
repl_3_4_4 = """- ③ 가구 등 지장물의 운반은 별도 계상한다.
|
||||
|
||||
제3장 건축 919
|
||||
|
||||
3-4-4 PVC계바닥재 교체('22년 신설)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(㎡) | 비 고 |
|
||||
|---|---|---|---|---|
|
||||
| 내장공 | 인 | 2 | 61 | |"""
|
||||
|
||||
target_3_4_5 = "3-4-5 타일 교체('22년 신설)(일당)시공량(㎡)구 분단 위수 량떠붙이기(벽)압착붙이기(바닥)타일공인278비고"
|
||||
repl_3_4_5 = """3-4-5 타일 교체('22년 신설)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(㎡) | | 비 고 |
|
||||
|---|---|---|---|---|---|
|
||||
| | | | 떠붙이기(벽) | 압착붙이기(바닥) | |
|
||||
| 타일공 | 인 | 2 | 7 | 8 | |"""
|
||||
|
||||
text3 = text3.replace(target_3_1_5, repl_3_1_5)
|
||||
text3 = text3.replace(target_3_2_12, repl_3_2_12)
|
||||
text3 = text3.replace(target_3_4_4, repl_3_4_4)
|
||||
text3 = text3.replace(target_3_4_5, repl_3_4_5)
|
||||
with open(ch3_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text3)
|
||||
print("Updated 제3장_건축.md successfully.")
|
||||
|
||||
# 4. 제4장 기계설비
|
||||
ch4_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문/제4장_기계설비.md"
|
||||
with open(ch4_path, 'r', encoding='utf-8') as f:
|
||||
text4 = f.read()
|
||||
|
||||
target_4_3_3 = "4-3-3 배관누수 검사('22년 신설)(일당)구 분단 위수 량시공량(회)배관공인22.8[주] ① 본 품은 급수용, 급탕용, 난방용 옥내배관(∅50mm이하)의 누수보수를 위해 배관을 검사하는 기준이다."
|
||||
repl_4_3_3 = """4-3-3 배관누수 검사('22년 신설)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 시공량(회) |
|
||||
|---|---|---|---|
|
||||
| 배관공 | 인 | 2 | 2.8 |
|
||||
|
||||
[주] ① 본 품은 급수용, 급탕용, 난방용 옥내배관(∅50mm이하)의 누수보수를 위해 배관을 검사하는 기준이다."""
|
||||
text4 = text4.replace(target_4_3_3, repl_4_3_3)
|
||||
with open(ch4_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text4)
|
||||
print("Updated 제4장_기계설비.md successfully.")
|
||||
@@ -0,0 +1,142 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base_dir = "resources/knowledge/original/원가계산/건설공사_표준품셈/05_유지관리부문"
|
||||
|
||||
job_words = [
|
||||
"포장공", "보통인부", "특별인부", "철근공", "보링공", "기계설비공", "철공", "조경공",
|
||||
"착암공", "용접공", "배관공", "할석공", "석공", "줄눈공", "미장공", "도장공", "방수공",
|
||||
"타일공", "내장공", "석면해체공", "궤도공", "중급기술자", "초급기술자", "고급기술자"
|
||||
]
|
||||
|
||||
files = [
|
||||
"제1장_공통.md",
|
||||
"제2장_토목.md",
|
||||
"제3장_건축.md",
|
||||
"제4장_기계설비.md"
|
||||
]
|
||||
|
||||
total_fixed = 0
|
||||
|
||||
for fname in files:
|
||||
fpath = os.path.join(base_dir, fname)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
file_fixes = 0
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if not (line_str.startswith('|') and line_str.endswith('|')):
|
||||
new_lines.append(line)
|
||||
continue
|
||||
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
|
||||
# Skip divider or note
|
||||
if all(re.match(r'^:?-+:?$', c) for c in cells if c) or (cells and cells[0] in ["비고", "비 고", "주", "[주]"]):
|
||||
new_lines.append(line)
|
||||
continue
|
||||
|
||||
# Find which cell has multiple job titles
|
||||
job_cell_idx = -1
|
||||
detected_jobs = []
|
||||
for c_idx, c in enumerate(cells):
|
||||
# Check if this cell contains multiple known job words
|
||||
# Notice in markdown it may be spaced: "철 근 공 보 통 인 부"
|
||||
# Normalize spaces inside tokens
|
||||
# Split tokens:
|
||||
toks = c.split()
|
||||
# If tokens end with common job characters
|
||||
if len(toks) >= 2 and all(any(tok.endswith(end) for end in ["공", "부", "사", "자"]) for tok in toks):
|
||||
# Candidate job cell
|
||||
# Check if next cells have matching "인 인..." and numbers
|
||||
job_cell_idx = c_idx
|
||||
detected_jobs = toks
|
||||
break
|
||||
# Also handle spaced letters like "포 장 공 보 통 인 부"
|
||||
# Combine characters and match
|
||||
c_nospace = c.replace(" ", "")
|
||||
matched_jobs = []
|
||||
cur_str = c_nospace
|
||||
for jw in sorted(job_words, key=len, reverse=True):
|
||||
while jw in cur_str:
|
||||
matched_jobs.append(jw)
|
||||
cur_str = cur_str.replace(jw, "", 1)
|
||||
if len(matched_jobs) >= 2 and cur_str == "":
|
||||
# Reconstruct in order of original string
|
||||
ordered_jobs = []
|
||||
pos = 0
|
||||
while pos < len(c_nospace):
|
||||
found = False
|
||||
for jw in sorted(job_words, key=len, reverse=True):
|
||||
if c_nospace[pos:].startswith(jw):
|
||||
ordered_jobs.append(jw)
|
||||
pos += len(jw)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
break
|
||||
if len(ordered_jobs) >= 2:
|
||||
job_cell_idx = c_idx
|
||||
detected_jobs = ordered_jobs
|
||||
break
|
||||
|
||||
if job_cell_idx != -1 and len(detected_jobs) >= 2:
|
||||
N = len(detected_jobs)
|
||||
# Find unit cell and qty cell
|
||||
unit_cell_idx = -1
|
||||
qty_cell_idx = -1
|
||||
|
||||
for c_idx in range(job_cell_idx + 1, len(cells)):
|
||||
toks = cells[c_idx].split()
|
||||
if len(toks) == N and all(t in ["인", "대", "hr", "시간", "식", "-"] for t in toks):
|
||||
unit_cell_idx = c_idx
|
||||
break
|
||||
|
||||
if unit_cell_idx != -1:
|
||||
# Find qty cell(s)
|
||||
# Check if there are columns with N numbers
|
||||
can_split = False
|
||||
col_splits = {} # c_idx -> list of N values
|
||||
for c_idx in range(unit_cell_idx + 1, len(cells)):
|
||||
toks = cells[c_idx].split()
|
||||
if len(toks) == N and all(re.match(r'^-?\d+(?:,\d+)?(?:\.\d+)?$', t) for t in toks):
|
||||
col_splits[c_idx] = toks
|
||||
can_split = True
|
||||
|
||||
if can_split:
|
||||
# Generate N rows
|
||||
# In other cells, first row gets the cell value, other rows get empty ""
|
||||
unit_toks = cells[unit_cell_idx].split()
|
||||
for r_idx in range(N):
|
||||
new_row_cells = []
|
||||
for c_idx in range(len(cells)):
|
||||
if c_idx == job_cell_idx:
|
||||
new_row_cells.append(detected_jobs[r_idx])
|
||||
elif c_idx == unit_cell_idx:
|
||||
new_row_cells.append(unit_toks[r_idx])
|
||||
elif c_idx in col_splits:
|
||||
new_row_cells.append(col_splits[c_idx][r_idx])
|
||||
else:
|
||||
# First row keeps existing value, subsequent rows get empty string
|
||||
if r_idx == 0:
|
||||
new_row_cells.append(cells[c_idx])
|
||||
else:
|
||||
new_row_cells.append("")
|
||||
new_line_str = "| " + " | ".join(new_row_cells) + " |\n"
|
||||
new_lines.append(new_line_str)
|
||||
file_fixes += 1
|
||||
total_fixed += 1
|
||||
continue
|
||||
|
||||
new_lines.append(line)
|
||||
|
||||
if file_fixes > 0:
|
||||
print(f"{fname}: fixed {file_fixes} multi-worker squashed rows")
|
||||
with open(fpath, 'w', encoding='utf-8') as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
print(f"Total multi-worker rows fixed in 유지관리부문: {total_fixed}")
|
||||
@@ -0,0 +1,237 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# 1. 제1장 배관공사
|
||||
ch1_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제1장_배관공사.md"
|
||||
with open(ch1_path, 'r', encoding='utf-8') as f:
|
||||
text1 = f.read()
|
||||
|
||||
target_1_6_2 = "1-6-2 폴리부틸렌(PB) 이중관 접합 및 배관('13, '19년 보완)(m당)수 량 (규격)구 분단 위ø16mmø20mm배관공인0.0480.053보통인부인0.0210.023[주] ① 본 품은 합성수지제 휨(가요) 전선관 중 CD(Combine Duct)관 내에 폴리부틸렌(PB)관이 삽입된 이중관의 옥내바닥배관 기준이다."
|
||||
repl_1_6_2 = """1-6-2 폴리부틸렌(PB) 이중관 접합 및 배관('13, '19년 보완)
|
||||
|
||||
(m당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (규격) | |
|
||||
|---|---|---|---|
|
||||
| | | ø16mm | ø20mm |
|
||||
| 배관공 | 인 | 0.048 | 0.053 |
|
||||
| 보통인부 | 인 | 0.021 | 0.023 |
|
||||
|
||||
[주] ① 본 품은 합성수지제 휨(가요) 전선관 중 CD(Combine Duct)관 내에 폴리부틸렌(PB)관이 삽입된 이중관의 옥내바닥배관 기준이다."""
|
||||
|
||||
target_1_6_3 = "1-6-3 가교화 폴리에틸렌관 접합 및 배관('13, '19년 보완)(m당)수 량 (규격)구 분단 위ø16mmø20mm배관공인0.0290.036보통인부인0.0140.018[주] ① 본 품은 가교화 폴리에틸렌(PE-X)관의 옥내난방배관 기준이다."
|
||||
repl_1_6_3 = """1-6-3 가교화 폴리에틸렌관 접합 및 배관('13, '19년 보완)
|
||||
|
||||
(m당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (규격) | |
|
||||
|---|---|---|---|
|
||||
| | | ø16mm | ø20mm |
|
||||
| 배관공 | 인 | 0.029 | 0.036 |
|
||||
| 보통인부 | 인 | 0.014 | 0.018 |
|
||||
|
||||
[주] ① 본 품은 가교화 폴리에틸렌(PE-X)관의 옥내난방배관 기준이다."""
|
||||
|
||||
text1 = text1.replace(target_1_6_2, repl_1_6_2)
|
||||
text1 = text1.replace(target_1_6_3, repl_1_6_3)
|
||||
with open(ch1_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text1)
|
||||
print("Updated 제1장_배관공사.md successfully.")
|
||||
|
||||
# 2. 제3장 보온공사
|
||||
ch3_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제3장_보온공사.md"
|
||||
with open(ch3_path, 'r', encoding='utf-8') as f:
|
||||
text3 = f.read()
|
||||
|
||||
target_3_3_1 = "3-3 덕트보온3-3-1 각형덕트 보온('14, '20, '24년 보완)(일당)시공량(㎡)보온두께구 분단 위수 량고무발포보온재유리면보온재(mm)발포폴리에틸렌보온재(글라스울)보온공인225이하9.58.0보통인부인150이하8.57.0[주] ① 본 품은 접착제가 부착된 고무발포 보온재, 발포 폴리에틸렌 보온재와 접착제가 부착되지 않은 유리면보온재(글라스울)를 사용한 각형덕트 보온 기준이다."
|
||||
repl_3_3_1 = """3-3 덕트보온
|
||||
|
||||
3-3-1 각형덕트 보온('14, '20, '24년 보완)
|
||||
|
||||
(일당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 | 보온두께(mm) | 시공량(㎡) | |
|
||||
|---|---|---|---|---|---|
|
||||
| | | | | 고무발포보온재 / 발포폴리에틸렌보온재 | 유리면보온재(글라스울) |
|
||||
| 보온공 | 인 | 2 | 25이하 | 9.5 | 8.0 |
|
||||
| 보통인부 | 인 | 1 | 50이하 | 8.5 | 7.0 |
|
||||
|
||||
[주] ① 본 품은 접착제가 부착된 고무발포 보온재, 발포 폴리에틸렌 보온재와 접착제가 부착되지 않은 유리면보온재(글라스울)를 사용한 각형덕트 보온 기준이다."""
|
||||
|
||||
target_3_4_2 = "3-4-2 분전함 설치('06년 신설, '14, '20년 보완)(개소당)구 분단 위수 량기계설비공인 0.271보통인부인0.135[주] ① 본 품은 발열선의 작동을 위한 분전함(제어부) 설치 기준이다."
|
||||
repl_3_4_2 = """3-4-2 분전함 설치('06년 신설, '14, '20년 보완)
|
||||
|
||||
(개소당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 기계설비공 | 인 | 0.271 |
|
||||
| 보통인부 | 인 | 0.135 |
|
||||
|
||||
[주] ① 본 품은 발열선의 작동을 위한 분전함(제어부) 설치 기준이다."""
|
||||
|
||||
text3 = text3.replace(target_3_3_1, repl_3_3_1)
|
||||
text3 = text3.replace(target_3_4_2, repl_3_4_2)
|
||||
with open(ch3_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text3)
|
||||
print("Updated 제3장_보온공사.md successfully.")
|
||||
|
||||
# 3. 제6장 측정기기공사
|
||||
ch6_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제6장_측정기기공사.md"
|
||||
with open(ch6_path, 'r', encoding='utf-8') as f:
|
||||
text6 = f.read()
|
||||
|
||||
target_6_1_3 = "6-1-3 세대용 계량기함 설치('26년 신설)(개소당)구 분단 위수 량배관공인0.086보통인부인0.086[주] ① 본 품은 유량계(ø32mm이하) 보호를 위한 거치대 계량기함(2단 이하)의 설치 기준이다."
|
||||
repl_6_1_3 = """6-1-3 세대용 계량기함 설치('26년 신설)
|
||||
|
||||
(개소당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 배관공 | 인 | 0.086 |
|
||||
| 보통인부 | 인 | 0.086 |
|
||||
|
||||
[주] ① 본 품은 유량계(ø32mm이하) 보호를 위한 거치대 계량기함(2단 이하)의 설치 기준이다."""
|
||||
|
||||
target_6_2_3 = "6-2-3 산업용 적산열량계 설치('19, '26년 보완)(대당)수 량 (규격)구 분단 위ø32mmø50mmø100mmø150mm플랜트배관공인0.710.750.850.95특별인부인0.710.750.850.95계장공인0.710.750.850.95[주] ① 본 품은 발전소 등 집단에너지의 생산·수송·분배 또는 사용을 위한 시설에 산업용으로 적산열량계를 설치하는 기준이다"
|
||||
repl_6_2_3 = """6-2-3 산업용 적산열량계 설치('19, '26년 보완)
|
||||
|
||||
(대당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (규격) | | | |
|
||||
|---|---|---|---|---|---|
|
||||
| | | ø32mm | ø50mm | ø100mm | ø150mm |
|
||||
| 플랜트배관공 | 인 | 0.71 | 0.75 | 0.85 | 0.95 |
|
||||
| 특별인부 | 인 | 0.71 | 0.75 | 0.85 | 0.95 |
|
||||
| 계장공 | 인 | 0.71 | 0.75 | 0.85 | 0.95 |
|
||||
|
||||
[주] ① 본 품은 발전소 등 집단에너지의 생산·수송·분배 또는 사용을 위한 시설에 산업용으로 적산열량계를 설치하는 기준이다."""
|
||||
|
||||
text6 = text6.replace(target_6_1_3, repl_6_1_3)
|
||||
text6 = text6.replace(target_6_2_3, repl_6_2_3)
|
||||
with open(ch6_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text6)
|
||||
print("Updated 제6장_측정기기공사.md successfully.")
|
||||
|
||||
# 4. 제7장 위생기구설비공사
|
||||
ch7_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제7장_위생기구설비공사.md"
|
||||
with open(ch7_path, 'r', encoding='utf-8') as f:
|
||||
text7 = f.read()
|
||||
|
||||
target_7_shower = "| 비 고 | - 샤워헤드걸이를 설치는 다음을 적용하여 가산한다. (개당) 구 분 단 위 고정식 높이조절식 위 생 공 인 0.071 0.099"
|
||||
repl_7_shower = """| 비 고 | - 샤워헤드걸이 설치는 다음을 적용하여 가산한다. (개당)
|
||||
|
||||
| 구 분 | 단 위 | 고정식 | 높이조절식 |
|
||||
|---|---|---|---|
|
||||
| 위생공 | 인 | 0.071 | 0.099 |"""
|
||||
text7 = text7.replace(target_7_shower, repl_7_shower)
|
||||
with open(ch7_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text7)
|
||||
print("Updated 제7장_위생기구설비공사.md successfully.")
|
||||
|
||||
# 5. 제8장 공기조화설비공사
|
||||
ch8_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제8장_공기조화설비공사.md"
|
||||
with open(ch8_path, 'r', encoding='utf-8') as f:
|
||||
text8 = f.read()
|
||||
|
||||
target_8_2_5 = "8-2-5 전열교환기 설치('20년 신설)(대당)수 량 (풍량 ㎥/h)구 분단 위250이하500이하800이하기계설비공인0.210.280.36보통인부인0.120.160.20[주] ① 본 품은 천장에 설치하여 덕트와 연결하는 환기시스템(전열교환기) 기준이다."
|
||||
repl_8_2_5 = """8-2-5 전열교환기 설치('20년 신설)
|
||||
|
||||
(대당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (풍량 ㎥/h) | | |
|
||||
|---|---|---|---|---|
|
||||
| | | 250이하 | 500이하 | 800이하 |
|
||||
| 기계설비공 | 인 | 0.21 | 0.28 | 0.36 |
|
||||
| 보통인부 | 인 | 0.12 | 0.16 | 0.20 |
|
||||
|
||||
[주] ① 본 품은 천장에 설치하여 덕트와 연결하는 환기시스템(전열교환기) 기준이다."""
|
||||
|
||||
target_8_3_3 = "8-3-3 가스보일러(가정용) 설치('92년 신설, '16, '20년 보완)(대당)수 량구 분단 위13,000 16,000 20,000 25,000 30,000 Kcal/hrKcal/hrKcal/hrKcal/hrKcal/hr보일러공인0.8450.9521.0281.1231.218보통인부인0.1640.1840.1990.2170.236비고"
|
||||
repl_8_3_3 = """8-3-3 가스보일러(가정용) 설치('92년 신설, '16, '20년 보완)
|
||||
|
||||
(대당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (Kcal/hr) | | | | | 비 고 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| | | 13,000 | 16,000 | 20,000 | 25,000 | 30,000 | |
|
||||
| 보일러공 | 인 | 0.845 | 0.952 | 1.028 | 1.123 | 1.218 | |
|
||||
| 보통인부 | 인 | 0.164 | 0.184 | 0.199 | 0.217 | 0.236 | |"""
|
||||
|
||||
target_8_4_2 = "8-4-2 전기온수기(벽걸이형) 설치('20년 신설)(대당)수 량구 분단 위15L30L50L보일러공인0.170.180.23보통인부인0.070.080.09[주] ① 본 품은 벽걸이형 전기온수기 설치 기준이다."
|
||||
repl_8_4_2 = """8-4-2 전기온수기(벽걸이형) 설치('20년 신설)
|
||||
|
||||
(대당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 (용량) | | |
|
||||
|---|---|---|---|---|
|
||||
| | | 15L | 30L | 50L |
|
||||
| 보일러공 | 인 | 0.17 | 0.18 | 0.23 |
|
||||
| 보통인부 | 인 | 0.07 | 0.08 | 0.09 |
|
||||
|
||||
[주] ① 본 품은 벽걸이형 전기온수기 설치 기준이다."""
|
||||
|
||||
text8 = text8.replace(target_8_2_5, repl_8_2_5)
|
||||
text8 = text8.replace(target_8_3_3, repl_8_3_3)
|
||||
text8 = text8.replace(target_8_4_2, repl_8_4_2)
|
||||
with open(ch8_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text8)
|
||||
print("Updated 제8장_공기조화설비공사.md successfully.")
|
||||
|
||||
# 6. 제10장 소방설비공사
|
||||
ch10_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제10장_소방설비공사.md"
|
||||
with open(ch10_path, 'r', encoding='utf-8') as f:
|
||||
text10 = f.read()
|
||||
|
||||
target_10_8_2 = "10-8-2 자동식 소화기 설치('99년 신설, '14년 보완)(개당)구 분 단 위수 량기계설비공인 0.212보통인부인0.117[주] ① 본 품은 세대내 레인지후드에 자동식 소화기를 설치하는 품이다."
|
||||
repl_10_8_2 = """10-8-2 자동식 소화기 설치('99년 신설, '14년 보완)
|
||||
|
||||
(개당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 기계설비공 | 인 | 0.212 |
|
||||
| 보통인부 | 인 | 0.117 |
|
||||
|
||||
[주] ① 본 품은 세대내 레인지후드에 자동식 소화기를 설치하는 품이다."""
|
||||
|
||||
target_10_9_1 = "10-9 피난기구10-9-1 완강기 설치('04년 신설, '09, '14년 보완)(개당)구 분 단 위수 량기계설비공인 0.094보통인부인0.046[주] ① 본 품은 피난용 완강기를 설치하는 품이다."
|
||||
repl_10_9_1 = """10-9 피난기구
|
||||
|
||||
10-9-1 완강기 설치('04년 신설, '09, '14년 보완)
|
||||
|
||||
(개당)
|
||||
|
||||
| 구 분 | 단 위 | 수 량 |
|
||||
|---|---|---|
|
||||
| 기계설비공 | 인 | 0.094 |
|
||||
| 보통인부 | 인 | 0.046 |
|
||||
|
||||
[주] ① 본 품은 피난용 완강기를 설치하는 품이다."""
|
||||
|
||||
text10 = text10.replace(target_10_8_2, repl_10_8_2)
|
||||
text10 = text10.replace(target_10_9_1, repl_10_9_1)
|
||||
with open(ch10_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text10)
|
||||
print("Updated 제10장_소방설비공사.md successfully.")
|
||||
|
||||
# 7. 제13장 플랜트설비공사 Buckstay
|
||||
ch13_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/04_기계설비부문/제13장_플랜트설비공사.md"
|
||||
with open(ch13_path, 'r', encoding='utf-8') as f:
|
||||
text13 = f.read()
|
||||
|
||||
target_13_buck = "→제13장 플랜트설비공사771작 업 구 분직 종단 위수 량Buckstay 조립조정플랜트기계설치공인/ton1.61조양된 buckstay를 alignment하고 tiebar 특별인부〃1.81취급함.플랜트용접공〃1.41[참고]"
|
||||
repl_13_buck = """→제13장 플랜트설비공사 771
|
||||
|
||||
| 작 업 구 분 | 직 종 | 단 위 | 수 량 | 비 고 |
|
||||
|---|---|---|---|---|
|
||||
| Buckstay 조립조정 | 플랜트기계설치공 | 인/ton | 1.61 | 조양된 buckstay를 alignment하고 tiebar 취급함. |
|
||||
| | 특별인부 | 〃 | 1.81 | |
|
||||
| | 플랜트용접공 | 〃 | 1.41 | |
|
||||
|
||||
[참고]"""
|
||||
text13 = text13.replace(target_13_buck, repl_13_buck)
|
||||
with open(ch13_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text13)
|
||||
print("Updated 제13장_플랜트설비공사.md successfully.")
|
||||
@@ -0,0 +1,91 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ch9_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md"
|
||||
with open(ch9_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
fixed_cnt = 0
|
||||
|
||||
tech_words = [
|
||||
"특급기술자", "고급기술자", "중급기술자", "초급기술자", "초급기능사(측량)", "초급기능사", "인부", "측량사", "측량사보"
|
||||
]
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if not (line_str.startswith('|') and line_str.endswith('|')):
|
||||
new_lines.append(line)
|
||||
continue
|
||||
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
if len(cells) < 3 or cells[0] in ["계", "합계"]:
|
||||
new_lines.append(line)
|
||||
continue
|
||||
|
||||
# Check if first cell contains multiple technician grades
|
||||
c0 = cells[0]
|
||||
c0_clean = c0.replace(" ", "")
|
||||
matched_grades = []
|
||||
temp_s = c0_clean
|
||||
for tw in sorted(tech_words, key=len, reverse=True):
|
||||
while tw in temp_s:
|
||||
matched_grades.append(tw)
|
||||
temp_s = temp_s.replace(tw, "", 1)
|
||||
|
||||
if len(matched_grades) >= 2 and temp_s == "":
|
||||
# Reconstruct in order
|
||||
ordered_grades = []
|
||||
pos = 0
|
||||
while pos < len(c0_clean):
|
||||
for tw in sorted(tech_words, key=len, reverse=True):
|
||||
if c0_clean[pos:].startswith(tw):
|
||||
ordered_grades.append(tw)
|
||||
pos += len(tw)
|
||||
break
|
||||
|
||||
N = len(ordered_grades)
|
||||
# Check column 1 (수량/산식)
|
||||
# Often contains N formulas ending with = number
|
||||
# e.g. "3×10/16×1.2×1.12= 2.52 24.5×10/16..."
|
||||
c1 = cells[1]
|
||||
# Match formulas like "...= <num>"
|
||||
f_matches = list(re.finditer(r'(.*?=\s*\d+(?:\.\d+)?)\s*(?=[^=]*=\s*\d+|$)', c1))
|
||||
c1_parts = [m.group(1).strip() for m in f_matches if m.group(1).strip()]
|
||||
|
||||
# Check column 2 (단가)
|
||||
c2 = cells[2] if len(cells) > 2 else ""
|
||||
c2_toks = c2.split()
|
||||
|
||||
# Check column 3 (금액)
|
||||
c3 = cells[3] if len(cells) > 3 else ""
|
||||
w_matches = list(re.finditer(r'(W\s*=\s*[^W]+?)(?=\s*W\s*=|(?:\s*1\s+)?$)', c3))
|
||||
c3_parts = [m.group(1).strip() for m in w_matches if m.group(1).strip()]
|
||||
|
||||
if len(c1_parts) == N:
|
||||
# We can split!
|
||||
for r_idx in range(N):
|
||||
g_name = ordered_grades[r_idx]
|
||||
qty_val = c1_parts[r_idx]
|
||||
rate_val = c2_toks[r_idx] if len(c2_toks) == N else (c2_toks[0] if c2_toks else "")
|
||||
amt_val = c3_parts[r_idx] if len(c3_parts) == N else ""
|
||||
|
||||
row_cells = [g_name, qty_val, rate_val, amt_val]
|
||||
# If there were more columns, append them
|
||||
for extra_c in range(4, len(cells)):
|
||||
row_cells.append("" if r_idx > 0 else cells[extra_c])
|
||||
new_line_str = "| " + " | ".join(row_cells) + " |\n"
|
||||
new_lines.append(new_line_str)
|
||||
fixed_cnt += 1
|
||||
print(f"L{idx+1}: Fixed survey calc table with {N} grades: {c0[:40]}")
|
||||
continue
|
||||
|
||||
new_lines.append(line)
|
||||
|
||||
print(f"\nTotal survey calc tables fixed: {fixed_cnt}")
|
||||
|
||||
if fixed_cnt > 0:
|
||||
with open(ch9_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(new_lines)
|
||||
print("Saved 제9장_측량.md successfully.")
|
||||
@@ -0,0 +1,87 @@
|
||||
import os, sys, re
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ch9_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문/제9장_측량.md"
|
||||
with open(ch9_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
fixed_tables = 0
|
||||
|
||||
# Common tasks lists in survey
|
||||
known_task_patterns = [
|
||||
["계획준비", "답사선점", "조표(매설)", "관 측", "계 산", "정리점검"],
|
||||
["계획준비", "답사선점", "조표(매설)", "관측", "계산", "정리점검"],
|
||||
["계획준비", "답사선점", "관 측", "계 산", "정리점검"],
|
||||
["계획준비", "답사선점", "관측", "계산", "정리점검"],
|
||||
["계획준비", "답사", "선점", "매설", "관측", "계산", "정리점검"],
|
||||
["계획준비", "선점", "매설", "관측", "계산", "정리점검"],
|
||||
]
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
line_str = line.strip()
|
||||
if not (line_str.startswith('|') and line_str.endswith('|')):
|
||||
new_lines.append(line)
|
||||
continue
|
||||
|
||||
cells = [c.strip() for c in line_str.split('|')[1:-1]]
|
||||
|
||||
# Check if first cell contains tasks
|
||||
matched_tasks = None
|
||||
c0 = cells[0]
|
||||
|
||||
# Try exact task sequence match
|
||||
for pat in known_task_patterns:
|
||||
pat_str = " ".join(pat)
|
||||
if c0 == pat_str or c0.replace(" ", "") == "".join(pat).replace(" ", ""):
|
||||
matched_tasks = pat
|
||||
break
|
||||
|
||||
if not matched_tasks:
|
||||
# Try generic tasks split if tokens are known tasks
|
||||
toks = c0.split()
|
||||
if len(toks) >= 4 and all(any(k in t for k in ["계획", "준비", "답사", "선점", "조표", "매설", "관측", "계산", "정리", "점검", "수준", "측량", "항공", "도화", "편집"]) for t in toks):
|
||||
matched_tasks = toks
|
||||
|
||||
if matched_tasks:
|
||||
N = len(matched_tasks)
|
||||
# Check if following data columns can be split into N tokens
|
||||
# Some columns might have single value (like 비고) or N tokens
|
||||
can_split = True
|
||||
col_splits = {}
|
||||
for c_idx in range(1, len(cells)):
|
||||
val = cells[c_idx]
|
||||
if not val or val == "-":
|
||||
col_splits[c_idx] = [val] * N
|
||||
continue
|
||||
v_toks = val.split()
|
||||
if len(v_toks) == N:
|
||||
col_splits[c_idx] = v_toks
|
||||
elif c_idx == len(cells) - 1: # Last column is often 비고
|
||||
col_splits[c_idx] = [val] + [""] * (N - 1)
|
||||
else:
|
||||
# Cannot cleanly split this column
|
||||
can_split = False
|
||||
break
|
||||
|
||||
if can_split:
|
||||
# Generate N rows
|
||||
for r_idx in range(N):
|
||||
row_cells = [matched_tasks[r_idx]]
|
||||
for c_idx in range(1, len(cells)):
|
||||
row_cells.append(col_splits[c_idx][r_idx])
|
||||
new_line_str = "| " + " | ".join(row_cells) + " |\n"
|
||||
new_lines.append(new_line_str)
|
||||
fixed_tables += 1
|
||||
print(f"L{idx+1}: Fixed survey table with {N} tasks: {cells[0][:30]}...")
|
||||
continue
|
||||
|
||||
new_lines.append(line)
|
||||
|
||||
print(f"\nTotal survey task tables fixed in 제9장_측량.md: {fixed_tables}")
|
||||
|
||||
if fixed_tables > 0:
|
||||
with open(ch9_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(new_lines)
|
||||
print("Saved 제9장_측량.md successfully.")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,317 @@
|
||||
[
|
||||
{
|
||||
"file": "01_공통부문\\제1장_적용기준.md",
|
||||
"original_lines": 728,
|
||||
"new_lines": 759,
|
||||
"lines_added": 31,
|
||||
"fixes_applied": 31
|
||||
},
|
||||
{
|
||||
"file": "01_공통부문\\제2장_가설공사.md",
|
||||
"original_lines": 1102,
|
||||
"new_lines": 1184,
|
||||
"lines_added": 82,
|
||||
"fixes_applied": 82
|
||||
},
|
||||
{
|
||||
"file": "01_공통부문\\제3장_토공사.md",
|
||||
"original_lines": 909,
|
||||
"new_lines": 948,
|
||||
"lines_added": 39,
|
||||
"fixes_applied": 39
|
||||
},
|
||||
{
|
||||
"file": "01_공통부문\\제4장_조경공사.md",
|
||||
"original_lines": 261,
|
||||
"new_lines": 279,
|
||||
"lines_added": 18,
|
||||
"fixes_applied": 18
|
||||
},
|
||||
{
|
||||
"file": "01_공통부문\\제5장_기초공사.md",
|
||||
"original_lines": 974,
|
||||
"new_lines": 989,
|
||||
"lines_added": 15,
|
||||
"fixes_applied": 15
|
||||
},
|
||||
{
|
||||
"file": "01_공통부문\\제6장_철근콘크리트공사.md",
|
||||
"original_lines": 998,
|
||||
"new_lines": 1037,
|
||||
"lines_added": 39,
|
||||
"fixes_applied": 39
|
||||
},
|
||||
{
|
||||
"file": "01_공통부문\\제7장_돌공사.md",
|
||||
"original_lines": 117,
|
||||
"new_lines": 125,
|
||||
"lines_added": 8,
|
||||
"fixes_applied": 8
|
||||
},
|
||||
{
|
||||
"file": "01_공통부문\\제8장_건설기계.md",
|
||||
"original_lines": 3889,
|
||||
"new_lines": 3923,
|
||||
"lines_added": 34,
|
||||
"fixes_applied": 34
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제1장_도로포장공사.md",
|
||||
"original_lines": 935,
|
||||
"new_lines": 992,
|
||||
"lines_added": 57,
|
||||
"fixes_applied": 57
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제2장_하천공사.md",
|
||||
"original_lines": 76,
|
||||
"new_lines": 84,
|
||||
"lines_added": 8,
|
||||
"fixes_applied": 8
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제3장_터널공사.md",
|
||||
"original_lines": 231,
|
||||
"new_lines": 241,
|
||||
"lines_added": 10,
|
||||
"fixes_applied": 10
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제4장_궤도공사.md",
|
||||
"original_lines": 321,
|
||||
"new_lines": 345,
|
||||
"lines_added": 24,
|
||||
"fixes_applied": 24
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제5장_강구조공사.md",
|
||||
"original_lines": 319,
|
||||
"new_lines": 329,
|
||||
"lines_added": 10,
|
||||
"fixes_applied": 10
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제6장_관부설및접합공사.md",
|
||||
"original_lines": 688,
|
||||
"new_lines": 711,
|
||||
"lines_added": 23,
|
||||
"fixes_applied": 23
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제7장_항만공사.md",
|
||||
"original_lines": 322,
|
||||
"new_lines": 341,
|
||||
"lines_added": 19,
|
||||
"fixes_applied": 19
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제8장_지반조사.md",
|
||||
"original_lines": 320,
|
||||
"new_lines": 333,
|
||||
"lines_added": 13,
|
||||
"fixes_applied": 13
|
||||
},
|
||||
{
|
||||
"file": "02_토목부문\\제9장_측량.md",
|
||||
"original_lines": 4604,
|
||||
"new_lines": 4664,
|
||||
"lines_added": 60,
|
||||
"fixes_applied": 60
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제10장_창호및유리공사.md",
|
||||
"original_lines": 142,
|
||||
"new_lines": 151,
|
||||
"lines_added": 9,
|
||||
"fixes_applied": 9
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제11장_칠공사.md",
|
||||
"original_lines": 267,
|
||||
"new_lines": 286,
|
||||
"lines_added": 19,
|
||||
"fixes_applied": 19
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제1장_철골공사.md",
|
||||
"original_lines": 371,
|
||||
"new_lines": 385,
|
||||
"lines_added": 14,
|
||||
"fixes_applied": 14
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제2장_조적공사.md",
|
||||
"original_lines": 162,
|
||||
"new_lines": 171,
|
||||
"lines_added": 9,
|
||||
"fixes_applied": 9
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제3장_타일공사.md",
|
||||
"original_lines": 99,
|
||||
"new_lines": 105,
|
||||
"lines_added": 6,
|
||||
"fixes_applied": 6
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제4장_목공사.md",
|
||||
"original_lines": 141,
|
||||
"new_lines": 153,
|
||||
"lines_added": 12,
|
||||
"fixes_applied": 12
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제5장_수장공사.md",
|
||||
"original_lines": 385,
|
||||
"new_lines": 409,
|
||||
"lines_added": 24,
|
||||
"fixes_applied": 24
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제6장_방수공사.md",
|
||||
"original_lines": 274,
|
||||
"new_lines": 297,
|
||||
"lines_added": 23,
|
||||
"fixes_applied": 23
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제7장_지붕및홈통공사.md",
|
||||
"original_lines": 138,
|
||||
"new_lines": 148,
|
||||
"lines_added": 10,
|
||||
"fixes_applied": 10
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제8장_금속공사.md",
|
||||
"original_lines": 219,
|
||||
"new_lines": 232,
|
||||
"lines_added": 13,
|
||||
"fixes_applied": 13
|
||||
},
|
||||
{
|
||||
"file": "03_건축부문\\제9장_미장공사.md",
|
||||
"original_lines": 213,
|
||||
"new_lines": 226,
|
||||
"lines_added": 13,
|
||||
"fixes_applied": 13
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제10장_소방설비공사.md",
|
||||
"original_lines": 170,
|
||||
"new_lines": 184,
|
||||
"lines_added": 14,
|
||||
"fixes_applied": 14
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제11장_가스설비공사.md",
|
||||
"original_lines": 177,
|
||||
"new_lines": 183,
|
||||
"lines_added": 6,
|
||||
"fixes_applied": 6
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제12장_자동제어설비공사.md",
|
||||
"original_lines": 167,
|
||||
"new_lines": 173,
|
||||
"lines_added": 6,
|
||||
"fixes_applied": 6
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제13장_플랜트설비공사.md",
|
||||
"original_lines": 3498,
|
||||
"new_lines": 3537,
|
||||
"lines_added": 39,
|
||||
"fixes_applied": 39
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제1장_배관공사.md",
|
||||
"original_lines": 357,
|
||||
"new_lines": 376,
|
||||
"lines_added": 19,
|
||||
"fixes_applied": 19
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제2장_덕트공사.md",
|
||||
"original_lines": 174,
|
||||
"new_lines": 186,
|
||||
"lines_added": 12,
|
||||
"fixes_applied": 12
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제3장_보온공사.md",
|
||||
"original_lines": 152,
|
||||
"new_lines": 162,
|
||||
"lines_added": 10,
|
||||
"fixes_applied": 10
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제4장_펌프및공기설비공사.md",
|
||||
"original_lines": 134,
|
||||
"new_lines": 143,
|
||||
"lines_added": 9,
|
||||
"fixes_applied": 9
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제5장_밸브설비공사.md",
|
||||
"original_lines": 88,
|
||||
"new_lines": 93,
|
||||
"lines_added": 5,
|
||||
"fixes_applied": 5
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제6장_측정기기공사.md",
|
||||
"original_lines": 79,
|
||||
"new_lines": 86,
|
||||
"lines_added": 7,
|
||||
"fixes_applied": 7
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제7장_위생기구설비공사.md",
|
||||
"original_lines": 201,
|
||||
"new_lines": 216,
|
||||
"lines_added": 15,
|
||||
"fixes_applied": 15
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제8장_공기조화설비공사.md",
|
||||
"original_lines": 345,
|
||||
"new_lines": 364,
|
||||
"lines_added": 19,
|
||||
"fixes_applied": 19
|
||||
},
|
||||
{
|
||||
"file": "04_기계설비부문\\제9장_기타공사.md",
|
||||
"original_lines": 193,
|
||||
"new_lines": 205,
|
||||
"lines_added": 12,
|
||||
"fixes_applied": 12
|
||||
},
|
||||
{
|
||||
"file": "05_유지관리부문\\제1장_공통.md",
|
||||
"original_lines": 505,
|
||||
"new_lines": 529,
|
||||
"lines_added": 24,
|
||||
"fixes_applied": 24
|
||||
},
|
||||
{
|
||||
"file": "05_유지관리부문\\제2장_토목.md",
|
||||
"original_lines": 1284,
|
||||
"new_lines": 1326,
|
||||
"lines_added": 42,
|
||||
"fixes_applied": 42
|
||||
},
|
||||
{
|
||||
"file": "05_유지관리부문\\제3장_건축.md",
|
||||
"original_lines": 297,
|
||||
"new_lines": 316,
|
||||
"lines_added": 19,
|
||||
"fixes_applied": 19
|
||||
},
|
||||
{
|
||||
"file": "05_유지관리부문\\제4장_기계설비.md",
|
||||
"original_lines": 184,
|
||||
"new_lines": 193,
|
||||
"lines_added": 9,
|
||||
"fixes_applied": 9
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
import sys, fitz
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
pdf_path = "resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.pdf"
|
||||
doc = fitz.open(pdf_path)
|
||||
|
||||
# Check 1-2-7 덤프적재표 (PDF page 73-75, printed page 17-19)
|
||||
print("=== 1-2-7 운반 PDF Page 74 (Printed page 18) ===")
|
||||
print(doc[73].get_text()[:1500])
|
||||
|
||||
# Check 1-3-1 재료할증 (PDF page 76-78, printed page 20-22)
|
||||
print("\n=== 1-3-1 재료할증 PDF Page 76 (Printed page 20) ===")
|
||||
print(doc[75].get_text()[:1500])
|
||||
@@ -0,0 +1,13 @@
|
||||
import os, sys, json
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
with open('scratch/const_md_flaws_scan.json', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
for k, v in data.items():
|
||||
t_list = v.get('collapsed_table_rows', [])
|
||||
print(f"\n=== {k}: {len(t_list)} collapsed rows ===")
|
||||
for idx, t in enumerate(t_list):
|
||||
snip = t['snippet'].replace('\n', ' ')
|
||||
print(f" [{idx+1}] L{t['line']} (tokens: {t['token_count']}): {snip[:90]}")
|
||||
@@ -0,0 +1,13 @@
|
||||
import json, sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
with open('scratch/wide_criteria_my_scope.json', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
survey_items = [d for d in data if '제9장_측량' in d['file']]
|
||||
print(f"Total wide rows in 제9장 측량: {len(survey_items)}")
|
||||
|
||||
for item in survey_items[:15]:
|
||||
print(f"\nL{item['line']}: {item['reasons']}")
|
||||
print(f" RAW: {item['raw_line']}")
|
||||
@@ -0,0 +1,60 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from scratch.enrich_tables import tables, get_chapter
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
master_tables = {}
|
||||
wi_map = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = t
|
||||
wi_map[tid] = wi
|
||||
|
||||
# Check chapters 1, 2, 5, 9, 13
|
||||
for ch_num in ['제1장', '제2장', '제5장', '제9장', '제13장']:
|
||||
print(f"\n==================== {ch_num} ====================")
|
||||
ch_tables = [t for t in tables if ch_num in t['chapter']]
|
||||
print(f"Total tables in {ch_num}: {len(ch_tables)}")
|
||||
|
||||
# Analyze form, basis, notes, variants
|
||||
for t in ch_tables:
|
||||
tid = t['table_id']
|
||||
mt = master_tables.get(tid)
|
||||
if not mt:
|
||||
continue
|
||||
|
||||
m_qty = mt.get('basis_quantity')
|
||||
m_unit = mt.get('basis_unit')
|
||||
pform = mt.get('pum_form')
|
||||
|
||||
pre_text = " // ".join(t['pre_context'][-3:])
|
||||
notes_text = " // ".join(t['notes'][:3]) if t['notes'] else ""
|
||||
header_text = t['lines'][0].strip() if t['lines'] else ""
|
||||
|
||||
# Report issues
|
||||
issues = []
|
||||
if pform == 'reference':
|
||||
# Check if this table contains actual numbers/costs
|
||||
issues.append(f"FORM_REFERENCE_SUSPECT (form={pform})")
|
||||
if m_qty is None or m_unit is None:
|
||||
# Check if MD has basis
|
||||
issues.append(f"BASIS_NULL (master={m_qty} {m_unit})")
|
||||
if notes_text and not mt.get('condition_note'):
|
||||
issues.append(f"NOTE_MISSING_IN_MASTER ({len(t['notes'])} notes)")
|
||||
|
||||
if issues:
|
||||
print(f"[{tid} | line {t['start_line']}] {t['section']} -> {', '.join(issues)}")
|
||||
if "REFERENCE" in str(issues) or "BASIS" in str(issues):
|
||||
print(f" pre: {pre_text}")
|
||||
print(f" hdr: {header_text}")
|
||||
if notes_text:
|
||||
print(f" notes: {notes_text}")
|
||||
@@ -0,0 +1,47 @@
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from scratch.enrich_tables import tables, get_chapter
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
with open('resources/data_work_item_master/work_item_master_2026-01-01.json', 'r', encoding='utf-8') as f:
|
||||
master = json.load(f)
|
||||
|
||||
master_tables = {}
|
||||
wi_map = {}
|
||||
for wi in master['work_items']:
|
||||
for t in wi.get('tables', []):
|
||||
tid = t.get('pum_table_id')
|
||||
if tid:
|
||||
master_tables[tid] = t
|
||||
wi_map[tid] = wi
|
||||
|
||||
# Check chapter 5, 9, 12 specifically
|
||||
for ch_num in ['제5장', '제9장', '제12장']:
|
||||
print(f"\n==================== {ch_num} ====================")
|
||||
ch_tables = [t for t in tables if ch_num in t['chapter']]
|
||||
for t in ch_tables:
|
||||
tid = t['table_id']
|
||||
mt = master_tables.get(tid)
|
||||
if not mt:
|
||||
continue
|
||||
m_qty = mt.get('basis_quantity')
|
||||
m_unit = mt.get('basis_unit')
|
||||
pform = mt.get('pum_form')
|
||||
|
||||
# Look for unit patterns in pre_context and headers
|
||||
pre_text = " // ".join(t['pre_context'][-5:])
|
||||
notes_text = " // ".join(t['notes'][:3]) if t['notes'] else ""
|
||||
header_text = t['lines'][0].strip() if t['lines'] else ""
|
||||
|
||||
# Check if basis missing or interesting
|
||||
if m_qty is None or m_unit is None or pform == 'reference':
|
||||
print(f"[{tid} | line {t['start_line']}] sec: {t['section']}")
|
||||
print(f" pre: {pre_text}")
|
||||
print(f" hdr: {header_text}")
|
||||
print(f" master basis: {m_qty} {m_unit} | form: {pform}")
|
||||
if notes_text:
|
||||
print(f" notes: {notes_text}")
|
||||
@@ -0,0 +1,34 @@
|
||||
import os, sys
|
||||
sys.path.append('scratch')
|
||||
from find_strict_grouped_tables import true_grouped_tables
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
target_fnames = [
|
||||
'제3장_터널공사.md',
|
||||
'제4장_궤도공사.md',
|
||||
'제5장_강구조공사.md',
|
||||
'제6장_관부설및접합공사.md',
|
||||
'제7장_항만공사.md',
|
||||
'제8장_지반조사.md',
|
||||
]
|
||||
|
||||
for fname in target_fnames:
|
||||
tbls = [t for t in true_grouped_tables if t['file'] == fname]
|
||||
print(f"\n#######################################################")
|
||||
print(f"### {fname} (총 {len(tbls)}개 표)")
|
||||
print(f"#######################################################")
|
||||
|
||||
# find path
|
||||
fpath = os.path.join('resources/knowledge/original/원가계산/건설공사_표준품셈/02_토목부문', fname)
|
||||
with open(fpath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for t in tbls:
|
||||
lno = t['line']
|
||||
print(f"\n--- [L{lno}] {t['title']} ---")
|
||||
print(f"Groups: {t['groups']}")
|
||||
start = max(0, lno - 1)
|
||||
end = min(len(lines), lno + t['total_rows'] + 2)
|
||||
for i in range(start, end):
|
||||
print(f"{i+1:4d}: {lines[i].rstrip()}")
|
||||
@@ -0,0 +1,38 @@
|
||||
import pypdf, sys, os
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
reader = pypdf.PdfReader('resources/knowledge/original/원가계산/건설공사_표준품셈/2026년_건설공사_표준품셈.pdf')
|
||||
|
||||
targets = [
|
||||
("2-8-7", "철골 안전망"),
|
||||
("2-8-10", "수직형 추락방망"),
|
||||
("2-11-5", "비산먼지"),
|
||||
("2-11-7", "슬러지 제거"),
|
||||
("4-4-4", "야자섬유매트"),
|
||||
("5-1-3", "버팀보"),
|
||||
("5-1-8", "복공판"),
|
||||
("7-4-2", "앵커 긴결"),
|
||||
("슬립폼", "설치 및 해체"),
|
||||
("지수판", "소켓 연결"),
|
||||
("정착구 설치", "쉬즈관"),
|
||||
]
|
||||
|
||||
for sec, kw in targets:
|
||||
found = False
|
||||
for idx, page in enumerate(reader.pages[:300]): # common part is in first 300 pages
|
||||
txt = page.extract_text()
|
||||
if sec in txt or (kw in txt and ("품" in txt or "공" in txt)):
|
||||
print(f"=== Found '{sec}' on PDF page index {idx} ===")
|
||||
# print surrounding lines
|
||||
lines = txt.split('\n')
|
||||
for l_idx, l in enumerate(lines):
|
||||
if sec in l or kw in l:
|
||||
start = max(0, l_idx - 3)
|
||||
end = min(len(lines), l_idx + 15)
|
||||
print("\n".join(lines[start:end]))
|
||||
print("-" * 50)
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
break
|
||||
@@ -0,0 +1,28 @@
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Check const_work_item_master
|
||||
const_path = Path('resources/data_work_item_master/const_work_item_master_2026-01-01.json')
|
||||
with open(const_path, 'r', encoding='utf-8') as f:
|
||||
const_master = json.load(f)
|
||||
|
||||
print("const_work_item_master keys:", list(const_master.keys()))
|
||||
const_items = const_master.get('work_items', [])
|
||||
print(f"const work_items count: {len(const_items)}")
|
||||
|
||||
# Count tables with notes
|
||||
tables_with_notes = 0
|
||||
total_const_tables = 0
|
||||
for wi in const_items:
|
||||
for t in wi.get('tables', []):
|
||||
total_const_tables += 1
|
||||
if t.get('notes'):
|
||||
tables_with_notes += 1
|
||||
|
||||
print(f"Total const tables: {total_const_tables}, with notes: {tables_with_notes}")
|
||||
|
||||
# Inspect git diff on const_work_item_master for notes removal
|
||||
# Run a git command to see what lines were removed in commit 32c4c5cf for const master
|
||||
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
pum_forest_path = Path('resources/data_cost_input_value/pum_forest_2026.json')
|
||||
with open(pum_forest_path, 'r', encoding='utf-8') as f:
|
||||
p = json.load(f)
|
||||
|
||||
pum_var = p.get('variables', {}).get('pum', {})
|
||||
print("pum_var keys:", list(pum_var.keys()))
|
||||
for k, v in pum_var.items():
|
||||
if isinstance(v, dict):
|
||||
print(f" {k}: dict with {len(v)} keys (sample: {list(v.keys())[:5]})")
|
||||
elif isinstance(v, list):
|
||||
print(f" {k}: list with {len(v)} items")
|
||||
else:
|
||||
print(f" {k}: {type(v)}")
|
||||
|
||||
# Check work_item_master structure details
|
||||
master_path = Path('resources/data_work_item_master/work_item_master_2026-01-01.json')
|
||||
with open(master_path, 'r', encoding='utf-8') as f:
|
||||
w = json.load(f)
|
||||
|
||||
print("\n--- Work Item Sample ---")
|
||||
wi_with_tables = [wi for wi in w['work_items'] if wi.get('tables')][:2]
|
||||
for wi in wi_with_tables:
|
||||
print(f"WI: {wi['work_item_code']} / {wi['name']} / level {wi['level']}")
|
||||
for tbl in wi['tables']:
|
||||
print(f" Table: {tbl.get('pum_table_id')} / {tbl.get('section')} / basis: {tbl.get('basis_quantity')} {tbl.get('basis_unit')} / form: {tbl.get('pum_form')}")
|
||||
if 'raw_markdown' in tbl:
|
||||
print(f" raw_markdown lines: {len(tbl['raw_markdown'].splitlines())}")
|
||||
|
||||
print(f"\nOrphan tables count: {len(w.get('orphan_tables', []))}")
|
||||
for ot in w.get('orphan_tables', [])[:5]:
|
||||
print(f" Orphan Table: {ot.get('pum_table_id')} / {ot.get('section')} / basis: {ot.get('basis_quantity')} {ot.get('basis_unit')}")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user