ui: aggregated read on top, hide stale rows, wire /log tone toggle; prompts v8
- dashboard grid: explicit "header" area as the first row so the
aggregated read panel renders at the top instead of being
auto-placed after the named areas.
- indicators: hide rows flagged stale (older than the group's
freshness threshold). Server still computes stale_symbols;
rendering can be re-enabled by removing the
`{% if not is_stale %}` wrapper in indicators.html.
- /log: add tone-changed to #log-content's hx-trigger and include
it in cassandraSetTone's selector list — toggling Novice /
Intermediate on the Log page was previously a no-op.
- prompts: bump PROMPT_VERSION 7→8. Strengthen the rational-vs-
irrational framing in the strategic-log system prompt from
aspirational to mandatory ("a paragraph without both lenses must
be rewritten"). Require the same lens in the per-group summary,
cross-asset aggregate, and portfolio commentary overrides.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
f326b41a08
commit
b98d8d003c
6 changed files with 60 additions and 16 deletions
|
|
@ -30,7 +30,7 @@ OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
|
|||
# v7 (2026-05-18): Forbid "(Updated HH:MM UTC)" clauses in the date header —
|
||||
# the model was hallucinating future times. The user prompt now carries the
|
||||
# actual current UTC time so the model has accurate temporal context.
|
||||
PROMPT_VERSION = 7
|
||||
PROMPT_VERSION = 8
|
||||
|
||||
|
||||
# --- Core: invariant across tone/analysis settings ----------------------------
|
||||
|
|
@ -85,16 +85,25 @@ omit the paragraph.
|
|||
"things to watch tomorrow". Each watch item should name a level/threshold \
|
||||
whose breach would change the regime, not a calendar-date event.
|
||||
|
||||
# Rational vs irrational framing
|
||||
# Rational vs irrational framing (MANDATORY in every paragraph)
|
||||
The reader's primary goal is to disconnect rational decisions from market \
|
||||
irrationality. In every sector or theme paragraph, separately identify:
|
||||
- The RATIONAL drivers: earnings, real-economy data, monetary policy, \
|
||||
structural geopolitical shifts, valuation vs fundamentals.
|
||||
- The IRRATIONAL drivers: positioning, narrative momentum, sentiment \
|
||||
extremes, concentration, flow-driven moves, options gamma, credit complacency.
|
||||
When the two diverge — price moving on irrational drivers while fundamentals \
|
||||
say otherwise, or vice versa — flag the divergence explicitly. Those gaps \
|
||||
are where the next regime change starts.
|
||||
irrationality. This is the single most important lens of the log — it MUST \
|
||||
appear in every sector or theme paragraph, not just where it feels natural. \
|
||||
For each paragraph, before writing it, ask yourself the two questions and \
|
||||
then make both answers visible in the prose:
|
||||
- The RATIONAL drivers — what the underlying factors justify: earnings, \
|
||||
real-economy data, monetary policy, structural geopolitical shifts, \
|
||||
valuation vs fundamentals.
|
||||
- The IRRATIONAL drivers — what the crowd is doing regardless of fundamentals: \
|
||||
positioning, narrative momentum, sentiment extremes, concentration, \
|
||||
flow-driven moves, options gamma, credit complacency.
|
||||
Then state the GAP: is price moving with the rational read, ahead of it, \
|
||||
or against it? If they agree, say so briefly and move on. If they diverge \
|
||||
— price moving on irrational drivers while fundamentals say otherwise, or \
|
||||
vice versa — name the divergence explicitly. Those gaps are where the next \
|
||||
regime change starts and are the whole point of this log.
|
||||
A paragraph that names only price action or only fundamentals, without \
|
||||
both lenses, is incomplete and must be rewritten.
|
||||
|
||||
# Discipline
|
||||
- No emojis, no marketing language, no "concerning" or "unprecedented" \
|
||||
|
|
@ -302,6 +311,13 @@ They can see the values. They CANNOT see the meaning. Your job is to \
|
|||
a regime-level interpretation, a fundamental driver identification, or a \
|
||||
cross-indicator implication — not a description of moves.
|
||||
|
||||
# Rational vs irrational lens (required at this length too)
|
||||
Even at 2-3 sentences, contrast what the underlying factors justify \
|
||||
(rational: fundamentals, policy, valuation) with what the crowd is doing \
|
||||
(irrational: positioning, narrative, flows) whenever the two diverge. If \
|
||||
they don't diverge, say so in one clause. Never just describe the move \
|
||||
without placing it on this axis.
|
||||
|
||||
# Hard constraints
|
||||
- Plain prose, ONE paragraph. No markdown, no headers, no lists, no labels.
|
||||
- Open IMMEDIATELY with substance. NEVER start with: "I need to", "I'll", \
|
||||
|
|
@ -350,6 +366,13 @@ Your job is NOT to summarise the moves. It is to explain what the moves, \
|
|||
which divergences are load-bearing, what fundamental story the cross-asset \
|
||||
behaviour tells.
|
||||
|
||||
# Rational vs irrational lens (required at this length too)
|
||||
The cross-asset tape's value is in the gap between what the underlying \
|
||||
factors justify (rational: fundamentals, policy, valuation) and what the \
|
||||
crowd is actually doing (irrational: positioning, narrative momentum, \
|
||||
flows). At least one of the 2-4 sentences must name this gap or, if the \
|
||||
two cohere, explicitly say so.
|
||||
|
||||
# Hard constraints
|
||||
- Plain prose, ONE paragraph. No markdown, headers, lists, or labels.
|
||||
- Open IMMEDIATELY with substance. NEVER start with: "I need to", "I'll", \
|
||||
|
|
|
|||
|
|
@ -250,6 +250,19 @@ implies X under scenario Y"), not advice ("buy X" / "sell Y" are forbidden).
|
|||
what would invalidate the current posture.
|
||||
- ~350 words. No bullet lists. No buy/sell recommendations.
|
||||
- Do not repeat the input data verbatim — interpret it.
|
||||
|
||||
# Rational vs irrational lens (mandatory)
|
||||
Carry the base prompt's rational-vs-irrational framing through to every
|
||||
paragraph of the portfolio read. For each section above, contrast:
|
||||
- The RATIONAL read: what the underlying factors (fundamentals,
|
||||
macro/policy regime, valuation, currency dynamics) justify for this
|
||||
exposure;
|
||||
- The IRRATIONAL read: what positioning, narrative momentum, sentiment
|
||||
or flows are doing to that same exposure right now.
|
||||
Then name the GAP — does the holder's posture line up with the rational
|
||||
read, or is it riding the irrational one? A paragraph that names only
|
||||
the pie's numbers or only the macro backdrop, without placing the
|
||||
holding on this rational-vs-irrational axis, is incomplete.
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -140,8 +140,9 @@ a:hover { text-decoration: underline; }
|
|||
padding: 14px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
|
||||
grid-template-rows: auto auto auto;
|
||||
grid-template-rows: auto auto auto auto;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"indicators log"
|
||||
"portfolio log"
|
||||
"news news";
|
||||
|
|
@ -150,10 +151,11 @@ a:hover { text-decoration: underline; }
|
|||
@media (max-width: 1100px) {
|
||||
.app-main {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas: "indicators" "portfolio" "log" "news";
|
||||
grid-template-areas: "header" "indicators" "portfolio" "log" "news";
|
||||
}
|
||||
}
|
||||
|
||||
#dash-header-container { grid-area: header; }
|
||||
#indicators-panel { grid-area: indicators; }
|
||||
#portfolio-panel { grid-area: portfolio; }
|
||||
#log-panel {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
// listen to. Simpler still: fire htmx.trigger on the well-known
|
||||
// panels. We use the simple path.
|
||||
['#dash-header-container', '#log-panel .panel-body',
|
||||
'#indicators-body'].forEach(function (sel) {
|
||||
'#indicators-body', '#log-content'].forEach(function (sel) {
|
||||
var el = document.querySelector(sel);
|
||||
if (el && window.htmx) window.htmx.trigger(el, 'tone-changed');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
<article id="log-content"
|
||||
class="log-page__content"
|
||||
hx-get="/api/log/by-date/{{ selected_iso }}?as=html"
|
||||
hx-trigger="load"
|
||||
hx-trigger="load, tone-changed"
|
||||
hx-swap="innerHTML">
|
||||
<div class="empty">loading log…</div>
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@
|
|||
<tbody>
|
||||
{% for q in quotes %}
|
||||
{% set is_stale = stale_symbols and q.symbol in stale_symbols %}
|
||||
<tr class="{% if is_stale %}row-stale{% endif %}">
|
||||
{# Stale rows (last observation older than the group's freshness
|
||||
threshold) are hidden from the default UI but the server still
|
||||
computes them — remove the `{% if not is_stale %}` guard to
|
||||
resurface the dimmed row + stale tag. #}
|
||||
{% if not is_stale %}
|
||||
<tr>
|
||||
{% set tip = notes.get(q.symbol, '') if notes else '' %}
|
||||
{# Long Eurostat ('dataset?...') and ONS ('topic/.../cdid/dataset') symbols
|
||||
get truncated for display; hover shows the full identifier via title.
|
||||
|
|
@ -39,7 +44,7 @@
|
|||
{% if '?' in short_sym %}{% set short_sym = short_sym.split('?')[0] %}{% endif %}
|
||||
{% if '/' in short_sym %}{% set short_sym = short_sym.split('/')[-2] | upper %}{% endif %}
|
||||
<td class="label has-tip" title="{{ q.symbol }}{% if tip %} — {{ tip }}{% endif %}">
|
||||
{{ short_sym }}{% if is_stale %} <span class="stale-tag" title="last observation older than 90 days">stale</span>{% endif %}
|
||||
{{ short_sym }}
|
||||
</td>
|
||||
<td {% if tip %}title="{{ tip }}"{% endif %}>{{ q.label or "" }}</td>
|
||||
<td class="num">{{ q.price | price }}</td>
|
||||
|
|
@ -58,6 +63,7 @@
|
|||
{% endif %}
|
||||
<td class="neu">{{ q.as_of or "" }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue