| """
|
| FastAPI Main Application
|
| Backend API for Cancer@Home
|
| """
|
|
|
| from fastapi import FastAPI, HTTPException
|
| from fastapi.middleware.cors import CORSMiddleware
|
| from fastapi.staticfiles import StaticFiles
|
| from fastapi.responses import HTMLResponse
|
| from strawberry.fastapi import GraphQLRouter
|
| from pathlib import Path
|
| import uvicorn
|
|
|
| from backend.neo4j.graphql_schema import schema
|
| from backend.neo4j.db_manager import DatabaseManager
|
| from backend.boinc.client import BOINCClient, BOINCTaskManager
|
| from backend.gdc.client import GDCClient
|
| from backend.pipeline import (
|
| FASTQProcessor,
|
| BLASTRunner,
|
| VariantCaller
|
| )
|
|
|
|
|
| app = FastAPI(
|
| title="Cancer@Home v2",
|
| description="Distributed cancer genomics research platform",
|
| version="2.0.0"
|
| )
|
|
|
|
|
| app.add_middleware(
|
| CORSMiddleware,
|
| allow_origins=["*"],
|
| allow_credentials=True,
|
| allow_methods=["*"],
|
| allow_headers=["*"],
|
| )
|
|
|
|
|
| graphql_app = GraphQLRouter(schema)
|
| app.include_router(graphql_app, prefix="/graphql")
|
|
|
|
|
| frontend_path = Path("frontend/dist")
|
| if frontend_path.exists():
|
| app.mount("/static", StaticFiles(directory=str(frontend_path)), name="static")
|
|
|
|
|
| @app.get("/", response_class=HTMLResponse)
|
| async def root():
|
| """Serve main dashboard"""
|
| html_file = Path("frontend/index.html")
|
| if html_file.exists():
|
| with open(html_file, 'r') as f:
|
| return f.read()
|
|
|
|
|
| return """
|
| <!DOCTYPE html>
|
| <html>
|
| <head>
|
| <title>Cancer@Home v2</title>
|
| <style>
|
| body {
|
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| margin: 0;
|
| padding: 0;
|
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| color: white;
|
| }
|
| .container {
|
| max-width: 1200px;
|
| margin: 0 auto;
|
| padding: 40px 20px;
|
| }
|
| h1 {
|
| font-size: 3em;
|
| margin-bottom: 20px;
|
| }
|
| .card {
|
| background: rgba(255, 255, 255, 0.1);
|
| border-radius: 10px;
|
| padding: 30px;
|
| margin: 20px 0;
|
| backdrop-filter: blur(10px);
|
| }
|
| .links {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| gap: 20px;
|
| margin-top: 30px;
|
| }
|
| .link-card {
|
| background: rgba(255, 255, 255, 0.15);
|
| border-radius: 8px;
|
| padding: 20px;
|
| text-decoration: none;
|
| color: white;
|
| transition: transform 0.2s;
|
| }
|
| .link-card:hover {
|
| transform: translateY(-5px);
|
| background: rgba(255, 255, 255, 0.25);
|
| }
|
| .link-card h3 {
|
| margin-top: 0;
|
| }
|
| </style>
|
| </head>
|
| <body>
|
| <div class="container">
|
| <h1>🧬 Cancer@Home v2</h1>
|
| <div class="card">
|
| <h2>Welcome to Cancer@Home</h2>
|
| <p>A distributed computing platform for cancer genomics research</p>
|
| </div>
|
|
|
| <div class="links">
|
| <a href="/api/docs" class="link-card">
|
| <h3>📚 API Documentation</h3>
|
| <p>Interactive API docs with Swagger UI</p>
|
| </a>
|
| <a href="/graphql" class="link-card">
|
| <h3>🔍 GraphQL Playground</h3>
|
| <p>Query cancer data with GraphQL</p>
|
| </a>
|
| <a href="http://localhost:7474" class="link-card">
|
| <h3>📊 Neo4j Browser</h3>
|
| <p>Visualize graph database</p>
|
| </a>
|
| <a href="/api/health" class="link-card">
|
| <h3>💚 Health Check</h3>
|
| <p>Check system status</p>
|
| </a>
|
| </div>
|
| </div>
|
| </body>
|
| </html>
|
| """
|
|
|
|
|
| @app.get("/api/health")
|
| async def health_check():
|
| """Health check endpoint"""
|
| db = DatabaseManager()
|
| try:
|
| db.execute_query("RETURN 1")
|
| neo4j_status = "healthy"
|
| except Exception as e:
|
| neo4j_status = f"unhealthy: {str(e)}"
|
| finally:
|
| db.close()
|
|
|
| return {
|
| "status": "healthy",
|
| "neo4j": neo4j_status,
|
| "version": "2.0.0"
|
| }
|
|
|
|
|
|
|
| @app.get("/api/boinc/tasks")
|
| async def get_boinc_tasks(status: str = None):
|
| """Get BOINC tasks"""
|
| client = BOINCClient()
|
| tasks = client.list_tasks(status=status)
|
| return {"tasks": [vars(t) for t in tasks]}
|
|
|
|
|
| @app.post("/api/boinc/submit")
|
| async def submit_boinc_task(workunit_type: str, input_file: str):
|
| """Submit new BOINC task"""
|
| manager = BOINCTaskManager()
|
|
|
| if workunit_type == "variant_calling":
|
| task_id = manager.submit_variant_calling(input_file)
|
| elif workunit_type == "blast_search":
|
| task_id = manager.submit_blast_search(input_file)
|
| else:
|
| task_id = manager.client.submit_task(workunit_type, input_file)
|
|
|
| return {"task_id": task_id, "status": "submitted"}
|
|
|
|
|
| @app.get("/api/boinc/statistics")
|
| async def get_boinc_statistics():
|
| """Get BOINC statistics"""
|
| client = BOINCClient()
|
| stats = client.get_statistics()
|
| return stats
|
|
|
|
|
|
|
| @app.get("/api/gdc/projects")
|
| async def get_gdc_projects():
|
| """Get available GDC projects"""
|
| projects = [
|
| {"id": "TCGA-BRCA", "name": "Breast Cancer", "cases": 1098},
|
| {"id": "TCGA-LUAD", "name": "Lung Adenocarcinoma", "cases": 585},
|
| {"id": "TCGA-COAD", "name": "Colon Adenocarcinoma", "cases": 461},
|
| {"id": "TCGA-GBM", "name": "Glioblastoma", "cases": 617},
|
| {"id": "TARGET-AML", "name": "Acute Myeloid Leukemia", "cases": 238},
|
| ]
|
| return {"projects": projects}
|
|
|
|
|
| @app.get("/api/gdc/files/{project_id}")
|
| async def search_gdc_files(project_id: str, limit: int = 10):
|
| """Search GDC files for a project"""
|
| client = GDCClient()
|
| files = client.get_project_files(project_id, limit=limit)
|
| return {"files": [vars(f) for f in files]}
|
|
|
|
|
| @app.post("/api/gdc/download")
|
| async def download_gdc_file(file_id: str):
|
| """Download a file from GDC"""
|
| client = GDCClient()
|
| file_path = client.download_file(file_id)
|
|
|
| if file_path:
|
| return {"status": "success", "file_path": str(file_path)}
|
| else:
|
| raise HTTPException(status_code=500, detail="Download failed")
|
|
|
|
|
|
|
| @app.post("/api/pipeline/fastq/qc")
|
| async def run_fastq_qc(file_path: str):
|
| """Run FASTQ quality control"""
|
| processor = FASTQProcessor()
|
| stats = processor.calculate_statistics(Path(file_path))
|
| return {"statistics": stats}
|
|
|
|
|
| @app.post("/api/pipeline/blast")
|
| async def run_blast(query_file: str):
|
| """Run BLAST search"""
|
| runner = BLASTRunner()
|
| output_file = runner.run_blastn(Path(query_file))
|
|
|
| if output_file:
|
| hits = runner.parse_results(output_file)
|
| return {
|
| "status": "success",
|
| "output_file": str(output_file),
|
| "total_hits": len(hits),
|
| "hits": hits[:10]
|
| }
|
| else:
|
| raise HTTPException(status_code=500, detail="BLAST search failed")
|
|
|
|
|
| @app.post("/api/pipeline/variants")
|
| async def call_variants(alignment_file: str, reference_genome: str):
|
| """Call variants from alignment"""
|
| caller = VariantCaller()
|
| vcf_file = caller.call_variants(
|
| Path(alignment_file),
|
| Path(reference_genome)
|
| )
|
|
|
| variants = caller.filter_variants(vcf_file)
|
|
|
| return {
|
| "status": "success",
|
| "vcf_file": str(vcf_file),
|
| "total_variants": len(variants),
|
| "variants": [vars(v) for v in variants]
|
| }
|
|
|
|
|
|
|
| @app.get("/api/neo4j/summary")
|
| async def get_database_summary():
|
| """Get database summary statistics"""
|
| db = DatabaseManager()
|
|
|
| query = """
|
| MATCH (g:Gene) WITH count(g) as genes
|
| MATCH (m:Mutation) WITH genes, count(m) as mutations
|
| MATCH (p:Patient) WITH genes, mutations, count(p) as patients
|
| MATCH (c:CancerType) WITH genes, mutations, patients, count(c) as cancer_types
|
| RETURN genes, mutations, patients, cancer_types
|
| """
|
|
|
| result = db.execute_query(query)
|
| db.close()
|
|
|
| return result[0] if result else {}
|
|
|
|
|
| @app.get("/api/neo4j/genes/{symbol}")
|
| async def get_gene_info(symbol: str):
|
| """Get gene information"""
|
| db = DatabaseManager()
|
| from backend.neo4j.db_manager import GeneRepository
|
|
|
| repo = GeneRepository(db)
|
| gene = repo.get_gene_by_symbol(symbol)
|
|
|
| if gene:
|
| mutations = repo.get_gene_mutations(gene['gene_id'])
|
| db.close()
|
| return {
|
| "gene": gene,
|
| "mutations": mutations,
|
| "mutation_count": len(mutations)
|
| }
|
|
|
| db.close()
|
| raise HTTPException(status_code=404, detail="Gene not found")
|
|
|
|
|
| if __name__ == "__main__":
|
| uvicorn.run(app, host="0.0.0.0", port=5000)
|
|
|