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:
parent
ee8384f1ba
commit
47dce1a1a4
38 changed files with 1188 additions and 279 deletions
|
|
@ -44,13 +44,9 @@
|
|||
Architecturally, the product is deliberately privacy-shaped:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Your portfolio lives in your browser. The server’s view is
|
||||
an aggregate set of tickers held across the whole user base,
|
||||
which on its own does not identify any individual user — 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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -97,7 +97,9 @@
|
|||
<kbd>×</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>
|
||||
|
|
|
|||
|
|
@ -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 “hallucinations”). 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 “hallucinations”). 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’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 “investment
|
||||
recommendation” 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 “relevant person” within MAR Art.
|
||||
3(1)(34).
|
||||
recommendation” within the meaning of
|
||||
<strong>Article 3(1)(35) of Regulation (EU) 596/2014 (Market
|
||||
Abuse Regulation)</strong>, with conduct duties under
|
||||
<strong>Article 20 MAR</strong> and
|
||||
<strong>Commission Delegated Regulation (EU) 2016/958</strong>
|
||||
(or the UK onshored equivalent), the operator’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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 — 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> →
|
||||
Need the full-day news feed, hourly strategic log, follow-up chat, daily digests, or the portfolio composition viewer? See <strong>Paid</strong> →
|
||||
</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">£7<span class="tier-card__price-unit"> / month</span></div>
|
||||
<div class="tier-card__price-hint">
|
||||
Or <strong>£70 / year</strong> — two months free, and
|
||||
|
|
@ -62,16 +62,8 @@
|
|||
<li><strong>Strategic log refreshed every hour</strong> instead of every six — track intraday moves as they unfold</li>
|
||||
<li><strong>Follow-up chat on any past log</strong> — ask the model a question against the day’s full context</li>
|
||||
<li><strong>Daily email digest</strong> (Mon–Sat) — ~600-word read of the session ahead, on top of the Sunday recap</li>
|
||||
<li><strong>Portfolio import</strong> from any broker CSV — Trading 212 natively, other formats auto-detected</li>
|
||||
<li><strong>AI portfolio read</strong> — diversification, sector and currency concentration, macro-regime fit on your holdings</li>
|
||||
<li><strong>Optional encrypted cloud sync</strong> — 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> — 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–Sat</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Portfolio import (broker CSV)</th>
|
||||
<td class="compare-table__none">—</td>
|
||||
<td class="compare-table__paid"><strong>Included</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">AI portfolio read</th>
|
||||
<td class="compare-table__none">—</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">—</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’s local storage by
|
||||
default. The server only learns which Yahoo tickers appear across the
|
||||
user base — 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’t recover it for you. Full details on the
|
||||
<a href="/privacy">privacy page</a>.
|
||||
Your portfolio holdings live in your browser’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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 — 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’t decrypt
|
||||
the blob to plaintext without your PIN, and we can’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’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’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)) — 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)) — 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)) — where you opt in to
|
||||
encrypted cloud sync (and the related caching of a derived
|
||||
encryption key in your browser’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> — 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’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> — 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 — which
|
||||
contains your holdings only when you press
|
||||
“Generate AI analysis” 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 — 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 — the provider’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 — the provider’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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 → Your Pie → ··· → 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;">
|
||||
|
|
|
|||
|
|
@ -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 £7/month or £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’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>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue