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>
This commit is contained in:
Giorgio Gilestro 2026-05-29 19:57:12 +02:00
parent ee8384f1ba
commit 47dce1a1a4
38 changed files with 1188 additions and 279 deletions

View file

@ -44,13 +44,9 @@
Architecturally, the product is deliberately privacy-shaped:
</p>
<ul>
<li>Your portfolio lives in your browser. The server&rsquo;s view is
an aggregate set of tickers held across the whole user base,
which on its own does not identify any individual user &mdash; see
the <a href="/privacy">Privacy notice</a> for the exact data
structures.</li>
<li>Cloud sync of your portfolio is opt-in and end-to-end encrypted
with a PIN only you know.</li>
<li>Your portfolio lives in your browser. CSVs you upload are parsed
and held locally; the server never sees or stores your
holdings.</li>
<li>No third-party tracking, no analytics SDKs, no ad cookies.</li>
</ul>
<p>

View file

@ -328,7 +328,9 @@
{% if cu.user %}
<a href="/settings" role="menuitem" class="user-menu__item">Settings</a>
{% endif %}
{% if SUBSCRIPTIONS_ENABLED %}
<a href="/pricing" role="menuitem" class="user-menu__item">Pricing</a>
{% endif %}
<a href="/terms" role="menuitem" class="user-menu__item">Terms</a>
<a href="/privacy" role="menuitem" class="user-menu__item">Privacy</a>
<a href="/disclaimer" role="menuitem" class="user-menu__item">Disclaimer</a>

View file

@ -97,7 +97,9 @@
<kbd>&times;</kbd> next to an existing row removes it.
</p>
</div>
<div id="pf-mount">
<div id="pf-mount"
data-ai-enabled="{{ 'true' if PORTFOLIO_AI_ENABLED else 'false' }}"
data-sync-enabled="{{ 'true' if PORTFOLIO_SYNC_ENABLED else 'false' }}">
<div class="empty">loading…</div>
</div>
</div>

View file

