Skip to content

AI Portfolio Export, Portfolio Engine Refactor & Dashboard Overhaul#8

Merged
Alfystar merged 34 commits into
mainfrom
dev
Jul 3, 2026
Merged

AI Portfolio Export, Portfolio Engine Refactor & Dashboard Overhaul#8
Alfystar merged 34 commits into
mainfrom
dev

Conversation

@Alfystar

@Alfystar Alfystar commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

AI Portfolio Export, Portfolio Engine Refactor & Dashboard Overhaul

Major backend engine rework (inline WAC, per-broker cash pools), a new
frontend-only AI advisor export feature, MWRR/period P&L fixes with
redesigned KPI cards, and broad chart/UX/docs polish.

Highlights:

New features:

  • AI Portfolio Export: two clipboard actions (Full AI Prompt / Data Only)
    build a structured Markdown+YAML dossier from the portfolio report +
    frontend-computed technical signals (EMA/RSI/MACD), for external AI
    advisors during the monthly PAC review. Frontend-only, no new backend
    endpoints. Includes geography/sector long-tail compaction, PAC context,
    investor-assumptions disclaimer, and documented allocation-basis
    exceptions
  • Capital Baseline + cash decomposition on GrowthChart: two-pool
    daily-stateful model splits cash into Asset Cost / Returns / Capital,
    P&L = NAV − Deposited Capital narrative
  • Emoji support for allocation charts + richer tooltips
  • Smooth chart animations (Growth, Allocation, KPI values) with
    stale-while-revalidate — no more blank/loading flicker on period change

Portfolio engine (backend):

  • Inline WAC computation replaces N×M DB calls; unified per-transaction
    loop fixes full-sell bug (proceeds no longer all routed to returns)
  • 3-pool cash model (Capital/Returns/Withdrawn) evolved to per-broker
    pools — K[broker]/R[broker] with correct CASH_TRANSFER handling
  • Price fallback chain: MARKET_PRICE → LAST_BUY_PRICE(visible brokers)
    → MISSING; pre-frame/frame separation avoids premature market eval
  • Fingerprint-keyed blob cache, range-aware reuse
  • 20+ new engine unit tests

Dashboard & metrics (BREAKING):

  • Fixed MWRR double-counting (initial_nav now from nav_snapshots[0]);
    added mwrr_annualized/mwrr_cumulative, removed legacy mwrr_percent/mwrr
  • Period-scoped P&L breakdown: unrealized delta, realized (WAC),
    income, fees/taxes, other_result — respects date_from/date_to
  • 3 redesigned KPI cards (Net Worth, P&L, Returns) with new
    KpiMetricBar/KpiDivergingFlowBar components
  • New PortfolioSummary fields: total_deposited, total_withdrawn,
    net_deposited_capital, period_market_value_start, period_fees,
    period_taxes, net_invested (additive)

Documentation:

  • New portfolio-engine.en.md: formal mathematical model (valuation
    chain, WAC, 3-pool per-broker, contribution, pre-frame/frame)
  • Dashboard user guide (kpi-cards, charts) + BRIM Generic CSV spec rewrite
  • WAC translated to localized PMC (IT) / PMP (ES, FR) across all docs

Fixes & cleanup:

  • Broker icon fallback chain unified (icon_url → favicon → plugin →
    initial letter) across BrokerIcon/BrokerBadge/filter panels
  • Import wizard Cancel-blocked fix, promote false-positive fix,
    bulk modal pagination, asset currency defaults
  • i18n: removed 194 dead/duplicate keys (1670 → 1476), 100% coverage
    maintained across EN/IT/FR/ES
  • CI: dev/nightly Docker tags, docs-deploy restricted to main/release

Alfystar added 30 commits June 22, 2026 16:51
…ase docs

- Add `push` trigger to `.github/workflows/release.yml` for the `dev` branch.
- Restrict `mkdocs deploy` to the `main` branch and `release` events, preventing
  pre-release documentation from overwriting the live documentation site.
- Implement conditional tags in `docker/metadata-action`: tag builds on `dev`
  as `nightly`, while keeping `latest` reserved for `main`.
- Isolate the Playwright screenshots cache for the `dev` branch as `screenshots-dev-`,
  allowing fast incremental builds on development pushes.
