# Implementation Plan: RAG Retrieval Quality + Topic Summary Persistence **Branch**: `004-rag-retrieval-quality` | **Date**: 2026-04-07 | **Spec**: [spec.md](spec.md) **Input**: Feature specification from `/specs/004-rag-retrieval-quality/spec.md` + user request: "Save summaries by topic; list previously saved summaries; button to generate new." ## Summary Improve RAG retrieval quality by (1) expanding user queries via LLM rewrite to bridge vocabulary gaps and (2) validating generated citations against the retrieved context to eliminate hallucinated references. Additionally, persist generated topic summaries to the database so students can revisit past summaries and generate new ones on demand. ## Technical Context **Language/Version**: Java 21 (backend), TypeScript / Node 20 (frontend) **Primary Dependencies**: Spring Boot 4.0.5, Spring AI 2.0.0-M4, OpenAI API (chat + embeddings), Vue 3.4, Pinia 2.1, Axios 1.7 **Storage**: PostgreSQL (JPA + Flyway), pgvector (`VectorStore`) **Testing**: JUnit 5, Spring Boot Test, Vitest **Target Platform**: Linux server **Project Type**: Web application (backend API + Vue frontend) **Performance Goals**: RAG query latency remains acceptable (one additional LLM call for query expansion, ~1–2s overhead) **Constraints**: KISS — no new architectural layers; no new external services; single deployable backend + frontend ## Constitution Check | Principle | Status | Notes | |-----------|--------|-------| | I — KISS | ✅ Pass | Query expansion = one LLM call. Citation validation = string scan. Summary persistence = one new table + minimal service change. No new layers. | | II — Easy to Change | ✅ Pass | `QueryExpansionService`, `CitationValidatorService`, `TopicSummaryRepository` are small, focused interfaces. Swappable without touching callers. | | III — Web-First | ✅ Pass | All new functionality exposed via REST at `/api/v1/...`. Frontend-only UI changes. | | IV — Documentation as Architecture | ⚠️ Action needed | README must be updated to reflect new `topic_summary` table and the summary history flow. Update in the same PR. | ## Project Structure ### Documentation (this feature) ```text specs/004-rag-retrieval-quality/ ├── plan.md # This file ├── research.md # Phase 0 — decisions on query expansion, citation grounding, summary persistence ├── data-model.md # Phase 1 — topic_summary table, ExpandedQuery, LabelledContext DTOs ├── contracts/ │ ├── chat-api.md # Existing chat API (updated for labelled sources) │ └── topics-api.md # New/updated topics API (summary list + detail endpoints) └── tasks.md # Phase 2 output (/speckit.tasks command) ``` ### Source Code ```text backend/ ├── src/main/java/com/aiteacher/ │ ├── retrieval/ │ │ ├── QueryExpansionService.java (NEW) — LLM rewrite of user query │ │ ├── ExpandedQuery.java (NEW) — value object {original, rewritten} │ │ ├── LabelledContext.java (NEW) — value object {sectionLabels, figureLabels, promptText} │ │ └── CitationValidatorService.java (NEW) — strip unknown [Sx]/[Fx] refs from answer text │ └── topic/ │ ├── TopicSummaryEntity.java (NEW) — JPA entity for topic_summary table │ ├── TopicSummaryRepository.java (NEW) — findByTopicIdOrderBySummaryNumberAsc │ ├── SavedSummaryItem.java (NEW) — DTO for list view (id, summaryNumber, generatedAt) │ ├── TopicSummaryResponse.java (MODIFY) — add id: UUID, summaryNumber: int fields │ ├── TopicSummaryService.java (MODIFY) — persist after generation; add list/get methods │ └── TopicController.java (MODIFY) — add GET /summaries and GET /summaries/{id} ├── src/main/resources/db/migration/ │ └── V6__topic_summary.sql (NEW) — create topic_summary table └── chat/ └── ChatService.java (MODIFY) — use QueryExpansionService + CitationValidatorService frontend/ └── src/ ├── stores/ │ └── topicStore.ts (MODIFY) — add fetchSummaries(), fetchSummaryDetail(), summaryList state └── views/ └── TopicsView.vue (MODIFY) — show summary list when topic selected, "Generate New" button ``` **Structure Decision**: Option 2 (web application). Existing `backend/` + `frontend/` directories. No new top-level directories. ## Complexity Tracking > No constitution violations introduced. --- ## Phase 0: Research (complete) See [research.md](research.md). All decisions resolved: - Query expansion: single LLM rewrite call - Citation grounding: ref-label tagging + post-generation string scan - Summary persistence: new `topic_summary` table, sequential numbering at insert time ## Phase 1: Design (complete) ### Data model See [data-model.md](data-model.md). New `topic_summary` table; two new in-memory value objects; `TopicSummaryResponse` extended with `id` + `summaryNumber`. ### API contracts See [contracts/topics-api.md](contracts/topics-api.md): - `POST /api/v1/topics/{id}/summary` — now persists and returns `id` + `summaryNumber` - `GET /api/v1/topics/{id}/summaries` — list metadata (no full text) - `GET /api/v1/topics/{id}/summaries/{summaryId}` — full detail ### Frontend behaviour When a **topic card is clicked** (before generating): 1. Call `GET /api/v1/topics/{id}/summaries`. 2. If summaries exist → show list panel with chips: "Summary #1 · Apr 6", "Summary #2 · Apr 7", … + a "Generate New" button. 3. If no summaries → show "Generate Summary" button (current behaviour). 4. Clicking a chip → call `GET /api/v1/topics/{id}/summaries/{summaryId}` → display the saved summary. 5. Clicking "Generate New" → call `POST /api/v1/topics/{id}/summary` → display new summary, refresh list.