🛡️ DataQualityBanner Component
Component and model for unified data quality warnings across the LibreFolio UI.
🔍 Overview
DataQualityIssue is the standard model for surfacing data quality problems (missing prices, missing FX, incomplete NAV, etc.) across three key pages:
- Dashboard 📊
- Asset Detail 📈
- Forex Detail 💱
DataQualityBanner is the single, reusable UI component that renders these issues consistently. Before this system existed, each page had its own ad-hoc inline banners — separate markup, no consistency, and no Call-To-Action (CTA). Now, all three pages share the same component and issue model.
📁 File Mapping
| File | Role |
|---|---|
🐍 backend/app/schemas/portfolio.py |
Defines IssueCode, IssueSeverity, IssueDomain, DataQualityIssue, and DataQualityReport schemas. |
⚙️ backend/app/services/portfolio_engine.py |
DerivedViewsBuilder.build_data_quality_report() — generates portfolio-wide issues. |
🎨 frontend/src/lib/components/ui/feedback/DataQualityBanner.svelte |
Reusable UI component (supporting grouped and flat modes). |
🌐 frontend/src/lib/i18n/{en,it,fr,es}.json |
dataQuality.* translation namespace for localized warnings. |
🏗️ Architecture & Data Flow
Here is how data quality reports are computed in the backend and rendered inside SvelteKit pages:
graph TD
Engine["⚙️ Backend Engine<br>(DerivedViewsBuilder)"] -->|"Computes issues"| Report["📄 DataQualityReport<br>Schema"]
Report -->|"JSON payload"| API["📡 API Endpoint<br>(POST /summary)"]
API -->|"issues[]"| Dash["📊 Dashboard Page"]
Dash -->|"grouped mode"| Banner["🛡️ DataQualityBanner"]
Detail["📈 Client Pages<br>(Asset/FX Detail)"] -->|"flat mode (per-issue)"| Banner
📁 Component Modes
1. grouped — Dashboard 📊
- Single amber/sky container grouping all issues.
- Issues are sorted by severity: Error 🔴 → Warning 🟡 → Info 🔵.
- The header adapts dynamically:
"1 error","2 warnings", or"Data quality note"(for info-only alerts). - First 3 issues are visible; an expand button appears when there are more.
2. flat — Asset Detail / FX Detail 📈 💱
- One banner is rendered per issue.
- Each banner displays: icon + message + FX pair flags + asset context (in parentheses) + CTA button.
🖱️ CTA (Call-to-Action) System
The component emits intent — it does not navigate or open modals itself.
🎯 Active CTA Actions
| Action | Target | Handled By | Description |
|---|---|---|---|
navigate_asset |
asset_id string | goto('/assets/' + target) |
Redirects the user to the specific asset details page. |
navigate_fx |
FX pair slug | goto('/fx/' + target + '?start=...&end=...') |
Redirects to the FX pair details page. |
add_fx_pair |
FX pair slug | FxPairAddModal |
Opens the modal to configure the missing FX pair. |
🏷️ Active IssueCodes
Do Not Add Future-Proof Codes
Every IssueCode must be actively generated by the backend engine or constructed client-side. Add a new code only in the same step where it is generated. Do not define unused placeholder codes.
📊 Portfolio Issues (generated by DerivedViewsBuilder.build_data_quality_report)
| Code | Severity | Condition | CTA Action |
|---|---|---|---|
MISSING_PRICE |
🔴 Error | Asset held with no PriceHistory and no WAC/cost basis fallback | navigate_asset |
TRANSACTION_IMPLIED |
🟡 Warning | Asset held with no PriceHistory but WAC/cost basis available — valued at cost temporarily | navigate_asset |
STALE_PRICE |
🟡 Warning | Latest price older than staleness threshold | navigate_asset |
MISSING_FX_MARKET |
🟡 Warning | Asset in foreign currency without a configured FX pair | add_fx_pair |
NAV_INCOMPLETE |
🔵 Info | One or more days had incomplete NAV (caused by MISSING_PRICE) | none |
MWRR_NOT_CALCULABLE |
🔵 Info | MWRR did not converge or period is too short | none |
📈 Asset Detail Issues (built client-side in assets/[id]/+page.svelte)
| Code | Severity | Condition | CTA Action |
|---|---|---|---|
ASSET_ARCHIVED |
🟡 Warning | assetInfo.active === false |
none |
RANGE_BEFORE_FIRST_DATA |
🔵 Info | Selected range starts before first PriceHistory | none |
FX_PAIR_MISSING |
🟡 Warning | Required FX pair not configured | add_fx_pair |
FX_PAIR_NO_DATA |
🟡 Warning | FX pair configured but no rate history | navigate_fx |
FX_PAIR_PARTIAL_GAP |
🔵 Info | FX data exists but range starts before first rate | navigate_fx |
💱 FX Detail Issues (built client-side in fx/[pair]/+page.svelte)
| Code | Severity | Condition | CTA Action |
|---|---|---|---|
RANGE_BEFORE_FIRST_DATA |
🔵 Info | Selected range starts before first rate in DB | none |
🧪 How to Trigger Issues (Manual Testing)
📊 Dashboard
| Issue | How to Trigger |
|---|---|
MISSING_PRICE |
Add a BUY transaction for an asset that has no PriceHistory entries AND no WAC/cost basis is available. The NAV will exclude it. |
TRANSACTION_IMPLIED |
Buy an asset (e.g. BTP in collocamento) before its first PriceHistory is available. WAC must exist. The engine uses WAC as a temporary proxy; the issue disappears once the first price becomes available. |
STALE_PRICE |
Use an asset that hasn't been synced recently, or manually set last_price_date far in the past in the DB. |
MISSING_FX_MARKET |
Add an asset in a foreign currency (e.g. USD) when the dashboard target currency is EUR and the EUR/USD pair is not configured. |
NAV_INCOMPLETE |
Same scenario as MISSING_PRICE — appears automatically when NAV is incomplete for ≥1 day. |
MWRR_NOT_CALCULABLE |
Portfolio with only 1 transaction on 1 day. MWRR needs ≥2 nav snapshots with a non-zero cash flow. |
📈 Asset Detail
| Issue | How to Trigger |
|---|---|
ASSET_ARCHIVED |
Asset detail → Edit modal → check "Archived" → save → reload the page. |
RANGE_BEFORE_FIRST_DATA |
In DateRangePicker, set start date to a year before the first PriceHistory entry (e.g. 2018 for an asset with data from 2020). |
FX_PAIR_MISSING |
Open an asset in a currency different from the display currency (e.g. USD asset, EUR display) when the EUR/USD pair is not configured. |
FX_PAIR_NO_DATA |
Create an FX pair via the FX page but skip sync — the pair exists but has no rates. |
FX_PAIR_PARTIAL_GAP |
Configure an FX pair with data from 2022. Open an asset detail with date range starting in 2020. |
💱 FX Detail
| Issue | How to Trigger |
|---|---|
RANGE_BEFORE_FIRST_DATA |
Navigate to /fx/EUR-USD?start=2000-01-01&end=2000-12-31. Alternatively set DateRangePicker to a very early start date. |
🔍 Debugging: Verify data_quality.issues Payload
Open browser DevTools → Network → find the POST /api/v1/portfolio/summary request.
In the response JSON, look for:
{
"data_quality": {
"issues": [
{
"code": "MISSING_PRICE",
"severity": "error",
"count": 2,
"affected_asset_names": ["BTP Più SC", "Apple Inc"],
"cta_action": "navigate_asset",
"cta_target": "123"
}
]
}
}
Note: The issues[] array is the single source of truth for dashboard banners. For asset/forex detail, issues are constructed client-side (no network request to inspect).
🛠️ How to Add a New IssueCode
- Add the code to
IssueCodeenum inbackend/app/schemas/portfolio.py. - Generate it in the backend engine (
build_data_quality_report) or construct it client-side on the relevant page. - Set
severity,cta_action,cta_target,affected_*fields appropriately. - Add
message_i18n_keyand its translations in all 4 language files underdataQuality.*. - If a new
cta_actionis needed, add its label indataQuality.cta.*and handle it in the parentonactioncallback. - Update this document.
- Add a backend unit test (if generated by engine) or E2E test (if visible in the UI).
- Document how to trigger the issue manually.
🧪 Test Coverage
🐍 Backend Unit Tests (test_data_quality_report.py)
- Covers all 5 portfolio codes: severity, affected fields, CTA, count, date_range.
- Empty inputs produce no issues.
- All 5 codes can appear together.
Run test suite:
(Coverstest_services/test_financial/ — includes test_data_quality_report.py and test_transaction_implied.py)
🎭 Frontend E2E Tests (e2e/portfolio/data-quality-banners.spec.ts)
- Dashboard loads without JS errors.
- Legacy inline banners removed (old testids gone).
- Grouped mode structure checks when issues present.
- Header does not show
"0 errors, 0 warnings"for info-only issues. - NAV incomplete message includes dates.
- Flat mode: checks that no grouped container is rendered.
- FX pair missing: CTA button checks.
Run test suite: