Files
flowsight/backend/internal/agents/agent.go
T
asepharyana 8c184ccae1 feat: add Citations and WatchlistChat components, integrate with API
- 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.
2026-09-15 12:36:48 +07:00

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}
}