From e9a95934ef7ee13c13e80a8c657e0ed5fabac7e6 Mon Sep 17 00:00:00 2001 From: hyungi Date: Sun, 7 Jun 2026 09:41:13 +0900 Subject: [PATCH] =?UTF-8?q?feat(study):=20=EC=B9=B4=EB=93=9C=20=EA=B2=80?= =?UTF-8?q?=EC=88=98=20=EA=B7=B8=EB=A3=B9=ED=95=91=20=E2=80=94=20manual(?= =?UTF-8?q?=EC=A7=81=EC=A0=91=20=EC=B6=94=EA=B0=80)=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20=EC=9E=90=EB=A3=8C(material)=EB=B3=84=20=EB=AC=B6?= =?UTF-8?q?=EC=9D=8C=20+=20source=5Fkind=20=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 직접 추가 자료 카드(source_kind='manual', 출처 문제 없음)가 검수 UI에서 null 한 덩어리로 뭉치지 않도록 extra.material 별 그룹("[자료] ...") + CardItem.source_kind 노출(프론트 '직접 추가 자료' 라벨). Co-Authored-By: Claude Opus 4.8 (1M context) --- app/api/study_cards.py | 42 +++++++++++++------ .../routes/study/cards-review/+page.svelte | 2 + 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/app/api/study_cards.py b/app/api/study_cards.py index c5047ba..5c4914e 100644 --- a/app/api/study_cards.py +++ b/app/api/study_cards.py @@ -38,6 +38,7 @@ class CardEvidence(BaseModel): class CardItem(BaseModel): id: int + source_kind: str = "question" format: str cue: str fact: str @@ -142,19 +143,34 @@ async def list_cards( ).all() q_meta = {r.id: (r.question_text, r.correct_choice) for r in q_rows} - # 그룹핑 (출제순서=rows 순서 유지) - groups: dict[int | None, CardQuestionGroup] = {} - order: list[int | None] = [] + # 그룹핑 (출제순서=rows 순서 유지). question 카드는 출처 문제별, + # manual(직접 추가) 카드는 extra.material 별로 묶는다. + groups: dict[str, CardQuestionGroup] = {} + order: list[str] = [] for c in rows: - key = c.source_question_id - if key not in groups: - qt, cc = q_meta.get(key, (None, None)) if key is not None else (None, None) - groups[key] = CardQuestionGroup(source_question_id=key, question_text=qt, correct_choice=cc, cards=[]) - order.append(key) - groups[key].cards.append( + if c.source_question_id is not None: + gkey = f"q:{c.source_question_id}" + else: + material = c.extra.get("material") if isinstance(c.extra, dict) else None + gkey = f"m:{material or '직접 추가'}" + if gkey not in groups: + if c.source_question_id is not None: + qt, cc = q_meta.get(c.source_question_id, (None, None)) + groups[gkey] = CardQuestionGroup( + source_question_id=c.source_question_id, question_text=qt, correct_choice=cc, cards=[] + ) + else: + material = c.extra.get("material") if isinstance(c.extra, dict) else None + groups[gkey] = CardQuestionGroup( + source_question_id=None, + question_text=(f"[자료] {material}" if material else "직접 추가 카드"), + correct_choice=None, cards=[], + ) + order.append(gkey) + groups[gkey].cards.append( CardItem( - id=c.id, format=c.format, cue=c.cue, fact=c.fact, cloze_text=c.cloze_text, - needs_review=c.needs_review, flagged_by=c.flagged_by, + id=c.id, source_kind=c.source_kind, format=c.format, cue=c.cue, fact=c.fact, + cloze_text=c.cloze_text, needs_review=c.needs_review, flagged_by=c.flagged_by, evidence=ev_by_card.get(c.id, []), ) ) @@ -221,8 +237,8 @@ async def update_card( raise HTTPException(status_code=409, detail="같은 정답의 중복 카드가 이미 있습니다") return CardItem( - id=card.id, format=card.format, cue=card.cue, fact=card.fact, cloze_text=card.cloze_text, - needs_review=card.needs_review, flagged_by=card.flagged_by, evidence=[], + id=card.id, source_kind=card.source_kind, format=card.format, cue=card.cue, fact=card.fact, + cloze_text=card.cloze_text, needs_review=card.needs_review, flagged_by=card.flagged_by, evidence=[], ) diff --git a/frontend/src/routes/study/cards-review/+page.svelte b/frontend/src/routes/study/cards-review/+page.svelte index 240fd02..ed828ba 100644 --- a/frontend/src/routes/study/cards-review/+page.svelte +++ b/frontend/src/routes/study/cards-review/+page.svelte @@ -211,6 +211,8 @@ {#if c.evidence?.length}
근거: {c.evidence[0].snippet}
+ {:else if c.source_kind === 'manual'} +
출처: 직접 추가 자료
{:else}
근거: 확정 풀이(비정량 개념)
{/if}