read.markets/app/templates/partials/portfolio.html
Giorgio Gilestro 47dce1a1a4 compliance: flag-gate AI portfolio + cloud sync + Stripe; de-risk prompts; harden reviewer
Implements docs/read-markets-compliance-changes.md as flag-gated changes
(no deletions) so paused features stay in the tree for future re-enable.
All four flags default False so a fresh deploy is compliance-safe.

- New env flags: PORTFOLIO_AI_ENABLED, PORTFOLIO_SYNC_ENABLED,
  TICKER_UNIVERSE_AGGREGATE_ENABLED, SUBSCRIPTIONS_ENABLED.
- Gates: /api/analyze, /api/portfolio/sync*, /api/stripe/*, /pricing,
  ticker_universe writes, portfolio_analysis.analyse(). is_paid_active()
  returns True for any auth'd user when subscriptions are paused.
- Prompts (PROMPT_VERSION 10): universal _COMPLIANCE_RIDER prepended to
  every system prompt; watch list removed; price-target / close-above-below
  / trigger / forward-state-as-description rules added; SPECULATIVE
  pivoted to regime-only scenarios; daily + weekly digests tightened.
- Reviewer: deterministic regex/lexicon pre-check fail-closed under the
  Haiku call; portfolio rider gated by PORTFOLIO_AI_ENABLED; base prompt
  sharpened for forward-state and MAR forward-opinion patterns;
  ReviewerVerdict audit table; generate_with_review retry helper.
- Migration 0026: purge portfolio_sync + ticker_universe; create
  reviewer_verdicts.
- Copy: MAR cite fixed to Art 3(1)(35) + Art 20 + Del Reg 2016/958;
  portfolio reframed as browser-only viewer in disclaimer / privacy /
  terms / about / pricing / landing (en + it). TODO(legal) marker for
  lawyer sign-off on disclaimer.
- Tests: 13 lexicon + 6 reviewer compliance regressions; conftest enables
  all flags so existing 402 tests still cover their code paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 19:57:12 +02:00

83 lines
3.2 KiB
HTML

{# Compliance: portfolio is a neutral composition viewer — report numbers,
never append a verdict. No "over-concentrated", no "consider X", no colour-
coded warning badges. See docs/read-markets-compliance-changes.md TASK 1. #}
{% if not portfolios %}
<div class="empty">no portfolio snapshots yet</div>
{% else %}
{% for p in portfolios %}
{# --- overall block --- #}
<div class="pf-overall">
<div class="pf-overall__head">
<span class="pf-name">{{ p.name }}</span>
<span class="pf-as-of">
{% if p.snapshot_at %}{{ p.snapshot_at.strftime("%Y-%m-%d %H:%M UTC") }}{% else %}—{% endif %}
</span>
</div>
<div class="pf-overall__grid">
<div class="pf-stat">
<div class="pf-stat-label">Total</div>
<div class="pf-stat-value">{{ p.total_value | money }} <span class="pf-ccy">{{ p.currency }}</span></div>
</div>
<div class="pf-stat">
<div class="pf-stat-label">Invested</div>
<div class="pf-stat-value">{{ p.invested | money }}</div>
</div>
<div class="pf-stat">
<div class="pf-stat-label">Cash</div>
<div class="pf-stat-value">{{ p.cash | money }}</div>
</div>
<div class="pf-stat">
<div class="pf-stat-label">Unrealised P/L</div>
<div class="pf-stat-value {% if p.unrealized_ppl is none %}neu{% elif p.unrealized_ppl >= 0 %}pos{% else %}neg{% endif %}">
{{ p.unrealized_ppl | signed }}
{% if p.total_cost and p.unrealized_ppl is not none %}
<span class="pf-pct">({{ "%+.2f"|format(p.unrealized_ppl / p.total_cost * 100) }}%)</span>
{% endif %}
</div>
</div>
<div class="pf-stat">
<div class="pf-stat-label">Realised P/L</div>
<div class="pf-stat-value {% if p.realized_ppl is none %}neu{% elif p.realized_ppl >= 0 %}pos{% else %}neg{% endif %}">
{{ p.realized_ppl | signed }}
</div>
</div>
<div class="pf-stat">
<div class="pf-stat-label">Positions</div>
<div class="pf-stat-value">{{ p.positions | length }}</div>
</div>
</div>
</div>
{# --- per-position table --- #}
<table class="dense">
<thead>
<tr>
<th>Ticker</th>
<th>Name</th>
<th class="num mobile-hide">Qty</th>
<th class="num mobile-hide">Avg</th>
<th class="num">Last</th>
<th class="num">P/L</th>
<th class="num">%</th>
</tr>
</thead>
<tbody>
{% for pos in p.positions %}
<tr>
<td class="label">{{ pos.ticker }}</td>
<td>{{ pos.name or "" }}</td>
<td class="num mobile-hide">{{ pos.quantity | price }}</td>
<td class="num neu mobile-hide">{{ pos.average_price | price }}</td>
<td class="num">{{ pos.current_price | price }}</td>
<td class="num {% if pos.ppl is none %}neu{% elif pos.ppl >= 0 %}pos{% else %}neg{% endif %}">
{{ pos.ppl | signed }}
</td>
<td class="num {% if pos.ppl_pct is none %}neu{% elif pos.ppl_pct >= 0 %}pos{% else %}neg{% endif %}">
{% if pos.ppl_pct is not none %}{{ "%+.2f"|format(pos.ppl_pct) }}%{% else %}—{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
{% endif %}