Files
Pier_v1/Code/교각모델링앱/web/templates/candidates.html
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:20:18 +09:00

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 %}