You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(ideas): wire up rule-based idea engine as LLM fallback
generateIdeas() has been fully implemented and imported by server.mjs since
v2.0.0, but was never called. Every user without an LLM key saw a permanently
empty "Leverageable Ideas" panel, and any transient LLM failure (timeout, 429,
malformed JSON) blanked the panel for that sweep — even though all the input
signals needed for deterministic ideas were already in the synthesized data.
- Add resolveIdeas(), a shared resolver that prefers LLM ideas and falls back
to the signal-rule engine when the provider is absent, empty, or throws.
Used by both the server sweep cycle and the `npm run inject` CLI path.
- synthesize() now seeds baseline ideas, so the instant-load path (existing
runs/latest.json) shows a populated panel before the first sweep finishes.
- Harden generateIdeas() against degraded sweeps: a failed FRED/EIA/BLS/Telegram
source no longer throws, non-finite metric values no longer emit ideas with
NaN in user-facing copy, and a zero oldest WTI price no longer divides by zero.
- Dashboard: SIGNAL BASED provenance badge for rule-generated ideas, and a
source-aware empty state (en/fr locales) instead of always blaming the LLM.
- Add `npm test` so the suite is runnable, plus 22 tests covering each rule's
trigger, the 8-idea cap, the output contract the dashboard renders, and all
four fallback paths.
Closes#132
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -146,7 +146,7 @@ The server runs a sweep cycle every 15 minutes (configurable). Each cycle:
146
146
1. Queries all 27 sources in parallel (~30s)
147
147
2. Synthesizes raw data into dashboard format
148
148
3. Computes delta from previous run (what changed, escalated, de-escalated) — visible in the **Sweep Delta** panel on the dashboard
149
-
4. Generates LLM trade ideas (if configured)
149
+
4. Generates trade ideas — LLM-generated if a provider is configured, otherwise (or if the provider fails) the deterministic signal-rule engine
150
150
5. Evaluates breaking news alerts — multi-tier (FLASH / PRIORITY / ROUTINE) with semantic dedup. Sends to Telegram and/or Discord if configured. Works with LLM evaluation or falls back to rule-based alerting when LLM is unavailable.
151
151
6. Pushes update to all connected browsers via SSE
title: pct>0 ? 'Oil Momentum Building' : 'Oil Under Pressure',
294
-
text: `WTI moved ${pct>0 ? '+' : ''}${pct}% recently to $${V2.energy.wti}/bbl. ${pct>0 ? 'Energy and commodity names benefit.' : 'Demand concerns may be emerging.'}`,
// Guard against a zero/absent oldest price — otherwise pct is Infinity/NaN.
309
+
if(isNum(latest)&&isNum(oldest)&&oldest!==0){
310
+
constpct=(latest-oldest)/oldest*100;
311
+
if(Math.abs(pct)>3){
312
+
ideas.push({
313
+
title: pct>0 ? 'Oil Momentum Building' : 'Oil Under Pressure',
314
+
text: `WTI moved ${pct>0 ? '+' : ''}${pct.toFixed(1)}% recently to $${energy.wti}/bbl. ${pct>0 ? 'Energy and commodity names benefit.' : 'Demand concerns may be emerging.'}`,
<div style="font-size:9px;margin-top:6px;opacity:0.6">Set LLM_PROVIDER + credentials in .env to enable AI-powered trade ideas</div>
1450
+
<div>${t('ideas.noTriggers','NO SIGNAL TRIGGERS')}</div>
1451
+
<div style="font-size:9px;margin-top:6px;opacity:0.6">${t('ideas.noTriggersHint','No signal thresholds crossed this sweep. Set LLM_PROVIDER + credentials in .env for AI-generated ideas.')}</div>
<div class="disclosure">FOR INFORMATIONAL PURPOSES ONLY. This is not financial advice, a recommendation to buy or sell any security, or a solicitation of any kind. All signal-based observations are derived from publicly available OSINT data and should not be relied upon for investment decisions. Consult a licensed financial advisor before making any investment. Past performance does not guarantee future results.</div>
Copy file name to clipboardExpand all lines: locales/en.json
+4-1Lines changed: 4 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -121,7 +121,10 @@
121
121
"pending": "PENDING",
122
122
"llmNotConfigured": "LLM NOT CONFIGURED",
123
123
"llmHelp": "Set LLM_PROVIDER + credentials in .env to enable AI-powered trade ideas",
124
-
"disclosure": "FOR INFORMATIONAL PURPOSES ONLY. This is not financial advice, a recommendation to buy or sell any security, or a solicitation of any kind. All signal-based observations are derived from publicly available OSINT data and should not be relied upon for investment decisions. Consult a licensed financial advisor before making any investment. Past performance does not guarantee future results."
124
+
"disclosure": "FOR INFORMATIONAL PURPOSES ONLY. This is not financial advice, a recommendation to buy or sell any security, or a solicitation of any kind. All signal-based observations are derived from publicly available OSINT data and should not be relied upon for investment decisions. Consult a licensed financial advisor before making any investment. Past performance does not guarantee future results.",
125
+
"signalBased": "SIGNAL BASED",
126
+
"noTriggers": "NO SIGNAL TRIGGERS",
127
+
"noTriggersHint": "No signal thresholds crossed this sweep. Set LLM_PROVIDER + credentials in .env for AI-generated ideas."
Copy file name to clipboardExpand all lines: locales/fr.json
+4-1Lines changed: 4 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -121,7 +121,10 @@
121
121
"pending": "EN ATTENTE",
122
122
"llmNotConfigured": "LLM NON CONFIGURÉ",
123
123
"llmHelp": "Définir LLM_PROVIDER + identifiants dans .env pour activer les idées de trade IA",
124
-
"disclosure": "À TITRE INFORMATIF UNIQUEMENT. Ceci ne constitue pas un conseil financier, une recommandation d'achat ou de vente de titre, ni une sollicitation quelconque. Toutes les observations basées sur les signaux sont dérivées de données OSINT publiques et ne doivent pas être utilisées pour prendre des décisions d'investissement. Consultez un conseiller financier agréé avant tout investissement. Les performances passées ne garantissent pas les résultats futurs."
124
+
"disclosure": "À TITRE INFORMATIF UNIQUEMENT. Ceci ne constitue pas un conseil financier, une recommandation d'achat ou de vente de titre, ni une sollicitation quelconque. Toutes les observations basées sur les signaux sont dérivées de données OSINT publiques et ne doivent pas être utilisées pour prendre des décisions d'investissement. Consultez un conseiller financier agréé avant tout investissement. Les performances passées ne garantissent pas les résultats futurs.",
125
+
"signalBased": "BASE SIGNAUX",
126
+
"noTriggers": "AUCUN SEUIL FRANCHI",
127
+
"noTriggersHint": "Aucun seuil de signal franchi lors de ce balayage. Definissez LLM_PROVIDER + identifiants dans .env pour des idees generees par IA."
0 commit comments