Skip to content

fix(ideas): wire up rule-based idea engine as LLM fallback - #133

Open
Chirag6722 wants to merge 1 commit into
calesthio:masterfrom
Chirag6722:feature/rule-based-ideas-fallback
Open

fix(ideas): wire up rule-based idea engine as LLM fallback#133
Chirag6722 wants to merge 1 commit into
calesthio:masterfrom
Chirag6722:feature/rule-based-ideas-fallback

Conversation

@Chirag6722

Copy link
Copy Markdown

Closes #132

What was broken

generateIdeas() in dashboard/inject.mjs — the 11-rule cross-domain signal engine — has been imported by server.mjs since the initial v2.0.0 release (ef2c647) and never called. The Ideas panel was only ever populated by the LLM path:

} else {
  synthesized.ideas = [];        // rule engine exists, never used
  synthesized.ideasSource = 'disabled';
}

So every user without an LLM key saw a permanently empty "Leverageable Ideas" panel, and any transient provider failure (timeout — see #87 — a 429, or malformed JSON) blanked the panel for that sweep, even though every input signal needed to produce deterministic ideas was already sitting in synthesized.

README.md:126 already documented the intended behaviour — "AI-generated trade ideas (with LLM) or signal-correlated ideas (without)" — the code just never implemented the second half.

What this PR does

Wires the fallback. New resolveIdeas(llmProvider, V2, delta, previousIdeas) in dashboard/inject.mjs prefers LLM ideas and falls back to generateIdeas() when the provider is absent, returns nothing, or throws. It never throws, so a provider outage cannot take down the sweep or blank the panel. Both call sites — runSweepCycle() in server.mjs and cliInject() (npm run inject) — now share it, replacing two divergent copies of the same branching.

Seeds baseline ideas in synthesize(). The instant-load path (runs/latest.json on startup) previously showed an empty panel until the first sweep finished; it now renders ideas immediately.

Hardens generateIdeas() for degraded sweeps. It previously dereferenced V2.fred, V2.tg.urgent, V2.energy.wtiRecent, V2.bls, V2.thermal and V2.treasury unguarded — a failed FRED or EIA source would throw and, now that it is on the sweep path, take the whole cycle down. Also:

  • metric guards moved from truthiness to Number.isFinite, so a null-valued FRED series no longer emits VIX at null copy;
  • the WTI-momentum rule no longer divides by a zero oldest price (previously Infinity%).

Makes the provenance visible. New ideasBadge() renders SIGNAL BASED for ideasSource === 'rules' alongside the existing AI ENHANCED, and the empty state is now source-aware (NO SIGNAL TRIGGERS) rather than always blaming an unconfigured LLM. en + fr locale strings added.

Adds npm test. The 9 existing test files under test/ had no runner — package.json had no test script and docker-publish.yml never invokes one. Added "test": "node --test \"test/*.test.mjs\"" so the existing 46 tests and the new ones are actually runnable.

Tests

22 new tests in test/ideas-rules.test.mjs:

  • Degraded input — empty sweep, null/undefined input, {} (all sources failed), and null-valued metrics all return [] instead of throwing or emitting NaN copy.
  • Each rule's trigger — volatility regime (incl. the confidence step at VIX 25 and the no-fire boundary at exactly 20), safe-haven demand, conflict-energy nexus, oil momentum up/down, the zero-price divide guard, yield curve both directions, fiscal trajectory, credit/vol divergence, satellite conflict confirmation, and the 8-idea cap.
  • Output contract — every idea carries the exact title / text / type / confidence / horizon fields jarvis.html reads, with values from the sets the CSS classes and rendering expect, and no NaN/Infinity/undefined in user-facing text.
  • All four fallback paths — no provider, unconfigured provider, provider throws, provider returns unusable output → ideasSource: 'rules' with a non-empty list; provider returns valid ideas → ideasSource: 'llm'.
$ npm test
ℹ tests 68
ℹ pass 67
ℹ fail 0
ℹ skipped 1   (Ollama integration — no local Ollama)

(46 → 68 tests; the one skip is the pre-existing network-gated integration test.)

Verified end-to-end against the real sweep baked into jarvis.html, with no LLM configured — 7 ideas where the panel was previously empty:

ideasSource: rules | count: 7
 - LONG   Conflict-Energy Nexus Active           | Medium | swing
 - HEDGE  Elevated Volatility Regime             | Medium | tactical
 - HEDGE  Safe Haven Demand Rising               | Medium | tactical
 - WATCH  Oil Under Pressure                     | Medium | swing
 - WATCH  Yield Curve Normalizing                | Medium | strategic
 - LONG   Fiscal Trajectory Supports Hard Assets | High   | strategic
 - WATCH  Satellite Confirms Conflict Intensity  | Medium | swing

Notes

  • ideasSource gains 'rules' and no longer emits 'disabled' or 'llm-failed'. The dashboard's else branch still renders PENDING, so an older cached value degrades gracefully rather than breaking.
  • The FOR INFORMATIONAL PURPOSES ONLY disclosure on the panel is unchanged and applies equally to rule-generated ideas.

🤖 Generated with Claude Code

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 calesthio#132

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rule-based idea engine is dead code — Ideas panel is permanently empty without an LLM key

1 participant