architecture.md
$ cat stack.json
{ "backend": "Spring Boot 3.5 / Java 21", "frontend": "Angular 20.3.7",
"db": "PostgreSQL 15", "realtime": "STOMP/SockJS",
"entities": 57, "controllers": 30, "kotlin_modules": 32 }
$ wc -l src/**/*.java src/**/*.kt
377 Java classes · 32 Kotlin modules · 257 TypeScript components
$

Technical Internals

How MarMoFin
is built.

A complete reference for engineering leads evaluating the architecture — stack choices, algorithmic design, data pipeline, and known trade-offs. No marketing language.

Spring Boot 3.5.0 Angular 20.3.7 Java 21 LTS Kotlin 2.2.0 PostgreSQL 15

Full Stack at a Glance

Every technology in production, with exact versions and rationale.

LayerTechnologyVersionRationale
Backend
Framework Spring Boot 3.5.0 Latest LTS; virtual threads, reactive support, actuator built-in
Primary Language Java 21 LTS Virtual threads (Project Loom) for scheduler/IO concurrency
Secondary Language Kotlin 2.2.0 Data classes for entities, extension functions in calculation services
ORM Spring Data JPA / Hibernate 3.5.x UPSERT via native queries; batch inserts at 50 records/chunk
Technical Analysis TA4J 0.22.1 Moving averages, candlestick patterns; avoids reimplementing financial math
HTTP Client OkHttp3 5.1.0 External API calls (Polygon, Yahoo Finance); connection pooling
Object Storage MinIO SDK 8.6.0 S3-compatible flat file ingestion from Massive EOD data provider
AI Framework Spring AI 1.0.0-SNAPSHOT Unified ChatClient abstraction over Ollama (local) + Azure AI Foundry (cloud); multimodal image support
Local LLM Ollama mistral-small default; vision models (llava) for chart image analysis; 8k context window
Real-time Spring WebSocket / STOMP 3.5.x Server-push for live price updates; SockJS fallback for proxies
Boilerplate Lombok 1.18.38 @Data, @Builder, @RequiredArgsConstructor on Java entities
Frontend
Framework Angular 20.3.7 Standalone components (v17+); hybrid migration from NgModules in progress
UI Library Angular Material 20.2.10 Tables, dialogs, select, autocomplete — consistent Material Design
Charting D3.js 7.9.0 Custom candlestick renderer, volume bars, RS overlay lines
Reactive RxJS 7.8.1 Observable chains for WebSocket streams, HTTP, and component state
WebSocket Client @stomp/stompjs + SockJS 7.3.0 / 1.6.1 STOMP protocol over WebSocket with auto-reconnect
Language TypeScript 5.8.3 Strict mode; 257 component/service files
Data & Infrastructure
Database PostgreSQL 15 / driver 42.7.9 Window functions (LAG, STDDEV_POP, AVG OVER) for analytics; 57 entities
Deployment Azure App Service JAR deployment via GitHub Actions; no containerisation
CI/CD GitHub Actions mvn clean install → artifact upload → Azure webapps-deploy

AI Integration

Two AI surfaces built on Spring AI 1.0.0-SNAPSHOT. Local inference via Ollama; cloud fallback to Azure AI Foundry. No vendor lock-in — only a property change to switch providers.

AiChatController.java POST /api/ai/chat

In-Chart AI Chatbox

Context-aware assistant embedded inside every stock chart. System prompt is assembled at runtime from stored ticker, sector, description, and any cached company analysis.

🤖 AI Chat NVDA
Is the RS line confirming the breakout?
Yes — NVDA's RS line reached a new high two sessions before the price breakout, a classic early-strength signal. Volume on the breakout day was ×1.4 the 50-day average, confirming institutional participation. The base was 7 weeks — tight enough to indicate controlled supply.
Chart data attached (60 candles)
Ask a question… (Enter to send)
Context built from Ticker · company name · sector description (400 chars) · cached AI analysis (1 500 chars)
Text attachment Last 60 visible OHLCV+MA50+RS candles serialised as CSV — works with any text model
Image attachment SVG → Canvas → base64 PNG capture; sent via Spring AI ByteArrayResource + MimeTypeUtils.IMAGE_PNG — requires a vision model (e.g. llava)
SecReportAnalysisService.java POST /api/reports/{ticker}/analyze

10-Q Report Analysis Pipeline

Single endpoint triggers end-to-end pipeline: SEC EDGAR fetch → XBRL Company Facts API → financial table → LLM prompt → persist. DB connection is not held during inference.

Pipeline — pseudocode
// 1. Fetch 10-Q (auto-downloads from SEC EDGAR if missing)
SecReport report = secReportService.getLatestReport(ticker);

// 2. Company Facts API — structured XBRL JSON (not raw XML)
//    GET data.sec.gov/api/xbrl/companyfacts/CIK{cik}.json
JsonNode facts = secReportService.fetchCompanyFacts(ticker);

// 3. Build table: 12 concepts × last 6 quarters
String table = buildFinancialTable(facts);
// Revenue, NetIncomeLoss, EPS, OperatingCF, LongTermDebt…

// 4. LLM call — no DB transaction here
String analysis = chatClient.prompt()
    .user(buildPrompt(ticker, report, table))
    .call().content();

// 5. Short @Transactional persist
return persist(ticker, analysis); // → company_analysis table
Why XBRL API Raw 10-Q XML starts with ~50 kB of namespace/schema declarations. The Company Facts API returns labelled, structured JSON with no parsing required.
Result reuse Persisted analysis is injected as system context into the chat surface for the same ticker — analysis and chat share data.
Spring AI Provider Architecture
Spring Boot
ChatClient.Builder
injected by Spring AI
Ollama (local)
mistral-small · llava
8 k ctx · localhost:11434
or
Azure AI Foundry
GPT-4o · cloud inference
app.ai.provider=foundry
No code changes required to switch provider — only application.properties. ChatClient.Builder is injected; the implementation is resolved at startup by Spring AI auto-configuration.