613 lines
16 KiB
Markdown
613 lines
16 KiB
Markdown
# Invest Copilot — Phase 1: Design-Driven Foundation
|
||||
|
|
|
|||
|
|
> **Goal**: Establish the project foundation, generate UI directions via Open Design, set up the development environment, and define all API contracts.
|
|||
|
|
>
|
|||
|
|
> **Duration**: Weeks 1-3
|
|||
|
|
>
|
|||
|
|
> **Deliverables**: Clickable UI prototype, API specification, deployed dev environment, CI/CD pipeline
|
|||
|
|
|
|||
|
|
## Week 1: Visual Identity & UI Prototypes
|
|||
|
|
|
|||
|
|
### Day 1-2: Design Direction via Open Design
|
|||
|
|
|
|||
|
|
**Task 1: Generate 3 visual directions**
|
|||
|
|
|
|||
|
|
Use Open Design (or equivalent) with these prompts:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Direction A: "Dark Finance Professional"
|
|||
|
|
- Deep navy background (#0a1628) with electric blue accents (#3b82f6)
|
|||
|
|
- Data-dense layout (like Bloomberg but modern)
|
|||
|
|
- Monospace numbers (JetBrains Mono), sans-serif labels (Inter)
|
|||
|
|
- Green/red for gains/losses with +/− icons (not color alone)
|
|||
|
|
- Reference: Bloomberg meets Linear
|
|||
|
|
|
|||
|
|
Direction B: "Minimal Intelligence"
|
|||
|
|
- Near-black background (#0d0d0d) with subtle blue tints
|
|||
|
|
- Maximum white space between data elements
|
|||
|
|
- Large typography for key metrics
|
|||
|
|
- Charts as the hero element
|
|||
|
|
- Reference: Apple Health meets Koyfin
|
|||
|
|
|
|||
|
|
Direction C: "Modern Terminal"
|
|||
|
|
- Dark gray background (#1a1a1a) with accent colors
|
|||
|
|
- Terminal-inspired typography and borders
|
|||
|
|
- Monospace throughout
|
|||
|
|
- Command-like navigation
|
|||
|
|
- Reference: TradingView dark mode meets VS Code
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Task 2: Select direction and create design tokens**
|
|||
|
|
|
|||
|
|
Based on the generated directions, create `tailwind.config.ts` with:
|
|||
|
|
- Custom color palette (OKLch-based)
|
|||
|
|
- Font families (Inter + JetBrains Mono)
|
|||
|
|
- Spacing scale
|
|||
|
|
- Breakpoint definitions
|
|||
|
|
- Animation definitions
|
|||
|
|
|
|||
|
|
**Task 3: Generate key screen prototypes**
|
|||
|
|
|
|||
|
|
Generate HTML/CSS prototypes for:
|
|||
|
|
1. Dashboard (watchlist overview)
|
|||
|
|
2. Stock profile (search → profile)
|
|||
|
|
3. Sector rotation dashboard
|
|||
|
|
|
|||
|
|
### Day 3-5: Component Library
|
|||
|
|
|
|||
|
|
Generate reusable components:
|
|||
|
|
- StockCard, PriceChart placeholder, SEC Filing Card
|
|||
|
|
- Insider Trade Row, Strategy Rule Builder, Alert Badge
|
|||
|
|
- Sector Heatmap, Watchlist Table, Navigation
|
|||
|
|
|
|||
|
|
## Week 2: Development Environment
|
|||
|
|
|
|||
|
|
### Task 4: Initialize Next.js 15 project
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
npx create-next-app@latest invest-copilot \
|
|||
|
|
--typescript \
|
|||
|
|
--tailwind \
|
|||
|
|
--app \
|
|||
|
|
--src-dir \
|
|||
|
|
--import-alias "@/*" \
|
|||
|
|
--turbopack \
|
|||
|
|
--use-npm
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 5: Install core dependencies
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# State management
|
|||
|
|
npm install zustand
|
|||
|
|
|
|||
|
|
# UI primitives
|
|||
|
|
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu
|
|||
|
|
npm install @radix-ui/react-tabs @radix-ui/react-tooltip
|
|||
|
|
npm install @radix-ui/react-select @radix-ui/react-switch
|
|||
|
|
|
|||
|
|
# Charts
|
|||
|
|
npm install lightweight-charts
|
|||
|
|
|
|||
|
|
# Data fetching
|
|||
|
|
npm install @tanstack/react-query
|
|||
|
|
|
|||
|
|
# Forms
|
|||
|
|
npm install react-hook-form zod @hookform/resolvers
|
|||
|
|
|
|||
|
|
# PWA
|
|||
|
|
npm install next-pwa
|
|||
|
|
|
|||
|
|
# Icons
|
|||
|
|
npm install lucide-react
|
|||
|
|
|
|||
|
|
# Date handling
|
|||
|
|
npm install date-fns
|
|||
|
|
|
|||
|
|
# Development
|
|||
|
|
npm install -D @types/node @types/react @types/react-dom
|
|||
|
|
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
|
|||
|
|
npm install -D prettier prettier-plugin-tailwindcss
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 6: Set up Docker Compose for infrastructure
|
|||
|
|
|
|||
|
|
Create `docker-compose.dev.yml`:
|
|||
|
|
```yaml
|
|||
|
|
services:
|
|||
|
|
postgres:
|
|||
|
|
image: timescale/timescaledb:latest-pg16
|
|||
|
|
environment:
|
|||
|
|
POSTGRES_DB: invest_copilot
|
|||
|
|
POSTGRES_USER: dev
|
|||
|
|
POSTGRES_PASSWORD: dev_password
|
|||
|
|
ports:
|
|||
|
|
- "5432:5432"
|
|||
|
|
volumes:
|
|||
|
|
- pgdata:/var/lib/postgresql/data
|
|||
|
|
|
|||
|
|
redis:
|
|||
|
|
image: redis:7-alpine
|
|||
|
|
ports:
|
|||
|
|
- "6379:6379"
|
|||
|
|
volumes:
|
|||
|
|
- redisdata:/data
|
|||
|
|
|
|||
|
|
minio:
|
|||
|
|
image: minio/minio
|
|||
|
|
command: server /data --console-address ":9001"
|
|||
|
|
environment:
|
|||
|
|
MINIO_ROOT_USER: dev
|
|||
|
|
MINIO_ROOT_PASSWORD: dev_password
|
|||
|
|
ports:
|
|||
|
|
- "9000:9000"
|
|||
|
|
- "9001:9001"
|
|||
|
|
volumes:
|
|||
|
|
- miniodata:/data
|
|||
|
|
|
|||
|
|
volumes:
|
|||
|
|
pgdata:
|
|||
|
|
redisdata:
|
|||
|
|
miniodata:
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 7: Set up CI/CD with GitHub Actions
|
|||
|
|
|
|||
|
|
Create `.github/workflows/ci.yml`:
|
|||
|
|
```yaml
|
|||
|
|
name: CI
|
|||
|
|
|
|||
|
|
on:
|
|||
|
|
push:
|
|||
|
|
branches: [main]
|
|||
|
|
pull_request:
|
|||
|
|
branches: [main]
|
|||
|
|
|
|||
|
|
jobs:
|
|||
|
|
lint-and-test:
|
|||
|
|
runs-on: ubuntu-latest
|
|||
|
|
steps:
|
|||
|
|
- uses: actions/checkout@v4
|
|||
|
|
- uses: actions/setup-node@v4
|
|||
|
|
with:
|
|||
|
|
node-version: 22
|
|||
|
|
cache: npm
|
|||
|
|
- run: npm ci
|
|||
|
|
- run: npm run lint
|
|||
|
|
- run: npm run build
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Week 3: API Contracts & Data Pipeline
|
|||
|
|
|
|||
|
|
### Task 8: Define OpenAPI/Swagger specification
|
|||
|
|
|
|||
|
|
Create `docs/api/openapi.yaml` with:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
openapi: 3.1.0
|
|||
|
|
info:
|
|||
|
|
title: Invest Copilot API
|
|||
|
|
version: 0.1.0
|
|||
|
|
description: AI-native investment research and portfolio copilot
|
|||
|
|
|
|||
|
|
paths:
|
|||
|
|
# Stock Search & Profile
|
|||
|
|
/api/v1/search:
|
|||
|
|
get:
|
|||
|
|
summary: Search stocks by ticker or name
|
|||
|
|
parameters:
|
|||
|
|
- name: q
|
|||
|
|
in: query
|
|||
|
|
required: true
|
|||
|
|
schema: { type: string }
|
|||
|
|
- name: limit
|
|||
|
|
in: query
|
|||
|
|
schema: { type: integer, default: 10, maximum: 50 }
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: Search results
|
|||
|
|
|
|||
|
|
/api/v1/stocks/{ticker}:
|
|||
|
|
get:
|
|||
|
|
summary: Get full stock profile
|
|||
|
|
parameters:
|
|||
|
|
- name: ticker
|
|||
|
|
in: path
|
|||
|
|
required: true
|
|||
|
|
schema: { type: string }
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: Stock profile with all data
|
|||
|
|
|
|||
|
|
/api/v1/stocks/{ticker}/peers:
|
|||
|
|
get:
|
|||
|
|
summary: Get peer group and relative performance
|
|||
|
|
parameters:
|
|||
|
|
- name: ticker
|
|||
|
|
in: path
|
|||
|
|
required: true
|
|||
|
|
schema: { type: string }
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: Peer group with relative strength
|
|||
|
|
|
|||
|
|
/api/v1/stocks/{ticker}/price/history:
|
|||
|
|
get:
|
|||
|
|
summary: Get historical prices
|
|||
|
|
parameters:
|
|||
|
|
- name: ticker
|
|||
|
|
in: path
|
|||
|
|
required: true
|
|||
|
|
schema: { type: string }
|
|||
|
|
- name: interval
|
|||
|
|
in: query
|
|||
|
|
schema: { type: string, enum: [1m, 5m, 15m, 30m, 1h, 1d, 1w, 1M] }
|
|||
|
|
- name: start
|
|||
|
|
in: query
|
|||
|
|
schema: { type: string, format: date-time }
|
|||
|
|
- name: end
|
|||
|
|
in: query
|
|||
|
|
schema: { type: string, format: date-time }
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: Price history
|
|||
|
|
|
|||
|
|
/api/v1/stocks/{ticker}/sec-filings:
|
|||
|
|
get:
|
|||
|
|
summary: Get SEC filings
|
|||
|
|
parameters:
|
|||
|
|
- name: ticker
|
|||
|
|
in: path
|
|||
|
|
required: true
|
|||
|
|
schema: { type: string }
|
|||
|
|
- name: form_type
|
|||
|
|
in: query
|
|||
|
|
schema: { type: string, enum: [10-K, 10-Q, 8-K, 4, 13F, 13D, 13G] }
|
|||
|
|
- name: limit
|
|||
|
|
in: query
|
|||
|
|
schema: { type: integer, default: 20 }
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: SEC filings
|
|||
|
|
|
|||
|
|
/api/v1/stocks/{ticker}/insider-trades:
|
|||
|
|
get:
|
|||
|
|
summary: Get insider trades
|
|||
|
|
parameters:
|
|||
|
|
- name: ticker
|
|||
|
|
in: path
|
|||
|
|
required: true
|
|||
|
|
schema: { type: string }
|
|||
|
|
- name: limit
|
|||
|
|
in: query
|
|||
|
|
schema: { type: integer, default: 20 }
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: Insider trades
|
|||
|
|
|
|||
|
|
/api/v1/stocks/{ticker}/sentiment:
|
|||
|
|
get:
|
|||
|
|
summary: Get sentiment signals
|
|||
|
|
parameters:
|
|||
|
|
- name: ticker
|
|||
|
|
in: path
|
|||
|
|
required: true
|
|||
|
|
schema: { type: string }
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: Sentiment signals from all sources
|
|||
|
|
|
|||
|
|
# Watchlists
|
|||
|
|
/api/v1/watchlists:
|
|||
|
|
get:
|
|||
|
|
summary: Get user's watchlists
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: List of watchlists
|
|||
|
|
post:
|
|||
|
|
summary: Create a new watchlist
|
|||
|
|
requestBody:
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
type: object
|
|||
|
|
required: [name]
|
|||
|
|
properties:
|
|||
|
|
name: { type: string }
|
|||
|
|
description: { type: string }
|
|||
|
|
responses:
|
|||
|
|
201:
|
|||
|
|
description: Watchlist created
|
|||
|
|
|
|||
|
|
/api/v1/watchlists/{id}:
|
|||
|
|
get:
|
|||
|
|
summary: Get watchlist with items and prices
|
|||
|
|
put:
|
|||
|
|
summary: Update watchlist name
|
|||
|
|
delete:
|
|||
|
|
summary: Delete watchlist
|
|||
|
|
|
|||
|
|
/api/v1/watchlists/{id}/items:
|
|||
|
|
get:
|
|||
|
|
summary: Get watchlist items with live prices
|
|||
|
|
post:
|
|||
|
|
summary: Add ticker to watchlist
|
|||
|
|
delete:
|
|||
|
|
summary: Remove ticker from watchlist
|
|||
|
|
|
|||
|
|
# Strategies
|
|||
|
|
/api/v1/strategies:
|
|||
|
|
get:
|
|||
|
|
summary: Get user's strategies
|
|||
|
|
post:
|
|||
|
|
summary: Create a new strategy
|
|||
|
|
/api/v1/strategies/{id}:
|
|||
|
|
get:
|
|||
|
|
summary: Get strategy details
|
|||
|
|
put:
|
|||
|
|
summary: Update strategy
|
|||
|
|
delete:
|
|||
|
|
summary: Delete strategy
|
|||
|
|
|
|||
|
|
# Screener
|
|||
|
|
/api/v1/screeners:
|
|||
|
|
get:
|
|||
|
|
summary: Get user's screeners
|
|||
|
|
post:
|
|||
|
|
summary: Create a new screener
|
|||
|
|
/api/v1/screeners/{id}/run:
|
|||
|
|
post:
|
|||
|
|
summary: Run screener and get results
|
|||
|
|
/api/v1/screeners/{id}/results:
|
|||
|
|
get:
|
|||
|
|
summary: Get screener results
|
|||
|
|
|
|||
|
|
# Sector Rotation
|
|||
|
|
/api/v1/sectors/rotation:
|
|||
|
|
get:
|
|||
|
|
summary: Get current sector rotation analysis
|
|||
|
|
post:
|
|||
|
|
summary: Trigger manual rotation scan
|
|||
|
|
/api/v1/sectors/rotation/history:
|
|||
|
|
get:
|
|||
|
|
summary: Get historical rotation data
|
|||
|
|
|
|||
|
|
# Alerts
|
|||
|
|
/api/v1/alerts:
|
|||
|
|
get:
|
|||
|
|
summary: Get user's alerts
|
|||
|
|
delete:
|
|||
|
|
summary: Clear resolved alerts
|
|||
|
|
/api/v1/alerts/{id}/resolve:
|
|||
|
|
post:
|
|||
|
|
summary: Mark alert as resolved
|
|||
|
|
/api/v1/alerts/{id}/dismiss:
|
|||
|
|
post:
|
|||
|
|
summary: Dismiss alert
|
|||
|
|
|
|||
|
|
# Real-time (SSE)
|
|||
|
|
/api/v1/stream/alerts:
|
|||
|
|
get:
|
|||
|
|
summary: Server-Sent Events stream for real-time alerts
|
|||
|
|
responses:
|
|||
|
|
200:
|
|||
|
|
description: SSE stream
|
|||
|
|
content:
|
|||
|
|
text/event-stream:
|
|||
|
|
schema:
|
|||
|
|
type: string
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 9: Set up Python data pipeline
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mkdir -p src/data-pipeline
|
|||
|
|
cd src/data-pipeline
|
|||
|
|
pip install yfinance finnhub-python requests beautifulsoup4
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Create `src/data-pipeline/ingest_prices.py`:
|
|||
|
|
```python
|
|||
|
|
"""Price data ingestion using yfinance (free tier)."""
|
|||
|
|
import yfinance as yf
|
|||
|
|
import psycopg2
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
|
|||
|
|
def fetch_prices(ticker: str, period: str = "5y") -> list[dict]:
|
|||
|
|
"""Fetch OHLCV data using yfinance."""
|
|||
|
|
tk = yf.Ticker(ticker)
|
|||
|
|
df = tk.history(period=period, auto_adjust=True)
|
|||
|
|
records = []
|
|||
|
|
for date, row in df.iterrows():
|
|||
|
|
records.append({
|
|||
|
|
"ticker": ticker,
|
|||
|
|
"date": date,
|
|||
|
|
"open": row["Open"],
|
|||
|
|
"high": row["High"],
|
|||
|
|
"low": row["Low"],
|
|||
|
|
"close": row["Close"],
|
|||
|
|
"volume": int(row["Volume"]),
|
|||
|
|
"adjusted_close": row["Close"], # adjusted = close for now
|
|||
|
|
})
|
|||
|
|
return records
|
|||
|
|
|
|||
|
|
def save_prices(records: list[dict]) -> None:
|
|||
|
|
"""Save prices to PostgreSQL/TimescaleDB."""
|
|||
|
|
conn = psycopg2.connect(
|
|||
|
|
host="localhost",
|
|||
|
|
database="invest_copilot",
|
|||
|
|
user="dev",
|
|||
|
|
password="dev_password"
|
|||
|
|
)
|
|||
|
|
cur = conn.cursor()
|
|||
|
|
for r in records:
|
|||
|
|
cur.execute("""
|
|||
|
|
INSERT INTO prices (ticker, date, open, high, low, close, volume, adjusted_close)
|
|||
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|||
|
|
ON CONFLICT (ticker, date) DO UPDATE SET
|
|||
|
|
open = EXCLUDED.open,
|
|||
|
|
high = EXCLUDED.high,
|
|||
|
|
low = EXCLUDED.low,
|
|||
|
|
close = EXCLUDED.close,
|
|||
|
|
volume = EXCLUDED.volume,
|
|||
|
|
adjusted_close = EXCLUDED.adjusted_close
|
|||
|
|
""", (
|
|||
|
|
r["ticker"], r["date"], r["open"], r["high"], r["low"],
|
|||
|
|
r["close"], r["volume"], r["adjusted_close"]
|
|||
|
|
))
|
|||
|
|
conn.commit()
|
|||
|
|
cur.close()
|
|||
|
|
conn.close()
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
import sys
|
|||
|
|
tickers = sys.argv[1:] if len(sys.argv) > 1 else ["AAPL", "MSFT", "GOOGL", "AMZN", "META"]
|
|||
|
|
for ticker in tickers:
|
|||
|
|
print(f"Ingesting {ticker}...")
|
|||
|
|
records = fetch_prices(ticker)
|
|||
|
|
save_prices(records)
|
|||
|
|
print(f" Saved {len(records)} records")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 10: Set up SEC EDGAR ingestion
|
|||
|
|
|
|||
|
|
Create `src/data-pipeline/ingest_sec.py`:
|
|||
|
|
```python
|
|||
|
|
"""SEC EDGAR filing ingestion."""
|
|||
|
|
import requests
|
|||
|
|
import json
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
EDGAR_BASE = "https://data.sec.gov"
|
|||
|
|
|
|||
|
|
def get_cik(ticker: str) -> str | None:
|
|||
|
|
"""Get CIK number for a ticker."""
|
|||
|
|
url = f"{EDGAR_BASE}/cgi-bin/browse-edgar?action=getcompany&CIK=TICKER&type=&date=coverage&date_range=365&count=1"
|
|||
|
|
url = url.replace("TICKER", ticker.upper())
|
|||
|
|
resp = requests.get(url, timeout=10)
|
|||
|
|
# Parse HTML to extract CIK
|
|||
|
|
import re
|
|||
|
|
match = re.search(r'CIK=(0{0,10}\d{6,10})', resp.text)
|
|||
|
|
return match.group(1) if match else None
|
|||
|
|
|
|||
|
|
def get_filings(cik: str, form_type: str | None = None, limit: int = 20) -> list[dict]:
|
|||
|
|
"""Get filings for a CIK."""
|
|||
|
|
url = f"{EDGAR_BASE}/submissions/CIK{int(cik):010d}.json"
|
|||
|
|
resp = requests.get(url, timeout=10)
|
|||
|
|
data = resp.json()
|
|||
|
|
|
|||
|
|
filings = []
|
|||
|
|
for i in range(min(limit * 20, len(data["filings"]["recent"]["form"]))):
|
|||
|
|
form = data["filings"]["recent"]["form"][i]
|
|||
|
|
if form_type and form != form_type:
|
|||
|
|
continue
|
|||
|
|
filings.append({
|
|||
|
|
"cik": cik,
|
|||
|
|
"form_type": form,
|
|||
|
|
"filing_date": data["filings"]["recent"]["filingDate"][i],
|
|||
|
|
"report_date": data["filings"]["recent"]["reportDate"][i],
|
|||
|
|
"accession_number": data["filings"]["recent"]["accessionNumber"][i],
|
|||
|
|
"primary_document": data["filings"]["recent"]["primaryDocument"][i],
|
|||
|
|
"url": f"{EDGAR_BASE}/Archives/edgar/data/{int(cik):010d}/"
|
|||
|
|
f"{data['filings']['recent']['accessionNumber'][i].replace('-', '')}/"
|
|||
|
|
f"{data['filings']['recent']['primaryDocument'][i]}",
|
|||
|
|
})
|
|||
|
|
if len([f for f in filings if "form_type" in f]) >= limit:
|
|||
|
|
break
|
|||
|
|
return filings
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
# Test: Get filings for AAPL
|
|||
|
|
cik = get_cik("AAPL")
|
|||
|
|
print(f"AAPL CIK: {cik}")
|
|||
|
|
if cik:
|
|||
|
|
filings = get_filings(cik, limit=5)
|
|||
|
|
print(json.dumps(filings, indent=2))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 11: Create project structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
invest-copilot/
|
|||
|
|
├── docs/ # Documentation (we already have this)
|
|||
|
|
│ ├── 00-vision.md
|
|||
|
|
│ ├── 01-architecture.md
|
|||
|
|
│ ├── 02-data-model.md
|
|||
|
|
│ ├── 03-domain-model.md
|
|||
|
|
│ ├── 04-open-design-integration.md
|
|||
|
|
│ ├── 05-phase1-plan.md
|
|||
|
|
│ └── api/
|
|||
|
|
│ └── openapi.yaml
|
|||
|
|
├── src/
|
|||
|
|
│ ├── frontend/ # Next.js app
|
|||
|
|
│ │ ├── app/
|
|||
|
|
│ │ ├── components/
|
|||
|
|
│ │ ├── lib/
|
|||
|
|
│ │ ├── styles/
|
|||
|
|
│ │ └── store/ # Zustand stores
|
|||
|
|
│ ├── backend/ # Node.js API (future)
|
|||
|
|
│ │ ├── src/
|
|||
|
|
│ │ └── tests/
|
|||
|
|
│ ├── data-pipeline/ # Python ingestion scripts
|
|||
|
|
│ │ ├── ingest_prices.py
|
|||
|
|
│ │ ├── ingest_sec.py
|
|||
|
|
│ │ ├── ingest_sentiment.py
|
|||
|
|
│ │ └── sector_rotation.py
|
|||
|
|
│ └── shared/ # Shared types (TypeScript)
|
|||
|
|
│ └── types.ts
|
|||
|
|
├── docker-compose.dev.yml
|
|||
|
|
├── docker-compose.prod.yml # (future)
|
|||
|
|
├── .github/
|
|||
|
|
│ └── workflows/
|
|||
|
|
│ ├── ci.yml
|
|||
|
|
│ └── deploy.yml # (future)
|
|||
|
|
├── prisma/ # (future, for DDD repo implementations)
|
|||
|
|
├── .eslintrc.json
|
|||
|
|
├── .prettierrc
|
|||
|
|
├── next.config.mjs
|
|||
|
|
├── tailwind.config.ts
|
|||
|
|
├── tsconfig.json
|
|||
|
|
├── package.json
|
|||
|
|
└── README.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Acceptance Criteria for Phase 1
|
|||
|
|
|
|||
|
|
- [ ] 3 visual directions generated via Open Design (or manual)
|
|||
|
|
- [ ] Selected design direction converted to TailwindCSS design tokens
|
|||
|
|
- [ ] Next.js 15 project running with TypeScript, TailwindCSS, Radix UI
|
|||
|
|
- [ ] Docker Compose dev environment with PostgreSQL+TimescaleDB, Redis, MinIO
|
|||
|
|
- [ ] GitHub Actions CI pipeline (lint + build)
|
|||
|
|
- [ ] Complete OpenAPI specification for all endpoints
|
|||
|
|
- [ ] Python data pipeline ingesting prices for at least 10 tickers
|
|||
|
|
- [ ] SEC EDGAR ingestion working for at least 5 companies
|
|||
|
|
- [ ] Dashboard UI mockup working with static data
|
|||
|
|
- [ ] Stock search UI mockup working with static data
|
|||
|
|
- [ ] Stock profile UI mockup working with static data
|
|||
|
|
- [ ] README.md with project overview, setup instructions, and architecture
|
|||
|
|
|
|||
|
|
## Pre-Phase 2 Checklist
|
|||
|
|
|
|||
|
|
Before moving to Phase 2 (Backend Foundation):
|
|||
|
|
- [ ] All Phase 1 acceptance criteria met
|
|||
|
|
- [ ] Design direction finalized and approved
|
|||
|
|
- [ ] API contracts reviewed and agreed upon
|
|||
|
|
- [ ] Data pipeline tested and verified
|
|||
|
|
- [ ] Dev environment stable and documented
|
|||
|
|
|
|||
|
|
## Resource Estimation
|
|||
|
|
|
|||
|
|
| Task | Effort |
|
|||
|
|
|---|---|
|
|||
|
|
| Visual directions + design tokens | 2 days |
|
|||
|
|
| Component library | 3 days |
|
|||
|
|
| Next.js project setup | 0.5 days |
|
|||
|
|
| Docker Compose infrastructure | 0.5 days |
|
|||
|
|
| CI/CD pipeline | 0.5 days |
|
|||
|
|
| OpenAPI specification | 1 day |
|
|||
|
|
| Price data pipeline | 2 days |
|
|||
|
|
| SEC EDGAR pipeline | 2 days |
|
|||
|
|
| UI mockups (3 screens) | 3 days |
|
|||
|
|
| README + documentation | 1 day |
|
|||
|
|
| **Total** | **~16 days (3 weeks)** |
|