import streamlit as st import requests import pandas as pd import time import json from datetime import datetime, timedelta import asyncio import aiohttp from typing import Dict, Any, List import xml.etree.ElementTree as ET import io import sys # Configuration st.set_page_config( page_title="🇾đŸ‡Ș API Data Fetcher", page_icon="🇾đŸ‡Ș", layout="wide", initial_sidebar_state="expanded" ) # Real API Configuration API_CONFIG = { "Skolverket": { "base_url": "https://api.skolverket.se", "endpoints": { "planned-educations": "/planned-educations/v3/compact-school-units", "skolenhetsregister": "/skolenhetsregister/v2/skolenhet", "syllabus": "/syllabus/v1/studievag" }, "headers": {"accept": "application/json"}, "rate_limit": None }, "SCB": { "base_url": "https://api.scb.se", "endpoints": { "befolkning": "/OV0104/v1/doris/sv/ssd/BE/BE0101/BE0101A/BefolkningNy" }, "headers": {"accept": "application/json"}, "rate_limit": {"requests": 10, "per_seconds": 10} }, "Kolada": { "base_url": "https://api.kolada.se", "endpoints": { "kpi": "/v2/kpi", "data": "/v2/data/kpi", "municipality": "/v2/municipality" }, "headers": {"accept": "application/json"}, "rate_limit": None }, "VĂ€rldsbanken": { "base_url": "https://api.worldbank.org", "endpoints": { "indicators": "/v2/country/{country}/indicator/{indicator}", "countries": "/v2/country" }, "headers": {"accept": "application/json"}, "rate_limit": None }, "Riksbanken": { "base_url": "https://api.riksbank.se", "endpoints": { "observations": "/swea/v1/Observations/{series}/{from_date}/{to_date}" }, "headers": {"accept": "application/json"}, "rate_limit": None }, "CSN": { "base_url": "https://statistik.csn.se", "endpoints": { "studiemedel": "/PXWeb/api/v1/sv/CSNstat/Studiestod/Studiemedel/Hogskola/{table}.px" }, "headers": {"accept": "application/json"}, "rate_limit": None } } class RealDataFetcher: def __init__(self): self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'API-Data-Fetcher/1.0 (Educational Purpose)' }) def fetch_skolverket_data(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]: """Fetch real data from Skolverket API""" try: config = API_CONFIG["Skolverket"] url = config["base_url"] + config["endpoints"][endpoint] # Set proper headers for Skolverket headers = config["headers"].copy() if endpoint == "planned-educations": headers["accept"] = "application/vnd.skolverket.plannededucations.api.v3.hal+json" response = self.session.get(url, params=params, headers=headers, timeout=30) response.raise_for_status() data = response.json() # Extract data based on endpoint if endpoint == "planned-educations": school_units = data.get("_embedded", {}).get("compactSchoolUnits", []) return { "status": "success", "data": school_units, "total": len(school_units), "timestamp": datetime.now().isoformat(), "source": "Skolverket" } elif endpoint == "skolenhetsregister": school_units = data.get("Skolenheter", []) return { "status": "success", "data": school_units, "total": len(school_units), "timestamp": datetime.now().isoformat(), "source": "Skolverket" } else: return { "status": "success", "data": data, "timestamp": datetime.now().isoformat(), "source": "Skolverket" } except Exception as e: return { "status": "error", "error": str(e), "timestamp": datetime.now().isoformat(), "source": "Skolverket" } def fetch_scb_data(self, query: str) -> Dict[str, Any]: """Fetch real data from SCB API""" try: config = API_CONFIG["SCB"] url = config["base_url"] + config["endpoints"]["befolkning"] # Parse query if it's JSON string if isinstance(query, str): try: query_data = json.loads(query) except: query_data = {"query": [], "response": {"format": "json"}} else: query_data = query response = self.session.post(url, json=query_data, headers=config["headers"], timeout=30) response.raise_for_status() data = response.json() return { "status": "success", "data": data.get("data", []), "columns": data.get("columns", []), "timestamp": datetime.now().isoformat(), "source": "SCB" } except Exception as e: return { "status": "error", "error": str(e), "timestamp": datetime.now().isoformat(), "source": "SCB" } def fetch_kolada_data(self, kpi: str = None, municipality: str = None, year: int = None) -> Dict[str, Any]: """Fetch real data from Kolada API""" try: config = API_CONFIG["Kolada"] if kpi: # Fetch KPI data url = config["base_url"] + config["endpoints"]["data"] + f"/{kpi}" params = {} if municipality: params["municipality"] = municipality if year: params["year"] = year else: # Fetch municipality list url = config["base_url"] + config["endpoints"]["municipality"] params = {} response = self.session.get(url, params=params, headers=config["headers"], timeout=30) response.raise_for_status() data = response.json() return { "status": "success", "data": data.get("values", []), "timestamp": datetime.now().isoformat(), "source": "Kolada" } except Exception as e: return { "status": "error", "error": str(e), "timestamp": datetime.now().isoformat(), "source": "Kolada" } def fetch_worldbank_data(self, country: str, indicator: str, year: int) -> Dict[str, Any]: """Fetch real data from World Bank API""" try: config = API_CONFIG["VĂ€rldsbanken"] url = config["base_url"] + config["endpoints"]["indicators"].format( country=country, indicator=indicator ) params = { "format": "json", "date": str(year) } response = self.session.get(url, params=params, headers=config["headers"], timeout=30) response.raise_for_status() data = response.json() # World Bank returns [metadata, data] array if len(data) > 1 and data[1]: result_data = data[1] else: result_data = [] return { "status": "success", "data": result_data, "timestamp": datetime.now().isoformat(), "source": "VĂ€rldsbanken" } except Exception as e: return { "status": "error", "error": str(e), "timestamp": datetime.now().isoformat(), "source": "VĂ€rldsbanken" } def fetch_riksbank_data(self, series: str, from_date: str, to_date: str) -> Dict[str, Any]: """Fetch real data from Riksbank API""" try: config = API_CONFIG["Riksbanken"] url = config["base_url"] + config["endpoints"]["observations"].format( series=series, from_date=from_date, to_date=to_date ) response = self.session.get(url, headers=config["headers"], timeout=30) response.raise_for_status() data = response.json() return { "status": "success", "data": data, "timestamp": datetime.now().isoformat(), "source": "Riksbanken" } except Exception as e: return { "status": "error", "error": str(e), "timestamp": datetime.now().isoformat(), "source": "Riksbanken" } def fetch_csn_data(self, table: str) -> Dict[str, Any]: """Fetch real data from CSN API""" try: config = API_CONFIG["CSN"] url = config["base_url"] + config["endpoints"]["studiemedel"].format(table=table) # PX-Web requires specific query format query_data = { "query": [], "response": {"format": "json"} } response = self.session.post(url, json=query_data, headers=config["headers"], timeout=30) response.raise_for_status() data = response.json() return { "status": "success", "data": data.get("data", []), "timestamp": datetime.now().isoformat(), "source": "CSN" } except Exception as e: return { "status": "error", "error": str(e), "timestamp": datetime.now().isoformat(), "source": "CSN" } # Initialize fetcher if 'fetcher' not in st.session_state: st.session_state.fetcher = RealDataFetcher() if 'fetch_history' not in st.session_state: st.session_state.fetch_history = [] if 'continuous_mode' not in st.session_state: st.session_state.continuous_mode = False # Header st.title("🇾đŸ‡Ș API Data Fetcher") st.markdown("**HĂ€mta RIKTIG data frĂ„n svenska myndigheter och internationella organisationer**") # Sidebar for API selection st.sidebar.title("🔧 API Konfiguration") # Continuous fetching toggle continuous_mode = st.sidebar.toggle("🔄 Kontinuerlig datahĂ€mtning", value=st.session_state.continuous_mode) st.session_state.continuous_mode = continuous_mode if continuous_mode: fetch_interval = st.sidebar.slider("⏱ HĂ€mtningsintervall (sekunder)", 10, 300, 60) st.sidebar.info("Kontinuerlig hĂ€mtning aktiverad") # API Selection selected_api = st.sidebar.selectbox( "VĂ€lj API", list(API_CONFIG.keys()), help="VĂ€lj vilken datakĂ€lla du vill hĂ€mta RIKTIG data frĂ„n" ) # Dynamic form based on selected API st.sidebar.subheader(f"⚙ {selected_api} InstĂ€llningar") def create_api_form(api_name: str): """Create dynamic form for selected API""" params = {} if api_name == "Skolverket": endpoint = st.sidebar.selectbox( "Endpoint", ["planned-educations", "skolenhetsregister", "syllabus"] ) params['endpoint'] = endpoint if endpoint == "planned-educations": params['coordinateSystemType'] = st.sidebar.selectbox("Koordinatsystem", ["WGS84", "SWEREF99"]) params['page'] = st.sidebar.number_input("Sida", 0, 100, 0) params['size'] = st.sidebar.number_input("Storlek", 1, 100, 20) elif endpoint == "skolenhetsregister": kommun = st.sidebar.text_input("Kommunkod (valfritt)", placeholder="t.ex. 1280") if kommun: params['kommunkod'] = kommun elif endpoint == "syllabus": params['studievagstyp'] = st.sidebar.selectbox("StudievĂ€gstyp", ["GY", "GR", "GRSÄR"]) elif api_name == "SCB": st.sidebar.warning("⚠ SCB har rate limit: 10 anrop/10 sek") params['query'] = st.sidebar.text_area( "SCB Query (JSON)", value='{"query": [], "response": {"format": "json"}}', help="Ange SCB query i JSON-format för RIKTIG data" ) elif api_name == "Kolada": kpi_code = st.sidebar.text_input("KPI-kod (valfritt)", placeholder="t.ex. N15020") municipality = st.sidebar.text_input("Kommun (valfritt)", placeholder="t.ex. 1280") year = st.sidebar.number_input("År (valfritt)", 2000, 2024, 2023) if kpi_code: params['kpi'] = kpi_code if municipality: params['municipality'] = municipality if year: params['year'] = year elif api_name == "VĂ€rldsbanken": country = st.sidebar.text_input("Land (ISO-kod)", value="se", placeholder="t.ex. se") indicator = st.sidebar.selectbox( "Indikator", ["NY.GDP.MKTP.CD", "SP.POP.TOTL", "SE.XPD.TOTL.GD.ZS"], format_func=lambda x: { "NY.GDP.MKTP.CD": "BNP (USD)", "SP.POP.TOTL": "Total befolkning", "SE.XPD.TOTL.GD.ZS": "Utbildningsutgifter (% av BNP)" }.get(x, x) ) year = st.sidebar.number_input("År", 1960, 2024, 2023) params['country'] = country params['indicator'] = indicator params['year'] = year elif api_name == "Riksbanken": series = st.sidebar.selectbox( "Dataserie", ["SEKEURPMI", "SEKUSDPMI", "SECOVERRPO"], format_func=lambda x: { "SEKEURPMI": "EUR/SEK kurs", "SEKUSDPMI": "USD/SEK kurs", "SECOVERRPO": "ReporĂ€nta" }.get(x, x) ) from_date = st.sidebar.date_input("FrĂ„n datum", datetime.now() - timedelta(days=30)) to_date = st.sidebar.date_input("Till datum", datetime.now()) params['series'] = series params['from_date'] = from_date.strftime('%Y-%m-%d') params['to_date'] = to_date.strftime('%Y-%m-%d') elif api_name == "CSN": table = st.sidebar.selectbox( "Tabell", ["SS0101B1", "SS0201A1"], format_func=lambda x: { "SS0101B1": "Studiemedel högskola", "SS0201A1": "Återbetalning studielĂ„n" }.get(x, x) ) params['table'] = table return params # Create form for selected API form_params = create_api_form(selected_api) # Main content area col1, col2 = st.columns([2, 1]) with col1: st.subheader(f"📊 {selected_api} RIKTIG Data") # Fetch button if st.button("🚀 HĂ€mta RIKTIG Data", type="primary"): with st.spinner(f"HĂ€mtar RIKTIG data frĂ„n {selected_api}..."): try: # Call real API based on selection if selected_api == "Skolverket": endpoint = form_params.get('endpoint', 'planned-educations') clean_params = {k: v for k, v in form_params.items() if k != 'endpoint'} result = st.session_state.fetcher.fetch_skolverket_data(endpoint, clean_params) elif selected_api == "SCB": result = st.session_state.fetcher.fetch_scb_data(form_params['query']) elif selected_api == "Kolada": result = st.session_state.fetcher.fetch_kolada_data( form_params.get('kpi'), form_params.get('municipality'), form_params.get('year') ) elif selected_api == "VĂ€rldsbanken": result = st.session_state.fetcher.fetch_worldbank_data( form_params['country'], form_params['indicator'], form_params['year'] ) elif selected_api == "Riksbanken": result = st.session_state.fetcher.fetch_riksbank_data( form_params['series'], form_params['from_date'], form_params['to_date'] ) elif selected_api == "CSN": result = st.session_state.fetcher.fetch_csn_data(form_params['table']) else: result = { "status": "error", "error": f"API {selected_api} inte implementerat Ă€n", "timestamp": datetime.now().isoformat(), "source": selected_api } # Add to history st.session_state.fetch_history.append(result) # Display results if result["status"] == "success": st.success(f"✅ RIKTIG data hĂ€mtad frĂ„n {selected_api}") # Convert to DataFrame if possible data = result["data"] if isinstance(data, list) and len(data) > 0: df = pd.DataFrame(data) st.dataframe(df, use_container_width=True) # Download buttons col_json, col_csv = st.columns(2) with col_json: st.download_button( "📄 Ladda ner JSON", data=json.dumps(data, indent=2, ensure_ascii=False, default=str), file_name=f"{selected_api}_REAL_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", mime="application/json" ) with col_csv: csv_buffer = io.StringIO() df.to_csv(csv_buffer, index=False) st.download_button( "📊 Ladda ner CSV", data=csv_buffer.getvalue(), file_name=f"{selected_api}_REAL_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", mime="text/csv" ) else: st.json(data) else: st.error(f"❌ Fel vid hĂ€mtning frĂ„n {selected_api}: {result.get('error', 'OkĂ€nt fel')}") except Exception as e: st.error(f"❌ Kritiskt fel: {str(e)}") st.session_state.fetch_history.append({ "status": "error", "error": str(e), "timestamp": datetime.now().isoformat(), "source": selected_api }) with col2: st.subheader("📈 HĂ€mtningshistorik") if st.session_state.fetch_history: # Show last 5 fetches recent_fetches = st.session_state.fetch_history[-5:] for i, fetch in enumerate(reversed(recent_fetches)): with st.expander(f"{fetch['source']} - {fetch['timestamp'][:19]}"): if fetch['status'] == 'success': st.success("✅ Lyckad hĂ€mtning (RIKTIG DATA)") if isinstance(fetch.get('data'), list): st.metric("Antal rader", len(fetch['data'])) elif isinstance(fetch.get('data'), dict): st.metric("Dataobjekt", "1 objekt") else: st.error("❌ Misslyckad hĂ€mtning") st.text(fetch.get('error', 'OkĂ€nt fel')) else: st.info("Ingen hĂ€mtningshistorik Ă€n") # Clear history button if st.button("đŸ—‘ïž Rensa historik"): st.session_state.fetch_history = [] st.rerun() # Continuous fetching implementation if continuous_mode and selected_api: st.info(f"🔄 Kontinuerlig hĂ€mtning aktiverad för {selected_api}") st.info(f"⏱ HĂ€mtar RIKTIG data var {fetch_interval} sekunder") # Manual refresh button for continuous mode if st.button("🔄 HĂ€mta nu (kontinuerlig mode)", type="secondary"): st.rerun() # Note about continuous fetching st.warning("⚠ Kontinuerlig hĂ€mtning Ă€r aktiverad. AnvĂ€nd knappen ovan för manuell uppdatering av RIKTIG data.") # Footer st.markdown("---") st.markdown("**🇾đŸ‡Ș API Data Fetcher** - HĂ€mtar RIKTIG data frĂ„n svenska myndigheter") st.markdown("✅ **ALLA API:ER ANVÄNDER RIKTIGA ENDPOINTS** - Inga mock-data!") st.markdown("Stödjer: Skolverket, SCB, Kolada, VĂ€rldsbanken, Riksbanken, CSN")