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>
42 lines
1.8 KiB
HTML
42 lines
1.8 KiB
HTML
{% if tag_vocabulary %}
|
|
<div class="news-tags" data-include="{{ active_include|join(',') }}" data-exclude="{{ active_exclude|join(',') }}">
|
|
{% for tag in tag_vocabulary %}
|
|
<button type="button" class="news-tag"
|
|
data-tag="{{ tag }}"
|
|
{% if tag in active_include %}data-state="include"{% elif tag in active_exclude %}data-state="exclude"{% endif %}
|
|
title="{{ tag_labels.get(tag, tag) }} — click to include only, shift-click to exclude">
|
|
{{ tag_labels.get(tag, tag) }}
|
|
</button>
|
|
{% endfor %}
|
|
{% if active_include or active_exclude %}
|
|
<button type="button" class="news-tag news-tag--clear" data-tag="" title="Clear all filters">clear</button>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if not headlines %}
|
|
<div class="empty">no headlines in window{% if active_include or active_exclude %} (after tag filter){% endif %}</div>
|
|
{% else %}
|
|
{% for h in headlines %}
|
|
<div class="news-row">
|
|
<span class="age">{{ h.age }}</span>
|
|
<span class="source">{{ h.source }}</span>
|
|
<a class="title" href="{{ h.url }}" target="_blank" rel="noopener">{{ h.title }}</a>
|
|
<span class="news-row__tags">
|
|
{% for t in h.tags or [] %}<span class="tag-chip" data-tag="{{ t }}">{{ tag_labels.get(t, t) }}</span>{% endfor %}
|
|
</span>
|
|
{% if h.iso %}
|
|
<time class="local" datetime="{{ h.iso }}" title="{{ h.iso }}">{{ h.utc_short }}</time>
|
|
{% else %}
|
|
<span class="local">—</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% 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>
|
|
for the full 24-hour feed plus daily and weekly email digests.
|
|
</div>
|
|
{% endif %}
|