- Keep manual screenshot regeneration capability via `workflow_dispatch` inputs.
- Only archive PNG screenshot files (`**/*.png`) in `release.yml` to prevent
  outdated markdown files from entering the upload package.
- Add new `release-pipeline.md` reference manual under `developer/docs/`
  with a detailed Mermaid flowchart explaining the CI/CD pipeline.
- Lower custom screenshot carousel z-indexes in `home-custom.css` below
  header defaults to prevent language dropdown overlap.
- Add `--force` flag to the `mkdocs gh-deploy` wrapper in `dev.py` to prevent
  push rejections.
…cons

- Add structured GitHub Issue Templates (.github/ISSUE_TEMPLATE/) for bug reports, feature requests, UX ideas, and plugin requests.
- Update localized contribution guides (contribute.*.md) to link directly to the new issue templates.
- Add localized "Request New Plugin" card-links using Lucide puzzle piece SVG to import, assets, and FX provider lists.
- Fix relative image path traversals for CSS Scraper and Scheduled Investment icons in both list cards and detail page headers across all languages.
- Ensure cards relative link (../../../community/contribute/) resolves correctly without 404.
…est/docs updates

- Fix MWRR double-counting: initial_nav now derived from nav_snapshots[0].nav (aligned with history)
- Add mwrr_cumulative + mwrr_annualized metrics across summary and history
- GrowthChart % mode now uses mwrr_cumulative (aligned with TWRR/ROI)
- BREAKING: removed legacy mwrr_percent (Summary) and mwrr (HistoryPoint)

- Remove transaction query limit (was 100)
- Scheduler: UTC storage, TZ-aware defaults, frontend selector, modal locked behind edit mode
- OpenDateRangeModel: add "min"/"max" sentinels + MAX preset in DateRangePicker
- DistributionEditor: auto-fill remaining weight to 100%
- Countries endpoint: include_other param
- i18n: Liquidity label + allocation chart icon
- Allocation & Growth charts: tooltip helpers, safeKey fix, appendToBody, UI/UX improvements

- E2E gallery: mock dashboard report API, dynamic date shifting, extended screenshots (desktop/mobile, all tabs/modes)

