Build stock-deep-evaluation v1: single-stock equity evaluation app
Next.js 15 + TypeScript app implementing the fully-specced first change. Pipeline: resolve -> market data -> pure evaluation engine -> budget guard -> analysis agent -> report. - market-data: DataProvider interface, offline FixtureProvider (DE/SPY seeded from the reference example), FmpProvider (FMP free tier), TTL cache + retry. - technicals: pure MA/volatility/swing/52-week math. - evaluation: instrument-aware pure engine; equity branch built, ETF gated to "not yet supported". Reproduces the DE example (P/E 34.5, fwd 29.3, $167.6B). - agent: AnalysisAgent interface; default Claude Code CLI transport (headless, subscription-backed, web-grounded), Anthropic API alternate via config. - cost-controls: price table, spend store, monthly budget guard. - UI: ticker search + deep-dive toggle, report view, price chart with marked entry/exit/stop levels, cost/budget display, ETF/not-found states. 31 vitest tests, typecheck, production build, and lint all pass. Verified end-to-end via the API for DE, SPY, and an unknown ticker. Live Claude CLI agent test is the documented pick-up point (see README). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,33 +15,74 @@ evaluation engine or UI.
|
||||
- **WHEN** a different agent implementation is configured
|
||||
- **THEN** the system uses it without changes to evaluation or UI code
|
||||
|
||||
### Requirement: Claude default implementation via Anthropic SDK
|
||||
### Requirement: Default agent via Claude Code CLI; API as swappable alternate
|
||||
|
||||
The system SHALL ship a default `AnalysisAgent` backed by Claude through the
|
||||
`@anthropic-ai/sdk`, targeting a current Claude model. The API key SHALL be
|
||||
supplied by the user (bring-your-own-key) via configuration and never hard-coded
|
||||
or committed.
|
||||
The system SHALL ship a default `AnalysisAgent` that invokes the local **Claude
|
||||
Code CLI** in headless mode (`claude -p --output-format json`), which runs on the
|
||||
operator's existing Claude authentication (e.g. subscription) rather than a metered
|
||||
API key. The system SHALL also ship an alternate implementation backed by the
|
||||
Anthropic API (`@anthropic-ai/sdk`), selectable via configuration without changes
|
||||
in consumers. Either way, the default model SHALL be **Claude Opus 4.8** (CLI alias
|
||||
`opus`) and a per-request "deep dive" option SHALL escalate to **Claude Fable 5**
|
||||
(CLI alias `fable`); the model SHALL be selectable per evaluation. Structured
|
||||
narrative output SHALL be requested via the CLI `--json-schema` option (or the
|
||||
equivalent structured-output mechanism on the API alternate). Any API key used by
|
||||
the alternate SHALL be loaded from configuration and never hard-coded or committed.
|
||||
|
||||
#### Scenario: Claude produces the written thesis
|
||||
- **WHEN** a valid Anthropic API key is configured and an evaluation is submitted
|
||||
- **THEN** the default agent calls Claude and returns the written thesis
|
||||
#### Scenario: Claude Code CLI produces the written thesis
|
||||
- **WHEN** the CLI agent is configured and an evaluation is submitted
|
||||
- **THEN** the system runs `claude -p` with model alias `opus` and returns the
|
||||
written thesis parsed from the CLI JSON output
|
||||
|
||||
#### Scenario: Key sourced from configuration
|
||||
- **WHEN** the app reads its configuration
|
||||
#### Scenario: Deep-dive escalates to Fable 5
|
||||
- **WHEN** an evaluation is requested with the deep-dive option enabled
|
||||
- **THEN** the agent runs the CLI with model alias `fable` for that request
|
||||
- **AND** requests without the option continue to use `opus`
|
||||
|
||||
#### Scenario: API alternate selected via configuration
|
||||
- **WHEN** the agent transport is configured to the API alternate with a valid key
|
||||
- **THEN** evaluations are served through `@anthropic-ai/sdk` instead of the CLI
|
||||
- **AND** no changes to evaluation or UI code are required
|
||||
|
||||
#### Scenario: Secrets sourced from configuration
|
||||
- **WHEN** the API alternate reads its configuration
|
||||
- **THEN** the Anthropic API key is loaded from environment/config, not source code
|
||||
|
||||
### Requirement: Live web grounding
|
||||
|
||||
The agent SHALL be able to retrieve current information via its web search and
|
||||
web fetch capability — the Claude Code CLI's `WebSearch`/`WebFetch` tools for the
|
||||
default transport, or Anthropic's server-side web tools for the API alternate — to
|
||||
source macro and news-driven facts the structured data layer does not provide:
|
||||
tariff/rate developments, analyst rating and price-target changes, management
|
||||
commentary, peer read-throughs, and dated catalysts. Facts drawn from the web SHALL
|
||||
carry their source so the output can attribute them, and grounding SHALL be scoped
|
||||
to the ticker under evaluation.
|
||||
|
||||
#### Scenario: Web-sourced facts are attributed
|
||||
- **WHEN** the agent uses a web-sourced fact in the analysis (e.g. an analyst
|
||||
target change or a catalyst date)
|
||||
- **THEN** the output attributes that fact to its source
|
||||
|
||||
#### Scenario: Grounding degrades gracefully
|
||||
- **WHEN** web search or fetch is unavailable or returns an error
|
||||
- **THEN** the agent still produces the analysis from the structured evaluation
|
||||
- **AND** notes that live macro/news grounding was unavailable
|
||||
|
||||
### Requirement: Grounded, structured written analysis
|
||||
|
||||
The agent SHALL base its narrative only on the supplied evaluation data and
|
||||
SHALL NOT invent figures. It SHALL produce the narrative sections reflected in the
|
||||
example evaluation: current standing, earnings recap and quality-of-earnings
|
||||
caveats, valuation with explicit over/undervalued reasoning, macro factors, timing,
|
||||
entry/exit/stop-loss rationale, bull-versus-bear, and an actionable plan. When a
|
||||
data caveat or discrepancy is present in the input, the agent SHALL preserve it.
|
||||
The agent SHALL base its narrative on the supplied evaluation data plus attributed
|
||||
web-sourced facts, and SHALL NOT invent figures. It SHALL produce the narrative
|
||||
sections reflected in the example evaluation: current standing, earnings recap and
|
||||
quality-of-earnings caveats, valuation with explicit over/undervalued reasoning,
|
||||
macro factors, timing, entry/exit/stop-loss rationale, bull-versus-bear, and an
|
||||
actionable plan. When a data caveat or discrepancy is present in the input, the
|
||||
agent SHALL preserve it.
|
||||
|
||||
#### Scenario: Narrative grounded in provided data
|
||||
- **WHEN** the agent writes the analysis
|
||||
- **THEN** every figure it cites is present in the supplied evaluation object
|
||||
- **THEN** every figure it cites is either present in the supplied evaluation
|
||||
object or attributed to a web source it retrieved
|
||||
|
||||
#### Scenario: Data caveats preserved
|
||||
- **WHEN** the evaluation object flags a discrepancy or one-time item
|
||||
@@ -51,16 +92,18 @@ data caveat or discrepancy is present in the input, the agent SHALL preserve it.
|
||||
- **WHEN** the agent returns its analysis
|
||||
- **THEN** the analysis-not-advice disclaimer is present
|
||||
|
||||
### Requirement: Graceful degradation without an API key
|
||||
### Requirement: Graceful degradation without an available agent
|
||||
|
||||
When no analysis-agent key is configured, the system SHALL still return the full
|
||||
structured evaluation and SHALL clearly indicate that the written narrative is
|
||||
unavailable until a key is provided.
|
||||
The system SHALL, when no analysis agent is available (the Claude Code CLI is not
|
||||
installed or not authenticated, or the configured API alternate has no key), still
|
||||
return the full structured evaluation and clearly indicate that the written
|
||||
narrative is unavailable and how to enable it.
|
||||
|
||||
#### Scenario: No key configured
|
||||
- **WHEN** an evaluation is requested and no agent API key is configured
|
||||
#### Scenario: Agent unavailable
|
||||
- **WHEN** an evaluation is requested and no agent transport is available
|
||||
- **THEN** the structured evaluation is returned
|
||||
- **AND** the response indicates the written analysis is unavailable pending a key
|
||||
- **AND** the response indicates the written analysis is unavailable and how to
|
||||
enable it (authenticate the CLI, or configure an API key)
|
||||
|
||||
### Requirement: Agent error handling
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Per-report usage capture
|
||||
|
||||
The system SHALL capture the actual usage of every evaluation that calls the
|
||||
analysis agent from the agent's own reported usage — the Claude Code CLI JSON
|
||||
output (`total_cost_usd` plus token usage) for the default transport, or the API
|
||||
response usage for the alternate — not from estimates. Captured fields SHALL
|
||||
include, where the transport reports them, token counts, model used, and a reported
|
||||
cost. When the transport runs on a subscription rather than metered billing, the
|
||||
reported marginal cost MAY be zero or near-zero; the system SHALL record what the
|
||||
transport reports.
|
||||
|
||||
#### Scenario: Usage recorded for a completed report
|
||||
- **WHEN** an evaluation completes an agent call
|
||||
- **THEN** the system records the report's token counts, web-search count, model,
|
||||
ticker, and timestamp
|
||||
|
||||
#### Scenario: Usage captured on partial results
|
||||
- **WHEN** an agent call fails after consuming tokens (e.g. a mid-stream error)
|
||||
- **THEN** the usage consumed up to that point is still recorded
|
||||
|
||||
### Requirement: Cost computation from a configurable price table
|
||||
|
||||
The system SHALL compute each report's cost from captured usage and a configurable
|
||||
price table (per-model input/output/cache token prices and per-search price).
|
||||
Prices SHALL be editable via configuration so provider price changes require no
|
||||
code changes.
|
||||
|
||||
#### Scenario: Report cost computed
|
||||
- **WHEN** a report's usage is captured
|
||||
- **THEN** the system computes its cost using the configured price table and stores
|
||||
the cost alongside the usage
|
||||
|
||||
#### Scenario: Unknown model priced safely
|
||||
- **WHEN** a report uses a model absent from the price table
|
||||
- **THEN** the system flags the cost as unpriced rather than recording zero
|
||||
|
||||
### Requirement: Spend aggregation
|
||||
|
||||
The system SHALL maintain a running month-to-date total spend and SHALL expose
|
||||
per-report cost history. The spend record MAY use lightweight local storage; a
|
||||
full database is out of scope for this change.
|
||||
|
||||
#### Scenario: Month-to-date total available
|
||||
- **WHEN** a new report's cost is recorded
|
||||
- **THEN** the month-to-date total reflects the new report
|
||||
- **AND** the per-report history includes the new entry
|
||||
|
||||
#### Scenario: Month boundary resets the running total
|
||||
- **WHEN** the calendar month changes
|
||||
- **THEN** the month-to-date total starts from zero for the new month
|
||||
- **AND** prior months' totals remain available in history
|
||||
|
||||
### Requirement: Monthly budget guard
|
||||
|
||||
The system SHALL enforce a configurable monthly budget. When month-to-date spend
|
||||
crosses a configurable soft threshold (default 80% of budget) the system SHALL warn
|
||||
but still allow evaluations. When spend reaches the budget cap the system SHALL
|
||||
block new evaluations and return a clear message rather than silently proceeding.
|
||||
A disabled/zero budget SHALL mean no enforcement.
|
||||
|
||||
#### Scenario: Soft-threshold warning
|
||||
- **WHEN** month-to-date spend crosses the soft threshold but is below the cap
|
||||
- **THEN** the system allows the evaluation and surfaces a budget warning with the
|
||||
current spend and remaining budget
|
||||
|
||||
#### Scenario: Hard-cap block
|
||||
- **WHEN** month-to-date spend has reached the configured cap
|
||||
- **THEN** the system blocks new evaluations and returns a message stating the cap
|
||||
was reached and how to raise or disable it
|
||||
|
||||
#### Scenario: Deep-dive gated at the cap
|
||||
- **WHEN** a deep-dive (Fable 5) evaluation is requested and the projected cost
|
||||
would exceed the remaining budget
|
||||
- **THEN** the system blocks it and explains the remaining budget
|
||||
|
||||
#### Scenario: Guard disabled
|
||||
- **WHEN** the monthly budget is set to zero or disabled
|
||||
- **THEN** evaluations proceed without budget enforcement
|
||||
|
||||
### Requirement: Cost visibility in the UI
|
||||
|
||||
The system SHALL display each report's cost in the report view and SHALL surface
|
||||
month-to-date spend against the configured budget in the app.
|
||||
|
||||
#### Scenario: Report cost shown
|
||||
- **WHEN** a report renders
|
||||
- **THEN** its computed cost is shown in the report view
|
||||
|
||||
#### Scenario: Budget status shown
|
||||
- **WHEN** the app is in use
|
||||
- **THEN** month-to-date spend and remaining budget are visible
|
||||
@@ -187,3 +187,24 @@ SHALL be represented explicitly rather than fabricated.
|
||||
#### Scenario: Missing data is not fabricated
|
||||
- **WHEN** a required input for a section is unavailable
|
||||
- **THEN** that section marks the value unavailable rather than inventing one
|
||||
|
||||
### Requirement: Instrument-type-aware evaluation
|
||||
|
||||
The evaluation SHALL carry the resolved instrument type (`equity` or `etf`) as a
|
||||
discriminant, and the fundamentals-driven sections (earnings recap,
|
||||
quality-of-earnings, financial/segment breakdown, valuation, over/undervalued
|
||||
reasoning) SHALL be modeled as the **equity** branch so an ETF branch (holdings,
|
||||
expense ratio, NAV premium/discount, weighted fundamentals) can be added later
|
||||
without changing the shared, instrument-agnostic sections (current standing,
|
||||
technicals, timing, entry/exit, stop-loss, macro). v1 SHALL implement the equity
|
||||
branch; requesting an evaluation for a non-equity type SHALL return an
|
||||
unsupported-type result rather than running the equity branch.
|
||||
|
||||
#### Scenario: Equity evaluated on the equity branch
|
||||
- **WHEN** an evaluation runs for an `equity` instrument
|
||||
- **THEN** the equity fundamentals/valuation sections are produced
|
||||
|
||||
#### Scenario: Non-equity type not forced through the equity branch
|
||||
- **WHEN** an evaluation is requested for an `etf` (or other non-equity) instrument
|
||||
- **THEN** the system returns an unsupported-type result
|
||||
- **AND** does not populate equity-only sections with fabricated values
|
||||
|
||||
@@ -21,15 +21,25 @@ consumers.
|
||||
### Requirement: Ticker resolution for NYSE and Nasdaq
|
||||
|
||||
The system SHALL accept a ticker symbol, validate that it resolves to an
|
||||
NYSE- or Nasdaq-listed equity, and return a normalized company profile
|
||||
(name, exchange, sector/industry, currency, shares outstanding).
|
||||
NYSE- or Nasdaq-listed security, and return a normalized profile that includes an
|
||||
**instrument type** discriminant (`equity` or `etf`) plus name, exchange, currency,
|
||||
and — for equities — sector/industry and shares outstanding. v1 fully supports the
|
||||
`equity` type; ETF-specific data is a later change, but resolution SHALL still
|
||||
classify ETFs so an unsupported-type path is explicit rather than silently wrong.
|
||||
|
||||
#### Scenario: Valid listed ticker resolves
|
||||
- **WHEN** a user submits a ticker listed on NYSE or Nasdaq (e.g. `DE`)
|
||||
- **THEN** the system returns a company profile with name, exchange, and sector
|
||||
#### Scenario: Valid listed equity resolves
|
||||
- **WHEN** a user submits an equity ticker listed on NYSE or Nasdaq (e.g. `DE`)
|
||||
- **THEN** the system returns a profile with instrument type `equity`, name,
|
||||
exchange, and sector
|
||||
|
||||
#### Scenario: ETF is classified, not misread as an equity
|
||||
- **WHEN** a user submits an ETF ticker (e.g. `SPY`)
|
||||
- **THEN** the system returns a profile with instrument type `etf`
|
||||
- **AND** the app reports ETF evaluation as not yet supported rather than running
|
||||
the equity branch against it
|
||||
|
||||
#### Scenario: Unknown or unlisted ticker is rejected
|
||||
- **WHEN** a user submits a symbol that does not resolve to an NYSE/Nasdaq equity
|
||||
- **WHEN** a user submits a symbol that does not resolve to an NYSE/Nasdaq security
|
||||
- **THEN** the system returns a not-found result with a clear message
|
||||
- **AND** no evaluation is attempted
|
||||
|
||||
|
||||
Reference in New Issue
Block a user