diff --git a/SPARC/api.py b/SPARC/api.py index a78c132..762da0b 100644 --- a/SPARC/api.py +++ b/SPARC/api.py @@ -9,7 +9,7 @@ from typing import Annotated, List from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, Query, Request from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse +from fastapi.responses import JSONResponse, StreamingResponse from pydantic import BaseModel, EmailStr, Field from slowapi import Limiter from slowapi.errors import RateLimitExceeded @@ -389,6 +389,61 @@ async def get_analytics( ) +# ============== Export Endpoints ============== + + +@app.get("/export/{company_name}", tags=["Export"]) +async def export_company_csv( + company_name: str, + _: UserResponse = Depends(get_current_user), +): + """Export analysis results for a company as a CSV file. + + Returns all stored analysis records for the given company, including + analysis type, model used, response text, and timestamp. + + Args: + company_name: Company name to export results for + + Returns: + CSV file download + """ + import csv + import io + + db = get_db_client() + # Query all non-cached analysis results for this company + with db.get_conn() as conn: + with conn.cursor() as cur: + cur.execute( + """ + SELECT company_name, analysis_type, model, response, timestamp + FROM llm_messages + WHERE LOWER(company_name) = LOWER(%s) AND is_cached = FALSE + ORDER BY timestamp DESC + """, + (company_name,), + ) + rows = cur.fetchall() + + if not rows: + raise HTTPException(status_code=404, detail=f"No analysis results found for '{company_name}'") + + output = io.StringIO() + writer = csv.writer(output) + writer.writerow(["company_name", "analysis_type", "model", "analysis", "timestamp"]) + for row in rows: + writer.writerow(row) + + output.seek(0) + safe_name = company_name.replace(" ", "_").lower() + return StreamingResponse( + iter([output.getvalue()]), + media_type="text/csv", + headers={"Content-Disposition": f'attachment; filename="sparc_{safe_name}_export.csv"'}, + ) + + # ============== System Endpoints ============== diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 037d59c..9a1c94f 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -126,6 +126,23 @@ export const analysisApi = { }, }; +// Export API +export const exportApi = { + exportCsv: async (companyName: string): Promise => { + const response = await api.get(`/export/${encodeURIComponent(companyName)}`, { + responseType: 'blob', + }); + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', `sparc_${companyName.toLowerCase().replace(/\s+/g, '_')}_export.csv`); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + }, +}; + // Analytics API export const analyticsApi = { getAnalytics: async (days = 30): Promise => { diff --git a/frontend/src/pages/Analysis.tsx b/frontend/src/pages/Analysis.tsx index 2dfd2f5..1c8c59b 100644 --- a/frontend/src/pages/Analysis.tsx +++ b/frontend/src/pages/Analysis.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useMutation } from '@tanstack/react-query'; -import { analysisApi } from '../api/client'; -import { Search, CheckCircle, AlertCircle, Clock, FileText } from 'lucide-react'; +import { analysisApi, exportApi } from '../api/client'; +import { Search, CheckCircle, AlertCircle, Clock, FileText, Download } from 'lucide-react'; import type { CompanyAnalysis } from '../types'; export function Analysis() { @@ -106,9 +106,18 @@ export function Analysis() { {/* Analysis Content */} {result.success && result.analysis && (
-

- AI Analysis Results -

+
+

+ AI Analysis Results +

+ +
{result.analysis}