- Docs: fix LaTeX rendering, clean headings, define MWRR/TWRR concepts, verify examples (CAGR, CF sign, exact values)
- Convert GitHub-style alerts (> [!TIP]/[!NOTE]) to standard MkDocs admonitions (!!! note/tip) in timing-effect, mwrr, and twrr docs.
- Add required blank line inside admonition blocks to prevent Prettier failures.
- Apply precision improvements to Timing Effect descriptions across all 4 languages (EN, IT, ES, FR) based on finance terminology review (clarifying investor gap, simple ROI in portfolio context, and tax/fee handling).
 Backend:
 - Fix MWRR double-counting: use nav_snapshots[0].nav, not total_invested
 - Add mwrr_annualized/mwrr_cumulative (remove mwrr_percent/mwrr)
 - Summary metrics now respect date_from (period-rebased ROI/TWRR/MWRR)
 - Add period P&L breakdown: unrealized_delta, realized_gain_loss (WAC),
   income (DIVIDEND+INTEREST), fees_taxes (FEE+TAX), other_result
 - Fix period_nav_start=0 when date_from before first event
 - Fix realized G/L: exclude SELL from WAC computation (excluded_tx_ids)
 - Force ROI/TWRR/MWRR first point to 0% unconditionally
 - Add annualized_to_cumulative() helper in roi_utils

 Frontend:
 - 3 specialized KPI cards: Net Worth (bars+markers), P&L (breakdown
   bars), Returns (timing effect hero + metric bars)
 - New KpiMetricBar.svelte component with tooltip and triangle marker
 - GrowthChart: all labels reactive to locale, tooltipPositionSide
 - All charts migrated from tooltipPositionAboveFinger to Side
 - DateRangePicker MAX preset translated (All/Tutti/Tout/Todo)
 - Resolve min/max sentinels in asset/FX detail (fix 422 errors)
 - Currency formatting via formatCurrencyAmountPlain

 BREAKING CHANGE: removed mwrr_percent (PortfolioSummary), mwrr
 (PortfolioHistoryPoint/AssetHistoryPoint). Use mwrr_annualized_percent
 / mwrr_annualized + mwrr_cumulative_percent / mwrr_cumulative.
 BREAKING CHANGE: PortfolioSummary adds total_deposited,
 total_withdrawn, net_deposited_capital, period_market_value_start,
 period_fees, period_taxes, net_invested on PortfolioHistoryPoint.
 Removed fields: none. All additive.

 MWRR series:
 - Fix warm-start contamination: guard extreme rates, retry cold-start
 - Fix initial_nav=0 degeneracy when date_from before portfolio start
 - Fix CF exclusion on period start date (strict > vs >=)
 - Add reliability fallback: compare series[-1] vs summary, null-out
   if divergent, emit MWRR_SERIES_UNRELIABLE DataQuality issue
 - ROI first point no longer jumps to 6% on deposit day

 Period P&L:
 - get_summary() now accepts date_to — income, fees, realized,
   net_deposited_capital all respect both boundaries
 - period_net_flows also filtered by date_to
 - MWRR end_date uses date_to (not always today)

 Card 1 (Net Worth):
 - Replace NAV bar with Market Value + start marker
 - Remove Unrealized G/L row (overlaps P&L card)
 - Add Net Deposited Capital with diverging bar
 - Add period scope note at bottom

 GrowthChart EUR:
 - Add net_invested field (cumulative deposits - withdrawals)
 - Tooltip shows P&L = NAV - net_invested
 - Stack order: book value → cash → in-transit → NAV line

 Other:
 - Directa plugin: fix Rit.cedola classified as INTEREST (→ TAX)
 - Manual assets (no provider): suppress MISSING_PRICE banner
 - Fees/taxes tooltip: split with emoji + right-aligned HTML table
 - KpiMetricBar/KpiDivergingFlowBar: add tooltipHtml prop
 BrokerInfo missing default_import_plugin: plugin-only brokers showed
 dot only; added to type + brokerMap. referrerpolicy="no-referrer" on
 all favicon imgs.

 Promote false positive: cashAmountsCancel() (exact sum=0) was only in
 selectedForPromote — root cause was localSuggestions + bannerSuggestions;
 now guarded in all three.

 BulkModal: enablePagination=true + alwaysShowPagination + [5,10,25,50]
 page sizes (was enablePagination=false).

 LIQUIDITY i18n key uppercase; AllocationPieChart type-mode now resolves
 $t("asset.types."+name.toUpperCase()) instead of showing raw name.

 AssetModal resetForm(): currency ← userSettings.base_currency (was "USD").
 AssetModal saveCreate(): full-history sync start=1975-01-01 after provider.

 SectorSearchSelect: getSectorEmoji() in label (DistributionEditor was
 wrong target). ImportWizard: checkAndPromptIdentifier on oncreated.

 Also: DataTable getRowHref + onauxclick middle-click (F),
 Tooltip mobile min(maxWidth, 100vw-20px) (I), responsive
 thresholds +100px after YTD/ALL (L), KPI tooltips + overlay
 icon fallback in DataTableColumnFilter/dashboard (J/A2-A3).
…hart

 GrowthChart ABS mode restructured around the narrative:
 P&L = NAV − Deposited Capital

 - Two-pool daily-stateful cash decomposition in portfolio engine
   (returns consumed first on BUY, principal returned on SELL)
 - New stacked areas: Asset Cost / Returns / Capital
 - Capital Baseline dashed overlay line
 - Tooltip shows Total P&L with formula hint
 - 5 new fields in PortfolioHistoryPoint DTO (non-breaking addition)
 - 9 unit tests covering examples A-F + edge cases

 The stateless formula was algebraically unable to distinguish
 deposit residuals from returns when book_asset_like > baseline.
 Delta-based two-pool model tracks provenance at cost-basis level
 without euro-per-euro ledger or DB persistence.
 Dashboard user guide:
 - Rewrite user/dashboard/index.en.md as overview
 - Add kpi-cards.en.md (Net Worth, Period P&L, Returns cards)
 - Add charts.en.md (Growth Chart ABS/%, Allocation Panel)

 Financial theory:
 - Add deposited-capital.en.md with delta-based cash decomposition
   algorithm, 6 worked examples A–F, and limitations
 - Extend period-pnl.en.md with Total P&L / Period P&L relationship
 - Add "Deposited Capital & Total P&L" to performance-metrics index

 BRIM documentation:
 - Rewrite generic_csv.md: sign conventions table, per-type guidance,
   P2P patterns, storno/reversal strategies, LLM tip with prompt
 - Rewrite architecture.md: full 6-step wizard, plugin contract
 - providers_list.md: favicon icons, links to user import pages

 User import pages (generic-csv ×4 languages):
 - Add column reference table with accepted aliases from HEADER_MAPPINGS
 - Add info banner linking to technical spec
