CoolFace
Apppublic

kussssh/IPO-Analyzer

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
app.py313 linesDownload Raw Back to backend
1"""2FastAPI Application — IPO Analyzer API3Endpoints: SEBI Scraper → Download PDF → Analyze → Get Results4Supports DRHP, RHP, and Prospectus document types.5"""6import json7import re8import uuid9import shutil10import threading11from pathlib import Path12from typing import Dict, Any13 14from fastapi import FastAPI, UploadFile, File, HTTPException15from fastapi.middleware.cors import CORSMiddleware16from fastapi.staticfiles import StaticFiles17from fastapi.responses import FileResponse, JSONResponse18 19from backend.config import UPLOAD_DIR, OUTPUT_DIR20from backend.pipeline import run_full_analysis21from backend.scraper import (22    scrape_drhp_list,23    scrape_rhp_list,24    scrape_prospectus_list,25    build_ipo_tracker,26    find_pdf_on_detail_page,27    download_pdf,28)29 30app = FastAPI(title="IPO Analyzer API", version="2.0.0")31 32# CORS for frontend33app.add_middleware(34    CORSMiddleware,35    allow_origins=["*"],36    allow_credentials=True,37    allow_methods=["*"],38    allow_headers=["*"],39)40 41# Serve frontend static files42FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend"43if FRONTEND_DIR.exists():44    app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR)), name="static")45 46# In-memory job tracker47jobs: Dict[str, Dict[str, Any]] = {}48 49 50@app.get("/")51async def root():52    """Serve frontend or health check."""53    index_path = FRONTEND_DIR / "index.html"54    if index_path.exists():55        return FileResponse(str(index_path))56    return {"status": "ok", "message": "IPO Analyzer API is running"}57 58 59@app.post("/analyze")60async def analyze_drhp(file: UploadFile = File(...)):61    """62    Upload a PDF and start analysis.63    Returns a job_id for polling progress.64    """65    if not file.filename.lower().endswith(".pdf"):66        raise HTTPException(status_code=400, detail="Only PDF files are accepted")67 68    # Save uploaded file69    job_id = str(uuid.uuid4())[:8]70    pdf_filename = f"{job_id}_{file.filename}"71    pdf_path = UPLOAD_DIR / pdf_filename72 73    with open(pdf_path, "wb") as f:74        shutil.copyfileobj(file.file, f)75 76    # Initialize job77    jobs[job_id] = {78        "status": "processing",79        "step": 0,80        "total_steps": 10,81        "message": "Starting analysis...",82        "pdf_filename": file.filename,83        "result": None,84        "error": None,85    }86 87    # Run analysis in background thread88    def _run():89        try:90            def progress_cb(step, total, msg):91                jobs[job_id]["step"] = step92                jobs[job_id]["total_steps"] = total93                jobs[job_id]["message"] = msg94 95            result = run_full_analysis(str(pdf_path), progress_callback=progress_cb)96            jobs[job_id]["status"] = "complete"97            jobs[job_id]["result"] = result98            jobs[job_id]["message"] = "Analysis complete!"99        except Exception as e:100            jobs[job_id]["status"] = "error"101            jobs[job_id]["error"] = str(e)102            jobs[job_id]["message"] = f"Error: {str(e)}"103            import traceback104            traceback.print_exc()105 106    thread = threading.Thread(target=_run, daemon=True)107    thread.start()108 109    return {"job_id": job_id, "message": "Analysis started"}110 111 112@app.get("/status/{job_id}")113async def get_status(job_id: str):114    """Poll analysis progress."""115    if job_id not in jobs:116        raise HTTPException(status_code=404, detail="Job not found")117    job = jobs[job_id]118    return {119        "job_id": job_id,120        "status": job["status"],121        "step": job["step"],122        "total_steps": job["total_steps"],123        "message": job["message"],124        "pdf_filename": job["pdf_filename"],125    }126 127 128@app.get("/result/{job_id}")129async def get_result(job_id: str):130    """Get completed analysis result."""131    if job_id not in jobs:132        raise HTTPException(status_code=404, detail="Job not found")133    job = jobs[job_id]134    if job["status"] == "processing":135        return JSONResponse(status_code=202, content={"message": "Still processing", "step": job["step"]})136    if job["status"] == "error":137        raise HTTPException(status_code=500, detail=job["error"])138    return job["result"]139 140 141@app.get("/results")142async def list_results():143    """List all saved analysis results."""144    results = []145    for f in OUTPUT_DIR.glob("*.json"):146        try:147            with open(f, "r", encoding="utf-8") as fp:148                data = json.load(fp)149            results.append({150                "filename": f.name,151                "company": data.get("company", "Unknown"),152                "verdict": data.get("verdict", "Unknown"),153                "score": data.get("score", {}),154            })155        except Exception:156            continue157    return results158 159 160@app.get("/results/{filename}")161async def get_saved_result(filename: str):162    """Get a previously saved analysis result by filename."""163    filepath = OUTPUT_DIR / filename164    if not filepath.exists():165        raise HTTPException(status_code=404, detail="Result not found")166    with open(filepath, "r", encoding="utf-8") as f:167        return json.load(f)168 169 170# ═══════════════════════════════════════════════════171# SEBI Scraper Endpoints172# ═══════════════════════════════════════════════════173 174@app.get("/sebi/drhp-list")175async def get_sebi_drhp_list():176    """Scrape SEBI website for DRHP filings (filtered)."""177    entries = scrape_drhp_list()178    return {"entries": entries, "count": len(entries)}179 180 181@app.get("/sebi/rhp-list")182async def get_sebi_rhp_list():183    """Scrape SEBI website for RHP filings (filtered)."""184    entries = scrape_rhp_list()185    return {"entries": entries, "count": len(entries)}186 187 188@app.get("/sebi/prospectus-list")189async def get_sebi_prospectus_list():190    """Scrape SEBI website for Prospectus filings (filtered)."""191    entries = scrape_prospectus_list()192    return {"entries": entries, "count": len(entries)}193 194 195@app.get("/sebi/ipo-tracker")196async def get_ipo_tracker():197    """198    Unified IPO tracker — cross-references DRHP, RHP, and Prospectus listings.199    Returns companies with their document availability across all 3 stages.200    """201    tracker = build_ipo_tracker()202    return {"companies": tracker, "count": len(tracker)}203 204 205@app.post("/sebi/analyze")206async def analyze_from_sebi(detail_url: str, name: str, doc_type: str = "drhp"):207    """208    Download a PDF from SEBI and start analysis.209    If already analyzed, returns cached result instantly.210    211    Args:212        detail_url: URL of the SEBI detail page or direct PDF213        name: Company name for display214        doc_type: Document type — 'drhp', 'rhp', or 'prospectus'215    """216    safe_name = re.sub(r'[^\w\-.]', '_', name)[:80]217 218    # ── Check for cached result ──219    for f in OUTPUT_DIR.glob("*.json"):220        if safe_name.lower() in f.name.lower():221            try:222                with open(f, "r", encoding="utf-8") as fp:223                    cached = json.load(fp)224                print(f"📦 Found cached result for: {name} → {f.name}")225                job_id = str(uuid.uuid4())[:8]226                jobs[job_id] = {227                    "status": "complete",228                    "step": 10,229                    "total_steps": 10,230                    "message": "Loaded from cache!",231                    "pdf_filename": name,232                    "result": cached,233                    "error": None,234                }235                return {"job_id": job_id, "message": "Loaded from cache (already analyzed)"}236            except Exception:237                break238 239    # ── Check if PDF already downloaded ──240    existing_pdf = None241    for f in UPLOAD_DIR.glob("*.pdf"):242        if safe_name.lower() in f.name.lower() and f.stat().st_size > 10000:243            existing_pdf = str(f)244            print(f"📦 Found cached PDF: {f.name}")245            break246 247    job_id = str(uuid.uuid4())[:8]248 249    jobs[job_id] = {250        "status": "processing",251        "step": 0,252        "total_steps": 12,253        "message": "Starting..." if existing_pdf else f"Finding {doc_type.upper()} PDF on SEBI...",254        "pdf_filename": name,255        "result": None,256        "error": None,257    }258 259    def _run():260        try:261            def progress_cb(step, total, msg):262                jobs[job_id]["step"] = step + 2263                jobs[job_id]["total_steps"] = total + 2264                jobs[job_id]["message"] = msg265 266            pdf_path = existing_pdf267 268            if not pdf_path:269                # Step 1: Find PDF URL270                jobs[job_id]["message"] = f"Finding {doc_type.upper()} PDF on SEBI detail page..."271                jobs[job_id]["step"] = 1272 273                if detail_url.lower().endswith(".pdf"):274                    pdf_url = detail_url275                else:276                    pdf_url = find_pdf_on_detail_page(detail_url)277 278                if not pdf_url:279                    jobs[job_id]["status"] = "error"280                    jobs[job_id]["error"] = "Could not find PDF on the SEBI detail page."281                    jobs[job_id]["message"] = "Error: PDF not found on detail page"282                    return283 284                # Step 2: Download PDF285                jobs[job_id]["message"] = f"Downloading {doc_type.upper()} PDF..."286                jobs[job_id]["step"] = 2287 288                pdf_filename = f"{job_id}_{safe_name}_{doc_type}.pdf"289                pdf_path = download_pdf(pdf_url, pdf_filename)290            else:291                jobs[job_id]["message"] = "Using cached PDF..."292                jobs[job_id]["step"] = 2293 294            # Step 3+: Run analysis295            result = run_full_analysis(pdf_path, progress_callback=progress_cb)296            jobs[job_id]["status"] = "complete"297            jobs[job_id]["result"] = result298            jobs[job_id]["message"] = "Analysis complete!"299        except Exception as e:300            jobs[job_id]["status"] = "error"301            jobs[job_id]["error"] = str(e)302            jobs[job_id]["message"] = f"Error: {str(e)}"303            import traceback304            traceback.print_exc()305 306    thread = threading.Thread(target=_run, daemon=True)307    thread.start()308 309    return {"job_id": job_id, "message": f"SEBI {doc_type.upper()} analysis started"}310 311 312from backend.api import app as app313