diff --git a/b-reference/frontend-nyla/formgenerator.md b/b-reference/frontend-nyla/formgenerator.md
index eb41926..b7304fc 100644
--- a/b-reference/frontend-nyla/formgenerator.md
+++ b/b-reference/frontend-nyla/formgenerator.md
@@ -19,6 +19,72 @@ Diese Referenz fokussiert auf **FormGeneratorTable** und das zugehoerige Backend
---
+## Page Layout Chain (Pflicht)
+
+`FormGeneratorTable` rendert intern mit `flex:1; min-height:0; overflow:hidden`. Damit die Tabelle die volle Seitenbreite/-hoehe ausnuetzt und nicht abgeschnitten wird, **muss** die Eltern-Hierarchie eine **bounded height chain** liefern. Andernfalls passiert eines davon:
+
+- Tabelle ist 0 px hoch (kollabiert) -- nur Toolbar sichtbar.
+- Tabelle waechst ueber den Viewport hinaus, horizontaler Scroll fehlt -- letzte Spalten werden abgeschnitten.
+- Page-Container hat `max-width` -> Tabelle ist auf z.B. 800 px begrenzt.
+
+### Korrektes Pattern (verwendet PromptsPage, TrusteePositionsView, TrusteeDataTablesView)
+
+```tsx
+import adminStyles from '../../admin/Admin.module.css';
+
+export const MyPage: React.FC = () => (
+
+
...
+
+
+
+
+
+);
+```
+
+Die drei CSS-Klassen aus `frontend_nyla/src/pages/admin/Admin.module.css`:
+
+| Klasse | Wirkung |
+|--------|---------|
+| `.adminPage` | `display:flex; flex-direction:column; width:100%; box-sizing:border-box; flex:0 0 auto` (Default fuer Standard-Seiten ohne Tabelle) |
+| `.adminPageFill` | `flex:1 1 auto; min-height:0; overflow:hidden` -- aktiviert die bounded height chain |
+| `.tableContainer` | `flex:1; min-height:0; overflow:hidden; display:flex; flex-direction:column` -- direktes Eltern-Element der Tabelle |
+
+`FeatureView`/`MainLayout` liefern bereits `height:100%` mit `min-height:0` -- Page-Komponenten brauchen also nur `adminPage adminPageFill` und einen `tableContainer` direkt um die `FormGeneratorTable`.
+
+### Bei Tabs / Wrapper-Komponenten
+
+Wenn die Tabelle in einem Tab-Wrapper (z.B. `TrusteeDataTab`) gemountet wird, muss **jeder** Wrapper die Flex-Chain weiterreichen. Pattern:
+
+```tsx
+const _rootStyle: React.CSSProperties = {
+ display: 'flex',
+ flexDirection: 'column',
+ flex: 1,
+ minHeight: 0,
+ width: '100%',
+};
+const _tableWrapStyle: React.CSSProperties = {
+ flex: 1,
+ minHeight: 0,
+ display: 'flex',
+ flexDirection: 'column',
+ width: '100%',
+};
+```
+
+Eine Toolbar oberhalb der Tabelle bekommt `flexShrink: 0`, der Tabellenwrapper `flex: 1; minHeight: 0`.
+
+### Anti-Patterns (verursachen das "Tabelle abgeschnitten"-Bug)
+
+- Outer-Wrapper mit `max-width: 800px` (z.B. `.expenseImportSection` aus `TrusteeViews.module.css`) -- begrenzt die Breite.
+- Outer `
` ohne `flex:1; min-height:0` -- Tabelle kollabiert auf 0 px Hoehe.
+- `.adminPage` ohne `.adminPageFill` -- Default ist `flex:0 0 auto`, das innere `.tableContainer flex:1` hat keinen Platz.
+- Padding auf einem Zwischen-Wrapper -- bricht das Box-Sizing der Chain.
+
+---
+
## FormGeneratorTable -- Props
### Datenanbindung
diff --git a/c-work/2-build/2026-04-trustee-data-tables-page.md b/c-work/2-build/2026-04-trustee-data-tables-page.md
new file mode 100644
index 0000000..fbcf072
--- /dev/null
+++ b/c-work/2-build/2026-04-trustee-data-tables-page.md
@@ -0,0 +1,159 @@
+
+
+
+
+# Trustee: Konsolidierte Daten-Tabellen-Seite
+
+## Beschreibung und Kontext
+
+Aktuell hat das Trustee-Feature zwei separate Top-Level-Seiten für Datensichten (`Positionen`, `Dokumente`). Die übrigen 11 Trustee-Tabellen (Stammdaten + Sync-Daten + Buchhaltungs-Config/Sync) sind im UI **nicht sichtbar** – nur als JSON-Export oder per AI-Agent erreichbar. Das ist intransparent: Anwender und Auditor:innen sehen die importierten Konten / Buchungen / Kontakte / Salden nirgends.
+
+Lösung: Eine neue konsolidierte Seite `Daten-Tabellen` mit einem Tab pro Trustee-Tabelle. Jeder Tab nutzt `FormGeneratorTable` mit Pagination, Sortierung, Filterung und Suche – exakt das Pattern, das `PromptsPage` und die bestehenden `TrusteePositionsView` / `TrusteeDocumentsView` etabliert haben.
+
+Business-Treiber:
+- Transparenz für Treuhänder:innen: «Was wurde wirklich aus Bexio/RMA importiert?»
+- Audit-Fähigkeit: Sync-Status und Sync-Snapshots direkt im UI prüfen.
+- Vereinheitlichung: Konsistente UI über alle Tabellen statt zwei isolierter Spezialseiten.
+
+Risiko bei Verzicht: Anwender greifen via Export/Excel auf interne Daten zu (umständlich, fehleranfällig); Sync-Probleme bleiben unbemerkt.
+
+## Fokus und kritische Details
+
+- **FormGeneratorTable-Vertrag** (siehe `wiki/b-reference/frontend-nyla/formgenerator.md`): Jede Tabelle braucht (a) ein `apiEndpoint` mit Unified Filter API (`mode=filterValues|ids`), (b) einen Hook mit `refetch / pagination / attributes / permissions`, (c) saubere Spaltendefinitionen aus `getModelAttributeDefinitions(...)`.
+- **`getModelAttributeDefinitions` ist bereits da** (`/api/trustee/{instanceId}/attributes/{entityType}`) – aber nur registriert für die **6 CRUD-Modelle** (`_TRUSTEE_ENTITY_MODELS` in `routeFeatureTrustee.py`, Zeile 195). Die 7 fehlenden Modelle (TrusteeData* + Accounting*) müssen in dieser Map ergänzt werden.
+- **Sync-Tabellen sind read-only** (User-Entscheidung). Endpunkte für TrusteeData*/AccountingSync sind aktuell **nicht** als generische REST-CRUDs implementiert – nur Aggregat-Endpunkte (`/accounting/import-status`, `/accounting/sync-status`, `/accounting/export-data`). Es braucht **neue Read-Endpunkte** mit Pagination + Unified Filter API.
+- **RBAC-Konsistenz**: Jede Tabelle hat in `mainTrustee.DATA_OBJECTS` einen `data.feature.trustee.`-Key. Der Hook (`_createTrusteeEntityHook`) löst Permissions via `data.feature.trustee.{entityName}` auf – das funktioniert bereits für die bestehenden 6 Modelle, muss für die neuen 7 ebenfalls greifen.
+- **Backwards-Compat**: Die bestehenden Seiten `Positionen` und `Dokumente` bleiben **vorerst** erhalten (Navigation + Routes + RBAC). Erst wenn die neue Seite verifiziert ist, werden sie in einer Folge-Iteration entfernt.
+- **Tab-State via URL** (`?tab=positions`): Konsistent mit `TrusteeImportProcessView`, `TrusteeAccountingSettingsView`, `TrusteeAbschlussView`. Erlaubt Deep-Links (z.B. Quick Actions / Notification-Links) auf konkrete Tabellen.
+- **Performance**: Lazy-Mount pro Tab (Hook ruft nur Daten, wenn Tab aktiv ist) – sonst werden 13 Tabellen parallel geladen.
+- **i18n-Pflicht**: Alle Tab-Labels via `t(...)`. Spalten-Labels kommen aus dem Backend-`@i18nModel`.
+- **Hidden System-Felder**: `mandateId`, `featureInstanceId`, technische `id`-UUIDs sollen tabellenweit hidden sein (gleiche Liste wie in PromptsPage `hiddenColumns`).
+- **Naming**: Funktionen `_`-Prefix für intern (`_buildEntityHookConfig`, `_renderTabBar`); camelCase überall.
+
+## Ziel und Nicht-Ziele
+
+- **Ziel**: Neue Seite `Daten-Tabellen` (URL: `/mandates/{mandateId}/trustee/{instanceId}/data-tables[?tab=]`) mit einem Tab pro Trustee-Tabelle. Jeder Tab nutzt `FormGeneratorTable` mit Pagination/Sort/Filter/Search/Inline-Edit (für CRUD-Tabellen) bzw. read-only (für Sync-Tabellen).
+- **Ziel**: Backend liefert Attribute & paginierte Daten via Unified Filter API auch für die 7 bisher UI-losen Tabellen.
+- **Ziel**: Tab-Sichtbarkeit folgt RBAC (`data.feature.trustee.` view-Permission).
+- **Ziel**: Konsistentes UX-Pattern mit `PromptsPage` (Header / Refresh / FormGeneratorTable / optional Edit-Modal via FormGeneratorForm für CRUD-Tabellen).
+- **Explizit NICHT**: Entfernen der alten `Positionen`/`Dokumente`-Seiten in dieser Iteration (Folge-Plan). Routen + Quick Actions + RBAC bleiben unangetastet.
+- **Explizit NICHT**: Inline-Edit oder Bulk-Sync für TrusteeData*-Tabellen – sie sind reine Sync-Spiegel.
+- **Explizit NICHT**: Neuer DB-Schema-Wandel oder Migration – nur Read-Endpunkte ergänzen.
+- **Explizit NICHT**: Beleg-Download-Spalte oder Sync-Status-Spalte im Positions-Tab – die Spezialdarstellung bleibt der eigenständigen `TrusteePositionsView` vorbehalten (Generic-Tab zeigt das «rohe» Modell).
+
+## Betroffene Module
+
+- **Gateway**:
+ - `gateway/modules/features/trustee/routeFeatureTrustee.py`: `_TRUSTEE_ENTITY_MODELS` um 7 Modelle erweitern; neue Read-Endpunkte mit Pagination + Unified Filter API für `TrusteeDataAccount`, `TrusteeDataJournalEntry`, `TrusteeDataJournalLine`, `TrusteeDataContact`, `TrusteeDataAccountBalance`, `TrusteeAccountingConfig`, `TrusteeAccountingSync`.
+ - `gateway/modules/features/trustee/mainTrustee.py`: `UI_OBJECTS` um `ui.feature.trustee.data-tables` ergänzen; `TEMPLATE_ROLES.accessRules` der relevanten Rollen erweitern.
+- **Frontend**:
+ - **Neu**: `frontend_nyla/src/pages/views/trustee/TrusteeDataTablesView.tsx` (Container mit Tab-Bar + URL-Sync).
+ - **Neu**: `frontend_nyla/src/pages/views/trustee/dataTables/TrusteeDataTab.tsx` (generischer Tab-Body mit FormGeneratorTable für CRUD- und Read-only-Modi).
+ - **Erweitern**: `frontend_nyla/src/hooks/useTrustee.ts` um Hooks für die 7 neuen Modelle (über die bestehenden Factories `_createTrusteeEntityHook` / `_createTrusteeOperationsHook` – read-only Variante für Sync-Tabellen).
+ - **Erweitern**: `frontend_nyla/src/api/trusteeApi.ts` um `fetchTrusteeData*`-Funktionen.
+ - **Anpassen**: `frontend_nyla/src/pages/FeatureView.tsx` (`VIEW_COMPONENTS.trustee` um `'data-tables': TrusteeDataTablesView` ergänzen).
+ - **Anpassen**: `frontend_nyla/src/pages/views/trustee/index.ts` (Export).
+ - **Anpassen**: `frontend_nyla/src/App.tsx` (Route `data-tables` registrieren).
+ - **Optional**: `frontend_nyla/src/types/mandate.ts` (`FEATURE_REGISTRY.trustee.views`) um `data-tables` ergänzen, falls noch genutzt.
+- **DB-Migration**: nein.
+- **Andere Komponenten**: keine.
+
+## Entscheidungen
+
+| Datum | Entscheidung | Begründung |
+|-------|-------------|------------|
+| 2026-04-20 | Seitenname: `Daten-Tabellen` | User-Entscheidung; klar, präzise, dt. |
+| 2026-04-20 | Alte Seiten `Positionen` + `Dokumente` bleiben vorerst | User-Entscheidung; Sicherheitsnetz bis neue Seite verifiziert ist – Aufräumen in Folge-Plan. |
+| 2026-04-20 | Sync-Tabellen (`TrusteeData*`, `TrusteeAccountingSync`, `TrusteeAccountingConfig`) sind read-only im UI | User-Entscheidung; Daten kommen aus externem System – Edits würden bei nächstem Sync überschrieben. |
+| 2026-04-20 | Alle 13 Tabellen erhalten einen Tab (User wählte "alle" – im Datenmodell sind es 13: 4 Stammdaten + 2 CRUD-Daten + 5 Sync-Daten + 2 Accounting). | User-Entscheidung; vollständige Transparenz. |
+| 2026-04-20 | Tab-State via URL-Param `?tab=` | Konsistent mit allen anderen Trustee-Tab-Seiten. |
+| 2026-04-20 | Lazy-Mount pro Tab (kein Pre-Fetch der inaktiven Tabs) | Performance: 13 paginierte Endpunkte parallel laden ist unnötig. |
+| 2026-04-20 | Generische Tab-Komponente (`TrusteeDataTab`) statt 13 spezialisierter Views | DRY; Spezialisierungen (Beleg-Download/Sync-Status-Spalte) bleiben ausschliesslich in `TrusteePositionsView` / `TrusteeDocumentsView`. |
+| 2026-04-20 | Keine Bulk-Aktionen im neuen Tab (kein Sync-Button etc.) | Bulk-Sync ist im Positions-Tab schon vorhanden – Generic-Seite bleibt schlank. |
+
+## Umsetzungs-Checkliste
+
+### Backend (Gateway)
+
+- [x] `_TRUSTEE_ENTITY_MODELS` in `routeFeatureTrustee.py` um die 7 neuen Modelle erweitert.
+- [x] Neue paginierte Read-Endpunkte (analog `get_documents`/`get_positions` mit `_handleDocumentMode`-Pattern) für jede der 7 Tabellen:
+ - `GET /api/trustee/{instanceId}/data/accounts`
+ - `GET /api/trustee/{instanceId}/data/journal-entries`
+ - `GET /api/trustee/{instanceId}/data/journal-lines`
+ - `GET /api/trustee/{instanceId}/data/contacts`
+ - `GET /api/trustee/{instanceId}/data/account-balances`
+ - `GET /api/trustee/{instanceId}/accounting/configs`
+ - `GET /api/trustee/{instanceId}/accounting/syncs`
+- [x] Helper extrahiert: `_paginatedReadEndpoint(...)` haelt Logik fuer alle 7 Endpunkte zentral.
+- [x] RBAC: jeder neue Endpunkt prüft via `_validateInstanceAccess`; Datenzugriff wird auf SQL-Ebene durch `getRecordsetPaginatedWithRBAC` (DATA-Context, `data.feature.trustee.`) gefiltert – identisch zum bestehenden Documents/Positions-Pattern.
+- [x] `mainTrustee.py`: Neuer UI-Object-Eintrag `ui.feature.trustee.data-tables` (Label «Daten-Tabellen»).
+- [x] `mainTrustee.py`: AccessRule fuer `ui.feature.trustee.data-tables` bei `trustee-viewer`, `trustee-user`, `trustee-accountant` ergaenzt; Admin hat Wildcard.
+
+### Frontend
+
+- [x] `frontend_nyla/src/api/trusteeApi.ts`: 7 neue Read-Funktionen (`fetchDataAccounts`, `fetchDataJournalEntries`, `fetchDataJournalLines`, `fetchDataContacts`, `fetchDataAccountBalances`, `fetchAccountingConfigs`, `fetchAccountingSyncs`) plus passende Typen.
+- [x] `frontend_nyla/src/hooks/useTrustee.ts`: 7 neue Read-only-Hooks via `_createTrusteeEntityHook` (`_buildReadOnlyConfig` injiziert no-op-Mutatoren).
+- [x] **Neu** `frontend_nyla/src/pages/views/trustee/dataTables/TrusteeDataTab.tsx` – generischer Tab-Body, bindet `FormGeneratorTable` ein, respektiert `readOnly`.
+- [x] **Neu** `frontend_nyla/src/pages/views/trustee/TrusteeDataTablesView.tsx`:
+ - 13 Tab-Defs, jede mit eigenem Wrapper-Component, das den passenden Hook aufruft.
+ - URL-Sync via `?tab=`, Lazy-Mount (nur aktiver Wrapper rendert -> kein Datenfetch fuer inaktive Tabs).
+ - Tab-Bar im Stil von `TrusteeAbschlussView`/`TrusteeImportProcessView`; Sync-Tabs zeigen «(read-only)»-Hinweis.
+ - RBAC-Filter im Frontend deaktiviert: einzelne Tab-Hooks blockieren Datenzugriff serverseitig (vermeidet sync-Permission-Lookups; spaetere Iteration kann Tab-Hiding nachziehen).
+- [x] `frontend_nyla/src/pages/views/trustee/index.ts`: Export ergaenzt.
+- [x] `frontend_nyla/src/pages/FeatureView.tsx`: Mapping `'data-tables': TrusteeDataTablesView` + Import.
+- [x] `frontend_nyla/src/App.tsx`: Route `` ergaenzt.
+- [x] `frontend_nyla/src/types/mandate.ts`: `FEATURE_REGISTRY.trustee.views` um `'data-tables'` ergaenzt.
+
+### Cross-Cutting
+
+- [ ] RBAC / Permissions: alle neuen Endpunkte enforced via bestehendes `_validateInstanceAccess`; UI-Sichtbarkeit per `data.feature.trustee.`-Check (Hook liefert das via `permissions.view`).
+- [ ] Neutralisierung betroffen? **Nein** – keine AI-Calls, kein neuer Datenfluss zu externen Systemen.
+- [ ] Navigation / Routing: neue UI-Route `/mandates/{m}/trustee/{i}/data-tables[?tab=]`.
+- [ ] Billing-Impact? **Nein**.
+- [ ] i18n: alle Tab-Labels und Header-Strings via `t(...)`; Spalten-Labels kommen aus `@i18nModel`-getaggten Modellen (bereits vorhanden für alle 13).
+- [ ] Quick Actions: keine Änderung – Quick Actions zeigen weiterhin auf `import-process` / `settings`. Optional in Folge-Iteration: Direktlinks auf einzelne Datentabs.
+
+## Akzeptanzkriterien
+
+| # | Kriterium (Given-When-Then) | Prio |
+|---|----------------------------|------|
+| 1 | Given ein Trustee-Admin auf einer Trustee-Instanz, When er auf Navigation «Daten-Tabellen» klickt, Then erscheint eine Seite mit Tab-Bar und allen 13 Trustee-Tabellen als Tabs. | must |
+| 2 | Given die Seite «Daten-Tabellen» mit Default-Tab, When der User sie öffnet, Then wird sofort der erste Tab geladen (FormGeneratorTable mit Daten, Pagination, Spalten aus `attributes`-API). | must |
+| 3 | Given ein Tab mit Sync-Daten (z.B. «Buchungen (Sync)»), When der User die Zeile inspiziert, Then sind keine Edit-/Delete-Buttons sichtbar (read-only). | must |
+| 4 | Given ein Tab mit CRUD-Daten (z.B. «Position»), When der User auf Edit klickt, Then erscheint ein FormGeneratorForm-Modal und Speichern persistiert via PUT-Endpunkt. | must |
+| 5 | Given die Seite «Daten-Tabellen», When der User auf Tab «Kontakte (Sync)» wechselt, Then ändert sich die URL auf `?tab=contacts` und nur der Kontakte-Tab ist gemountet (Network-Tab zeigt nur den Kontakte-API-Call). | must |
+| 6 | Given ein Tab mit aktiviertem `apiEndpoint`, When der User in einer Spalte einen Filter setzt, Then werden Distinct-Werte vom Backend via `?mode=filterValues&column=` geladen. | must |
+| 7 | Given ein User OHNE Permission `data.feature.trustee.TrusteeDataContact` (view), When er die Seite öffnet, Then ist der Tab «Kontakte (Sync)» nicht sichtbar oder disabled. | should |
+| 8 | Given die Seite mit Deep-Link `?tab=accounts`, When der User per URL einsteigt, Then wird der Konten-Tab direkt aktiv geladen. | should |
+| 9 | Given die alten Seiten «Positionen»/«Dokumente», When der User sie aufruft, Then funktionieren sie unverändert weiter (kein Regress). | must |
+| 10 | Given ein Tab mit grosser Datenmenge (>1'000 Einträge), When der User scrollt, Then wird Server-Pagination angewandt (page-Size 25, kein Frontend-Speicher-Overload). | must |
+| 11 | Given Tabellen-Sortierung, When der User auf einen Spalten-Header klickt, Then wird per `pagination.sort` serverseitig sortiert. | must |
+
+## Testplan
+
+| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
+|----|----|-----|--------------|-----------|--------|
+| T1 | 1 | manual UI | nein | – | pending |
+| T2 | 2,5 | api | ja | gateway/tests/test_routeFeatureTrustee_data_tables.py | pending |
+| T3 | 3,4 | api | ja | gateway/tests/test_routeFeatureTrustee_data_tables.py | pending |
+| T4 | 6 | api | ja | gateway/tests/test_routeFeatureTrustee_data_tables.py (Unified Filter API: mode=filterValues / mode=ids) | pending |
+| T5 | 7 | api | ja | gateway/tests/test_routeFeatureTrustee_data_tables.py (RBAC-Gate) | pending |
+| T6 | 8 | manual UI | nein | – | pending |
+| T7 | 9 | manual + e2e | teils | bestehende Position/Document Tests laufen weiter | pending |
+| T8 | 10,11 | api | ja | gateway/tests/test_routeFeatureTrustee_data_tables.py (Pagination/Sort) | pending |
+
+## Links
+
+- PR: –
+- Issue: –
+- Vorlage: `wiki/b-reference/frontend-nyla/formgenerator.md`
+- Vorbild-Seite: `frontend_nyla/src/pages/basedata/PromptsPage.tsx`
+- Vorbild-Tabs: `frontend_nyla/src/pages/views/trustee/TrusteeAbschlussView.tsx`, `TrusteeAccountingSettingsView.tsx`
+
+## Abschluss
+
+- [ ] `b-reference/gateway/features/trustee.md` (oder analog) aktualisieren: neue Endpunkte + neue UI-Seite dokumentieren.
+- [ ] `b-reference/frontend-nyla/architecture.md`: kurze Erwähnung der konsolidierten `TrusteeDataTablesView` + Tab-Pattern für mehrere Modelle.
+- [ ] `TOPICS.md`: keine Änderung nötig (kein neues Thema).
+- [ ] Folge-Plan `2026-MM-trustee-cleanup-positions-documents.md`: Aufräumen der alten Seiten, sobald Daten-Tabellen produktiv verifiziert.
+- [ ] Dieses Dokument → `c-work/2-build/` (bei Umsetzungsbeginn) → `c-work/3-validate/` → `z-archive/`.
diff --git a/c-work/4-done/2026-04-period-picker-billing-audit-migration.md b/c-work/4-done/2026-04-period-picker-billing-audit-migration.md
new file mode 100644
index 0000000..3a4e7d0
--- /dev/null
+++ b/c-work/4-done/2026-04-period-picker-billing-audit-migration.md
@@ -0,0 +1,267 @@
+
+
+
+
+
+# Statistik-Endpunkte: Clean-Cut auf `dateFrom`/`dateTo` + `bucketSize`
+
+## Beschreibung und Kontext
+
+Folge-Arbeit zum [`PeriodPicker`](../../local/notes/changelog.txt). Die
+Komponente liefert semantisch sauber `{ preset, fromDate, toDate }`. Drei
+Statistik-Endpunkte im Gateway erwarten heute aber Legacy-Parameter, die
+Date-Range mit anderen Konzepten vermischen:
+
+| Endpunkt | Heute | Problem |
+|---|---|---|
+| `GET /api/audit/stats` | `?timeRange={days}` (relative Tagesanzahl ab heute) | Kein echter Bereich; im Frontend muss `PeriodValue` per Adapter `_periodToDays` auf eine Tagesanzahl reduziert werden, dadurch wird "01.04 - 03.04" als "letzte 3 Tage ab heute" ausgewertet → falsche KPIs. |
+| `GET /api/billing/statistics/{period}` | `period: 'day' \| 'month' \| 'year'`, `year`, `month?` | `period` mischt zwei Verantwortungen: Date-Range-Berechnung **und** Bucket-Granularitaet; aerbitraere Ranges (z.B. "letzte 12 Monate", Custom) sind nicht ausdrueckbar. |
+| `GET /api/billing/view/statistics` | `period: 'day' \| 'month'`, `year`, `month?` | Dito; zusaetzlich gibt `period` an `getTransactionStatisticsAggregated(..., period=)` die Bucket-Groesse weiter. |
+
+Konsequenz heute: der PeriodPicker-Rollout in `BillingDashboard` und
+`BillingDataView` ist blockiert; in `ComplianceAuditPage` arbeiten wir mit
+einem dokumentierten Hack (`_periodToDays`).
+
+**Diese Arbeit ist ein Clean-Cut**: die Legacy-Parameter werden entfernt, die
+Endpunkte akzeptieren ausschliesslich `dateFrom`/`dateTo` (+ `bucketSize`,
+wo Time-Series ausgegeben werden), und alle Frontend-Caller werden in derselben
+Aenderung migriert. Keine Backwards-Compat-Aliase, kein "altes UND neues",
+kein toleranter Fallback im Backend.
+
+## Fokus und kritische Details
+
+- **Eine Aenderung = eine PR**: Backend-Refactor + alle Frontend-Caller
+ zusammen, da ohne Aliase ein gemischter Zustand nicht laeuft.
+- **`/api/billing/view/statistics` mischt zwei Konzepte**: (a) Date-Range
+ (`startTs/endTs`) und (b) Bucket-Granularitaet fuer Time-Series
+ (`getTransactionStatisticsAggregated(..., period=)` -> `day` vs `month`).
+ Diese **muessen getrennt werden**: Date-Range = `dateFrom`/`dateTo`,
+ Bucket-Groesse = neuer expliziter Param `bucketSize: 'day' | 'month' | 'year'`.
+ Default-Heuristik nur im Frontend, nicht im Backend.
+- **Timezones**: alle Audit-Logger-Records sind UTC-Epoch-Sekunden; PeriodPicker
+ liefert lokale ISO-Daten (`YYYY-MM-DD`) ohne Timezone. Konversion eindeutig
+ und an genau einer Stelle (im Endpunkt selbst, vor der DB-Abfrage):
+ `dateFrom` -> `00:00:00 UTC` desselben Tages, `dateTo` -> `23:59:59.999 UTC`
+ desselben Tages (inklusive). Sonst fehlt der letzte Tag in der Aggregation.
+ → Ein gemeinsamer Helper `_isoDateRangeToUtcEpoch(dateFrom, dateTo)` in
+ `gateway/modules/shared/dateRange.py`.
+- **Validierung im Backend**: `dateFrom <= dateTo`, beide Pflicht (kein
+ optional/None), `bucketSize` aus geschlossenem Enum. Bei Verletzung HTTP 400
+ mit klarer Message. Keine impliziten Defaults.
+- **Aufrufende Stellen vollstaendig erfassen** (geprueft per ripgrep, siehe
+ unten - keine externen Konsumenten ausserhalb des Frontends).
+
+## Ziel und Nicht-Ziele
+
+- **Ziel:**
+ - `/api/audit/stats` akzeptiert ausschliesslich `dateFrom`/`dateTo` (ISO
+ `YYYY-MM-DD`, Pflicht). Antwort enthaelt `dateFrom`/`dateTo`/`days` statt
+ `timeRangeDays`.
+ - `/api/billing/statistics` (Endpunkt-Pfad-Param `{period}` entfaellt, neuer
+ Pfad `GET /api/billing/statistics`) akzeptiert ausschliesslich
+ `dateFrom`/`dateTo` (Pflicht) + optional `bucketSize`.
+ - `/api/billing/view/statistics` analog.
+ - `aiAuditLogger.getAiAuditStats(mandateId, *, fromTs, toTs, groupBy)` -
+ Pflicht-Range.
+ - `getTransactionStatisticsAggregated(..., bucketSize=)` - Param `period`
+ umbenannt zu `bucketSize`.
+ - Frontend: `BillingDashboard`, `BillingDataView`, `ComplianceAuditPage`
+ nutzen `` und schicken `dateFrom`/`dateTo`. Adapter
+ `_periodToDays` ersatzlos entfernt. `useBilling.loadStatistics`-Signatur
+ nur noch `({ dateFrom, dateTo, bucketSize? })`.
+- **Explizit NICHT:**
+ - Aliase `timeRange` / `period` / `year` / `month` im Backend behalten.
+ - Stripe-`current_period_*` umbenennen oder beruehren - Stripe-API.
+ - Andere Stat-Endpunkte (`/api/admin/database-health/stats`, etc.) anfassen -
+ eigener Scope, sobald Bedarf.
+ - DB-Schema aendern.
+
+## Betroffene Module
+
+- **Gateway:**
+ - `gateway/modules/routes/routeAudit.py` (`getAuditStats`, Z. 303-317)
+ - `gateway/modules/shared/aiAuditLogger.py` (`getAiAuditStats`, Z. 195-249)
+ - `gateway/modules/routes/routeBilling.py`
+ (`getStatistics` Z. 526-604, `getUserViewStatistics` Z. 1619 ff.,
+ `UsageReportResponse` Z. 260)
+ - `gateway/modules/interfaces/interfaceBilling.py` (oder konkrete Impl):
+ `calculateStatisticsFromTransactions` (akzeptiert bereits `startDate`/`endDate`
+ → keine Aenderung), `getTransactionStatisticsAggregated`
+ (`period` → `bucketSize`).
+ - **Neu**: `gateway/modules/shared/dateRange.py` - kleiner Helper
+ `parseIsoDateRange(dateFrom, dateTo) -> (date, date)` mit Validierung,
+ `isoDateRangeToUtcEpoch(dateFrom, dateTo) -> (float, float)`.
+- **Frontend:**
+ - `frontend_nyla/src/api/billingApi.ts` (`fetchStatistics`,
+ `fetchViewStatistics`)
+ - `frontend_nyla/src/hooks/useBilling.ts` (`loadStatistics` Signatur
+ aendern)
+ - `frontend_nyla/src/pages/billing/BillingDashboard.tsx` (Selects raus,
+ PeriodPicker rein, optional separater `bucketSize`-Toggle)
+ - `frontend_nyla/src/pages/billing/BillingDataView.tsx`
+ (FormGeneratorReport: `periodSelector` raus, `dateRangeSelector` rein)
+ - `frontend_nyla/src/pages/ComplianceAuditPage.tsx`: `_periodToDays`
+ entfernen, `_loadStats({ dateFrom, dateTo })`.
+ - `frontend_nyla/src/api/auditApi.ts` (oder direkt in der Page, je nach
+ aktueller Struktur).
+- **DB-Migration:** nein.
+- **Tests:** Vorhandene Tests auf den 3 Endpunkten mitziehen (siehe
+ Aufwandsschaetzung).
+
+## Externe Konsumenten - Pruefung
+
+Vor Clean-Cut explizit verifizieren, dass keine externen Aufrufer
+(Webhooks, Skripte, Excel-Reports, Teams-Bot, private-llm) die Endpunkte
+direkt nutzen:
+
+- [ ] `rg "billing/statistics" gateway/ private-llm/ teams-bot/`
+- [ ] `rg "audit/stats" gateway/ private-llm/ teams-bot/`
+- [ ] `rg "/api/billing/statistics|/api/audit/stats" frontend_nyla/`
+
+Wenn Treffer ausserhalb des Frontends auftauchen, werden sie im selben PR
+mitgezogen. Wenn nicht, kann der Clean-Cut erfolgen.
+
+## Entscheidungen
+
+| Datum | Entscheidung | Begründung |
+|-------|-------------|------------|
+| 2026-04-20 | Clean-Cut, keine Backwards-Compat-Aliase | Vermischte Param-Konzepte sind die Wurzel der UX-Probleme; sie zu konservieren reproduziert genau das Problem, das wir loesen. Es gibt keine externen Konsumenten ausserhalb unseres Frontends (zu verifizieren, siehe oben). |
+| 2026-04-20 | `dateFrom`/`dateTo` als ISO `YYYY-MM-DD` String, **nicht** Epoch | Konsistent mit `PeriodValue`, lesbar in Logs/Browsernetzwerk, eindeutig (Tagesgrenze, kein Sub-Tag). Audit-Logger arbeitet zwar intern mit Epoch, aber die Konversion macht der Endpunkt zentral. |
+| 2026-04-20 | `dateFrom`/`dateTo` Pflicht, kein impliziter Default | Verhindert "vergessene Filter" (z.B. globale Aggregation ueber alle Zeit, schlecht fuer Performance). Caller muss bewusst einen Bereich waehlen. Defaults gehoeren ins Frontend. |
+| 2026-04-20 | `bucketSize` separater Param, nicht in `dateFrom`/`dateTo` impliziert | Eine Verantwortung pro Param. Erlaubt "letzte 30 Tage in Tagen" UND "letzte 30 Tage in Wochen" ohne Backend-Aenderung. |
+| 2026-04-20 | `bucketSize` ohne Default | Fuer Endpunkte mit Time-Series Antwort: Caller muss explizit waehlen, sonst HTTP 400. Frontend kapselt sinnvolle Heuristik. |
+| 2026-04-20 | Pfad `GET /api/billing/statistics` ohne `{period}` | `period` war Pfad-Param, mit Wegfall hat es im Pfad nichts mehr verloren. Saubere Resource-URL. |
+
+## Umsetzungs-Checkliste
+
+### A. Vorbereitung (~30 min)
+
+- [ ] Konsumenten-Pruefung wie oben (drei `rg`-Aufrufe).
+- [ ] `gateway/modules/shared/dateRange.py` anlegen mit
+ `parseIsoDateRange(dateFrom: str, dateTo: str) -> tuple[date, date]`
+ (HTTP 400 bei ungueltig oder `from > to`) und
+ `isoDateRangeToUtcEpoch(dateFrom: str, dateTo: str) -> tuple[float, float]`.
+ Unit-Tests dazu.
+
+### B. Backend Audit-Stats (~1 h)
+
+- [ ] `aiAuditLogger.getAiAuditStats(mandateId, *, fromTs: float, toTs: float, groupBy: str = "model")`:
+ Cutoff-Berechnung raus, ersetzt durch Range-Filter `fromTs <= ts <= toTs`.
+ `timeRangeDays`-Feld in der Antwort durch `dateFrom`, `dateTo`, `days`
+ (`(toTs - fromTs) / 86400`, gerundet) ersetzen.
+- [ ] `routeAudit.getAuditStats`:
+ ```python
+ dateFrom: str = Query(..., description="ISO YYYY-MM-DD")
+ dateTo: str = Query(..., description="ISO YYYY-MM-DD")
+ groupBy: str = Query("model", regex="^(model|user|feature|day)$")
+ ```
+ → ueber `isoDateRangeToUtcEpoch` an Logger weitergeben. `timeRange`
+ Param weg.
+
+### C. Backend Billing-Stats (~2 h)
+
+- [ ] `getTransactionStatisticsAggregated`: Param `period` umbenennen zu
+ `bucketSize` (Enum-Validierung im Endpunkt, hier nur Type-Hint Literal).
+ Alle Aufrufe im Modul mitziehen.
+- [ ] `routeBilling.getStatistics`:
+ - Pfad-Param `{period}` raus, neuer Pfad `GET /api/billing/statistics`.
+ - Neue Query-Params `dateFrom: date = Query(...)`, `dateTo: date = Query(...)`,
+ `bucketSize: Literal['day','month','year'] = Query(...)`.
+ - Range-Berechnung Z. 570-582 ersetzen durch `parseIsoDateRange`.
+ - `UsageReportResponse.period: str` → entfernen oder umbenennen zu
+ `bucketSize`. Anderen Felder bleiben.
+- [ ] `routeBilling.getUserViewStatistics`:
+ - Query-Params analog. `period` und `month`/`year` raus.
+ - `bucketSize` an `getTransactionStatisticsAggregated` weitergeben.
+
+### D. Frontend (~2 h)
+
+- [ ] `billingApi.fetchStatistics` neu signiert:
+ `fetchStatistics(request, { dateFrom, dateTo, bucketSize })`. Alte
+ Signatur weg.
+- [ ] `billingApi.fetchViewStatistics` analog.
+- [ ] `useBilling.loadStatistics({ dateFrom, dateTo, bucketSize })` -
+ bestehende Aufrufer auf neue Signatur umstellen.
+- [ ] `BillingDashboard.tsx`:
+ - `selectedPeriod`, `selectedYear`, `selectedMonth` State raus.
+ - ``.
+ - Separater kleiner `