…n chain

 - AssetModal: internal ConfirmModals use zIndex+20 instead of
   hardcoded 70/80; fixes cancel being blocked when opened from
   ImportWizardModal at zIndex=90
 - broker icon fallback chain (icon_url → portal favicon → plugin
   icon → initial letter) extracted into brokerIconChain.svelte.ts;
   BrokerIcon and BrokerBadge both use it — single source of truth
 - BrokerBadge: drops colored-dot fallback, uses initial letter
   (consistent with BrokerIcon); gains full multi-URL retry on load error
 - brokerStore: ensureBrokersLoaded + refreshAllBrokers both fire
   ensurePluginIconsLoaded; fixes brokers/ page showing "R" because
   plugin cache was empty at render time
 - brokerHelpers: correct priority order restored (favicon before
   plugin icon); getBrokerIconCandidates replaces duplicated inline
   new URL(portal_url).origin+'/favicon.ico' in 4 modal files
 - ChartSignalsSection: remove local ASSET_TYPE_ICON_MAP, use
   getAssetTypeIconUrl
 - resolveValidationMessage: use getAssetTypeIconUrl instead of
   inline path construction

 fix(docs): responsive scaling for landing page on narrow mobile

 - logo+h1 hero row: clamp(2.2rem,9vw,4rem) + proportional logo;
   prevents overflow on 320px+ viewports
 - h2 subtitle: clamp(1.1rem,5.5vw,2.2rem) + suppress mobile-break
   span so text wraps naturally instead of forced at 768px
 - all landing text elements scale proportionally: badges, buttons,
   deep-dive h2/p, provider-info, lf-fit cells, quick-install paragraphs
 - badge gap: clamp(0.5rem,3vw,2rem)
 - nav cards: grid layout (icon+title row 1 / desc row 2 full-width)
   replaces flex; card-desc uses grid-area:desc for guaranteed
   full-width on every viewport
…uy_price

 Replace N×M compute_wac_iterative DB calls with single-pass inline
 WAC computation. Restructure engine around per-tx unified loop:

 - Pre-frame/frame separation (no market eval before t0)
 - Price fallback: MARKET_PRICE → LAST_BUY_PRICE(V(u)) → MISSING
 - 3-pool event-driven: read WAC → update K/R → reduce pool
 - DailyPositionState emitted at t0/t1 + period accumulators
 - Blob cache fingerprint-keyed (range-aware, reuse if contained)

 Key fix: SELL now reads WAC before reducing pool, preventing
 full-sell bug where entire proceeds went to returns (R) instead
 of splitting capital (K) + gain (R).

 Adds 20 unit tests validating: inline WAC, last_buy_price chain,
 3-pool decomposition, position snapshots, period accumulators,
 pre-frame/frame separation.
 Dashboard charts (Growth, AllocationHistory) use xAxis type:'time'
 with namedPoint({name: date, value}) for ECharts shift animation —
 shared dates translate vertically, new dates enter, removed exit.
 Partial setOption on data-only updates avoids full chart rebuild.

 - Centralize animation config in echartsAnimationConfig.ts
 - Add TweenedValue component for numeric interpolation (900ms cubicOut)
 - Apply TweenedValue to 14 KPI numbers (3 hero + 11 metric bars)
 - Persistent DOM containers prevent chart instance destruction
 - Stale-while-revalidate: keep old data visible during fetch
 - FX page: single bulk HTTP call via ensureFxRangeLoadedBulk()
 - Pie chart: notMerge:false for segment morph animation
 - LineChart/Candlestick/PriceChartFull: merge-mode animation
 - KPI bars: CSS transition duration-700 + marker caret animation
