Files
Brancheneinstufung2/heatmap-tool/backend/main.py

138 lines
4.8 KiB
Python

from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import pandas as pd
import io
from pydantic import BaseModel
from typing import Dict, List
app = FastAPI()
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allows all origins
allow_credentials=True,
allow_methods=["*"], # Allows all methods
allow_headers=["*"], # Allows all headers
)
# --- In-memory Storage & Data Loading ---
df_storage = None
plz_column_name = None
plz_geocoord_df = None
@app.on_event("startup")
def load_plz_data():
global plz_geocoord_df
try:
print("--- Loading PLZ geocoordinates dataset... ---")
df = pd.read_csv("plz_geocoord.csv", dtype={'plz': str})
df['plz'] = df['plz'].str.zfill(5)
plz_geocoord_df = df.set_index('plz')
print(f"--- Successfully loaded {len(plz_geocoord_df)} PLZ coordinates. ---")
except FileNotFoundError:
print("--- FATAL ERROR: plz_geocoord.csv not found. Geocoding will not work. ---")
# In a real app, you might want to exit or handle this more gracefully
plz_geocoord_df = pd.DataFrame()
# --- Pydantic Models ---
class FilterRequest(BaseModel):
filters: Dict[str, List[str]]
# --- API Endpoints ---
@app.get("/")
def read_root():
return {"message": "Heatmap Tool Backend"}
@app.post("/api/upload")
async def upload_file(file: UploadFile = File(...)):
global df_storage, plz_column_name
print(f"--- Received request to /api/upload for file: {file.filename} ---")
if not file.filename.endswith('.xlsx'):
raise HTTPException(status_code=400, detail="Invalid file format. Please upload an .xlsx file.")
try:
contents = await file.read()
df = pd.read_excel(io.BytesIO(contents), dtype=str) # Read all as string to be safe
df.fillna('N/A', inplace=True)
# --- PLZ Column Detection ---
temp_plz_col = None
for col in df.columns:
if 'plz' in col.lower():
temp_plz_col = col
break
if not temp_plz_col:
raise HTTPException(status_code=400, detail="No column with 'PLZ' found in the file.")
plz_column_name = temp_plz_col
# Normalize PLZ data
df[plz_column_name] = df[plz_column_name].str.strip().str.zfill(5)
# --- Dynamic Filter Detection ---
filters = {}
for col in df.columns:
if col != plz_column_name:
unique_values = df[col].unique().tolist()
filters[col] = sorted(unique_values)
df_storage = df
print(f"Successfully processed file. Found PLZ column: '{plz_column_name}'. Detected {len(filters)} filterable columns.")
return {"filename": file.filename, "filters": filters, "plz_column": plz_column_name}
except Exception as e:
print(f"ERROR processing file: {e}")
raise HTTPException(status_code=500, detail=f"An error occurred while processing the file: {e}")
@app.post("/api/heatmap")
async def get_heatmap_data(request: FilterRequest):
global df_storage, plz_column_name, plz_geocoord_df
print(f"--- Received request to /api/heatmap with filters: {request.filters} ---")
if df_storage is None:
print("ERROR: No data in df_storage. File must be uploaded first.")
raise HTTPException(status_code=404, detail="No data available. Please upload a file first.")
if plz_geocoord_df.empty:
raise HTTPException(status_code=500, detail="Geocoding data is not available on the server.")
try:
filtered_df = df_storage.copy()
# Apply filters from the request
for column, values in request.filters.items():
if values:
filtered_df = filtered_df[filtered_df[column].isin(values)]
if filtered_df.empty:
return []
# Aggregate data by PLZ
plz_counts = filtered_df.groupby(plz_column_name).size().reset_index(name='count')
# --- Geocoding Step ---
# Merge the aggregated counts with the geocoding dataframe
merged_df = pd.merge(
plz_counts,
plz_geocoord_df,
left_on=plz_column_name,
right_index=True,
how='inner'
)
# Rename columns to match frontend expectations ('lon' and 'lat')
merged_df.rename(columns={'x': 'lon', 'y': 'lat'}, inplace=True)
# Convert to the required JSON format
heatmap_data = merged_df[['plz', 'lat', 'lon', 'count']].to_dict(orient='records')
print(f"Generated heatmap data with {len(heatmap_data)} PLZ points.")
return heatmap_data
except Exception as e:
print(f"ERROR generating heatmap: {e}")
raise HTTPException(status_code=500, detail=f"An error occurred while generating heatmap data: {e}")