feat(study): 카드 검수 그룹핑 — manual(직접 추가) 카드를 자료(material)별 묶음 + source_kind 노출
직접 추가 자료 카드(source_kind='manual', 출처 문제 없음)가 검수 UI에서 null 한 덩어리로
뭉치지 않도록 extra.material 별 그룹("[자료] ...") + CardItem.source_kind 노출(프론트 '직접 추가 자료' 라벨).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+29
-13
@@ -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=[],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -211,6 +211,8 @@
|
||||
</div>
|
||||
{#if c.evidence?.length}
|
||||
<div class="mt-2 text-[11px] text-dim">근거: {c.evidence[0].snippet}</div>
|
||||
{:else if c.source_kind === 'manual'}
|
||||
<div class="mt-2 text-[11px] text-faint">출처: 직접 추가 자료</div>
|
||||
{:else}
|
||||
<div class="mt-2 text-[11px] text-faint">근거: 확정 풀이(비정량 개념)</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user