A comprehensive overview of how SecSignal ingests SEC filings, processes them through Snowflake Cortex, and serves intelligent answers via an agentic RAG pipeline.
SecSignal is an agentic RAG (Retrieval-Augmented Generation) system that transforms raw SEC EDGAR filings into actionable financial intelligence. Users ask natural-language questions about company trends, cross-company comparisons, or filing anomalies. A LangGraph supervisor classifies the query, routes it to a specialist agent, enriches it with real-time web data, and synthesizes a final answer with charts, sources, and anomaly scores — all powered by Snowflake Cortex AI functions.
┌──────────────┐ ┌───────────────────────────────────────────────────┐
│ SEC EDGAR │────▶│ Ingestion Layer │
│ (EFTS + │ │ EdgarClient ─▶ FilingParser ─▶ Snowflake RAW │
│ Submissions) │ └───────────────────────┬───────────────────────────┘
└──────────────┘ │
▼
┌──────────────────────────────────┐
│ Processing Layer │
│ AI_PARSE_DOCUMENT (LAYOUT mode) │
│ Image extraction + Chart OCR │
└──────────────┬───────────────────┘
│
┌────────────────────┬┴────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ dbt Pipeline │ │ Text Embeddings │ │ Image Embeddings│
│ staging ─▶ int │ │ Arctic embed │ │ Voyage multi- │
│ ─▶ marts │ │ 1024d │ │ modal-3 1024d │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└────────────────────┼─────────────────────┘
▼
┌──────────────────────────────────┐
│ Cortex Search Service │
│ FILING_SEARCH_SERVICE │
│ (semantic retrieval) │
└──────────────┬───────────────────┘
│
▼
┌────────────┐ ┌─────────────────────────────────────────────────────┐
│ User │───▶│ LangGraph Agent System │
│ Query │ │ classify ─▶ specialist ─▶ web_search ─▶ synthesize│
└────────────┘ └──────────────────────┬──────────────────────────────┘
│
▼
┌──────────────────────────────────┐
│ FastAPI (SSE streaming) │
│ /api/query + /api/query/stream │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ Next.js 16 Frontend │
│ App Router + shadcn/ui │
│ recharts + react-markdown │
└──────────────────────────────────┘EdgarClient queries the EDGAR Full-Text Search (EFTS) API and Submissions API with async HTTP, rate-limited to SEC guidelines. Results are filtered by form type (10-K, 10-Q) and date range.
FilingParser performs section-aware HTML parsing using BeautifulSoup. It identifies Items (1, 1A, 7, 7A, 8) via regex patterns against a FilingSection enum, applies TOC deduplication (keeping the longest match per item), and calls chunk_text() to split sections into overlapping chunks (1500 chars, 200 overlap) for embedding.
EDGAR EFTS API ──▶ EdgarClient.search_filings()
│
▼
EdgarClient.fetch_filing_document()
│
▼
FilingParser.parse()
├── extract_sections() [regex Item matching]
├── deduplicate_toc() [keep longest per Item]
└── chunk_text() [1500 char chunks, 200 overlap]
│
▼
Snowflake RAW tables
├── RAW_COMPANIES
├── RAW_FILINGS
└── RAW_FILING_SECTIONSAI_PARSE_DOCUMENT (LAYOUT mode) extracts structured text and embedded images from filing PDFs. Images are stored in FILING_IMAGES and analyzed via CORTEX.AI_EXTRACT to produce chart metadata in CHART_EXTRACTS.
SecSignal uses two embedding models in the same 1024-dimensional space:
EMBED_TEXT_1024('snowflake-arctic-embed-l-v2.0'). Populates TEXT_EMBEDDINGS and SEARCH_CORPUS for Cortex Search.EMBED_TEXT_1024('voyage-multimodal-3'). Encodes chart image descriptions into IMAGE_EMBEDDINGS for cross-modal retrieval.Text chunks ──▶ Arctic embed (1024d) ──▶ TEXT_EMBEDDINGS
└──▶ SEARCH_CORPUS (Cortex Search)
Image descriptions ──▶ Voyage multimodal-3 (1024d) ──▶ IMAGE_EMBEDDINGSA 12-model dbt project transforms raw data through three layers:
STAGING (stg_*) INTERMEDIATE (int_*) MARTS (fct_*, dim_*) ───────────── ──────────────── ────────────────── stg_companies ──▶ int_parsed_sections ──▶ fct_risk_factors stg_filings ──▶ int_chart_extracts ──▶ fct_financials stg_sections ──▶ fct_chart_data stg_filing_images ──▶ dim_filing_images stg_chart_extracts
The agent system is a LangGraph StateGraph with a FilingState TypedDict. List fields use Annotated[list, operator.add] so multiple nodes can contribute items that auto-merge.
┌─────────────────┐
│ classify_query │
│ (Supervisor) │
└────────┬────────┘
│ conditional routing
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────────┐ ┌───────────┐ ┌───────────────┐
│ trend_agent │ │ comparison│ │ anomaly_agent │
│ │ │ _agent │ │ │
└──────┬───────┘ └─────┬─────┘ └──────┬────────┘
│ │ │
└───────────────┼──────────────┘
▼
┌──────────────────┐
│ web_search_agent │
│ (Cortex Agent + │
│ AGENT_RUN) │
└────────┬─────────┘
▼
┌──────────────────┐
│ synthesizer │
│ (Cortex │
│ COMPLETE) │
└──────────────────┘
State fields (operator.add annotation):
├── sources: list[dict] # filing citations
├── charts: list[dict] # retrieved chart data
├── anomaly_scores: list[dict] # z-score anomalies
├── generated_charts: list[dict]# LLM-extracted charts
├── web_sources: list[dict] # web search citations
└── trajectory: list[str] # step-by-step traceFastAPI serves two endpoints: POST /api/query for synchronous responses and POST /api/query/stream for Server-Sent Events (SSE). The streaming endpoint uses an asyncio.Queuebridge — run_query_stream() yields trajectory steps and the final result into the queue, while FastAPI's StreamingResponse drains it to the client.
Next.js 16 App Router with a single-page chat interface. The useQueryStream hook connects to the SSE endpoint via EventSource, parsing trajectory and result events into React state. Charts are rendered with recharts (BarChart, LineChart, ResponsiveContainer), and markdown answers use react-markdown with remark-gfm for pipe table support. The UI is styled with shadcn/ui + Tailwind 4 using oklch colors (warm off-black background, muted teal primary).
A weekly Airflow DAG orchestrates the end-to-end pipeline:
fetch_metadata ──▶ process_filings ──┬──▶ process_images ──▶ embed_images
│
└──▶ run_dbt ──────────▶ embed_text
Schedule: Weekly (Sunday 2:00 AM UTC)
Fan-out: process_filings triggers both image and dbt branches in parallel
Fan-in: embed_images and embed_text run after their respective upstream tasksEnd-to-end data flow from SEC EDGAR to the user's browser:
SEC EDGAR (EFTS API)
│
▼
RAW_COMPANIES ◄── company metadata (CIK, name, ticker, SIC)
RAW_FILINGS ◄──── filing metadata (accession, form type, date)
RAW_FILING_SECTIONS ◄── parsed section text (Item 1, 1A, 7, 7A, 8)
│
▼ (dbt staging)
stg_companies, stg_filings, stg_sections
│
▼ (dbt intermediate)
int_parsed_sections ◄── joined filings + sections + companies
int_chart_extracts ◄── enriched chart data with filing context
│
▼ (dbt marts)
fct_risk_factors ◄── risk factor text by company/filing/period
fct_financials ◄── extracted financial metrics (revenue, net income, etc.)
fct_chart_data ◄── chart data points with filing attribution
dim_filing_images ◄── image metadata dimension tableRAW_FILINGS
│
▼ AI_PARSE_DOCUMENT (LAYOUT mode)
FILING_IMAGES ◄── extracted images with page location
│
▼ AI_EXTRACT (chart analysis)
CHART_EXTRACTS ◄── chart type, title, data points, description
│
▼ Voyage multimodal-3
IMAGE_EMBEDDINGS ◄── 1024d cross-modal vectorsINT_PARSED_SECTIONS
│
▼ chunk_text() [1500 chars, 200 overlap]
Text chunks
│
▼ Arctic embed (snowflake-arctic-embed-l-v2.0)
TEXT_EMBEDDINGS ◄── 1024d text vectors
│
▼ Cortex Search indexing
SEARCH_CORPUS ──▶ FILING_SEARCH_SERVICE (semantic retrieval)User question
│
▼ classify_query (CORTEX.COMPLETE)
Query type + tickers extracted
│
├──▶ Specialist Agent
│ ├── CORTEX.SEARCH_PREVIEW() ──▶ FILING_SEARCH_SERVICE ──▶ sources
│ ├── SQL queries ──▶ fct_financials, fct_risk_factors ──▶ charts
│ └── SQL queries ──▶ EXTRACTED_FINANCIALS ──▶ anomaly z-scores
│
├──▶ Web Search Agent
│ ├── CORTEX.AGENT_RUN() ──▶ SECSIGNAL_WEB_AGENT ──▶ web_context
│ └── CORTEX.COMPLETE() ──▶ chart data extraction ──▶ generated_charts
│
▼ synthesizer (CORTEX.COMPLETE)
Final answer (markdown + charts + sources + anomalies)
│
▼ FastAPI SSE stream
│
▼ Next.js EventSource
Browser (react-markdown + recharts)Rather than a monolithic RAG pipeline, SecSignal uses a multi-agent architecture. A supervisor node classifies the query type, then conditionally routes to a specialist agent (trend, comparison, or anomaly). Each specialist retrieves different data and produces structured artifacts. A final synthesizer merges everything into a coherent answer. This separation of concerns allows each agent to be optimized independently.
LangGraph's Annotated[list, operator.add] annotation on state fields (sources, charts, trajectory, etc.) enables multiple nodes to append items without overwriting each other. When a specialist adds 3 sources and the web agent adds 2 more, the synthesizer sees all 5 automatically.
Text-to-text retrieval uses Arctic embed for precise semantic search over filing sections. Cross-modal retrieval uses Voyage multimodal-3 to match text queries against chart image descriptions. Both produce 1024-dimensional vectors, enabling unified similarity searches across modalities.
The LangGraph stream generator yields trajectory steps synchronously. An asyncio.Queuebridges this into FastAPI's async world — a background thread runs the generator and enqueues events, while the SSE response handler dequeues them. This prevents blocking the event loop during long Cortex LLM calls.
Web search and chart extraction results are cached in module-level dictionaries with a 5-minute TTL. Cache keys combine the normalized query and sorted ticker list. This avoids redundant Cortex AGENT_RUN and COMPLETE calls for repeated or similar queries within a short window.
SEC filings often have a Table of Contents that duplicates section headers. The parser uses regex to match Item headings, then deduplicates by keeping only the longest text match per Item. This ensures embedding and search indices contain the full section content rather than TOC stubs.
add_conditional_edges dispatches to the correct specialist based on the classified query type. The routing function returns a node name string, keeping the graph definition declarative and the routing logic testable in isolation.
The weekly DAG fans out after filing processing: one branch extracts and embeds images, the other runs dbt transforms and embeds text. Both branches converge implicitly at DAG completion. This parallelizes the two heaviest operations (image processing and dbt) for faster pipeline throughput.
| Layer | Technology | Purpose |
|---|---|---|
| Data Warehouse | Snowflake | Storage, compute, Cortex AI functions |
| LLM | Snowflake Cortex COMPLETE | Classification, synthesis, chart extraction |
| Search | Cortex Search (SEARCH_PREVIEW) | Semantic retrieval over filing sections |
| Web Search | Cortex Agent (AGENT_RUN) | Real-time market data and news |
| Embeddings | Arctic embed + Voyage multimodal-3 | Text and cross-modal embeddings (1024d) |
| Doc Parsing | Cortex AI_PARSE_DOCUMENT | PDF layout extraction with images |
| Agent Framework | LangGraph (StateGraph) | Multi-agent orchestration with shared state |
| Transforms | dbt | SQL-based data transformation pipeline |
| Orchestration | Apache Airflow | Weekly DAG scheduling and monitoring |
| API | FastAPI | REST + SSE streaming endpoints |
| Frontend | Next.js 16 (App Router) | React-based UI with server components |
| UI Components | shadcn/ui + Tailwind 4 | Component library with oklch color system |
| Charts | recharts | BarChart, LineChart, ResponsiveContainer |
| Markdown | react-markdown + remark-gfm | Answer rendering with GFM table support |
Powered by Snowflake Cortex · Agentic RAG