@ -37,18 +37,20 @@
<section class="public-section">
<h2 class="public-section__head">About the AI output</h2>
<p>
The strategic log, indicator summaries, and portfolio analysis are
generated by large language models from publicly available market
data and news. They can be wrong, incomplete, or out of date. Numbers
can be misread. Models occasionally generate inaccurate or invented
information (often called &ldquo;hallucinations&rdquo;). Treat them
as a <em>prompt to think</em>, not as facts to act on.
The strategic log and indicator summaries are generated by large
language models from publicly available market data and news. They
can be wrong, incomplete, or out of date. Numbers can be misread.
Models occasionally generate inaccurate or invented information
(often called &ldquo;hallucinations&rdquo;). Treat them as a
<em>prompt to think</em>, not as facts to act on.
</p>
<p>
The portfolio analysis is an interpretation of holdings <em>you
supplied</em>. It does not consider your overall wealth, debts, tax
position, or anything we don&rsquo;t see. It is not personalised
advice.
The portfolio feature is a browser-only composition viewer: it
parses a CSV you supply, computes neutral statistics (weights,
sector / currency / concentration breakdown), and shows them to
you. Your holdings stay in your browser; they are never sent to
or stored on the server. There is no AI commentary on your
portfolio.
</p>
</section>
@ -79,13 +81,22 @@
EU/EEA member state, nor in any jurisdiction where its provision
would require local licensing or registration. Where any output of
the Service could be construed as an &ldquo;investment
recommendation&rdquo; under Regulation (EU) 596/2014 (Market Abuse
Regulation) or its UK equivalent, it is non-personalised, produced
by a non-regulated source for educational purposes only, and the
operator (a) has no position in, or remuneration linked to, the
specific instruments mentioned in any individual piece of commentary,
and (b) is not a &ldquo;relevant person&rdquo; within MAR Art.
3(1)(34).
recommendation&rdquo; within the meaning of
<strong>Article&nbsp;3(1)(35) of Regulation (EU) 596/2014 (Market
Abuse Regulation)</strong>, with conduct duties under
<strong>Article&nbsp;20 MAR</strong> and
<strong>Commission Delegated Regulation (EU) 2016/958</strong>
(or the UK onshored equivalent), the operator&rsquo;s position is
that the Service publishes <em>non-personalised commentary on
public market data by a non-regulated source for educational
purposes</em>, and does not directly propose a particular
investment decision in any specific instrument. The operator has
no position in, and no remuneration linked to, the specific
instruments mentioned in any individual piece of commentary.
</p>
<p style="font-size:12px; color: var(--muted);">
{# TODO(legal): final wording on the MAR paragraph above is pending
lawyer sign-off. See docs/read-markets-compliance-changes.md. #}
</p>
</section>

View file

@ -10,10 +10,10 @@
<div class="hero__ctas">
{% if cu and (cu.user or cu.is_admin) %}
<a class="btn-primary" href="/">{{ t.hero.cta_dashboard }}</a>
<a class="btn-secondary" href="/pricing">{{ t.hero.cta_pricing }}</a>
{% if SUBSCRIPTIONS_ENABLED %}<a class="btn-secondary" href="/pricing">{{ t.hero.cta_pricing }}</a>{% endif %}
{% else %}
<a class="btn-primary" href="/login">{{ t.hero.cta_signup }}</a>
<a class="btn-secondary" href="/pricing">{{ t.hero.cta_pricing }}</a>
{% if SUBSCRIPTIONS_ENABLED %}<a class="btn-secondary" href="/pricing">{{ t.hero.cta_pricing }}</a>{% endif %}
{% endif %}
</div>
</section>

View file

@ -33,7 +33,7 @@
</div>
{% endfor %}
{% endif %}
{% if capped %}
{% if capped and SUBSCRIPTIONS_ENABLED %}
<div class="news-capped-note" style="margin-top:14px; padding:10px 12px; border:1px dashed var(--border); color:var(--muted); font-size:12px; line-height:1.55;">
Free tier — showing the last {{ window_hours|int }} hours of news.
<a href="/pricing" style="color:var(--accent);">Upgrade</a>

View file

@ -1,3 +1,6 @@
{# 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 %}

View file

@ -10,9 +10,9 @@
6-hour news feed, the cross-asset indicator panels, and a strategic
log refreshed every six hours. Paid stretches the news feed to a
full 24 hours, runs the strategic log hourly, unlocks the follow-up
chat against past logs, adds portfolio import with AI analysis, and
turns on the daily email digest on top of the Sunday recap everyone
gets.
chat against past logs, adds a browser-only portfolio composition
viewer, and turns on the daily email digest on top of the Sunday
recap everyone gets.
</p>
</section>
@ -33,7 +33,7 @@
<li><strong>Sunday weekly digest</strong> by email &mdash; week behind + week ahead, one-click unsubscribe</li>
</ul>
<div class="tier-card__more">
Need the full-day news feed, hourly strategic log, follow-up chat, daily digests, or portfolio analysis? See <strong>Paid</strong> &rarr;
Need the full-day news feed, hourly strategic log, follow-up chat, daily digests, or the portfolio composition viewer? See <strong>Paid</strong> &rarr;
</div>
<div class="tier-card__cta">
{% if cu and (cu.user or cu.is_admin) %}
@ -47,7 +47,7 @@
<div class="tier-card tier-card--featured">
<div class="tier-card__badge">Best value</div>
<h2 class="tier-card__name">Paid</h2>
<div class="tier-card__tagline">Full-day news feed, hourly strategic log, follow-up chat, and AI portfolio analysis.</div>
<div class="tier-card__tagline">Full-day news feed, hourly strategic log, follow-up chat, and the browser-only portfolio composition viewer.</div>
<div class="tier-card__price">&pound;7<span class="tier-card__price-unit"> / month</span></div>
<div class="tier-card__price-hint">
Or <strong>&pound;70 / year</strong> &mdash; two months free, and
@ -62,16 +62,8 @@
<li><strong>Strategic log refreshed every hour</strong> instead of every six &mdash; track intraday moves as they unfold</li>
<li><strong>Follow-up chat on any past log</strong> &mdash; ask the model a question against the day&rsquo;s full context</li>
<li><strong>Daily email digest</strong> (Mon&ndash;Sat) &mdash; ~600-word read of the session ahead, on top of the Sunday recap</li>
<li><strong>Portfolio import</strong> from any broker CSV &mdash; Trading 212 natively, other formats auto-detected</li>
<li><strong>AI portfolio read</strong> &mdash; diversification, sector and currency concentration, macro-regime fit on your holdings</li>
<li><strong>Optional encrypted cloud sync</strong> &mdash; PIN-derived encryption in your browser, second-layer wrap on the server, no plaintext holdings server-side</li>
<li><strong>Browser-only portfolio composition viewer</strong> &mdash; drop a broker CSV and see your sector, currency, and concentration breakdown, computed entirely in your browser</li>
</ul>
<p class="tier-card__more" style="font-style: italic;">
The portfolio feature does not produce buy, sell or hold
recommendations and does not consider your wider finances, debts,
tax position or objectives. It is not regulated investment advice
or a personal recommendation under FSMA / FCA COBS.
</p>
<div class="tier-card__cta">
{% if paid %}
<a class="btn-secondary btn-block" href="/settings">Manage subscription</a>
@ -190,17 +182,7 @@
<td class="compare-table__paid"><strong>Sunday + daily Mon&ndash;Sat</strong></td>
</tr>
<tr>
<th scope="row">Portfolio import (broker CSV)</th>
<td class="compare-table__none">&mdash;</td>
<td class="compare-table__paid"><strong>Included</strong></td>
</tr>
<tr>
<th scope="row">AI portfolio read</th>
<td class="compare-table__none">&mdash;</td>
<td class="compare-table__paid"><strong>Included</strong></td>
</tr>
<tr>
<th scope="row">Encrypted cloud sync</th>
<th scope="row">Browser-only portfolio composition viewer</th>
<td class="compare-table__none">&mdash;</td>
<td class="compare-table__paid"><strong>Included</strong></td>
</tr>
@ -265,18 +247,10 @@
<section class="public-section">
<h2 class="public-section__head">How the data is handled</h2>
<p>
Your portfolio holdings live in your browser&rsquo;s local storage by
default. The server only learns which Yahoo tickers appear across the
user base &mdash; an anonymous union, with no link back to any specific
user.
</p>
<p>
If you opt in to <strong>encrypted cloud sync</strong>, your pie is
encrypted in your browser with a PIN you choose, then sent to the
server. We add a second layer of encryption with a key only the
server holds. We never see your holdings as plaintext, and forgetting
the PIN means we can&rsquo;t recover it for you. Full details on the
<a href="/privacy">privacy page</a>.
Your portfolio holdings live in your browser&rsquo;s local storage.
The CSV is parsed in your browser, the resulting pie is kept there,
and nothing about your holdings is sent to or stored on the server.
Full details on the <a href="/privacy">privacy page</a>.
</p>
</section>

View file

@ -37,24 +37,9 @@
It contains your user id only and is signed so we can detect
tampering. Cookie is marked Secure and HttpOnly.
</li>
<li>
<strong>Anonymous ticker universe</strong>: when you upload a
portfolio CSV we record which Yahoo tickers appear, with
<em>no link</em> to your account. The same row would exist whether
any specific user holds the ticker or not &mdash; once a ticker is in
the universe, the row carries no signal as to whose import added it.
</li>
<li>
<strong>If you opt in to encrypted cloud sync</strong>: an opaque
blob of bytes per user. The blob is your portfolio, encrypted in
your browser with a PIN you choose, then wrapped a second time on
the server with a key only the server holds. We can&rsquo;t decrypt
the blob to plaintext without your PIN, and we can&rsquo;t recover
your PIN if you forget it. By enabling cloud sync you give your
consent (UK-GDPR Art. 6(1)(a)) to this processing; you can
withdraw consent at any time by disabling sync in Settings, which
also removes the server-side blob.
</li>
{# Cloud sync + server-side per-ticker aggregate union are flag-gated off.
See docs/read-markets-compliance-changes.md and app/config.py
(PORTFOLIO_SYNC_ENABLED, TICKER_UNIVERSE_AGGREGATE_ENABLED). #}
<li>
<strong>Anonymised cost ledger</strong> of AI calls (model, tokens,
cost). No portfolio or personal data is attached to ledger rows.
@ -76,10 +61,12 @@
<h2 class="public-section__head">What we don&rsquo;t collect</h2>
<ul>
<li>
<strong>Your portfolio holdings as plaintext on the server.</strong>
Parsed pies are returned to your browser and kept in
<code>localStorage</code>. The server&rsquo;s view is the anonymous
ticker universe described above.
<strong>Your portfolio holdings, in any form, on the server.</strong>
The portfolio feature is a browser-only composition viewer:
uploaded CSVs are parsed and returned to your browser, kept in
<code>localStorage</code>, and never sent back to or stored on
the server. The server records no per-ticker aggregate of what
anyone holds.
</li>
<li>
<strong>Third-party analytics or ad cookies.</strong> No Google
@ -104,24 +91,14 @@
<ul>
<li>
<strong>Performance of a contract</strong> (Art. 6(1)(b)) &mdash; for
operating your account, the sign-in flow, paid features, and the
mechanics of encrypted cloud sync.
operating your account, the sign-in flow, and any paid features.
</li>
<li>
<strong>Legitimate interests</strong> (Art. 6(1)(f)) &mdash; for the
anonymous ticker universe, the anonymised cost ledger, job-run
telemetry, and reverse-proxy access logs. Our interest is the
secure, abuse-resistant, cost-controlled operation of a free
public service, balanced against the minimal and de-identified
nature of the data.
</li>
<li>
<strong>Consent</strong> (Art. 6(1)(a)) &mdash; where you opt in to
encrypted cloud sync (and the related caching of a derived
encryption key in your browser&rsquo;s <code>sessionStorage</code>).
You can withdraw consent at any time by disabling sync in
Settings; the cached key is cleared and the server-side blob is
removed.
anonymised cost ledger, job-run telemetry, and reverse-proxy access
logs. Our interest is the secure, abuse-resistant, cost-controlled
operation of a free public service, balanced against the minimal
and de-identified nature of the data.
</li>
</ul>
</section>
@ -131,9 +108,10 @@
<p>
The Service does not make decisions about you that produce legal or
similarly significant effects in an automated way (UK-GDPR Art. 22).
The AI portfolio analysis is editorial commentary on the holdings
you upload; it does not approve, reject or rank you, and you remain
the sole decision-maker about anything in your account.
The strategic log and indicator summaries are general editorial
commentary on public market data, not personalised assessments of
you, and you remain the sole decision-maker about anything in your
account.
</p>
</section>
@ -151,13 +129,9 @@
browser.
</li>
<li>
<strong>Local portfolio + cached sync key</strong> &mdash; parsed pies
live in <code>localStorage</code> on your device. If you enable
cloud sync, the derived encryption key is cached in
<code>sessionStorage</code> so you don&rsquo;t have to re-enter
your PIN on every navigation. This caching is performed only with
your consent (given when you enable sync); it is cleared when you
close the tab or disable sync.
<strong>Local portfolio</strong> &mdash; parsed pies live in
<code>localStorage</code> on your device. They are not sent to
or stored on the server.
</li>
</ul>
</section>
@ -177,14 +151,15 @@
currently inside the UK; if that changes we will update this notice.
</li>
<li>
<strong>AI provider calls</strong> for the strategic log, indicator
summaries, and (paid) portfolio analysis. Where the provider sits
outside the UK, we rely on the UK International Data Transfer
Agreement (IDTA) / the UK Addendum to the EU Standard Contractual
Clauses where no adequacy decision applies. Each outbound request
carries an explicit no-training opt-out header
<strong>AI provider calls</strong> for the strategic log and
indicator summaries. Where the provider sits outside the UK, we
rely on the UK International Data Transfer Agreement (IDTA) / the
UK Addendum to the EU Standard Contractual Clauses where no
adequacy decision applies. Each outbound request carries an
explicit no-training opt-out header
(<code>X-OR-Allow-Training: false</code> on OpenRouter); see the
Third parties section below for the caveats.
Third parties section below for the caveats. None of these
outbound requests contain user holdings or other portfolio data.
</li>
</ul>
</section>
@ -200,15 +175,6 @@
<strong>Session cookies</strong>: expire automatically; you can
sign out at any time to revoke.
</li>
<li>
<strong>Ticker universe</strong>: rows untouched for 60 days are
evicted by a nightly job. Active tickers remain.
</li>
<li>
<strong>Encrypted portfolio blob</strong>: kept until you disable
cloud sync (one click in Settings) or delete your account. We hold
one row per user; new uploads overwrite the previous blob.
</li>
<li>
<strong>Account</strong>: held until you ask us to delete it.
Email <a href="mailto:{{ OPERATOR_EMAIL }}">{{ OPERATOR_EMAIL }}</a>.
@ -230,22 +196,18 @@
</li>
<li>
<strong>AI provider(s)</strong>: DeepSeek (primary) with OpenRouter
as a fallback. They see the prompt for the strategic log, the
indicator summaries, and the portfolio analysis call &mdash; which
contains your holdings only when you press
&ldquo;Generate AI analysis&rdquo; on a paid plan, and only for the
duration of that single call. The portfolio analysis output is not
persisted on the server.
as a fallback. They see the prompt for the strategic log and the
indicator summaries. These prompts contain public market data and
headlines &mdash; never any user holdings or portfolio data.
<br>
<strong>No-training opt-out.</strong> Every OpenRouter request
carries the <code>X-OR-Allow-Training: false</code> header, which
signals to OpenRouter and any compatible upstream that the prompt
must not be used to train or improve models. DeepSeek does not
currently expose a per-request opt-out; if you do not want your
holdings to leave our server at all, do not use the AI portfolio
analysis feature. We do not control retention or training policies
on the provider side beyond the headers we set &mdash; the provider&rsquo;s
own published data policy is the binding statement on that point.
currently expose a per-request opt-out. We do not control
retention or training policies on the provider side beyond the
headers we set &mdash; the provider&rsquo;s own published data policy is
the binding statement on that point.
</li>
<li>
<strong>Market-data sources</strong>: Yahoo Finance and a small set
@ -263,7 +225,7 @@
<li>Have inaccurate data corrected (Art. 16, rectification).</li>
<li>Have your account and associated data deleted (Art. 17, erasure).</li>
<li>Export the data you can recognise (Art. 20, portability): your
email, any active encrypted blob, your referral linkage.</li>
email and your referral linkage.</li>
<li>Restrict processing (Art. 18).</li>
<li>Object specifically to processing carried out on the basis of
legitimate interests (Art. 21), including any direct marketing.</li>

View file

@ -36,7 +36,9 @@
{{ BRAND_NAME }}
</a>
<nav class="public-header__nav">
{% if SUBSCRIPTIONS_ENABLED %}
<a href="/pricing" class="{% if request.url.path == '/pricing' %}active{% endif %}">Pricing</a>
{% endif %}
<a href="/about" class="{% if request.url.path == '/about' %}active{% endif %}">About</a>
{# Lang switch — currently only the landing page opts in
(lang_switch=true). When other public pages get translated
@ -66,7 +68,7 @@
<span class="public-footer__tagline">{{ TAGLINE }}</span>
</div>
<nav class="public-footer__links">
<a href="/pricing">Pricing</a>
{% if SUBSCRIPTIONS_ENABLED %}<a href="/pricing">Pricing</a>{% endif %}
<a href="/about">About</a>
<a href="/terms">Terms</a>
<a href="/privacy">Privacy</a>

View file

@ -18,6 +18,7 @@
<div class="settings-row__value">{{ user.email }}</div>
</div>
{% if SUBSCRIPTIONS_ENABLED %}
<div class="settings-row">
<div class="settings-row__label">Tier</div>
<div class="settings-row__value" style="display:flex; align-items:flex-start; gap:10px; flex:1;">
@ -59,8 +60,9 @@
{% endif %}
</div>
</div>
{% endif %}
{% if paid and paid.active and paid.source != "credit" and user.stripe_customer_id %}
{% if SUBSCRIPTIONS_ENABLED and paid and paid.active and paid.source != "credit" and user.stripe_customer_id %}
<script>
(function () {
var btn = document.getElementById('stripe-portal-btn');
@ -104,7 +106,9 @@
<span class="neu">Investing &rarr; Your Pie &rarr; &middot;&middot;&middot; &rarr; Export</span>.</span>
</p>
<div id="drop-zone" class="dz" data-paid="{{ 'true' if paid and paid.active else 'false' }}">
<div id="drop-zone" class="dz"
data-paid="{{ 'true' if paid and paid.active else 'false' }}"
data-sync-enabled="{{ 'true' if PORTFOLIO_SYNC_ENABLED else 'false' }}">
<input type="file" id="file-input" name="file" accept=".csv,text/csv" hidden>
<div class="dz__icon"></div>
<div class="dz__label">Drop your broker's portfolio CSV here</div>
@ -264,6 +268,9 @@
</details>
{# --- Cloud sync block --------------------------------------------- #}
{# Gated by PORTFOLIO_SYNC_ENABLED — see app/config.py. Holdings stay
in the browser when the flag is off; this whole section is hidden. #}
{% if PORTFOLIO_SYNC_ENABLED %}
<details class="settings-section">
<summary class="settings-section__head">Cloud sync (encrypted)</summary>
<p class="settings-section__lede">
@ -287,6 +294,7 @@
</p>
{% endif %}
</details>
{% endif %}
{# Future: Paddle subscription block, AI-spend ledger summary, etc. #}
@ -295,7 +303,7 @@
</div>
</section>
{% if user and paid and paid.active %}
{% if PORTFOLIO_SYNC_ENABLED and user and paid and paid.active %}
<div id="sync-modal" class="modal"
style="position:fixed;inset:0;background:rgba(0,0,0,0.45);
display:none;align-items:center;justify-content:center;z-index:1000;">

View file

@ -25,12 +25,21 @@
<h2 class="public-section__head">2. The Service</h2>
<p>
{{ BRAND_NAME }} provides a macro-strategy dashboard with curated
market data, news, and AI-generated commentary. Paid features include
portfolio import, AI portfolio analysis, and optional end-to-end
encrypted cloud sync of your portfolio. Feature lists, tiers, and
pricing are described on the <a href="/pricing">Pricing page</a> and
may change over time.
market data, news, and AI-generated commentary on public market
data (strategic log, indicator summaries, and a follow-up chat
grounded on those reads). It also includes a browser-only portfolio
composition viewer: CSVs you upload are parsed in your browser and
used to compute neutral statistics (weights, sector / currency /
concentration breakdown). Your holdings stay in your browser; they
are not sent to or stored on the server, and the Service does not
produce AI commentary on them.
</p>
{% if SUBSCRIPTIONS_ENABLED %}
<p>
Feature tiers and pricing are described on the
<a href="/pricing">Pricing page</a> and may change over time.
</p>
{% endif %}
<p>
Nothing produced by the Service is investment advice. See the
<a href="/disclaimer">Disclaimer</a> for the full position.
@ -76,6 +85,7 @@
<section class="public-section">
<h2 class="public-section__head">5. Paid plans</h2>
{% if SUBSCRIPTIONS_ENABLED %}
<p>
Paid plans are available at &pound;7/month or &pound;70/year (terms
and current prices on the <a href="/pricing">pricing page</a>). New
@ -88,6 +98,15 @@
stated. Detailed refund and cancellation rights are set out in
section 6 below.
</p>
{% else %}
<p>
Paid plans are not currently available; the Service is offered to
signed-in users at no cost while the subscription system is paused.
Sections 5 and 6 (paid plans and refunds) are retained for reference
and will apply again if subscriptions resume; their terms are not in
force at the moment.
</p>
{% endif %}
</section>
<section class="public-section">
@ -181,9 +200,11 @@
permission.
</p>
<p>
Any portfolio you upload remains your data. The Service does not
persist your holdings as plaintext (see the
<a href="/privacy">Privacy notice</a>).
Any portfolio CSV you upload remains your data. The portfolio
feature is browser-only: CSVs are parsed in your browser, the
resulting pie is kept in your browser&rsquo;s local storage, and
nothing about your holdings is sent to or stored on the server
(see the <a href="/privacy">Privacy notice</a>).
</p>
</section>