Files

613 lines
16 KiB
Markdown
Raw Permalink Normal View History

2026-05-30 11:28:59 -04:00
# 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)** |