import csv import uuid from datetime import datetime, timezone from pathlib import Path from flask import Flask, redirect, render_template, request, send_file, url_for from app.diagram import render_diagram from app.explorer import render_explorer from app.parser import parse_gam_raw_csv, sharing_matrix, summarize, top_stats BASE_DIR = Path(__file__).resolve().parent.parent DATA_DIR = BASE_DIR / "data" DATA_DIR.mkdir(parents=True, exist_ok=True) app = Flask( __name__, template_folder=str(BASE_DIR / "templates"), static_folder=str(BASE_DIR / "static"), ) app.config["MAX_CONTENT_LENGTH"] = 50 * 1024 * 1024 STATE = { "last_job": None, "last_error": None, } def write_csv(path: Path, rows: list[dict]) -> None: if not rows: return with path.open("w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=list(rows[0].keys())) w.writeheader() w.writerows(rows) @app.get("/") def index(): last_job = STATE["last_job"] context = { "has_data": False, "stats": {}, "diagram_path": None, "cleaned_path": None, "summary_path": None, "sharing_matrix_path": None, "explorer_path": None, "explorer_url": None, "build_id": None, "last_upload_time": None, "process_ms": None, "curated_count": None, "error": STATE.get("last_error"), } if last_job: context.update(last_job) context["has_data"] = True return render_template("index.html", **context) @app.post("/upload") def upload_csv(): upload = request.files.get("csv_file") if not upload or not upload.filename: return redirect(url_for("index")) try: t0 = datetime.now(timezone.utc) job_id = uuid.uuid4().hex[:10] job_dir = DATA_DIR / job_id job_dir.mkdir(parents=True, exist_ok=True) raw_path = job_dir / "gdrive_test_raw.csv" upload.save(raw_path) curated_rows = parse_gam_raw_csv(raw_path) summary_rows = summarize(curated_rows) sharing_rows = sharing_matrix(curated_rows) stats = top_stats(curated_rows) cleaned_csv = job_dir / "gdrive_curated.csv" summary_csv = job_dir / "gdrive_summary.csv" sharing_csv = job_dir / "gdrive_sharing_matrix.csv" diagram_html = job_dir / "diagram.html" explorer_html = job_dir / "explorer.html" write_csv(cleaned_csv, curated_rows) write_csv(summary_csv, summary_rows) write_csv(sharing_csv, sharing_rows) render_diagram(summary_rows, diagram_html) build_id = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S") render_explorer(curated_rows, explorer_html, build_id=build_id) t1 = datetime.now(timezone.utc) process_ms = int((t1 - t0).total_seconds() * 1000) STATE["last_job"] = { "stats": stats, "diagram_path": f"/data/{job_id}/diagram.html", "cleaned_path": f"/download/{job_id}/cleaned", "summary_path": f"/download/{job_id}/summary", "sharing_matrix_path": f"/download/{job_id}/sharing-matrix", "explorer_path": f"/data/{job_id}/explorer.html", "explorer_url": f"/data/{job_id}/explorer.html?v={build_id}", "build_id": build_id, "last_upload_time": t1.strftime("%Y-%m-%d %H:%M:%S UTC"), "process_ms": process_ms, "curated_count": len(curated_rows), "job_id": job_id, } STATE["last_error"] = None except Exception as exc: STATE["last_error"] = ( "Upload failed. Check CSV format and try again. " f"Error: {exc}" ) return redirect(url_for("index")) @app.get("/download//") def download(job_id: str, kind: str): job_dir = DATA_DIR / job_id if kind == "cleaned": return send_file(job_dir / "gdrive_curated.csv", as_attachment=True) if kind == "summary": return send_file(job_dir / "gdrive_summary.csv", as_attachment=True) if kind == "sharing-matrix": return send_file(job_dir / "gdrive_sharing_matrix.csv", as_attachment=True) return redirect(url_for("index")) @app.get("/data//diagram.html") def serve_diagram(job_id: str): return send_file(DATA_DIR / job_id / "diagram.html") @app.get("/data//explorer.html") def serve_explorer(job_id: str): return send_file(DATA_DIR / job_id / "explorer.html") if __name__ == "__main__": app.run(host="0.0.0.0", port=8080, debug=False)