feat([2fd88f42]): implement smart PLZ column selection
This commit is contained in:
@@ -45,6 +45,9 @@ def load_plz_data():
|
|||||||
class FilterRequest(BaseModel):
|
class FilterRequest(BaseModel):
|
||||||
filters: Dict[str, List[str]]
|
filters: Dict[str, List[str]]
|
||||||
|
|
||||||
|
class PlzColumnRequest(BaseModel):
|
||||||
|
plz_column: str
|
||||||
|
|
||||||
# --- API Endpoints ---
|
# --- API Endpoints ---
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def read_root():
|
def read_root():
|
||||||
@@ -61,7 +64,7 @@ async def upload_file(file: UploadFile = File(...)):
|
|||||||
contents = await file.read()
|
contents = await file.read()
|
||||||
df = pd.read_excel(io.BytesIO(contents), dtype=str) # Read all as string to be safe
|
df = pd.read_excel(io.BytesIO(contents), dtype=str) # Read all as string to be safe
|
||||||
df.fillna('N/A', inplace=True)
|
df.fillna('N/A', inplace=True)
|
||||||
|
df_storage = df # Store dataframe temporarily
|
||||||
|
|
||||||
# --- PLZ Column Detection ---
|
# --- PLZ Column Detection ---
|
||||||
temp_plz_col = None
|
temp_plz_col = None
|
||||||
@@ -71,30 +74,53 @@ async def upload_file(file: UploadFile = File(...)):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not temp_plz_col:
|
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
|
plz_column_name = temp_plz_col
|
||||||
# Normalize PLZ data
|
|
||||||
df[plz_column_name] = df[plz_column_name].str.strip().str.zfill(5)
|
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 = {}
|
filters = {}
|
||||||
for col in df.columns:
|
for col in df.columns:
|
||||||
if col != plz_column_name:
|
if col != plz_column_name:
|
||||||
unique_values = df[col].unique().tolist()
|
unique_values = df[col].unique().tolist()
|
||||||
filters[col] = sorted(unique_values)
|
filters[col] = sorted(unique_values)
|
||||||
|
|
||||||
df_storage = df
|
print(f"Successfully processed file. Found PLZ column: '{plz_column_name}'.")
|
||||||
|
return {"plz_column_needed": False, "filters": filters, "plz_column": plz_column_name}
|
||||||
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:
|
except Exception as e:
|
||||||
print(f"ERROR processing file: {e}")
|
print(f"ERROR processing file: {e}")
|
||||||
raise HTTPException(status_code=500, detail=f"An error occurred while processing the 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")
|
@app.post("/api/heatmap")
|
||||||
async def get_heatmap_data(request: FilterRequest):
|
async def get_heatmap_data(request: FilterRequest):
|
||||||
global df_storage, plz_column_name, plz_geocoord_df
|
global df_storage, plz_column_name, plz_geocoord_df
|
||||||
|
|||||||
@@ -31,12 +31,43 @@ function App() {
|
|||||||
const [radiusMultiplier, setRadiusMultiplier] = useState(1);
|
const [radiusMultiplier, setRadiusMultiplier] = useState(1);
|
||||||
const [viewMode, setViewMode] = useState<MapMode>('points');
|
const [viewMode, setViewMode] = useState<MapMode>('points');
|
||||||
|
|
||||||
const handleUploadSuccess = (newFilters: FilterOptions) => {
|
const [plzColumnNeeded, setPlzColumnNeeded] = useState(false);
|
||||||
setFilters(newFilters);
|
const [availableColumns, setAvailableColumns] = useState<string[]>([]);
|
||||||
setHeatmapData([]); // Clear previous heatmap data
|
|
||||||
|
|
||||||
|
const handleUploadSuccess = (response: any) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
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
|
// Automatically fetch data with no filters on successful upload
|
||||||
handleFilterChange({});
|
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) => {
|
const handleFilterChange = async (selectedFilters: FilterOptions) => {
|
||||||
@@ -72,6 +103,14 @@ function App() {
|
|||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
setError={setError}
|
setError={setError}
|
||||||
/>
|
/>
|
||||||
|
{plzColumnNeeded ? (
|
||||||
|
<PlzSelector
|
||||||
|
columns={availableColumns}
|
||||||
|
onSubmit={handlePlzColumnSubmit}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<FilterPanel
|
<FilterPanel
|
||||||
filters={filters}
|
filters={filters}
|
||||||
onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
@@ -102,6 +141,8 @@ function App() {
|
|||||||
disabled={viewMode === 'heatmap'}
|
disabled={viewMode === 'heatmap'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="map-container">
|
<div className="map-container">
|
||||||
{isLoading && <p>Loading map data...</p>}
|
{isLoading && <p>Loading map data...</p>}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const FileUpload: React.FC<FileUploadProps> = ({ onUploadSuccess, setIsLoading,
|
|||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
onUploadSuccess(response.data.filters);
|
onUploadSuccess(response.data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (axios.isAxiosError(error) && error.response) {
|
if (axios.isAxiosError(error) && error.response) {
|
||||||
setError(`Upload failed: ${error.response.data.detail || error.message}`);
|
setError(`Upload failed: ${error.response.data.detail || error.message}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user