read.markets/app/routers/public.py
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

76 lines
2.3 KiB
Python

"""Unauthenticated marketing + legal pages.
This router carries no auth dependency — every route is reachable to
anonymous visitors and is also reachable to logged-in users (the
templates branch off `cu` to flip the top-right CTA between
"Sign in / sign up" and "Dashboard").
The dual-purpose root (`/`) lives in `app/routers/pages.py` because it
also has to render the dashboard when authenticated. Pure public-only
pages live here.
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from app.auth import CurrentUser, maybe_current_user
from app.services.access import is_paid_active
from app.services.feature_flags import require_flag
from app.templates_env import templates
router = APIRouter(tags=["public"])
def _ctx(request: Request, cu: CurrentUser | None) -> dict:
"""Minimal context every public template expects. `cu` is injected
into the template so the header CTA can flip between
'Sign in / sign up' and 'Dashboard'."""
return {"cu": cu}
@router.get(
"/pricing",
response_class=HTMLResponse,
dependencies=[Depends(require_flag("SUBSCRIPTIONS_ENABLED"))],
)
async def pricing_page(
request: Request,
cu: CurrentUser | None = Depends(maybe_current_user),
):
ctx = _ctx(request, cu)
ctx["paid"] = is_paid_active(cu)
return templates.TemplateResponse(request, "pricing.html", ctx)
@router.get("/about", response_class=HTMLResponse)
async def about_page(
request: Request,
cu: CurrentUser | None = Depends(maybe_current_user),
):
return templates.TemplateResponse(request, "about.html", _ctx(request, cu))
@router.get("/terms", response_class=HTMLResponse)
async def terms_page(
request: Request,
cu: CurrentUser | None = Depends(maybe_current_user),
):
return templates.TemplateResponse(request, "terms.html", _ctx(request, cu))
@router.get("/privacy", response_class=HTMLResponse)
async def privacy_page(
request: Request,
cu: CurrentUser | None = Depends(maybe_current_user),
):
return templates.TemplateResponse(request, "privacy.html", _ctx(request, cu))
@router.get("/disclaimer", response_class=HTMLResponse)
async def disclaimer_page(
request: Request,
cu: CurrentUser | None = Depends(maybe_current_user),
):
return templates.TemplateResponse(request, "disclaimer.html", _ctx(request, cu))