Spaces:
Sleeping
Sleeping
| """ | |
| app.py β Flask API for FYP Dashboard | |
| Serves precomputed_data.json generated by analyze.py | |
| Endpoints: | |
| GET /api/summary β summary stats (cards) | |
| GET /api/titles β top job titles + salary by title | |
| GET /api/skills β top skills, skills by title, optimal skills | |
| GET /api/trends β salary trends + skill trends over time | |
| GET /api/location β remote breakdown + top countries | |
| GET /api/all β everything in one call (used by dashboard) | |
| """ | |
| import json | |
| import os | |
| from flask import Flask, jsonify | |
| from flask_cors import CORS | |
| app = Flask(__name__) | |
| # Allow your Vercel frontend domain β update this after deploying | |
| CORS(app, origins=[ | |
| "http://localhost:3000", # local dev | |
| "https://*.vercel.app", # any vercel preview | |
| ]) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Load precomputed data once at startup | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| DATA_PATH = os.path.join(os.path.dirname(__file__), "precomputed_data.json") | |
| def load_data(): | |
| if not os.path.exists(DATA_PATH): | |
| raise FileNotFoundError( | |
| f"precomputed_data.json not found at {DATA_PATH}\n" | |
| "Run analyze.py first to generate it." | |
| ) | |
| with open(DATA_PATH, "r") as f: | |
| return json.load(f) | |
| try: | |
| DATA = load_data() | |
| print(f"[OK] Loaded precomputed data") | |
| print(f" Generated at: {DATA['meta']['generated_at']}") | |
| print(f" Total rows: {DATA['meta']['total_rows_processed']:,}") | |
| except FileNotFoundError as e: | |
| print(f"[ERROR] {e}") | |
| DATA = None | |
| def data_required(fn): | |
| """Decorator β returns 503 if data not loaded.""" | |
| from functools import wraps | |
| def wrapper(*args, **kwargs): | |
| if DATA is None: | |
| return jsonify({ | |
| "error": "Data not ready. Run analyze.py first." | |
| }), 503 | |
| return fn(*args, **kwargs) | |
| return wrapper | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Routes | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def index(): | |
| if DATA is None: | |
| return jsonify({"status": "error", "message": "Run analyze.py first"}), 503 | |
| return jsonify({ | |
| "status": "ok", | |
| "generated_at": DATA["meta"]["generated_at"], | |
| "total_rows": DATA["meta"]["total_rows_processed"], | |
| "endpoints": [ | |
| "/api/summary", | |
| "/api/titles", | |
| "/api/skills", | |
| "/api/trends", | |
| "/api/location", | |
| "/api/all", | |
| ] | |
| }) | |
| def get_summary(): | |
| return jsonify(DATA["summary_stats"]) | |
| def get_titles(): | |
| return jsonify({ | |
| "top_titles": DATA["top_titles"], | |
| "salary_by_title": DATA["salary_by_title"], | |
| }) | |
| def get_skills(): | |
| return jsonify({ | |
| "top_skills": DATA["top_skills"], | |
| "skills_by_title": DATA["skills_by_title"], | |
| "optimal_skills": DATA["optimal_skills"], | |
| }) | |
| def get_trends(): | |
| return jsonify({ | |
| "salary_trends": DATA["salary_trends"], | |
| "skill_trends": DATA["skill_trends"], | |
| }) | |
| def get_location(): | |
| return jsonify({ | |
| "remote_breakdown": DATA["remote_breakdown"], | |
| "top_countries": DATA["top_countries"], | |
| }) | |
| def get_all(): | |
| """Single endpoint β dashboard calls this once on load.""" | |
| return jsonify(DATA) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Run | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| port = int(os.environ.get("PORT", 7860)) | |
| debug = os.environ.get("FLASK_ENV") != "production" | |
| print(f"Starting Flask on port {port} (debug={debug})") | |
| app.run(host="0.0.0.0", port=port, debug=debug) |