feat: 프론트에 판례 적재·인용 현황 표시
판례가 적재돼 있는지, 이번 판단에 실제로 쓰였는지가 화면에 전혀 드러나지 않았다. 엔진은 적재본 545건을 모두 로드하고 판정마다 저작물 유형·법적 태그가 맞는 상위 5건을 인용하는데, 사용자는 "판례를 쓰고 있는가"를 알 수 없었다. 세 곳에 표시한다. 헤더 배지 ● 정상 · 코퍼스 3건 · 판례 545건 푸터 판례 적재: 545건 판단 카드 관련 판례 사건번호 인용 3건 / 적재 545건 적재 수를 함께 보여 주는 것이 핵심이다. "인용 3건"만 있으면 판례를 3개만 쓰는 것처럼 읽히는데, "/ 적재 545건"이 붙어야 545건 중 이 건에 맞는 3건을 고른 결과임이 드러난다. 인용 0건과 판례 미적재도 구분했다. 겉보기엔 둘 다 판례가 없지만 앞은 정상 동작이고, 뒤는 엔진이 insufficient_precedent_data 를 내며 판단 자체를 보류하는 상태다. 설정 오류일 가능성이 높아 배지를 경고색으로 바꾼다. API 는 이미 /v1/health 의 precedent_count 와 응답의 precedent_ids 로 필요한 값을 주고 있었다. 프론트만 쓰지 않고 있었으므로 서버 변경은 없다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a45c79211c
commit
500a4a4600
@ -40,6 +40,7 @@
|
||||
}
|
||||
header .badge.ok { color: var(--success); border-color: var(--success); }
|
||||
header .badge.err { color: var(--danger); border-color: var(--danger); }
|
||||
header .badge.warn { color: var(--warning); border-color: var(--warning); }
|
||||
main { padding: 24px 28px; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 24px; max-width: 1600px; margin: 0 auto; }
|
||||
@media (max-width: 980px) { main { grid-template-columns: 1fr; } }
|
||||
|
||||
@ -211,6 +212,10 @@
|
||||
}
|
||||
.legal-risk-section { margin-top: 12px; }
|
||||
.legal-risk-section strong { display: block; color: var(--muted); font-size: 11px; margin-bottom: 5px; }
|
||||
.legal-risk-section strong .count {
|
||||
float: right; font-weight: 400; color: var(--muted); font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.precedent-empty { color: var(--warning); }
|
||||
.legal-risk-list { margin: 0; padding-left: 18px; font-size: 12px; }
|
||||
.legal-risk-list li { margin: 3px 0; }
|
||||
.precedent-chip {
|
||||
@ -431,7 +436,8 @@
|
||||
<a href="docs">API 문서 (Swagger)</a> ·
|
||||
<a href="openapi.json">OpenAPI 스펙</a> ·
|
||||
엔진 버전: <span id="engine-version">…</span> ·
|
||||
레퍼런스 코퍼스: <span id="corpus-size">…</span>건
|
||||
레퍼런스 코퍼스: <span id="corpus-size">…</span>건 ·
|
||||
판례 적재: <span id="precedent-count">…</span>건
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
@ -665,9 +671,18 @@ function renderLegalRisk(risk) {
|
||||
const list = (items, emptyText) => items && items.length
|
||||
? `<ul class="legal-risk-list">${items.map(item => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`
|
||||
: `<div class="legal-risk-meta">${escapeHtml(emptyText)}</div>`;
|
||||
// 적재본이 있는데 인용이 0건인 것과, 애초에 판례가 없는 것은 다른 상황이다.
|
||||
// 앞은 매칭 실패, 뒤는 판단 자체가 성립하지 않는다. 문구로 구분한다.
|
||||
const noCorpus = precedentCount === 0;
|
||||
const precedents = selectedIds.length
|
||||
? selectedIds.map(id => `<span class="precedent-chip">${escapeHtml(id)}</span>`).join("")
|
||||
: '<div class="legal-risk-meta">선택된 관련 판례 없음</div>';
|
||||
: noCorpus
|
||||
? '<div class="legal-risk-meta precedent-empty">판례가 적재되지 않아 판단 근거를 제시할 수 없습니다.</div>'
|
||||
: '<div class="legal-risk-meta">이 건과 저작물 유형·법적 태그가 맞는 판례가 없습니다.</div>';
|
||||
// 적재 수를 함께 보여 줘야 "판례를 안 쓰는 것"이 아니라 "이 건에 맞는 게 없는 것"임이 드러난다.
|
||||
const precedentCountLabel = Number.isFinite(precedentCount)
|
||||
? `<span class="count">인용 ${selectedIds.length}건 / 적재 ${precedentCount}건</span>`
|
||||
: "";
|
||||
const summary = risk.judgment_summary
|
||||
|| "관련 판례와 탐지 증거를 바탕으로 추가 검토가 필요합니다.";
|
||||
|
||||
@ -681,7 +696,7 @@ function renderLegalRisk(risk) {
|
||||
${confidence}
|
||||
</div>
|
||||
<div class="legal-judgment-summary">${escapeHtml(summary)}</div>
|
||||
<div class="legal-risk-section"><strong>관련 판례 사건번호</strong>${precedents}</div>
|
||||
<div class="legal-risk-section"><strong>관련 판례 사건번호${precedentCountLabel}</strong>${precedents}</div>
|
||||
<div class="legal-risk-section"><strong>판단을 지지하는 근거</strong>${list(risk.supporting_reasons, "GPT 근거 없음 또는 규칙 기반 결과")}</div>
|
||||
<div class="legal-risk-section"><strong>반대 근거</strong>${list(risk.counter_reasons, "제공된 반대 근거 없음")}</div>
|
||||
<div class="legal-risk-section"><strong>추가 확인 필요</strong>${list(risk.missing_factors, "추가 확인사항 없음")}</div>
|
||||
@ -937,16 +952,28 @@ function escapeJs(s) {
|
||||
}
|
||||
|
||||
// 헬스 체크 + 코퍼스 정보 표시
|
||||
// 적재된 판례 수. 저작권 판단 카드에서 '인용 N / 적재 M' 을 보여주는 데 쓴다.
|
||||
let precedentCount = null;
|
||||
|
||||
async function checkHealth() {
|
||||
try {
|
||||
const resp = await fetch("v1/health");
|
||||
if (!resp.ok) throw new Error("not ok");
|
||||
const data = await resp.json();
|
||||
const autobio = data.autobiography_mode ? ' · 자서전모드' : '';
|
||||
document.getElementById("health-badge").textContent = `● 정상 · 코퍼스 ${data.corpus_size}건${autobio}`;
|
||||
document.getElementById("health-badge").className = "badge ok";
|
||||
// 판례 적재 여부는 저작권 판단의 전제다. 0건이면 엔진이 판단 자체를 보류하므로
|
||||
// (insufficient_precedent_data) 배지 색으로 먼저 드러낸다.
|
||||
precedentCount = Number.isFinite(data.precedent_count) ? data.precedent_count : 0;
|
||||
const badge = document.getElementById("health-badge");
|
||||
const loaded = precedentCount > 0;
|
||||
badge.textContent = `● 정상 · 코퍼스 ${data.corpus_size}건 · 판례 ${loaded ? precedentCount + "건" : "미적재"}${autobio}`;
|
||||
badge.className = loaded ? "badge ok" : "badge warn";
|
||||
badge.title = loaded
|
||||
? `판례 ${precedentCount}건 적재됨. 저작권 판단 시 저작물 유형·법적 태그가 맞는 상위 판례를 근거로 인용합니다.`
|
||||
: "판례가 적재되지 않아 저작권 침해 판단을 보류합니다. PRECEDENTS_PATH 설정을 확인하세요.";
|
||||
document.getElementById("engine-version").textContent = data.engine_version;
|
||||
document.getElementById("corpus-size").textContent = data.corpus_size;
|
||||
document.getElementById("precedent-count").textContent = loaded ? precedentCount : "0";
|
||||
if (data.taxonomy_version) {
|
||||
const footer = document.querySelector("footer");
|
||||
footer.innerHTML += ` · 분류체계: ${escapeHtml(data.taxonomy_version)}`;
|
||||
@ -954,6 +981,7 @@ async function checkHealth() {
|
||||
} catch (err) {
|
||||
document.getElementById("health-badge").textContent = "● 연결 실패";
|
||||
document.getElementById("health-badge").className = "badge err";
|
||||
document.getElementById("precedent-count").textContent = "확인 불가";
|
||||
}
|
||||
}
|
||||
checkHealth();
|
||||
|
||||
@ -34,3 +34,30 @@ def test_frontend_renders_llm_legal_judgment_and_safety_fields():
|
||||
assert "판례에 비추어 저작권 침해 의심" in HTML
|
||||
assert "GPT 판례 비교" not in HTML
|
||||
assert "GPT 판정" not in HTML
|
||||
|
||||
|
||||
def test_frontend_shows_precedent_corpus_is_loaded():
|
||||
"""판례 적재 여부가 화면에 드러나야 한다.
|
||||
|
||||
엔진은 적재본 전체를 로드하고 판정마다 상위 몇 건을 인용하는데, 화면에
|
||||
그 사실이 전혀 없어서 "판례를 쓰고 있는가"를 사용자가 알 수 없었다.
|
||||
"""
|
||||
# 헬스 배지와 푸터에 적재 수가 나온다
|
||||
assert 'id="precedent-count"' in HTML
|
||||
assert "data.precedent_count" in HTML
|
||||
# 판단 카드에 '인용 N건 / 적재 M건'
|
||||
assert "precedentCountLabel" in HTML
|
||||
assert "인용 ${selectedIds.length}건 / 적재 ${precedentCount}건" in HTML
|
||||
|
||||
|
||||
def test_frontend_distinguishes_no_match_from_no_corpus():
|
||||
"""매칭 실패와 판례 미적재는 다른 상황이므로 문구가 갈려야 한다.
|
||||
|
||||
앞은 이 건에 맞는 판례가 없는 것이고, 뒤는 판단 자체가 성립하지 않는다
|
||||
(엔진이 insufficient_precedent_data 를 낸다).
|
||||
"""
|
||||
assert "판례가 적재되지 않아 판단 근거를 제시할 수 없습니다." in HTML
|
||||
assert "저작물 유형·법적 태그가 맞는 판례가 없습니다." in HTML
|
||||
# 미적재는 헬스 배지에서도 경고색으로 구분된다
|
||||
assert "badge warn" in HTML
|
||||
assert "header .badge.warn" in HTML
|
||||
|
||||
Loading…
Reference in New Issue
Block a user