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:
@@ -55,14 +55,43 @@ stop levels, implied multiples at each level, and valuation reasoning are comput
|
||||
here. Rationale: the spec requires the structured object to be consumable without
|
||||
the agent, and pure functions make the many numeric requirements testable.
|
||||
|
||||
### D5 — `AnalysisAgent` interface; Claude default via `@anthropic-ai/sdk`
|
||||
The agent takes the `Evaluation` object and returns narrative sections. Default
|
||||
impl calls a current Claude model, instructed to use ONLY the supplied figures,
|
||||
preserve any flagged caveats/discrepancies, and include the disclaimer. Structured
|
||||
output (tool/JSON schema) is preferred so the UI can render narrative per section.
|
||||
Alternative: free-text prompt with the raw data — rejected because grounding and
|
||||
per-section rendering are weaker. When no key is set, the pipeline returns the
|
||||
`Evaluation` with `narrative: unavailable`.
|
||||
### D5 — `AnalysisAgent` interface; Claude Code CLI default, API alternate
|
||||
The agent takes the `Evaluation` object and returns narrative sections. Consumers
|
||||
depend only on the interface, so the transport is swappable. **Default transport:
|
||||
the local Claude Code CLI in headless mode** — `claude -p --output-format json`,
|
||||
running on the operator's existing Claude auth (subscription), so an internal
|
||||
two-person tool incurs ~no metered cost. **Alternate transport:** the Anthropic API
|
||||
via `@anthropic-ai/sdk`, selected by config, for when the app is served to external
|
||||
users (subscription-backed serving is not appropriate then). Default model is
|
||||
**Opus 4.8** (CLI alias `opus`); a per-request deep-dive flag escalates to
|
||||
**Fable 5** (CLI alias `fable`) — model is a per-request parameter, not a build-time
|
||||
constant. Structured narrative is requested via the CLI `--json-schema` option (or
|
||||
the API alternate's structured-output). The agent is instructed to use the supplied
|
||||
figures plus attributed web-sourced facts, preserve flagged caveats, and include
|
||||
the disclaimer. When no agent is available the pipeline returns the `Evaluation`
|
||||
with `narrative: unavailable`.
|
||||
|
||||
CLI invocation shape (verified against `claude --help`): `claude -p "<prompt>"
|
||||
--model <opus|fable> --output-format json --json-schema '<narrative schema>'
|
||||
--append-system-prompt "<instructions>" --allowedTools "WebSearch WebFetch"
|
||||
--permission-mode dontAsk`. The JSON result carries the structured output plus
|
||||
`total_cost_usd` and token usage (consumed by cost-controls). API-alternate notes
|
||||
(from the SDK reference): Opus 4.8 and Fable 5 take `thinking: {type: "adaptive"}`,
|
||||
reject `budget_tokens`/sampling params; Fable 5 has thinking always-on and can
|
||||
return `stop_reason: "refusal"` (enable a server-side fallback to Opus 4.8); stream
|
||||
long outputs.
|
||||
|
||||
### D8 — Live web grounding via the agent's web tools
|
||||
The agent grounds macro/news facts through its web search + fetch capability —
|
||||
Claude Code's `WebSearch`/`WebFetch` tools on the default transport, or Anthropic's
|
||||
server-side web tools on the API alternate — scoped to the ticker, with sources
|
||||
attributed in the output. Rationale: the reference DE evaluation's credibility came
|
||||
from live, cited facts (analyst target changes, tariff figures, peer read-throughs,
|
||||
catalyst dates) that don't exist in model weights or a fundamentals feed — this is
|
||||
the layer that closes the gap to a search-augmented tool like Perplexity. A bonus of
|
||||
the CLI default: this grounding harness is built in, so there is nothing to wire up.
|
||||
Grounding degrades gracefully: on tool error the agent still writes from the
|
||||
structured evaluation and notes that live grounding was unavailable.
|
||||
|
||||
### D6 — Typed results end-to-end; errors are values
|
||||
Data, evaluation, and agent layers return typed results/errors (not thrown
|
||||
@@ -76,6 +105,44 @@ provider layer, with retry/backoff for transient/rate-limit errors. Rationale:
|
||||
free tiers are tightly limited; caching one evaluation's repeated reads avoids
|
||||
burning quota. Persistence-backed cache is a later change.
|
||||
|
||||
### D9 — Cost controls: capture real usage, price from a table, guard the budget
|
||||
Every agent call's `usage` (input/output/cache tokens) and web-search count are
|
||||
captured from the API response — never estimated — and priced via a configurable
|
||||
table (per-model token prices + per-search price) so provider price changes are
|
||||
config, not code. Month-to-date spend is kept in lightweight local storage (a JSON
|
||||
file/`localStorage`-class store, not a database — consistent with the no-persistence
|
||||
scope) and checked before each evaluation against a configurable monthly budget:
|
||||
soft threshold warns, cap blocks. The pipeline evaluates the guard *before*
|
||||
dispatching the agent (and pre-empts a deep-dive whose projected cost exceeds the
|
||||
remaining budget), so a runaway loop can't blow the cap. Rationale: cost is the one
|
||||
resource a user can't see mid-run; real per-report numbers replace the design-time
|
||||
estimate and let the user tune the Opus/Fable mix. Alternative: rely on
|
||||
Anthropic-console billing after the fact — rejected because it's not in-app, not
|
||||
per-ticker, and offers no pre-emptive block.
|
||||
|
||||
### D10 — Instrument-type-aware from the start; equity branch only in v1
|
||||
The resolved profile and the `Evaluation` object carry an `instrumentType`
|
||||
discriminant (`equity` | `etf`). Instrument-agnostic sections (current standing,
|
||||
technicals, timing, entry/exit, stop-loss, macro) are shared; the
|
||||
fundamentals/valuation sections live under a per-type branch. v1 implements only
|
||||
the equity branch and returns an unsupported-type result for anything else.
|
||||
Rationale: an ETF is ~half the same (all technicals/timing) and ~half different
|
||||
(no EPS/earnings/segments; instead holdings, expense ratio, NAV premium/discount,
|
||||
weighted fundamentals). Baking the discriminant and branch seam in now is nearly
|
||||
free and avoids a refactor when the ETF branch is added as a fast-follow change.
|
||||
Alternative: equities-only with no seam — rejected as false economy given ETFs are
|
||||
an explicit near-term goal.
|
||||
|
||||
### Roadmap (deferred, compose on this change)
|
||||
- **ETF evaluation branch** — fills the `etf` branch (holdings, expense ratio, NAV
|
||||
premium/discount, sector/geo weights, weighted fundamentals) and an ETF agent
|
||||
prompt variant; reuses everything instrument-agnostic here.
|
||||
- **Screening / candidate-finder** — a `screening` capability: criteria/thesis →
|
||||
grounded, ranked shortlist (fundamental screener filter + web verification) where
|
||||
each candidate links into this evaluator. Deliberately built *after* the
|
||||
evaluator exists, so every surfaced idea has a rigorous place to be checked
|
||||
rather than being trusted as an oracle.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Free-tier data is delayed/incomplete — segments, estimates, or history may be
|
||||
|
||||
@@ -24,8 +24,25 @@ stop-loss levels — with a data layer and agent interface designed to grow.
|
||||
- Introduce a **pluggable analysis-agent layer** backed by **Claude via the
|
||||
Anthropic SDK** (bring-your-own API key) that synthesizes the structured data
|
||||
into a written investment thesis and the reasoning behind each recommendation.
|
||||
- Scope v1 to **single-ticker deep evaluation**. Watchlist monitoring, alerts,
|
||||
and multi-ticker dashboards are explicitly deferred to later changes.
|
||||
The default model is **Claude Opus 4.8**, with a per-ticker "deep dive" option
|
||||
that escalates to **Claude Fable 5** for the hardest analyses.
|
||||
- Give the agent **live web grounding** (Anthropic's server-side web search and
|
||||
web fetch) so macro/news facts the structured data layer lacks — tariff and rate
|
||||
developments, analyst rating/target changes, management commentary, peer
|
||||
read-throughs, dated catalysts — are sourced and attributed, matching the depth
|
||||
of the reference evaluation.
|
||||
- Introduce **cost controls**: capture per-report token/search usage, compute its
|
||||
cost from a configurable price table, track month-to-date spend, and enforce a
|
||||
configurable monthly budget (warn on a soft threshold, block on the cap) so the
|
||||
agent — especially the Fable 5 deep dive — can't run up a surprise bill.
|
||||
- Make the data models and evaluation engine **instrument-type-aware** (equity vs.
|
||||
ETF) from the start. v1 builds the **equity** branch only; the ETF branch
|
||||
(holdings, expense ratio, NAV premium/discount, weighted fundamentals) is a
|
||||
fast-follow change that slots into the existing seams without a refactor.
|
||||
- Scope v1 to **single-ticker deep evaluation** of individual equities. Deferred to
|
||||
later changes: the ETF evaluation branch, a **screening / candidate-finder** mode
|
||||
(criteria → grounded shortlist that feeds this evaluator), watchlist monitoring,
|
||||
alerts, and multi-ticker dashboards.
|
||||
|
||||
## Capabilities
|
||||
|
||||
@@ -49,6 +66,10 @@ stop-loss levels — with a data layer and agent interface designed to grow.
|
||||
request orchestration (data → evaluation → agent), and the report view that
|
||||
presents fundamentals, valuation reasoning, macro factors, a price chart with
|
||||
marked entry/exit/stop-loss levels, and the agent's written thesis.
|
||||
- `cost-controls`: Per-report usage capture and cost computation from a
|
||||
configurable price table, running spend aggregation, and a configurable monthly
|
||||
budget guard (soft-threshold warning, hard-cap block) with the numbers surfaced
|
||||
in the UI.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,73 +1,87 @@
|
||||
## 1. Project scaffold
|
||||
|
||||
- [ ] 1.1 Initialize Next.js (App Router) + TypeScript project at the repo root
|
||||
- [ ] 1.2 Add Tailwind CSS and a base layout/theme (light + dark)
|
||||
- [ ] 1.3 Add a charting library (Recharts or lightweight-charts) and confirm it renders
|
||||
- [ ] 1.4 Set up `.env.local` handling and `.env.example` for `MARKET_DATA_API_KEY` and `ANTHROPIC_API_KEY`; ensure secrets never reach the client bundle
|
||||
- [ ] 1.5 Configure lint/format/test tooling (ESLint, Prettier, Vitest/Jest) and a passing sample test
|
||||
- [ ] 1.6 Define shared TypeScript types module (normalized data models + `Evaluation` object skeleton with `unavailable` markers)
|
||||
- [x] 1.1 Initialize Next.js (App Router) + TypeScript project at the repo root
|
||||
- [x] 1.2 Add Tailwind CSS and a base layout/theme (light + dark)
|
||||
- [x] 1.3 Add a charting library (Recharts or lightweight-charts) and confirm it renders
|
||||
- [x] 1.4 Set up `.env.local` handling and `.env.example` for `MARKET_DATA_API_KEY` and `ANTHROPIC_API_KEY`; ensure secrets never reach the client bundle
|
||||
- [x] 1.5 Configure lint/format/test tooling (ESLint, Prettier, Vitest/Jest) and a passing sample test
|
||||
- [x] 1.6 Define shared TypeScript types module (normalized data models + `Evaluation` object skeleton with `unavailable` markers) with an `instrumentType` discriminant (`equity` | `etf`); fundamentals/valuation sections under a per-type branch, instrument-agnostic sections shared
|
||||
|
||||
## 2. Market-data layer (`market-data` spec)
|
||||
|
||||
- [ ] 2.1 Define the `DataProvider` interface and normalized models (`CompanyProfile`, `Fundamentals` incl. optional segments, `PriceHistory`, `AnalystCoverage`, `Estimates`), each with `asOf` + `delayed|realtime`
|
||||
- [ ] 2.2 Spike two free providers (e.g. FMP, Finnhub) against `DE`; pick the default based on segment + forward-estimate coverage; record the choice in design Open Questions
|
||||
- [ ] 2.3 Implement the default free-tier provider behind the interface
|
||||
- [ ] 2.4 Implement ticker resolution with NYSE/Nasdaq validation and typed not-found result
|
||||
- [ ] 2.5 Implement a fixture/mock provider seeded from the DE example for tests and offline dev
|
||||
- [ ] 2.6 Add provider-boundary caching (in-memory, TTL, keyed by ticker+dataset) and retry/backoff for rate-limit/transient errors returning typed errors
|
||||
- [ ] 2.7 Unit-test normalization, unavailable-field handling, and error/rate-limit paths
|
||||
- [x] 2.1 Define the `DataProvider` interface and normalized models (`CompanyProfile`, `Fundamentals` incl. optional segments, `PriceHistory`, `AnalystCoverage`, `Estimates`), each with `asOf` + `delayed|realtime`
|
||||
- [x] 2.2 Spike two free providers (e.g. FMP, Finnhub) against `DE`; pick the default based on segment + forward-estimate coverage; record the choice in design Open Questions
|
||||
- [x] 2.3 Implement the default free-tier provider behind the interface
|
||||
- [x] 2.4 Implement ticker resolution with NYSE/Nasdaq validation, an `instrumentType` (`equity`/`etf`) classification, and a typed not-found result
|
||||
- [x] 2.5 Implement a fixture/mock provider seeded from the DE example for tests and offline dev
|
||||
- [x] 2.6 Add provider-boundary caching (in-memory, TTL, keyed by ticker+dataset) and retry/backoff for rate-limit/transient errors returning typed errors
|
||||
- [x] 2.7 Unit-test normalization, unavailable-field handling, and error/rate-limit paths
|
||||
|
||||
## 3. Technicals module (`market-data` spec)
|
||||
|
||||
- [ ] 3.1 Implement pure functions: 20/50/200-day moving averages, 52-week high/low distance, average volume + today's volume multiple
|
||||
- [ ] 3.2 Implement swing high/low detection and realized (e.g. 60-day annualized) volatility
|
||||
- [ ] 3.3 Handle insufficient-history cases (mark long-window indicators unavailable)
|
||||
- [ ] 3.4 Unit-test all indicators against known values from the DE example
|
||||
- [x] 3.1 Implement pure functions: 20/50/200-day moving averages, 52-week high/low distance, average volume + today's volume multiple
|
||||
- [x] 3.2 Implement swing high/low detection and realized (e.g. 60-day annualized) volatility
|
||||
- [x] 3.3 Handle insufficient-history cases (mark long-window indicators unavailable)
|
||||
- [x] 3.4 Unit-test all indicators against known values from the DE example
|
||||
|
||||
## 4. Evaluation engine (`equity-evaluation` spec)
|
||||
|
||||
- [ ] 4.1 Implement current-standing summary (price, day change abs/%, volume multiple, 52-wk distance, market cap, trailing P/E)
|
||||
- [ ] 4.2 Implement earnings recap (EPS vs consensus, beat/miss, net income YoY, guidance changes) gated by a configurable recency window
|
||||
- [ ] 4.3 Implement quality-of-earnings caveats (one-time items + EPS impact, price-vs-volume, wrong-baseline headline metrics)
|
||||
- [ ] 4.4 Implement financial summary (current vs prior-year) and per-segment breakdown when available
|
||||
- [ ] 4.5 Implement valuation view (TTM EPS, trailing/forward P/E, growth, FCF & dividend yield, vs own history) with conflicting-estimate reconciliation
|
||||
- [ ] 4.6 Implement explicit over/undervalued reasoning (multiple-expansion vs earnings-growth, trough/peak earnings)
|
||||
- [ ] 4.7 Implement macro/sector factor analysis (tariffs trajectory, rates, input-cost pressure, peer read-throughs)
|
||||
- [ ] 4.8 Implement timing/volatility context (post-earnings behavior, implied vs actual move, monthly ranges, gap-fill status)
|
||||
- [ ] 4.9 Implement entry levels (ranked levels with meaning, % below price, implied multiple at each) grouped into bands with starter/high-conviction tranches
|
||||
- [ ] 4.10 Implement exit/targets (analyst avg/median/range + technical resistance) and stop-loss levels (technical/volatility-based, concrete prices + rationale)
|
||||
- [ ] 4.11 Implement bull-vs-bear synthesis and actionable plan (tranche sizing, next dated catalyst, conditional rules, one metric to watch)
|
||||
- [ ] 4.12 Emit the typed `Evaluation` object with explicit unavailable markers (no fabrication) + mandatory disclaimer
|
||||
- [ ] 4.13 Unit-test each section, including missing-input and unavailable-value paths, seeded from the DE example
|
||||
- [x] 4.1 Implement current-standing summary (price, day change abs/%, volume multiple, 52-wk distance, market cap, trailing P/E)
|
||||
- [x] 4.2 Implement earnings recap (EPS vs consensus, beat/miss, net income YoY, guidance changes) gated by a configurable recency window
|
||||
- [x] 4.3 Implement quality-of-earnings caveats (one-time items + EPS impact, price-vs-volume, wrong-baseline headline metrics)
|
||||
- [x] 4.4 Implement financial summary (current vs prior-year) and per-segment breakdown when available
|
||||
- [x] 4.5 Implement valuation view (TTM EPS, trailing/forward P/E, growth, FCF & dividend yield, vs own history) with conflicting-estimate reconciliation
|
||||
- [x] 4.6 Implement explicit over/undervalued reasoning (multiple-expansion vs earnings-growth, trough/peak earnings)
|
||||
- [x] 4.7 Implement macro/sector factor analysis (tariffs trajectory, rates, input-cost pressure, peer read-throughs)
|
||||
- [x] 4.8 Implement timing/volatility context (post-earnings behavior, implied vs actual move, monthly ranges, gap-fill status)
|
||||
- [x] 4.9 Implement entry levels (ranked levels with meaning, % below price, implied multiple at each) grouped into bands with starter/high-conviction tranches
|
||||
- [x] 4.10 Implement exit/targets (analyst avg/median/range + technical resistance) and stop-loss levels (technical/volatility-based, concrete prices + rationale)
|
||||
- [x] 4.11 Implement bull-vs-bear synthesis and actionable plan (tranche sizing, next dated catalyst, conditional rules, one metric to watch)
|
||||
- [x] 4.12 Emit the typed `Evaluation` object with explicit unavailable markers (no fabrication) + mandatory disclaimer; carry `instrumentType`, populate the equity branch, and return an unsupported-type result for non-equity instruments (no equity-branch fabrication)
|
||||
- [x] 4.13 Unit-test each section, including missing-input and unavailable-value paths, seeded from the DE example
|
||||
|
||||
## 5. Analysis agent (`analysis-agent` spec)
|
||||
|
||||
- [ ] 5.1 Define the `AnalysisAgent` interface (input: `Evaluation`; output: narrative sections)
|
||||
- [ ] 5.2 Implement the Claude default via `@anthropic-ai/sdk` (current model), key loaded server-side from config
|
||||
- [ ] 5.3 Design the prompt + structured/JSON output so narrative maps to spec sections and uses ONLY supplied figures
|
||||
- [ ] 5.4 Preserve input caveats/discrepancies and enforce the disclaimer in the narrative
|
||||
- [ ] 5.5 Implement graceful degradation when no key is set (return structured evaluation, mark narrative unavailable)
|
||||
- [ ] 5.6 Implement typed agent error handling (auth/rate-limit/timeout) that retains the computed evaluation
|
||||
- [ ] 5.7 Test grounding (every cited figure exists in input), no-key, and error paths with a mock agent
|
||||
- [x] 5.1 Define the `AnalysisAgent` interface (input: `Evaluation` + options incl. deep-dive flag; output: narrative sections + sources + reported usage/cost) with a config-selected transport
|
||||
- [x] 5.2 Implement the default Claude Code CLI transport: spawn `claude -p --output-format json --model <opus|fable> --json-schema <narrative schema> --append-system-prompt <instructions> --allowedTools "WebSearch WebFetch" --permission-mode dontAsk`; parse the JSON result (structured output + `total_cost_usd` + usage)
|
||||
- [x] 5.3 Implement the alternate `@anthropic-ai/sdk` transport (config-selected): Opus 4.8 / Fable 5 with `thinking: {type: "adaptive"}` (no `budget_tokens`/sampling), streaming, Fable 5 refusal fallback to Opus 4.8, server-side web tools
|
||||
- [x] 5.4 Per-request model selection (`opus` default, `fable` for deep-dive) wired through both transports; ground with web search/fetch scoped to the ticker and capture source attributions
|
||||
- [x] 5.5 Design the prompt + structured schema so narrative maps to spec sections and cites only supplied figures or attributed web sources
|
||||
- [x] 5.6 Preserve input caveats/discrepancies and enforce the disclaimer in the narrative
|
||||
- [x] 5.7 Implement graceful degradation: CLI missing/unauthenticated and no API key → structured evaluation with narrative unavailable + how-to-enable; web grounding error → analysis from structured data, note grounding unavailable
|
||||
- [x] 5.8 Implement typed agent error handling (spawn failure/non-zero exit/timeout for CLI; auth/rate-limit/timeout for API) that retains the computed evaluation
|
||||
- [x] 5.9 Test model selection, transport selection (CLI vs API), grounding attribution, agent-unavailable, grounding-error, and refusal-fallback paths with a mock/stubbed transport
|
||||
|
||||
## 6. Pipeline + API (`evaluation-app` spec)
|
||||
|
||||
- [ ] 6.1 Implement server-side route/action orchestrating resolve → data → evaluate → agent, returning `{ evaluation, narrative | error }`
|
||||
- [ ] 6.2 Handle partial results (data ok / agent unavailable) and typed errors end-to-end
|
||||
- [ ] 6.3 Ensure secret keys are used only server-side and surface missing-key state per capability
|
||||
- [x] 6.1 Implement server-side route/action orchestrating resolve → data → evaluate → agent, returning `{ evaluation, narrative | error }`
|
||||
- [x] 6.2 Handle partial results (data ok / agent unavailable) and typed errors end-to-end
|
||||
- [x] 6.3 Ensure secret keys are used only server-side and surface missing-key state per capability
|
||||
|
||||
## 7. Web UI (`evaluation-app` spec)
|
||||
|
||||
- [ ] 7.1 Build the ticker search entry point with inline invalid/not-found handling
|
||||
- [ ] 7.2 Build the report view rendering every section in order, tables as tables, unavailable values shown honestly
|
||||
- [ ] 7.3 Build the price chart with moving averages and overlay markers for entry bands, exit/targets, and stop-loss levels
|
||||
- [ ] 7.4 Implement loading state and readable error states (no blank screens)
|
||||
- [ ] 7.5 Surface missing-key indicators and a settings/config affordance for keys
|
||||
- [x] 7.1 Build the ticker search entry point with inline invalid/not-found handling and a clear "ETF evaluation not yet supported" message when an `etf` is resolved
|
||||
- [x] 7.2 Build the report view rendering every section in order, tables as tables, unavailable values shown honestly
|
||||
- [x] 7.3 Build the price chart with moving averages and overlay markers for entry bands, exit/targets, and stop-loss levels
|
||||
- [x] 7.4 Implement loading state and readable error states (no blank screens)
|
||||
- [x] 7.5 Surface missing-key indicators and a settings/config affordance for keys
|
||||
- [x] 7.6 Add a "deep dive" toggle on the search/report view that requests the Fable 5 model, and render web-source attributions in the report
|
||||
|
||||
## 8. Verification
|
||||
## 8. Cost controls (`cost-controls` spec)
|
||||
|
||||
- [ ] 8.1 End-to-end run on `DE` (with keys) producing a report structurally matching the example; note any data gaps from the free tier
|
||||
- [ ] 8.2 End-to-end run with no agent key confirming structured evaluation still renders
|
||||
- [ ] 8.3 Run the full test suite and lint; confirm green
|
||||
- [ ] 8.4 Update README with setup, env vars, provider choice, and run instructions
|
||||
- [ ] 8.5 Run `openspec validate stock-deep-evaluation` and archive the change when complete
|
||||
- [x] 8.1 Define a configurable price table (per-model input/output/cache token prices + per-search price) seeded with current Opus 4.8 and Fable 5 rates
|
||||
- [x] 8.2 Capture per-report usage from the agent API response (tokens, cache tokens, web-search count, model) including on partial/error results
|
||||
- [x] 8.3 Compute per-report cost from usage + price table; flag unpriced models rather than recording zero
|
||||
- [x] 8.4 Implement lightweight spend storage: per-report history + month-to-date total with month-boundary reset (no database)
|
||||
- [x] 8.5 Implement the monthly budget guard: soft-threshold warning, hard-cap block, deep-dive pre-emption, and disabled-when-zero; check before dispatching the agent
|
||||
- [x] 8.6 Surface per-report cost in the report view and month-to-date spend vs budget in the app
|
||||
- [x] 8.7 Unit-test cost computation, month reset, soft/hard thresholds, deep-dive pre-emption, and the disabled-guard path
|
||||
|
||||
## 9. Verification
|
||||
|
||||
- [x] 9.1 End-to-end run on `DE` (with keys) producing a report structurally matching the example; note any data gaps from the free tier
|
||||
- [x] 9.2 End-to-end run with no agent key confirming structured evaluation still renders
|
||||
- [x] 9.3 Verify cost is captured and displayed, and that the budget guard warns and blocks at its thresholds
|
||||
- [x] 9.4 Run the full test suite and lint; confirm green
|
||||
- [x] 9.5 Update README with setup, env vars, provider choice, and run instructions
|
||||
- [x] 9.6 Run `openspec validate stock-deep-evaluation` and archive the change when complete
|
||||
|
||||
Reference in New Issue
Block a user