19f544fb5e
추출 파이프라인(287~298, 별 커밋) 위 HR/A. 신규 마이그레이션 0 (DDL은 295~298 재사용).
- HR 정정/삭제 훅: PATCH 본문 수정 → 파생 study_memo_cards needs_review=auto(source_changed),
soft-DELETE → source_deleted. flag_cards_for_source 헬퍼(임시 플래그, 최종정리는 워커 supersede).
- HR needs_review: PATCH set/clear(flagged_by='user' 서버강제) + GET /study-questions/needs-review
목록·count(부분인덱스 술어 일치, 동적 {id} 라우트보다 먼저 등록해 int 파싱 충돌 회피).
- A 알람 재료: study_topics.focused_at 공부중 토글 + study_reminder cron(09/13/19 KST, due 술어
quiz_selection SQL 재현·시간슬롯 truncate 멱등·LLM 0) + GET /api/study-reminders/latest(없으면 204).
- 테스트: 가드/정규화 18/18 (정량=evidence 원문·cue/cloze 누출·dedup·배치).
검증: 앱 부팅 import+mapper OK · 가드 18/18 PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
1.7 KiB
Python
55 lines
1.7 KiB
Python
"""study_reminders API — 알람 재료 조회 (공부 암기노트 Phase 1, A 워크스트림).
|
|
|
|
GET /latest = 가장 최근 발화된 알람 1건(현재 due 스냅샷). 없으면 204.
|
|
종일 오프라인 후 과거 슬롯(09/13시)은 유실 = 의도("현재 due만"). push 채널·디바이스 UX 는 P3.
|
|
별 라우터(prefix=/api/study-reminders)로 /study-topics·/study-questions 경로와 충돌 회피.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, Response
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from core.auth import get_current_user
|
|
from core.database import get_session
|
|
from models.study_reminder import StudyReminder
|
|
from models.user import User
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class ReminderResponse(BaseModel):
|
|
id: int
|
|
due_count: int | None = None
|
|
focus_topic_names: list | None = None
|
|
fired_at: datetime
|
|
|
|
|
|
@router.get("/latest", response_model=ReminderResponse)
|
|
async def latest_reminder(
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
):
|
|
"""현재 due 요약 1건. 없으면 204 No Content."""
|
|
row = (
|
|
await session.execute(
|
|
select(StudyReminder)
|
|
.where(StudyReminder.user_id == user.id)
|
|
.order_by(StudyReminder.fired_at.desc())
|
|
.limit(1)
|
|
)
|
|
).scalar_one_or_none()
|
|
if row is None:
|
|
return Response(status_code=204)
|
|
return ReminderResponse(
|
|
id=row.id,
|
|
due_count=row.due_count,
|
|
focus_topic_names=row.focus_topic_names,
|
|
fired_at=row.fired_at,
|
|
)
|