- Added Step 3 (Analysis & Parsing) explanation table for grid emojis in all languages
- Clarified that Step 3 TODO action indicators (orange/red) are not errors but rather missing values to be solved during the bulk revision step
- Added UNIQUE (green) and UNRESOLVED (red) badges and matching rules to duplicate detection tables across all languages
- Translated and aligned Dashboard index.md page for Italian, French, and Spanish
- Fixed Mermaid diagram tab layout indentations in asset create-edit guides
- Fixed missing translation keys for navigation menus in mkdocs.yml ("Create & Edit" and "Events")
Stale-while-revalidate pattern: old data stays visible during fetch,
new data animates in when ready. Eliminates blank/loading flicker
on period change for repeat visits.

- GrowthChart/AllocationHistory: xAxis time + namedPoint shift anim
- AllocationPieChart: partial setOption for segment morph
- TweenedValue component: 14 KPI numbers interpolate (900ms cubicOut)
- KpiMetricBar: CSS transition on bars + markers (duration-700)
- Persistent DOM containers prevent chart instance destruction
- FX page: single bulk HTTP call via ensureFxRangeLoadedBulk()
- Detail page charts (Line/Candle) unchanged: instant render
…stamp command

## Translation pipeline
- Add `translate-stamp` subcommand to translate_docs.py:
  - Updates .translate-hashes.json to current MD5 without invoking the LLM
  - Supports --file, --lang, --dry-run flags
  - Preserves all existing cache metadata (analysis, critique, etc.)
  - Available as both `./dev.py mkdocs translate-stamp` and standalone
  - Use after manual edits to prevent spurious re-translation on next run
- Stamp 51 already-translated files to re-sync cache after manual edits

## Docs: performance-metrics structural alignment (EN → IT/FR/ES)
- Add "Relationship with Total P&L" section to period-pnl.{it,fr,es}.md
  (mirrors existing EN section with LaTeX formulas and Growth Chart reference)
- Add Deposited Capital & Total P&L row to index.{it,fr,es}.md metrics table
- Confirm 381/387 translation files structurally clean (6 intentional minor deltas)

## Docs: missing translation inventory
- Confirmed 4 files with no translations yet:
  deposited-capital.en.md, kpi-cards.en.md, charts.en.md, host_installation.en.md
New dedicated page with formal notation for the full engine:
valuation chain, WAC iterative update, portfolio aggregation,
3-pool cash model (K/R/W), period contribution, pre-frame/frame.

Existing metric pages slimmed down to reference engine page.
Fix KaTeX rendering: replace € and {,} in math blocks with
\text{ EUR} and \, (thin space).
Enum filter popovers still consumed `iconUrl`, so broker options
fed with `iconCandidates` rendered dot-only fallbacks.

Keep tx broker-name hooks on BrokerBadge and add focused
regressions for dashboard and transaction broker surfaces.
Frontend-only feature: two-button dropdown next to Sync
builds structured prompt from portfolio report + technical
signals, copies Markdown+YAML to clipboard for external AI.

- 11 new files in lib/features/ai-export/
- types, builder, renderers (full prompt + data-only)
- generic cross-detection engine for signal events
- daily→weekly sampling for 3M technical window
- EMA20/50/200, RSI14, MACD computed via existing signals
- normalized return % from common 3M base date
- i18n keys added (EN/IT/FR/ES)
- svelte-check: 0 errors, 0 warnings
…chors

- Translated WAC (Weighted Average Cost) and CMP (Coût Moyen Pondéré) to localized equivalents: PMC (Prezzo Medio di Carico) in Italian and PMP (Precio/Prix Moyen Pondéré) in Spanish and French.
- Aligned mathematical formulas in `weighted-average-cost.md` using localized terms (e.g., `PMC_{\text{attuale}}`, `PMP_{\text{nuevo}}`).
- Cleaned up remaining WAC/CMP references across all user guides, transaction forms, and broker import documents.
- Fixed relative paths for transaction icon assets in `portfolio-engine` tables (`../../../static/`).
- Updated cross-file links to point to correct localized section anchors.
- Updated `.translate-hashes.json` cache stamps via `translate-stamp` for modified English files to prevent LLM re-translation.
Bug fixes:
- Dropdown outside-click: use closest() instead of ===
- Replace BrainCircuit icon with Brain

