diff --git a/app/services/openrouter.py b/app/services/openrouter.py
index b3bc2c7..f1402f4 100644
--- a/app/services/openrouter.py
+++ b/app/services/openrouter.py
@@ -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", \
diff --git a/app/services/portfolio_analysis.py b/app/services/portfolio_analysis.py
index 9d2ec7f..eb8a349 100644
--- a/app/services/portfolio_analysis.py
+++ b/app/services/portfolio_analysis.py
@@ -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.
"""
diff --git a/app/static/css/cassandra.css b/app/static/css/cassandra.css
index e1e04fc..9bb7ffc 100644
--- a/app/static/css/cassandra.css
+++ b/app/static/css/cassandra.css
@@ -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 {
diff --git a/app/templates/base.html b/app/templates/base.html
index 29a2425..9f08c7e 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -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');
});
diff --git a/app/templates/log.html b/app/templates/log.html
index 34c37f9..3e56727 100644
--- a/app/templates/log.html
+++ b/app/templates/log.html
@@ -25,7 +25,7 @@