CoolFace
Apppublic

antiperfect/refugee-movement-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
app.py394 linesDownload Raw Back to root
1from flask import Flask, render_template, request, jsonify2from flask_cors import CORS3import joblib4import pandas as pd5import json6import os7import requests as http_requests8 9app = Flask(__name__)10CORS(app)11 12# Load models13BASE_DIR = os.path.dirname(os.path.abspath(__file__))14 15time_model = joblib.load(os.path.join(BASE_DIR, 'time_model.pkl'))16resource_model = joblib.load(os.path.join(BASE_DIR, 'resource_model.pkl'))17 18# Load mapping19with open(os.path.join(BASE_DIR, 'origin_mapping.json')) as f:20    mapping = json.load(f)21 22# Load historical data for lags23DATA_PATH = os.path.join(BASE_DIR, 'data', 'persons_of_concern.csv')24historical_df = pd.read_csv(DATA_PATH)25historical_df['Total_Refugees'] = historical_df['Refugees'] + historical_df['Asylum-seekers']26 27# India's neighboring / nearby countries (subset for highlight)28NEIGHBORS = [29    'Afghanistan', 'Bangladesh', 'China', 'Myanmar',30    'Pakistan', 'Sri Lanka', 'Nepal', 'Bhutan'31]32 33# Cache for predictions34PREDICTION_CACHE = {}35SERIES_CACHE = {}36 37def precompute_predictions(year=2026):38    """Precompute all predictions for a given year to avoid slow runtime calculations."""39    print(f"Precomputing predictions for {year}...", flush=True)40    for country in mapping.keys():41        result = predict_for_country(country, year)42        if result:43            PREDICTION_CACHE[country] = result44    45    # Precompute All Origins Series46    MAX_HIST_YEAR = int(historical_df['Year'].max())47    print(f"Precomputing All Origins Series (2000-2030). Last historical year: {MAX_HIST_YEAR}", flush=True)48    series = []49    50    # Historical51    hist_agg = historical_df.groupby('Year')['Total_Refugees'].sum().reset_index()52    hist_agg.fillna(0, inplace=True)53    for _, row in hist_agg.iterrows():54        y = int(row['Year'])55        if 2000 <= y <= MAX_HIST_YEAR:56            series.append({'x': int(y), 'y': int(row['Total_Refugees'])})57    58    # Predicted59    pred_map = {}60    for c in mapping.keys():61        c_df = historical_df[historical_df['Country of Origin'] == c].sort_values('Year')62        if c_df.empty: continue63        64        last_y = int(c_df.iloc[-1]['Year'])65        l1 = int(c_df.iloc[-1]['Total_Refugees'])66        l2 = int(c_df.iloc[-2]['Total_Refugees'] if len(c_df) > 1 else l1)67        68        if pd.isna(l1): l1 = 069        if pd.isna(l2): l2 = 070        71        enc = mapping[c]72        cy = last_y + 173        74        while cy <= 2030:75            input_df = pd.DataFrame({'Year': [cy], 'Origin_Encoded': [enc], 'Lag_1': [l1], 'Lag_2': [l2]})76            p = max(0, int(time_model.predict(input_df)[0]))77            78            # Only add to pred_map if it's AFTER the historical data for this country79            # and ALSO after the global historical data cutoff to avoid overlaps80            if cy > MAX_HIST_YEAR:81                pred_map[cy] = pred_map.get(cy, 0) + p82            83            l2, l1, cy = l1, p, cy + 184            85    for y, val in sorted(pred_map.items()):86        series.append({'x': int(y), 'y': int(val)})87    88    SERIES_CACHE['all'] = series89    print("Precomputation complete.", flush=True)90 91def predict_for_country(country, year):92    """Run both models for a single country+year and return dict."""93    if country not in mapping:94        return None95 96    origin_encoded = mapping[country]97 98    # Filter historical data for this country99    country_df = historical_df[historical_df['Country of Origin'] == country].sort_values('Year')100    101    if country_df.empty:102        return None103 104    # ---------------- TIME MODEL (Iterative Lags) ----------------105    # If year is in the past or present data106    if year <= country_df.iloc[-1]['Year']:107        row = country_df[country_df['Year'] == year]108        if not row.empty:109            refugees = int(row['Total_Refugees'].values[0])110        else:111            # Default to last known value if specific year missing from history112            refugees = int(country_df.iloc[-1]['Total_Refugees'])113    114    # If year is in the future115    else:116        lag_1 = country_df.iloc[-1]['Total_Refugees']117        lag_2 = country_df.iloc[-2]['Total_Refugees'] if len(country_df) > 1 else lag_1118        119        if pd.isna(lag_1): lag_1 = 0120        if pd.isna(lag_2): lag_2 = 0121        122        current_year = country_df.iloc[-1]['Year'] + 1123        refugees = lag_1 # fallback124 125        while current_year <= year:126            input_df = pd.DataFrame({127                'Year': [current_year],128                'Origin_Encoded': [origin_encoded],129                'Lag_1': [lag_1],130                'Lag_2': [lag_2]131            })132            133            pred = int(time_model.predict(input_df)[0])134            pred = max(0, pred)135            136            lag_2 = lag_1137            lag_1 = pred138            refugees = pred139            current_year += 1140 141    # ---------------- RESOURCE MODEL ----------------142    growth = 0.1  # baseline assumption143    input_data = pd.DataFrame({144        'Year': [year],145        'Origin_Encoded': [origin_encoded],146        'Total_Refugees': [refugees],147        'Refugee_Growth': [growth]148    })149    pred = resource_model.predict(input_data)150 151    return {152        'country': country,153        'year': year,154        'refugees': refugees,155        'food': max(0, int(pred[0][0])),156        'shelter': max(0, int(pred[0][1])),157        'medical': max(0, int(pred[0][2])),158        'water': max(0, int(pred[0][3])),159        'is_neighbor': country in NEIGHBORS160    }161 162 163# ================== HTML ROUTES (original) ==================164 165@app.route('/')166def home():167    return jsonify({168        "status": "online",169        "message": "Refugee Movement Prediction API is running",170        "version": "1.0.0"171    })172 173@app.route('/predict', methods=['POST'])174def predict():175    year = int(request.form['year'])176    country = request.form['country']177    result = predict_for_country(country, year)178 179    if result is None:180        return "Country not found", 404181 182    return render_template('result.html',183                           refugees=result['refugees'],184                           food=result['food'],185                           shelter=result['shelter'],186                           medical=result['medical'],187                           water=result['water'],188                           country=country,189                           year=year)190 191 192# ================== JSON API ROUTES (for React frontend) ==================193 194@app.route('/api/predict', methods=['GET'])195def api_predict():196    """Single country prediction.197    GET /api/predict?country=Afghanistan&year=2026198    """199    country = request.args.get('country')200    year = request.args.get('year', 2026, type=int)201 202    if not country or country not in mapping:203        return jsonify({'error': 'Invalid country'}), 400204 205    result = predict_for_country(country, year)206    return jsonify(result)207 208 209@app.route('/api/predict-all', methods=['GET'])210def api_predict_all():211    """Predict for ALL countries in the model.212    GET /api/predict-all?year=2026213    Returns array of predictions sorted by refugee count descending.214    """215    year = request.args.get('year', 2026, type=int)216    217    # If it's the default year and we have a cache, use it218    if year == 2026 and PREDICTION_CACHE:219        results = list(PREDICTION_CACHE.values())220    else:221        results = []222        for country in mapping.keys():223            result = predict_for_country(country, year)224            if result:225                results.append(result)226 227    # Sort by refugee count descending228    results.sort(key=lambda x: x['refugees'], reverse=True)229    return jsonify(results)230 231 232@app.route('/api/countries', methods=['GET'])233def api_countries():234    """Return list of all available countries."""235    countries = list(mapping.keys())236    countries.sort()237    return jsonify({238        'countries': countries,239        'neighbors': [c for c in NEIGHBORS if c in mapping],240        'total': len(countries)241    })242 243 244@app.route('/api/series', methods=['GET'])245def api_series():246    """Return combined historical (2000-MAX_HIST_YEAR) and predicted (MAX_HIST_YEAR+1 - 2030) series.247    GET /api/series?country=Afghanistan248    If no country provided, returns sum of all countries.249    """250    country = request.args.get('country')251    series = []252 253    if country:254        # Single Country255        if country not in mapping:256            return jsonify({'error': 'Country not found'}), 404257        258        # Historical259        MAX_HIST_YEAR = int(historical_df['Year'].max())260        hist = historical_df[historical_df['Country of Origin'] == country].copy()261        hist.fillna(0, inplace=True)262        for _, row in hist.iterrows():263            y = int(row['Year'])264            if 2000 <= y <= MAX_HIST_YEAR:265                series.append({'x': int(y), 'y': int(row['Total_Refugees'])})266        267        # Predicted268        country_df = historical_df[historical_df['Country of Origin'] == country].sort_values('Year')269        if not country_df.empty:270            last_y = int(country_df.iloc[-1]['Year'])271            lag_1 = int(country_df.iloc[-1]['Total_Refugees'])272            lag_2 = int(country_df.iloc[-2]['Total_Refugees'] if len(country_df) > 1 else lag_1)273            274            if pd.isna(lag_1): lag_1 = 0275            if pd.isna(lag_2): lag_2 = 0276            277            origin_encoded = mapping[country]278            279            curr_y = last_y + 1280            while curr_y <= 2030:281                input_df = pd.DataFrame({282                    'Year': [curr_y],283                    'Origin_Encoded': [origin_encoded],284                    'Lag_1': [lag_1],285                    'Lag_2': [lag_2]286                })287                pred = int(time_model.predict(input_df)[0])288                pred = max(0, pred)289                290                if curr_y > MAX_HIST_YEAR:291                    series.append({'x': int(curr_y), 'y': int(pred)})292                293                lag_2, lag_1 = lag_1, pred294                curr_y += 1295    else:296        # All Origins (Aggregated)297        if 'all' in SERIES_CACHE:298            return jsonify(SERIES_CACHE['all'])299            300        # Historical301        MAX_HIST_YEAR = int(historical_df['Year'].max())302        hist_agg = historical_df.groupby('Year')['Total_Refugees'].sum().reset_index()303        hist_agg.fillna(0, inplace=True)304        for _, row in hist_agg.iterrows():305            y = int(row['Year'])306            if 2000 <= y <= MAX_HIST_YEAR:307                series.append({'x': int(y), 'y': int(row['Total_Refugees'])})308        309        # Predicted for All Origins310        pred_map = {} # year -> sum311        for c in mapping.keys():312            c_df = historical_df[historical_df['Country of Origin'] == c].sort_values('Year')313            if c_df.empty: continue314            315            last_y = int(c_df.iloc[-1]['Year'])316            l1 = int(c_df.iloc[-1]['Total_Refugees'])317            l2 = int(c_df.iloc[-2]['Total_Refugees'] if len(c_df) > 1 else l1)318            enc = mapping[c]319            cy = last_y + 1320            321            while cy <= 2030:322                input_df = pd.DataFrame({'Year': [cy], 'Origin_Encoded': [enc], 'Lag_1': [l1], 'Lag_2': [l2]})323                p = max(0, int(time_model.predict(input_df)[0]))324                if cy > MAX_HIST_YEAR:325                    pred_map[cy] = pred_map.get(cy, 0) + p326                l2, l1, cy = l1, p, cy + 1327        328        for y, val in sorted(pred_map.items()):329            series.append({'x': y, 'y': val})330 331    series.sort(key=lambda x: x['x'])332    return jsonify(series)333 334 335@app.route('/api/news', methods=['GET'])336def api_news():337    """Fetch humanitarian news โ€” tries GDELT first, then UN News RSS fallback."""338    # Try GDELT339    try:340        gdelt_url = 'https://api.gdeltproject.org/api/v2/doc/doc'341        params = {342            'query': 'refugee displacement India humanitarian',343            'mode': 'ArtList',344            'format': 'json',345            'maxrecords': 15,346            'sort': 'DateDesc'347        }348        resp = http_requests.get(gdelt_url, params=params, timeout=15)349        if resp.status_code == 200:350            data = resp.json()351            if 'articles' in data and len(data['articles']) > 0:352                articles = []353                for art in data['articles'][:15]:354                    articles.append({355                        'title': art.get('title', 'Untitled'),356                        'url': art.get('url', '#'),357                        'seendate': art.get('seendate', ''),358                        'domain': art.get('domain', 'News'),359                    })360                return jsonify({'articles': articles, 'source': 'GDELT'})361    except Exception:362        pass  # Fall through to RSS fallback363 364    # Fallback: UN News RSS feed (refugee/migration topic)365    try:366        import xml.etree.ElementTree as ET367        rss_url = 'https://news.un.org/feed/subscribe/en/news/topic/migrants-and-refugees/feed/rss.xml'368        resp = http_requests.get(rss_url, timeout=10)369        if resp.status_code == 200:370            root = ET.fromstring(resp.content)371            articles = []372            for item in root.findall('.//item')[:15]:373                title = item.find('title')374                link = item.find('link')375                pubdate = item.find('pubDate')376                articles.append({377                    'title': title.text if title is not None else 'Untitled',378                    'url': link.text if link is not None else '#',379                    'seendate': pubdate.text if pubdate is not None else '',380                    'domain': 'UN News',381                })382            if articles:383                return jsonify({'articles': articles, 'source': 'UN News RSS'})384    except Exception:385        pass386 387    return jsonify({'articles': [], 'source': 'none', 'error': 'All news sources unavailable'})388 389 390# ================== RUN ==================391if __name__ == '__main__':392    # Precompute for the default year (2026) to ensure fast startup393    precompute_predictions(2026)394    app.run(debug=False, port=5000)