Semantic fixes:
- Multiply ratio-format *_percent fields by 100
- Add metric_definitions to methodology section
- Add allocation_basis + Other/Unallocated if gap >0.5%
- Filter closed positions with no period activity
- Add is_open flag and valuation_source proxy
- Rename "Return %" column to "Return from 3M base %"

Noise reduction:
- Add epsilon to price/EMA cross detection (~0.1%)
- Add minGapDays (3-5) between same-type events
- MACD histogram epsilon 0.05 (ignore near-zero)
- Limit to 5 events/asset, 40 total
- snake_case YAML keys (ema20, macd_histogram, rsi14)
- Filter backward-filled (non-trading) prices

Prompt improvements:
- Add PAC amount absent instruction
- Tighten web research scope
Semantic fixes:
- Replace deposited_capital with total_invested (lifetime denominator)
- Add period_net_deposits (period-scoped, formerly misleading)
- Fix metric_definitions to match actual computation
- Change allocation_basis to nav_including_cash
- Remove closed positions entirely (not relevant for PAC)
- Remove is_open field (all exported positions are open)

New sections:
- technical_summary: compact per-asset overview (return_3m,
  RSI, MACD, price_vs_ema%) before full tables
- pac_context: explicit PAC parameters (amount unavailable,
  EUR, no sale suggestions, new assets unknown)

Prompt improvements:
- Analysis request: 3 named PAC scenarios (rebalance,
  core accumulation, cautious/risk-control)
- Explicit "not investment advice" framing

Events: max 4/asset (was 5), 30 total (was 40)
Allocation:
- by_asset, by_currency, by_broker now include cash to match
  allocation_basis: nav_including_cash (previously excluded cash,
  causing sums <100% inconsistent with broker_summary)
- by_asset_type/by_sector already included cash via backend
  Liquidity bucket; by_geography left as-is (cash has no geography)

