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)** |
|