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