- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
117 lines
8.9 KiB
HTML
117 lines
8.9 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}표 후보값 · {{ p.name }}{% endblock %}
|
|
{% block body %}
|
|
<h1>표 후보값</h1>
|
|
<p class="muted">자동 인식 범위: 교량받침 ELEVATION TABLE, 솔플레이트 상세 TABLE, 철근재료표(SD400·SD500). 운곡교 P1 도면 1세트로만 검증한 규칙입니다.
|
|
후보값은 반드시 도면과 대조한 뒤 반영하고, 반영 후 해당 묶음을 다시 확정하세요. 그 밖의 값은 문자 검색으로 찾아 직접 입력합니다.</p>
|
|
<p class="muted">같은 표 값이 <code>expected</code>(검사 기준) 행과 <code>params</code>(모델 생성값) 행으로 따로 나옵니다.
|
|
둘을 함께 반영하면 표를 잘못 읽었을 때 모델과 검사가 같이 틀려 게이트가 통과하므로, params 행은 도면과 따로 대조한 뒤 선택하세요.</p>
|
|
|
|
{% from "_prism.html" import grade_badge %}
|
|
<!-- stage C 2026-10-19 -->
|
|
<div class="prism" id="out-of-table">
|
|
<div class="stack">
|
|
<h2>표 밖 치수 후보 (앵커 따라가기)</h2>
|
|
{% if not ameta.anchors %}
|
|
<div class="banner tip"><span class="m" aria-hidden="true">◆</span><div>이 교각에는 앵커가 없습니다. 앵커는 같은 형식의 도면을 이미 읽은 템플릿에서 "각 값을 도면 어디서 읽었는가"를 기억한 것입니다.
|
|
같은 도면 틀(예: 운곡교 P1~P4)에 속한다면 템플릿을 골라 불러오세요.</div></div>
|
|
{% if tpl_anchors %}<form method="post" action="{{ url_for('anchors_load', name=p.name) }}" class="row" style="margin:0">
|
|
<label for="tpl">템플릿 앵커 불러오기</label>
|
|
<select id="tpl" name="template">{% for t in tpl_anchors %}<option>{{ t }}</option>{% endfor %}</select>
|
|
<button class="btn" type="submit">불러오기</button></form>{% endif %}
|
|
{% elif not ameta.texts %}<p class="cap muted">도면 문자 색인이 없습니다. 도면을 먼저 올리세요.</p>
|
|
{% else %}
|
|
{% set na = arows|selectattr('grade', 'equalto', 'A')|list|length %}{% set nb = arows|selectattr('grade', 'equalto', 'B')|list|length %}{% set nc = arows|selectattr('grade', 'equalto', 'C')|list|length %}
|
|
{% set nd = arows|rejectattr('same')|list|length %}
|
|
<section class="strip">
|
|
<p class="concl">후보 {{ arows|length }}건 — 현재 값과 다름 {{ nd }}건 · A {{ na }} · B {{ nb }} · C {{ nc }} · 후보를 못 찾음 {{ amiss|length }}건</p>
|
|
<p class="cap">도면 틀 일치율 {{ '%d' % ((ameta.coverage or 0) * 100) }} % (앵커를 만든 도면과 이 도면에 공통인 문자의 비율). 70 % 미만이면 틀이 다른 도면으로 보고 모든 후보를 C등급으로 낮춥니다.
|
|
A = 같은 치수가 여러 곳에서 같게 읽힘 · B = 한 곳 또는 일부만 · C = 근거 약함(목록만, 반영 시 사유 필요). 시험에서 A등급은 틀린 후보가 없었고 B등급은 약 15 %가 틀렸습니다.</p>
|
|
</section>
|
|
{% if ameta.self_source %}<div class="banner"><span class="m" aria-hidden="true">▲</span><div>이 앵커는 이 교각 자신의 도면에서 만들어졌습니다. 같은 도면을 다시 읽은 것이라 앱 확인의 근거로 쓰지 않습니다.</div></div>{% endif %}
|
|
{% if arows %}
|
|
<form method="post" action="{{ url_for('anchors_apply', name=p.name) }}" class="stack">
|
|
<div class="tw"><table class="t">
|
|
<caption>표 밖 치수 후보 — 다른 것부터, 등급 좋은 것부터</caption>
|
|
<thead><tr><th scope="col">반영</th><th scope="col">대상</th><th class="num" scope="col">현재</th><th class="num" scope="col">후보</th><th scope="col">등급</th><th scope="col">근거</th><th scope="col">도면 위치</th></tr></thead>
|
|
<tbody>
|
|
{% for r in arows %}
|
|
<tr{% if not r.same %} class="sel"{% endif %}>
|
|
<td>{% if not r.same and not r.self_source %}<input type="checkbox" name="field" value="{{ r.field }}" aria-label="{{ r.field }} 반영" style="width:1.25rem;height:1.25rem">{% endif %}</td>
|
|
<td><b>{{ r.field }}</b></td><td class="num">{{ r.current }}</td><td class="num">{{ r.value }}</td>
|
|
<td>{{ grade_badge(r.grade) }}{% if r.same %} <span class="cap muted">같음</span>{% endif %}</td>
|
|
<td class="cap">{{ r.followed }}곳 중 {{ r.agree }}곳 일치{% if r.shared > 1 %} · 같은 값의 필드 {{ r.shared }}종{% endif %}
|
|
{% if r.table_confirmed %}<br><b>표 값과도 일치</b>{% elif r.table is defined %}<br><b>표 값 {{ r.table }}와 다름 — 표를 따르세요</b>{% endif %}</td>
|
|
<td><span class="chip">{{ r.dwg }} '{{ r.text }}' ({{ '%.0f' % r.x }}, {{ '%.0f' % r.y }})</span>
|
|
<a class="cap" href="{{ url_for('view_drawing', name=p.name, dwg=r.dwg, x=r.x, y=r.y) }}" target="_blank">도면 보기</a></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table></div>
|
|
<div class="row"><span class="cap">빠른 선택(A등급 중 현재 값과 다른 것):</span>
|
|
<button class="btn sm" type="button" data-sel-a="expected">expected만</button>
|
|
<button class="btn sm" type="button" data-sel-a="params">params만</button>
|
|
<button class="btn sm quiet" type="button" data-sel-a="">선택 해제</button>
|
|
<span class="cap muted">params와 expected에 같은 도면 문자를 함께 반영할 수 없어 한쪽씩 고릅니다.</span></div>
|
|
<div class="field"><label for="why">반영 사유<span class="req">C등급 후보 반영에는 필수</span></label><input id="why" type="text" name="why" style="max-width:36rem"></div>
|
|
<div class="banner tip"><span class="m" aria-hidden="true">◆</span><div>같은 도면 문자에서 나온 후보를 params와 expected에 함께 반영할 수 없습니다(검사의 독립성). 한쪽만 고르고, 나머지는 도면에서 따로 확인하세요.</div></div>
|
|
<div class="row"><button class="btn primary" type="submit">선택한 후보 반영</button></div>
|
|
</form>
|
|
<script>
|
|
(function () {
|
|
/* sel-a */
|
|
var rows = document.querySelectorAll('#out-of-table tbody tr');
|
|
document.querySelectorAll('[data-sel-a]').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var file = btn.getAttribute('data-sel-a');
|
|
rows.forEach(function (tr) {
|
|
var cb = tr.querySelector('input[type=checkbox][name=field]');
|
|
if (!cb) return;
|
|
var isA = tr.textContent.indexOf('등급 A') >= 0;
|
|
cb.checked = !!file && isA && cb.value.indexOf(file + ':') === 0;
|
|
});
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if chains %}
|
|
<h3>도면 치수 사슬 검산 (R2)</h3>
|
|
<p class="cap">{{ chains|length }}개 중 검산(개수 × 간격 = 합계)에 맞지 않는 것 {{ chain_bad|length }}개{% if chain_bad %}:{% endif %}
|
|
{% for c in chain_bad %}<span class="b bad"><span class="m" aria-hidden="true">■</span>{{ c.dwg }} {{ c.text }} → 계산 {{ c.n * c.pitch }} mm</span> <a class="cap" href="{{ url_for('view_drawing', name=p.name, dwg=c.dwg, x=c.x, y=c.y) }}" target="_blank">도면 보기</a> {% endfor %}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<h2>후보값 비교 ({{ rows|length }}건 · 다름 {{ rows|rejectattr('same')|list|length }}건)</h2>
|
|
{% if rows %}
|
|
<form method="post" action="{{ url_for('candidates_apply', name=p.name) }}">
|
|
<div class="wrap"><table>
|
|
<tr><th>반영</th><th>대상</th><th>현재값</th><th>후보값</th><th>상태</th><th>근거</th></tr>
|
|
{% for r in rows %}
|
|
<tr><td>{% if not r.same %}<input type="checkbox" name="row" value="{{ loop.index0 }}">{% endif %}</td>
|
|
<td><code>{{ r.file }}.{{ r.path|join('.') }}</code></td>
|
|
<td><code>{{ dumps(r.current) }}</code></td><td><code>{{ dumps(r.candidate) }}</code></td>
|
|
<td>{% if r.same %}<span class="ok">일치</span>{% else %}<span class="warn">다름</span>{% endif %}</td>
|
|
<td>{{ r.src }}</td></tr>
|
|
{% endfor %}
|
|
</table></div>
|
|
<p><button>선택한 후보값 반영</button></p>
|
|
</form>
|
|
{% else %}<p class="muted">인식된 표가 없습니다.</p>{% endif %}
|
|
|
|
<h2>인식한 표 ({{ tables|length }})</h2>
|
|
{% for t in tables %}
|
|
<details><summary>{{ t.key }} · {{ t.anchor.dwg }} '{{ t.anchor.text }}' ({{ t.anchor.x }}, {{ t.anchor.y }}) · {{ t.rows|length }}행</summary>
|
|
<div class="wrap"><table>{% for r in t.rows %}<tr>{% for c in r.cells %}<td>{{ c.text }}</td>{% endfor %}</tr>{% endfor %}</table></div>
|
|
</details>
|
|
{% endfor %}
|
|
|
|
<h2>EL 표기 ({{ els|length }})</h2>
|
|
<p class="muted">EL 문자는 위치에 따라 뜻이 달라 자동 반영하지 않습니다.</p>
|
|
<table><tr><th>도면</th><th>레이어</th><th>X</th><th>Y</th><th>문자</th><th>mm</th></tr>
|
|
{% for e in els %}<tr><td>{{ e.dwg }}</td><td>{{ e.layer }}</td><td>{{ e.x }}</td><td>{{ e.y }}</td><td>{{ e.text }}</td><td>{{ e.value_mm }}</td></tr>{% endfor %}
|
|
</table>
|
|
{% endblock %}
|