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>
76 lines
2.3 KiB
Python
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))
|