diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..047198f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + python: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgl1 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -e . + + - name: Lint with flake8 + run: | + flake8 src/ --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 src/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Test with pytest + run: | + pytest tests/ -v --tb=short + + frontend: + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Type check and build + run: npm run build diff --git a/.gitignore b/.gitignore index 9f24f71..4ba3bec 100644 --- a/.gitignore +++ b/.gitignore @@ -40,8 +40,8 @@ ENV/ !notebooks/*.ipynb # Data -data/ -datasets/ +/data/ +/datasets/ *.tif *.tiff *.h5 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bcba074..d29cd37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,33 @@ We are committed to providing a welcoming and inclusive environment. Please be r #### First Time Contributors -Look for issues labeled `good first issue` - these are specifically chosen for newcomers. +Look for issues labeled `good first issue` — these are specifically chosen for newcomers. + +**Recommended first issues (ready to pick up):** + +| Issue | What You'll Learn | Time Estimate | +|-------|-----------------|---------------| +| [#9: Add frontend unit tests](https://github.com/Climate-Vision/ClimateVision/issues/9) | Vitest, React Testing Library, Vite | 2–4 hours | +| [#13: Add Docker Compose](https://github.com/Climate-Vision/ClimateVision/issues/13) | Docker, multi-service orchestration | 3–6 hours | + +**How to claim an issue:** +1. Read the issue description and acceptance criteria +2. Comment "I'd like to work on this" — a maintainer will assign you +3. Fork the repo and create a branch: `git checkout -b feature/issue-9-frontend-tests` +4. Open a **draft PR** within 48 hours (even if incomplete) so we can give early feedback + +**Need help?** Tag `@Climate-Vision/maintainers` in the issue or open a [Discussion](https://github.com/Climate-Vision/ClimateVision/discussions). + +#### Intermediate Contributors + +Ready for something meatier? These issues close critical gaps in our production pipeline: + +| Issue | Area | Skills You'll Build | +|-------|------|-------------------| +| [#10: Alert delivery worker](https://github.com/Climate-Vision/ClimateVision/issues/10) | Backend | FastAPI BackgroundTasks, SMTP, webhooks | +| [#11: WebSocket real-time updates](https://github.com/Climate-Vision/ClimateVision/issues/11) | Full-stack | FastAPI WebSockets, React hooks, graceful degradation | +| [#12: ONNX Runtime inference](https://github.com/Climate-Vision/ClimateVision/issues/12) | MLOps | ONNX Runtime, PyTorch export, latency benchmarking | +| [#14: Carbon analytics API](https://github.com/Climate-Vision/ClimateVision/issues/14) | Analytics | Feature flags, API schema design, geospatial math | #### Development Process diff --git a/frontend/src/api.ts b/frontend/src/api.ts index e72eb07..5991563 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -190,6 +190,7 @@ export async function predictJson(payload: PredictJsonRequest): Promise<{ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) + if (!res.ok) throw new Error('Prediction failed') return res.json() } diff --git a/frontend/src/components/ui/ApiError.tsx b/frontend/src/components/ui/ApiError.tsx new file mode 100644 index 0000000..d8328c3 --- /dev/null +++ b/frontend/src/components/ui/ApiError.tsx @@ -0,0 +1,25 @@ +// frontend/src/components/ui/ApiError.tsx +import { AlertCircle, X } from 'lucide-react' +import { useState } from 'react' + +type ApiErrorProps = { + message: string +} + +export function ApiError({ message }: ApiErrorProps) { + const [visible, setVisible] = useState(true) + + if (!visible) return null + + return ( +
+ + +
{message}
+ + +
+ ) +} diff --git a/frontend/src/pages/NewAnalysis.tsx b/frontend/src/pages/NewAnalysis.tsx index e992b81..16de690 100644 --- a/frontend/src/pages/NewAnalysis.tsx +++ b/frontend/src/pages/NewAnalysis.tsx @@ -3,13 +3,14 @@ import { useNavigate } from 'react-router-dom' import { Loader2 } from 'lucide-react' import type { AnalysisType } from '../api' import { predictJson } from '../api' -import { MapBBoxPicker } from '../components/map/MapBBoxPicker' +import { MapBBoxPicker } from '../components/Map/MapBBoxPicker' import { AnalysisTypeSelector } from '../components/ui/AnalysisTypeSelector' import { ResultsPanel } from '../components/results/ResultsPanel' import { ErrorBoundary } from '../components/ui/ErrorBoundary' import { useToast } from '../contexts/ToastContext' import { useApp } from '../contexts/AppContext' import type { Run } from '../api' +import { ApiError } from '../components/ui/ApiError' const PRESETS = [ { label: 'Last 30d', days: 30 }, @@ -33,6 +34,7 @@ function SectionLabel({ step, label }: { step: number; label: string }) { } export default function NewAnalysis() { + const [error, setError] = useState(null) const { showToast } = useToast() const { googleMapsApiKey } = useApp() const navigate = useNavigate() @@ -61,7 +63,7 @@ export default function NewAnalysis() { showToast('error', 'Start date must be before end date.') return } - + setError(null) setBusy(true) setResultRun(null) setResultPayload(null) @@ -85,8 +87,10 @@ export default function NewAnalysis() { label: 'View in history', onClick: () => navigate('/runs'), }) - } catch (e) { - showToast('error', String(e)) + } catch (e: any) { + const message = e?.response?.data?.detail || e?.response?.data?.message || e?.message || 'Prediction failed' + setError(message) + showToast('error', message) } finally { setBusy(false) } @@ -94,7 +98,6 @@ export default function NewAnalysis() { return (
- {/* Step 1 — Analysis Type */}
@@ -147,7 +150,7 @@ export default function NewAnalysis() {
- + {error && } {/* Submit */}