From 25307f3ed4d253449ca9b0d458eaafff230ac380 Mon Sep 17 00:00:00 2001 From: Floke Date: Wed, 4 Feb 2026 14:18:23 +0000 Subject: [PATCH] feat([2fd88f42]): implement smart PLZ column selection --- heatmap-tool/backend/main.py | 44 +++++-- heatmap-tool/frontend/src/App.tsx | 107 ++++++++++++------ .../frontend/src/components/FileUpload.tsx | 2 +- 3 files changed, 110 insertions(+), 43 deletions(-) diff --git a/heatmap-tool/backend/main.py b/heatmap-tool/backend/main.py index f6fdbe18..0f12cd8c 100644 --- a/heatmap-tool/backend/main.py +++ b/heatmap-tool/backend/main.py @@ -45,6 +45,9 @@ def load_plz_data(): class FilterRequest(BaseModel): filters: Dict[str, List[str]] +class PlzColumnRequest(BaseModel): + plz_column: str + # --- API Endpoints --- @app.get("/") def read_root(): @@ -61,7 +64,7 @@ async def upload_file(file: UploadFile = File(...)): 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) - + df_storage = df # Store dataframe temporarily # --- PLZ Column Detection --- temp_plz_col = None @@ -71,30 +74,53 @@ async def upload_file(file: UploadFile = File(...)): break if not temp_plz_col: - raise HTTPException(status_code=400, detail="No column with 'PLZ' found in the file.") + print("PLZ column not found automatically. Asking user for selection.") + return {"plz_column_needed": True, "columns": list(df.columns)} + # If we found a column, proceed as before plz_column_name = temp_plz_col - # Normalize PLZ data df[plz_column_name] = df[plz_column_name].str.strip().str.zfill(5) + df_storage = df # Update storage with normalized PLZ - - # --- 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} + print(f"Successfully processed file. Found PLZ column: '{plz_column_name}'.") + return {"plz_column_needed": False, "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/set-plz-column") +async def set_plz_column(request: PlzColumnRequest): + global df_storage, plz_column_name + print(f"--- Received request to set PLZ column to: {request.plz_column} ---") + if df_storage is None: + raise HTTPException(status_code=400, detail="No data available. Please upload a file first.") + + plz_column_name = request.plz_column + if plz_column_name not in df_storage.columns: + raise HTTPException(status_code=400, detail=f"Column '{plz_column_name}' not found in the uploaded file.") + + # Normalize PLZ data + df_storage[plz_column_name] = df_storage[plz_column_name].str.strip().str.zfill(5) + + # --- Dynamic Filter Detection --- + filters = {} + for col in df_storage.columns: + if col != plz_column_name: + unique_values = df_storage[col].unique().tolist() + filters[col] = sorted(unique_values) + + print(f"Successfully set PLZ column. Detected {len(filters)} filterable columns.") + return {"plz_column_needed": False, "filters": filters, "plz_column": plz_column_name} + + @app.post("/api/heatmap") async def get_heatmap_data(request: FilterRequest): global df_storage, plz_column_name, plz_geocoord_df diff --git a/heatmap-tool/frontend/src/App.tsx b/heatmap-tool/frontend/src/App.tsx index cdd8fb3e..0bb50d5a 100644 --- a/heatmap-tool/frontend/src/App.tsx +++ b/heatmap-tool/frontend/src/App.tsx @@ -30,13 +30,44 @@ function App() { const [error, setError] = useState(null); const [radiusMultiplier, setRadiusMultiplier] = useState(1); const [viewMode, setViewMode] = useState('points'); + + const [plzColumnNeeded, setPlzColumnNeeded] = useState(false); + const [availableColumns, setAvailableColumns] = useState([]); - const handleUploadSuccess = (newFilters: FilterOptions) => { - setFilters(newFilters); - setHeatmapData([]); // Clear previous heatmap data + + const handleUploadSuccess = (response: any) => { setError(null); - // Automatically fetch data with no filters on successful upload - handleFilterChange({}); + if (response.plz_column_needed) { + setAvailableColumns(response.columns); + setPlzColumnNeeded(true); + setFilters({}); + setHeatmapData([]); + } else { + setPlzColumnNeeded(false); + setFilters(response.filters); + setHeatmapData([]); // Clear previous heatmap data + // Automatically fetch data with no filters on successful upload + handleFilterChange({}); + } + }; + + const handlePlzColumnSubmit = async (selectedColumn: string) => { + setIsLoading(true); + setError(null); + try { + const response = await axios.post('/api/set-plz-column', { + plz_column: selectedColumn, + }); + handleUploadSuccess(response.data); // Re-use the success handler + } catch (error: any) { + if (axios.isAxiosError(error) && error.response) { + setError(`Failed to set PLZ column: ${error.response.data.detail || error.message}`); + } else { + setError(`Failed to set PLZ column: ${error.message}`); + } + } finally { + setIsLoading(false); + } }; const handleFilterChange = async (selectedFilters: FilterOptions) => { @@ -72,36 +103,46 @@ function App() { setIsLoading={setIsLoading} setError={setError} /> - -
-

Map Settings

-
- - -
- - setRadiusMultiplier(parseFloat(e.target.value))} - style={{ width: '100%' }} - disabled={viewMode === 'heatmap'} /> -
+ ) : ( + <> + +
+

Map Settings

+
+ + +
+ + setRadiusMultiplier(parseFloat(e.target.value))} + style={{ width: '100%' }} + disabled={viewMode === 'heatmap'} + /> +
+ + )}
{isLoading &&

Loading map data...

} diff --git a/heatmap-tool/frontend/src/components/FileUpload.tsx b/heatmap-tool/frontend/src/components/FileUpload.tsx index a50e39ef..510c5125 100644 --- a/heatmap-tool/frontend/src/components/FileUpload.tsx +++ b/heatmap-tool/frontend/src/components/FileUpload.tsx @@ -44,7 +44,7 @@ const FileUpload: React.FC = ({ onUploadSuccess, setIsLoading, 'Content-Type': 'multipart/form-data', }, }); - onUploadSuccess(response.data.filters); + onUploadSuccess(response.data); } catch (error: any) { if (axios.isAxiosError(error) && error.response) { setError(`Upload failed: ${error.response.data.detail || error.message}`);