From a4a661f829084af9894c4775682a06928d8f1080 Mon Sep 17 00:00:00 2001 From: Schalli Date: Tue, 21 Jul 2026 14:57:09 +0200 Subject: [PATCH] =?UTF-8?q?docs(11):=20phase=20context=20from=20user=20req?= =?UTF-8?q?uirements=20=E2=80=94=20filters,=20triage,=20saved=20searches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../11-CONTEXT.md | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .planning/phases/11-filter-engine-results-ui-saved-searches/11-CONTEXT.md diff --git a/.planning/phases/11-filter-engine-results-ui-saved-searches/11-CONTEXT.md b/.planning/phases/11-filter-engine-results-ui-saved-searches/11-CONTEXT.md new file mode 100644 index 0000000..440a514 --- /dev/null +++ b/.planning/phases/11-filter-engine-results-ui-saved-searches/11-CONTEXT.md @@ -0,0 +1,98 @@ +# Phase 11: Filter Engine, Results UI & Saved Searches - Context + +**Gathered:** 2026-07-21 +**Status:** Ready for planning +**Mode:** mvp (vertical slices UI→API→DB) + + +## Phase Boundary + +Die Anzeige- und Interaktionsschicht des Ausschreibungs-Radar-Moduls. Phase 10 hat die globale `Tender`-Tabelle befüllt (aktuell ~1671 echte DÖE-Einträge). Diese Phase macht sie für Endnutzer nutzbar: durchsuchbare/sortierbare Trefferliste, Filter (Freitext, Region/PLZ/Bundesland, CPV, Frist, Wert), Detailansicht, persönliche Suchprofile und persönliche Triage (gelesen/ungelesen, Favoriten) — plus transparente Abdeckungs-Anzeige (Oberschwelle vs. Unterschwelle), damit „keine Treffer" nicht als Bug missverstanden wird. + +Requirements: FILTER-01, FILTER-02, FILTER-03, FILTER-04, FILTER-05, FILTER-06, UI-01, UI-02, UI-03, UI-04, UI-05. + +Nicht in dieser Phase: E-Mail-Benachrichtigungen (Phase 12), Scraping-Adapter + Cross-Source-Dedup (Phase 13), RSS/E-Mail-Quellen + ausgeschlossene-Portale-Transparenz UI-06 + i18n-Rollout (Phase 14), CSV/Excel-Export (Backlog). + + + +## Implementation Decisions + +Bestätigt durch User-Abfrage 2026-07-21 (alle Filter + alle Triage-Funktionen gewählt): + +### Filter (alle in dieser Phase) +- **D-01:** Freitext-Suche über Titel + Auftraggeber (`buyerName`). FILTER-01. (REQUIREMENTS nennt Titel/Beschreibung — Beschreibung liegt ggf. in `rawPayload`; Freitext auf indexierte Felder Titel+buyerName, `rawPayload`-Volltext ist Claude's Discretion.) +- **D-02:** Region / PLZ / Bundesland. FILTER-02. Felder liegen vor (`region`, `plz`, `bundesland`). +- **D-03:** CPV-Code / Branche — hierarchische Auswahl mit Autocomplete. FILTER-03. `cpvCodes` liegt als Array vor. Hierarchie/Autocomplete-Datenquelle (CPV-Katalog) = Research-Punkt. +- **D-04:** Abgabefrist inkl. Option „nur noch offene" (Standard-Ansicht). FILTER-04. `deadlineAt` liegt vor. +- **D-05:** Geschätzter Auftragswert min/max. FILTER-05. **Ausschreibungen ohne Wertangabe (`estimatedValue` null) dürfen NIE ausgeschlossen oder zu Fehlern führen** — sie erscheinen weiter, nur nicht bei aktivem min/max-Wertfilter, oder mit klarer „keine Wertangabe"-Markierung (genaue Semantik = Plan-Entscheidung, aber „graceful" ist Pflicht-Erfolgskriterium). + +### Trefferliste & Detail +- **D-06:** Durchsuchbare, sortierbare Liste — Sortierung nach Frist, Wert, Veröffentlichungsdatum. UI-01. +- **D-07:** Detailansicht pro Ausschreibung: alle Felder + Link zur Quelle (`sourceUrl`) + ggf. Dokument-URLs aus dem Notice. **Keine lokale Spiegelung der Vergabeunterlagen** — nur Links. UI-02. + +### Persönliche Funktionen (pro Nutzer, mandantenbewusst) +- **D-08:** Suchprofile: Filter-Kombination benannt speichern, bearbeiten, löschen. **Pro Nutzer**, auf den eigenen Mandanten gescoped. FILTER-06. +- **D-09:** Gelesen/Ungelesen pro Nutzer. UI-03. +- **D-10:** Favorit/Merkliste pro Nutzer + Merklisten-Filter in der Liste. UI-04. +- **D-11:** Triage-Zustände (gelesen, favorit) und Suchprofile sind **per-user**, NICHT tenant-weit geteilt. + +### Abdeckungs-Transparenz +- **D-12:** UI weist Datenabdeckung aus: Oberschwelle vs. Unterschwelle. UI-05. Aktuell nur DÖE-Quelle (Oberschwelle-lastig) — die Anzeige verhindert, dass eine dünne/leere Liste als Fehler gelesen wird. Genaue Formulierung/Platzierung = Plan-Entscheidung; der Hinweis MUSS aber existieren. + +### Claude's Discretion +- Query-Architektur: serverseitige Filter/Sortierung/Pagination über die globale `Tender`-Tabelle (kein `forTenant`-Scoping — Tender ist global), Prisma `where`-Aufbau, Indizes für Filter-Performance. +- Neue **per-user/per-tenant** Tabellen für Suchprofile + Triage (read/favourite) — Namen, Schema, Unique-Constraints. `FavoriteLink` existiert bereits für ein anderes Feature; NICHT wiederverwenden, eigenes Tender-Triage-Modell. +- CPV-Hierarchie/Autocomplete-Umsetzung (statischer Katalog vs. abgeleitet aus vorhandenen `cpvCodes`). +- Frontend-Aufteilung: Liste, Filter-Panel, Detailseite, Suchprofil-Verwaltung — Komponenten + TanStack-Query-Hooks. + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Phasen-Vorgaben +- `.planning/ROADMAP.md` § Phase 11 — Ziel + 5 Erfolgskriterien (Liste+Filter, Detail, Suchprofile, Triage, Abdeckungs-Anzeige). **Mode: mvp.** +- `.planning/REQUIREMENTS.md` — FILTER-01..06, UI-01..05. + +### Phase-10-Fundament (verbindlich — darauf baut alles auf) +- `apps/api/prisma/schema.prisma` → Modell `Tender` (globale Tabelle, Felder: title, buyerName, cpvCodes[], region, plz, bundesland, deadlineAt, estimatedValue, procedureType, status, sourceUrl, sourcePortal, publishedAt, rawPayload …) und `TenderSourcePollConfig`. +- `apps/api/src/tenders/` — bestehendes `TendersController` (`GET /modules/tender-radar` Liste mit page/limit/status, `GET /:id` Detail), `TenderQueryDto`, Module-Registrierung. **Filter/Sort erweitern diese vorhandenen Read-Endpunkte.** +- `apps/web/src/app/(portal)/modules/tender-radar/` — bestehende Modulseite (aktuell Platzhalter „Ausschreibungen werden erfasst.") + `tender-radar-api.ts` Client + Settings-Seite. Die Modulseite wird zur echten Trefferliste ausgebaut. +- **Route-Order-Falle beachten** (Phase-10-Bug): statische Routen VOR `@Get(':id')` deklarieren, sonst 404-Shadowing. Siehe `10-VERIFICATION.md` Re-Verification. + +### Milestone-Research +- `.planning/research/ARCHITECTURE.md` — global-vs-tenant-Split (Tender global; Suchprofile/Triage per-user/tenant), Schema-Konventionen. +- `.planning/research/SUMMARY.md` — Build-Order, Modul-Architektur. + + + +## Existing Code Insights + +### Reusable Assets +- `apps/api/src/tenders/tenders.controller.ts` / `tenders.module.ts` — vorhandene Read-Endpunkte; Filter/Sortierung/Pagination hier erweitern (nicht neues Modul). +- `apps/web/.../modules/tender-radar/` — bestehende Modulseite, `tender-radar-api.ts`, Settings-Form als UI-/Client-Muster. +- Bestehende Modul-UIs (Cert-Manager, DKV, Domaincheck) als Template für Listen-/Detail-/Formular-Layout, shadcn/ui-Komponenten, TanStack-Query-Nutzung. +- `apps/api/src/prisma/prisma-tenant.extension.ts` (`forTenant`) — für die NEUEN per-user/per-tenant Tabellen (Suchprofile, Triage) zwingend nutzen; die globale `Tender`-Tabelle bleibt ungescoped. +- Auth/User-Kontext (JWT, `@CurrentUser`-Muster) für per-user Triage/Profile. + +### Established Patterns +- shadcn/ui + Tailwind, next-intl (DE/EN), TanStack Query für Client-Datenabruf. +- Prisma-Migrationen als handgeschriebene timestamped Ordner unter `apps/api/prisma/migrations/`. +- Native `fetch` im API; `/api-proxy/*`-Rewrite im Web. + +### Integration Points +- Neue Tabellen: Suchprofil (per user+tenant) und Tender-Triage (per user, Status gelesen + favorit). Migration erforderlich. +- Trefferliste konsumiert erweiterten `GET /modules/tender-radar` (Filter-Query-Params). +- Detailseite konsumiert `GET /modules/tender-radar/:id`. +- Sidebar-/Modul-Routing existiert bereits (`/modules/procurement/tender-radar`). + + + +## Specific Ideas + +- User-Frust-Auslöser adressieren: aktuell zeigt die Modulseite NICHTS. Kern dieser Phase = die 1671 vorhandenen Einträge sichtbar, durchsuchbar und triagierbar machen. +- „nur noch offene" als sinnvoller Default-Filter (D-04) — Nutzer will bietbare, aktuelle Vergaben, nicht abgelaufene. +- Wert-ohne-Angabe graceful behandeln (D-05) ist explizites Erfolgskriterium — nicht rausfiltern, nicht crashen. +- Abdeckungs-Hinweis (D-12) ist bewusst drin, weil bei nur einer Quelle (DÖE) eine dünne Liste sonst wie ein Defekt wirkt — genau die Verwirrung, die der User gerade hatte. +