- Implemented Citations component to display citation data. - Created WatchlistDrawer and ChatSidebar components for managing watchlists and AI chat functionality. - Integrated API calls for watchlist management and chat interactions. - Updated index.tsx to include new components in the main application layout. - Added API client in lib/api.ts for structured API interactions. - Developed Alerts, Dashboard, Portfolio, Routines, Screener, and Report pages with relevant data fetching and UI components. - Introduced styles in tokens.css for consistent theming across the application. - Configured TypeScript and Vite for project setup and development.
112 lines
2.9 KiB
Go
112 lines
2.9 KiB
Go
// Package agents implements the 7 FlowSight specialists (A1..A6) plus the
|
|
// Master Synthesizer (A7). Contract per docs/AGENT-SPECS.md:
|
|
//
|
|
// analyze(ticker, snapshots) -> AgentResult(values[], score, citations[])
|
|
//
|
|
// Agents never fetch live; they read snapshots from the store. Detection
|
|
// rules and scores are computed locally; the LLM only refines prose and
|
|
// never invents numbers (every value carries citations).
|
|
package agents
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
|
|
"flowsight/internal/llm"
|
|
"flowsight/internal/model"
|
|
"flowsight/internal/store"
|
|
)
|
|
|
|
// Deps wires one analysis run.
|
|
type Deps struct {
|
|
DB *store.DB
|
|
LLM *llm.Client
|
|
TriageModel string
|
|
SynthModel string
|
|
Now time.Time
|
|
}
|
|
|
|
// Analyzer is one specialist.
|
|
type Analyzer func(ctx context.Context, d Deps, ticker string) model.AgentResult
|
|
|
|
// Registry runs in fixed order A1..A6; the synthesizer (A7) runs after.
|
|
var Registry = []struct {
|
|
Name string
|
|
Fn Analyzer
|
|
}{
|
|
{"smart-money", AnalyzeSmartMoney},
|
|
{"broker-intel", AnalyzeBrokerIntel},
|
|
{"sentiment", AnalyzeSentiment},
|
|
{"fundamental", AnalyzeFundamental},
|
|
{"technical", AnalyzeTechnical},
|
|
{"catalyst", AnalyzeCatalyst},
|
|
}
|
|
|
|
// RunAll executes A1..A6 in parallel via goroutines and returns results in
|
|
// registry order. One failing agent yields a zero-score result with the error
|
|
// in Summary — it never aborts the other five.
|
|
func RunAll(ctx context.Context, d Deps, ticker string) []model.AgentResult {
|
|
out := make([]model.AgentResult, len(Registry))
|
|
var wg sync.WaitGroup
|
|
for i, a := range Registry {
|
|
wg.Add(1)
|
|
go func(i int, name string, fn Analyzer) {
|
|
defer wg.Done()
|
|
res := fn(ctx, d, ticker)
|
|
res.Agent = name
|
|
out[i] = res
|
|
}(i, a.Name, a.Fn)
|
|
}
|
|
wg.Wait()
|
|
return out
|
|
}
|
|
|
|
// payload loads the newest snapshot for (ticker, source) and unmarshals it.
|
|
// ok=false when no snapshot exists (agents treat missing input as neutral,
|
|
// never as an error — the Citations list simply stays short).
|
|
func payload(db *store.DB, ticker, source string, v any) (date string, ok bool) {
|
|
raw, d, err := db.LatestSnapshot(ticker, source)
|
|
if err != nil || raw == "" {
|
|
return "", false
|
|
}
|
|
if err := json.Unmarshal([]byte(raw), v); err != nil {
|
|
return "", false
|
|
}
|
|
return d, true
|
|
}
|
|
|
|
// cite builds one citation for (source-as-endpoint, ticker, snapshot date).
|
|
func cite(source, ticker, date string) model.Citation {
|
|
return model.Cite("v2/"+source+"/", ticker, date)
|
|
}
|
|
|
|
// avg returns the mean of xs (0 on empty).
|
|
func avg(xs []float64) float64 {
|
|
if len(xs) == 0 {
|
|
return 0
|
|
}
|
|
sum := 0.0
|
|
for _, x := range xs {
|
|
sum += x
|
|
}
|
|
return sum / float64(len(xs))
|
|
}
|
|
|
|
// clampScore bounds a score to [lo, hi].
|
|
func clampScore(v, lo, hi float64) float64 {
|
|
if v < lo {
|
|
return lo
|
|
}
|
|
if v > hi {
|
|
return hi
|
|
}
|
|
return v
|
|
}
|
|
|
|
// fail builds an error result that keeps the pipeline green.
|
|
func fail(agent, msg string) model.AgentResult {
|
|
return model.AgentResult{Agent: agent, Summary: "error: " + msg}
|
|
}
|