Removed:
- valuation_source field from positions (product decision: exported
  values are already best-estimate, AI shouldn't reason about source)

Added:
- methodology.technical_context_policy — clarifies 3M technical
  window is independent from the selected portfolio date range
- investor_assumptions section (risk_tolerance, investment_horizon,
  target_allocation: unavailable) + prompt instruction not to assume
  user profile
- Data-only export now includes methodology (was missing entirely,
  needed to interpret wac_policy/valuation_policy without prompt text)

Reordered sections in both renderers:
Methodology → Export Metadata → Snapshot → Allocation → Positions →
Broker Summary → PAC Context → Investor Assumptions → Technical
Summary → Technical Series → Events → Unavailable → Data Quality

Prompt: refined underweight/overweight wording to state assumptions
explicitly rather than imply objective thresholds.

Kept unchanged per product decision: period_pnl/period_pnl_percent
on positions, technical event noise filters (already adequate).
Long lists of sub-1% countries or sectors reduce signal-to-noise
for the LLM when the export contains diversified portfolios.

Added allocationCompaction.ts:
- Static ISO3->continent lookup (~230 codes, 5 continents:
  Europe, Asia-Pacific, North America, South America, Africa)
- compactGeography(): countries >=1% kept individually, <1%
  grouped into "<Continent> minor countries"; unmapped codes
  fall into "Other minor countries"
- compactSector(): sectors >=1% kept individually, <1% merged
  into a single "Minor sectors" bucket
- Unknown/Liquidity never merged into minor buckets regardless
  of weight — kept as standalone signals

by_geography now also includes cash explicitly as a "Liquidity"
line (previously excluded entirely from geography, unlike
by_asset_type/by_sector which already had it via backend).

methodology.allocation_compaction_policy declares thresholds and
grouping rules so the AI knows small exposures are aggregated.
Requested Analysis text updated with the same note.

by_asset, by_asset_type, by_currency, by_broker intentionally
left uncompacted — either already short or needed at full detail
for PAC decisions.

Validated: svelte-check 0/0, i18n audit 1476/0 incomplete.
- Remove 194 unused/duplicate keys across en/it/fr/es (1670 -> 1476),
  verified against frontend literal + template usages, backend
  message_i18n_key data refs, and e2e tests
- Merge 52 fully-duplicate key groups into common.* canonicals,
  repointing all call sites; keep dynamic-prefix namespaces
  (transactions.fields/types/errors, chartSettings.*, assets.types,
  etc.) untouched since they're resolved via `$t(prefix.${var})`
- Normalize a handful of incidental translation inconsistencies found
  along the way (casing drift, missing accents)
- Fix i18n-audit.py false positives: detect backend-driven
  message_i18n_key="ns.key" data refs and camelCase-continuation
  dynamic templates (historyDays${day}) that the static scanner missed
- Document accepted-duplicate and protected-namespace rules in the
  devpy-i18n skill for future audits

122 duplicate groups remain, all reviewed and intentionally kept
(protected dynamic prefixes, semantic/context differences, or
grammar-dependent casing). Coverage 100% across all 4 languages,
svelte-check clean, 0 orphaned key references.
Root cause: by_type/by_sector already fold cash into their backend
denominator (via a "Liquidity" bucket), but by_geography does not —
the backend has no geographic classification for cash, so its
country percentages already sum to ~100% of market_value alone.

Adding a NAV-based Liquidity line on top of that already-complete
100% pushed the section to ~101%, and ensureAllocSums100() then
emitted a negative "Other / Unallocated" residual to force the sum
back to 100 — invalid, confusing data for the AI advisor.

Fix (no rescaling, per product decision):
- by_geography no longer gets a Liquidity line added; countries are
  exported exactly as returned by the backend, on their own
  market_value_excluding_cash basis
- Declared explicitly via methodology.allocation_basis_exceptions
  so the AI doesn't assume by_geography sums to 100% of NAV like
  the other sections
- ensureAllocSums100() now guards against negative residuals: only
  adds a positive bucket above +0.5pp tolerance; a negative gap is
  logged as a console warning and silently omitted, never exported

Also added metric_definitions.position_period_pnl_percent to
distinguish the position-level P&L percentage (period_pnl /
|start_value|) from the technical return_3m_percent shown in the
Technical Summary — same name, different meaning, now disambiguated.
Alfystar added 4 commits July 1, 2026 15:02
…nition

The position_period_pnl_percent metric definition contained a literal
"&" ("P&L"). Any downstream Markdown-to-HTML rendering of the copied
clipboard text (e.g. viewing it through a Markdown-aware tool) would
legitimately re-encode it as "&amp;" per CommonMark/HTML rules, which
could confuse an AI reading the rendered version. Reworded to
"gain/loss" to sidestep the ambiguity entirely — the clipboard writer
itself does no escaping of its own.

Added metric_definitions.simple_roi_percent, previously undocumented:
"(NAV - period_net_deposits) / period_net_deposits" — verified against
calculate_simple_roi() and its call site in portfolio_service.py; the
denominator is the selected period's net deposits (same field already
exported as period_net_deposits in Portfolio Snapshot), not lifetime
total_invested.

No changes to by_geography, allocation values, valuation_source,
technical calculations, or backend/API.
Both flagged as mathematically inconsistent against real exported
data — traced to actual backend code, confirmed as genuine errors:

simple_roi_percent:
Round-6 definition "(NAV - period_net_deposits) / period_net_deposits"
gave ~688% with real numbers (nav=51004.25, period_net_deposits=6468.26)
vs actual 11.63%. Traced portfolio_service.py: the real denominator
is period_nav_start + net cash flows after period start (via a
synthetic -period_start_nav cash flow), not period_net_deposits alone.

Added period_nav_start to AiPortfolioSnapshot (existing backend field
summary.period_nav_start, no backend changes) and reworded the
definition as an explicit approximation — not an exact formula, since
LibreFolio's ROI calc and period_pnl calc use slightly different
period-boundary conventions internally and aren't guaranteed to match
to the last decimal.

by_geography basis:
Round-5 text claimed percentages "are not rescaled to NAV and do not
sum to 100%" — contradicted by real data (countries + minor buckets
≈ 100%). Verified portfolio_engine.py: backend excludes cash from
by_geo's own denominator before normalizing to 100%, so the section
genuinely sums to ~100%, just of invested market value excluding cash
(not of NAV like every other allocation section). Corrected the
allocation_basis_exceptions text to state this precisely, with the
practical inflation effect noted for the AI reader.
@Alfystar Alfystar merged commit 18d60ee into main Jul 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant