Skip to content

Replace intent dimensions with corpus-backed problem picker#18

Merged
smilebank7 merged 6 commits into
mainfrom
feat/intent-corpus-picker
Jun 10, 2026
Merged

Replace intent dimensions with corpus-backed problem picker#18
smilebank7 merged 6 commits into
mainfrom
feat/intent-corpus-picker

Conversation

@smilebank7

@smilebank7 smilebank7 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

요약

S3(intent) 단계를 재설계했습니다. 정적 예시 3개 대신 실제 corpus에서 기준 문항을 골라 출제하고, 파이프라인에서 무의미하던 "보존해야 하는 능력"(평가 차원, dims) 섹션을 전면 제거합니다. 이를 위해 agent에 읽기 전용 엔드포인트를 신설합니다.

  • dims = no-op: intent-extraction 프롬프트가 dims를 참조하지 않고, 성취기준 Strategy YAML이 런타임에 덮어쓰며(43개 토픽 전부 보유), 검증 verdict도 차원을 비교하지 않습니다 → 사용자가 고르는 의미가 없었음.
  • source_problem_text = 의미 있음: RAG 부스트 + 생성 프롬프트의 변형 기준 seed. 그래서 "어떤 문제를 기준으로?"를 실제 DB에서 고르게 했습니다.

변경 (3 commits)

  1. Add source-problems read endpointGET /api/source-problems (school/grade/topic/difficulty/limit). 기존 RagClient 재사용, CORS 기존 설정 그대로. figure 의존 문항 제외 + topic 전체를 훑어 limit 채움.
  2. Add corpus fetch and verification storage-key helpers — web lib/source-problems-client.ts(SSR+client fetch), lib/verification-storage-key.ts(검증 결과 sessionStorage 키 단일화 — 기존 3중 중복 제거).
  3. Replace intent dimensions with corpus-backed problem picker — S3 picker를 corpus 단일 선택 + 난이도 필터 + KaTeX 렌더로 교체하고, dims를 verify/result/export/스트림/스토리지키에서 lockstep으로 제거.

기준 문항 표시 품질

  • 수식: 카드가 LatexMixed$…$를 렌더(원시 텍스트 노출 X).
  • 그림 의존 문항 제외: corpus엔 "그림 필요" 구조 플래그가 없습니다(이미지 경로는 100% 존재, VISUAL 태그는 자기완결 도형까지 포함). 그래서 명시적 시각 참조만 정규식으로 제외: 그림, 다음/아래/위 그래프, 그래프와 같이, 다음/아래 도형, 다음/아래/위의 표는·를, <table>. 맨 그래프·도형··좌표(자기완결)는 유지 — 휴리스틱, 정밀도 우선.
  • 난이도 필터는 칩 클릭마다 서버 refetch(corpus 앞쪽이 한 난이도에 쏠려 있어 클라이언트 필터링은 빈 결과를 냄).

검증

  • pnpm test 178 green (Node 158 + Python 20), typecheck 0(양 패키지). pre-commit/pre-push 훅 통과.
  • 라이브 curl: 9수02-09 → 30개(그림 0), difficulty=hard 정상 narrowing, 미존재 토픽 → [], school_level 누락 → 400.
  • 브라우저 e2e(:27182): S3 30카드·KaTeX 렌더·단일 선택·난이도 refetch·dims 섹션 없음 → verify로 srcRef+sessionStorage 핸드오프 → 6단계 SSE 진행. 콘솔 에러 0.

참고 / 범위 밖

  • figure 필터는 휴리스틱(구조적 신호 부재). 더 공격적으로 가면 자기완결 도형 문제도 빠져서 정밀도 우선으로 뒀습니다.
  • corpus 토픽 라벨 노이즈(예: "원과 직선" 배지에 정육면체 문제)는 별도 데이터 작업 영역.
  • S4 실제 생성은 LLM 환경 의존(기존 사항). 이 PR 범위는 S3 입력 + dims 제거 + 엔드포인트.

Summary by cubic

Replaced the S3 intent step with a corpus-backed problem picker and removed “dimensions” across the flow. Added LaTeX bracket/paren delimiter support and updated picker cards to show a short preview (3 lines) and expand to full text only when selected.

  • New Features

    • Agent: GET /api/source-problems (school_level/grade/topic_code/difficulty/limit). Reuses RagClient, filters figure-dependent items via explicit-reference regex, no LLM, existing CORS preserved.
    • Web: getSourceProblems fetcher for SSR and difficulty refetch; intent picker with single-select cards, KaTeX (supports $...$, $$...$$, \[...\], \(...\)), difficulty chips (cached, race-safe), and a responsive grid with uniform previews that expand on selection. Selected source stored to sessionStorage and passed as srcRef. Unified verification result key via verification-storage-key; SSE now sends source_item_id and writes results under the new key. Added tests for the agent route, latex renderer, and storage key.
  • Migration

    • Removed “dimensions” from UI, SSE, storage, and pages; deleted dim candidates data.
    • URLs now use srcRef={item_id} instead of dims and source.
    • Result/export read from the new sessionStorage key; old cached entries are ignored.

Written for commit 9e096e2. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 20 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/web/app/app/new/intent/picker.tsx">

<violation number="1" location="packages/web/app/app/new/intent/picker.tsx:171">
P1: Cached chip clicks do not cancel/invalidate in-flight fetches, so stale responses can overwrite the current difficulty selection.</violation>

<violation number="2" location="packages/web/app/app/new/intent/picker.tsx:200">
P2: Error handling assumes `getSourceProblems` throws, but it always returns `[]` on failure, so fetch failures are misclassified as empty results.</violation>
</file>

<file name="packages/web/app/app/new/verify/view.tsx">

<violation number="1" location="packages/web/app/app/new/verify/view.tsx:142">
P2: Stale `intentSource` is not cleared when `srcRef` mismatches, which can run verification with the wrong source item.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment on lines +171 to +180
if (cached !== undefined) {
setDisplayed(cached);
if (
selectedItemId.length > 0 &&
!cached.some((c) => c.item_id === selectedItemId)
) {
setSelectedItemId("");
}
return next;
});
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Cached chip clicks do not cancel/invalidate in-flight fetches, so stale responses can overwrite the current difficulty selection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/app/app/new/intent/picker.tsx, line 171:

<comment>Cached chip clicks do not cancel/invalidate in-flight fetches, so stale responses can overwrite the current difficulty selection.</comment>

<file context>
@@ -131,31 +118,167 @@ export function IntentPicker({ schoolLevel, grade, topic, candidates }: Props) {
+    if (cache === null) return;
+
+    const cached = cache.get(chip);
+    if (cached !== undefined) {
+      setDisplayed(cached);
+      if (
</file context>
Suggested change
if (cached !== undefined) {
setDisplayed(cached);
if (
selectedItemId.length > 0 &&
!cached.some((c) => c.item_id === selectedItemId)
) {
setSelectedItemId("");
}
return next;
});
return;
}
if (cached !== undefined) {
requestIdRef.current += 1;
setLoadingDiff(false);
setDisplayed(cached);
if (
selectedItemId.length > 0 &&
!cached.some((c) => c.item_id === selectedItemId)
) {
setSelectedItemId("");
}
return;
}

difficulty: chip,
limit: 30,
});
} catch (err) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Error handling assumes getSourceProblems throws, but it always returns [] on failure, so fetch failures are misclassified as empty results.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/app/app/new/intent/picker.tsx, line 200:

<comment>Error handling assumes `getSourceProblems` throws, but it always returns `[]` on failure, so fetch failures are misclassified as empty results.</comment>

<file context>
@@ -131,31 +118,167 @@ export function IntentPicker({ schoolLevel, grade, topic, candidates }: Props) {
+        difficulty: chip,
+        limit: 30,
+      });
+    } catch (err) {
+      if (requestIdRef.current === myRequestId) {
+        console.warn(
</file context>

}
const validated = parseIntentSource(parsed);
if (validated === null) return;
if (validated.item_id !== srcRef) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Stale intentSource is not cleared when srcRef mismatches, which can run verification with the wrong source item.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/app/app/new/verify/view.tsx, line 142:

<comment>Stale `intentSource` is not cleared when `srcRef` mismatches, which can run verification with the wrong source item.</comment>

<file context>
@@ -91,41 +96,79 @@ function stateLabel(status: Step["status"]): string {
     }
+    const validated = parseIntentSource(parsed);
+    if (validated === null) return;
+    if (validated.item_id !== srcRef) return;
+    setIntentSource(validated);
+  }, [srcRef]);
</file context>
Suggested change
if (validated.item_id !== srcRef) return;
if (validated.item_id !== srcRef) {
setIntentSource(null);
return;
}

@smilebank7 smilebank7 merged commit 3d224a8 into main Jun 10, 2026
5 checks passed
@smilebank7 smilebank7 deleted the feat/intent-corpus-picker branch June 10, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant