Unmeshraj commited on
Commit
3ee8a7e
·
1 Parent(s): 9323a76
Files changed (2) hide show
  1. BestModel.pt +3 -0
  2. app.py +603 -20
BestModel.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ee571cc7163696ad7b3b4e2ef470d0524e55601975241350659e37ee446c25c5
3
+ size 404203145
app.py CHANGED
@@ -1,41 +1,322 @@
1
- from datetime import datetime
 
 
 
 
 
 
 
2
  from flask import Flask, jsonify, request
3
- from flask_cors import CORS #type:ignore
4
 
5
  app = Flask(__name__)
6
  CORS(app)
7
  app.config["JSON_SORT_KEYS"] = False
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- def load_model():
11
  """
12
- will load your ML model here later.
 
13
  """
14
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
 
17
- MODEL = load_model()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
 
20
- def predict_hotspots(city: str, threshold: float):
 
 
 
 
 
 
 
21
  """
22
- placeholder function to simulate hotspot predictions.
 
 
 
 
 
 
 
 
 
 
 
 
23
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
 
 
 
25
  mock_data = {
26
  "bangalore": [
27
  {"lat": 12.9352, "lon": 77.6245, "risk": 0.85},
28
  {"lat": 12.9716, "lon": 77.5946, "risk": 0.72},
29
  {"lat": 13.0027, "lon": 77.5914, "risk": 0.61},
 
 
30
  ],
31
  "delhi": [
32
  {"lat": 28.7041, "lon": 77.1025, "risk": 0.89},
33
  {"lat": 28.6328, "lon": 77.2197, "risk": 0.76},
 
34
  ],
35
  }
36
-
37
  city_data = mock_data.get(city.lower(), mock_data["bangalore"])
38
-
39
  results = []
40
  for i, point in enumerate(city_data):
41
  if point["risk"] >= threshold:
@@ -43,6 +324,7 @@ def predict_hotspots(city: str, threshold: float):
43
  "id": f"{city}-hotspot-{i}",
44
  "latitude": point["lat"],
45
  "longitude": point["lon"],
 
46
  "riskLevel": (
47
  "high" if point["risk"] >= 0.75
48
  else "medium" if point["risk"] >= 0.6
@@ -50,24 +332,186 @@ def predict_hotspots(city: str, threshold: float):
50
  ),
51
  "crimeCount": int(point["risk"] * 50) + 10,
52
  })
53
-
54
  return results
55
 
 
 
 
56
  @app.route("/api/health", methods=["GET"])
57
  def health():
 
 
58
  return jsonify({
59
  "status": "ok",
60
- "timestamp": datetime.utcnow().isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  })
62
 
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  @app.route("/api/hotspots", methods=["GET"])
65
  def get_hotspots():
 
 
 
 
66
  city = request.args.get("city", "bangalore")
67
  threshold = float(request.args.get("threshold", 0.5))
68
-
69
- hotspots = predict_hotspots(city, threshold)
70
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  return jsonify({
72
  "city": city,
73
  "threshold": threshold,
@@ -76,12 +520,133 @@ def get_hotspots():
76
  })
77
 
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  @app.route("/api/predictions", methods=["GET"])
80
  def predictions():
 
81
  city = request.args.get("city", "bangalore")
82
-
83
- data = predict_hotspots(city, threshold=0.0)
84
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  return jsonify({
86
  "city": city,
87
  "timestamp": datetime.utcnow().isoformat(),
@@ -91,7 +656,25 @@ def predictions():
91
 
92
  @app.errorhandler(404)
93
  def not_found(_):
94
- return jsonify({"error": "not found"}), 404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  if __name__ == "__main__":
97
- app.run(host="0.0.0.0", port=7860)
 
1
+ # app.py - Enhanced Flask API with all GUI.py features
2
+ import os
3
+ import sys
4
+ from datetime import datetime, timedelta
5
+ from typing import Optional, Dict, List, Tuple
6
+
7
+ import numpy as np
8
+ import torch
9
  from flask import Flask, jsonify, request
10
+ from flask_cors import CORS
11
 
12
  app = Flask(__name__)
13
  CORS(app)
14
  app.config["JSON_SORT_KEYS"] = False
15
 
16
+ # Ensure we can import project modules in `code/`
17
+ ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
18
+ if ROOT_DIR not in sys.path:
19
+ sys.path.append(ROOT_DIR)
20
+
21
+ # Import project modules
22
+ try:
23
+ from code import config
24
+ from code.DataPreprocessing import DataPreprocessing
25
+ from code.LSTMModel import ConvLSTMModel
26
+ from code.WeatherModel import WeatherModel
27
+ from code.TimeseriesModel import TimeseriesModel
28
+ except Exception as e:
29
+ print(f"Warning: Could not import project modules: {e}")
30
+ ConvLSTMModel = None
31
+ DataPreprocessing = None
32
+ WeatherModel = None
33
+ TimeseriesModel = None
34
+ config = None
35
+
36
+
37
+ # Global variables for models and data
38
+ MODEL = None
39
+ MODEL_DEVICE = "cpu"
40
+ DATA_PREP = None
41
+ WEATHER_MODEL = None
42
+ TIMESERIES_MODEL = None
43
+ FEATURES = None
44
+ LABELS = None
45
+ DATA_PIVOT = None
46
+ CRIME_DATA = None
47
+ NYC_SHAPE = None
48
+
49
+
50
+ def initialize_system():
51
+ """
52
+ Initialize all models and data preprocessing.
53
+ This mimics the caching behavior of GUI.py's @st.cache decorators.
54
+ """
55
+ global MODEL, MODEL_DEVICE, DATA_PREP, WEATHER_MODEL, TIMESERIES_MODEL
56
+ global FEATURES, LABELS, DATA_PIVOT, CRIME_DATA, NYC_SHAPE
57
+
58
+ if config is None or ConvLSTMModel is None:
59
+ print("Warning: Running in mock mode - models not available")
60
+ return False
61
+
62
+ try:
63
+ # Load NYC Shape (for filtering grids not on map)
64
+ print("Loading NYC Shape...")
65
+ nyc_shape_path = os.path.join(config.PROJECT_DIR, 'Data/PreprocessedDatasets/NYCGridsShape.pkl')
66
+ if os.path.isfile(nyc_shape_path):
67
+ import pickle
68
+ with open(nyc_shape_path, 'rb') as file:
69
+ NYC_SHAPE = pickle.load(file)
70
+ else:
71
+ NYC_SHAPE = [] # Empty for custom datasets like Bengaluru
72
+
73
+ # Load Dataset
74
+ print("Loading Dataset...")
75
+ DATA_PREP = DataPreprocessing(config.PROJECT_DIR)
76
+ FEATURES = DATA_PREP.features
77
+ LABELS = DATA_PREP.labels
78
+ DATA_PIVOT = DATA_PREP.dataPivot
79
+ CRIME_DATA = DATA_PREP.data
80
+
81
+ # Load ConvLSTM Model
82
+ print("Loading ConvLSTM Model...")
83
+ model_path = 'BestModel.pt'
84
+ if os.path.isfile(model_path):
85
+ checkpoint = torch.load(model_path, map_location=torch.device(config.DEVICE))
86
+ MODEL_DEVICE = config.DEVICE
87
+ MODEL = ConvLSTMModel(
88
+ input_dim=config.CRIME_TYPE_NUM,
89
+ hidden_dim=config.HIDDEN_DIM,
90
+ kernel_size=config.KERNEL_SIZE,
91
+ bias=True
92
+ )
93
+ state = checkpoint.get("model") if isinstance(checkpoint, dict) else checkpoint
94
+ MODEL.load_state_dict(state)
95
+ MODEL.to(torch.device(MODEL_DEVICE))
96
+ MODEL.eval()
97
+
98
+ # Load Weather Model
99
+ print("Loading Weather Model...")
100
+ WEATHER_MODEL = WeatherModel(config.PROJECT_DIR)
101
+
102
+ # Load Timeseries Model
103
+ print("Loading Timeseries Model...")
104
+ TIMESERIES_MODEL = TimeseriesModel(config.PROJECT_DIR, CRIME_DATA)
105
+
106
+ print("System initialization complete!")
107
+ return True
108
+
109
+ except Exception as e:
110
+ print(f"Error during initialization: {e}")
111
+ import traceback
112
+ traceback.print_exc()
113
+ return False
114
+
115
+
116
+ def get_date_range():
117
+ """Get valid date range for predictions."""
118
+ if config is None:
119
+ return None, None
120
+
121
+ start_date = datetime.strptime(config.START_SELECT_DATE[1:-1], '%Y-%m-%d')
122
+ end_date = datetime.strptime(config.END_DATE[1:-1], '%Y-%m-%d')
123
+ return start_date, end_date
124
+
125
 
126
+ def validate_date(date_str: str) -> Tuple[bool, Optional[datetime], Optional[str]]:
127
  """
128
+ Validate if a date is within the valid prediction range.
129
+ Returns: (is_valid, datetime_object, error_message)
130
  """
131
+ if config is None:
132
+ return False, None, "Configuration not available"
133
+
134
+ try:
135
+ dt = datetime.strptime(date_str, '%Y-%m-%d')
136
+ except ValueError:
137
+ return False, None, "Invalid date format. Use YYYY-MM-DD"
138
+
139
+ minus_days = config.SEQ_LEN + 1
140
+ start_date = datetime.strptime(config.START_DATE[1:-1], '%Y-%m-%d')
141
+ left_limit = start_date + timedelta(days=minus_days)
142
+ right_limit = datetime.strptime(config.END_DATE[1:-1], '%Y-%m-%d')
143
+
144
+ if dt <= left_limit:
145
+ return False, None, f"Date must be after {left_limit.strftime('%Y-%m-%d')}"
146
+ elif dt > right_limit:
147
+ return False, None, f"Date must be before or on {right_limit.strftime('%Y-%m-%d')}"
148
+
149
+ return True, dt, None
150
 
151
 
152
+ def get_prediction_data_by_date(
153
+ date: str,
154
+ crime_type_index: int,
155
+ use_temporal_factors: bool = True
156
+ ) -> Optional[Dict]:
157
+ """
158
+ Get predictions for a specific date and crime type.
159
+ This replicates the getPredDataByDate function from GUI.py.
160
+
161
+ Args:
162
+ date: Date string in format 'YYYY-MM-DD'
163
+ crime_type_index: Index of crime type (0-7)
164
+ use_temporal_factors: Whether to apply weather and timeseries factors
165
+
166
+ Returns:
167
+ Dictionary with prediction data or None on error
168
+ """
169
+ if MODEL is None or DATA_PIVOT is None or FEATURES is None:
170
+ return None
171
+
172
+ # Validate date
173
+ is_valid, dt, error = validate_date(date)
174
+ if not is_valid:
175
+ return {"error": error}
176
+
177
+ # Determine start index
178
+ minus_days = config.SEQ_LEN + 1
179
+ if DATA_PIVOT.query(f"date < {config.START_DATE}").shape[0] == 0:
180
+ start_index = 0
181
+ else:
182
+ start_index = int(
183
+ DATA_PIVOT.query(f"date < {config.START_DATE}").shape[0] / config.CRIME_TYPE_NUM - minus_days
184
+ )
185
+
186
+ # Get feature index for the given date
187
+ date_query = f"'{date}'"
188
+ found_index = int(
189
+ DATA_PIVOT.query(f"date < {date_query}").shape[0] / config.CRIME_TYPE_NUM - minus_days
190
+ ) - start_index
191
+
192
+ if found_index < 0 or found_index >= len(FEATURES):
193
+ return {"error": "Date index out of range"}
194
+
195
+ # Get features and labels
196
+ features_by_date = FEATURES[found_index]
197
+ labels_by_date = LABELS[found_index] if LABELS is not None else None
198
+
199
+ # Run prediction through ConvLSTM
200
+ processed_features = torch.from_numpy(features_by_date).to(MODEL_DEVICE).unsqueeze(0).float()
201
+ with torch.no_grad():
202
+ pred_data = MODEL(processed_features)[0][0]
203
+
204
+ # Get temporal factors
205
+ weather_factor = 1.0
206
+ timeseries_factors = [1.0] * config.CRIME_TYPE_NUM
207
+
208
+ if use_temporal_factors and WEATHER_MODEL is not None and TIMESERIES_MODEL is not None:
209
+ try:
210
+ weather_factor = WEATHER_MODEL.getWeatherFactor(date)
211
+ crime_types = [crime.lower() for crime in config.CRIME_TYPE]
212
+ timeseries_factors = [
213
+ TIMESERIES_MODEL.getTimeseriesFactor(crime_name, date)
214
+ for crime_name in crime_types
215
+ ]
216
+ except Exception as e:
217
+ print(f"Warning: Could not get temporal factors: {e}")
218
+
219
+ return {
220
+ "date": date,
221
+ "crime_type_index": crime_type_index,
222
+ "prediction": pred_data.cpu().numpy(),
223
+ "labels": labels_by_date if labels_by_date is not None else None,
224
+ "weather_factor": weather_factor,
225
+ "timeseries_factors": timeseries_factors,
226
+ "use_temporal_factors": use_temporal_factors
227
+ }
228
 
229
 
230
+ def get_hexagon_data(
231
+ pred_data: np.ndarray,
232
+ weather_factor: float,
233
+ timeseries_factors: List[float],
234
+ crime_type_index: int,
235
+ threshold: float,
236
+ use_temporal_factors: bool = True
237
+ ) -> List[Dict]:
238
  """
239
+ Convert prediction data to hotspot list with lat/lon coordinates.
240
+ This replicates the getHexagonData function from GUI.py.
241
+
242
+ Args:
243
+ pred_data: Prediction array from model
244
+ weather_factor: Weather adjustment factor
245
+ timeseries_factors: Timeseries adjustment factors per crime type
246
+ crime_type_index: Which crime type to extract
247
+ threshold: Minimum probability threshold
248
+ use_temporal_factors: Whether to apply temporal adjustments
249
+
250
+ Returns:
251
+ List of hotspot dictionaries
252
  """
253
+ if NYC_SHAPE is None or config is None:
254
+ return []
255
+
256
+ hotspots = []
257
+ hotspot_id = 0
258
+
259
+ for x in range(pred_data.shape[1]):
260
+ for y in range(pred_data.shape[2]):
261
+ # Skip grids not on the map (NYC shape filtering)
262
+ if (((x, y) in NYC_SHAPE or (x+1, y) in NYC_SHAPE or
263
+ (x, y+1) in NYC_SHAPE or (x+1, y+1) in NYC_SHAPE) and
264
+ (x < config.LAT_GRIDS - 1 and y < config.LON_GRIDS - 1)):
265
+ continue
266
+
267
+ if x >= config.LAT_GRIDS - 1 or y >= config.LON_GRIDS - 1:
268
+ continue
269
+
270
+ # Get base weight from prediction
271
+ weight = float(pred_data[crime_type_index][x][y])
272
+
273
+ # Apply temporal factors if enabled
274
+ if use_temporal_factors:
275
+ weight = weight * weather_factor * timeseries_factors[crime_type_index]
276
+
277
+ # Apply threshold multiplier for values below threshold
278
+ if weight < threshold:
279
+ weight = weight * config.MULTIPLY_FACTOR
280
+
281
+ # Calculate lat/lon for this grid cell
282
+ lat = config.LAT_BINS[x] + config.DIFF_LAT
283
+ lon = config.LON_BINS[y] + config.DIFF_LON
284
+
285
+ # Only include if above absolute minimum
286
+ if weight > 0.01:
287
+ risk_level = "high" if weight >= 0.75 else "medium" if weight >= 0.6 else "low"
288
+
289
+ hotspots.append({
290
+ "id": f"hotspot-{hotspot_id}",
291
+ "latitude": float(lat),
292
+ "longitude": float(lon),
293
+ "risk": float(weight),
294
+ "riskLevel": risk_level,
295
+ "crimeCount": int(weight * 50) + 10,
296
+ })
297
+ hotspot_id += 1
298
+
299
+ return hotspots
300
 
301
+
302
+ def get_mock_hotspots(city: str, threshold: float) -> List[Dict]:
303
+ """Fallback mock data when models are not available."""
304
  mock_data = {
305
  "bangalore": [
306
  {"lat": 12.9352, "lon": 77.6245, "risk": 0.85},
307
  {"lat": 12.9716, "lon": 77.5946, "risk": 0.72},
308
  {"lat": 13.0027, "lon": 77.5914, "risk": 0.61},
309
+ {"lat": 12.9141, "lon": 77.6411, "risk": 0.78},
310
+ {"lat": 12.9698, "lon": 77.6489, "risk": 0.65},
311
  ],
312
  "delhi": [
313
  {"lat": 28.7041, "lon": 77.1025, "risk": 0.89},
314
  {"lat": 28.6328, "lon": 77.2197, "risk": 0.76},
315
+ {"lat": 28.5355, "lon": 77.3910, "risk": 0.68},
316
  ],
317
  }
318
+
319
  city_data = mock_data.get(city.lower(), mock_data["bangalore"])
 
320
  results = []
321
  for i, point in enumerate(city_data):
322
  if point["risk"] >= threshold:
 
324
  "id": f"{city}-hotspot-{i}",
325
  "latitude": point["lat"],
326
  "longitude": point["lon"],
327
+ "risk": point["risk"],
328
  "riskLevel": (
329
  "high" if point["risk"] >= 0.75
330
  else "medium" if point["risk"] >= 0.6
 
332
  ),
333
  "crimeCount": int(point["risk"] * 50) + 10,
334
  })
 
335
  return results
336
 
337
+
338
+ # ==================== API ENDPOINTS ====================
339
+
340
  @app.route("/api/health", methods=["GET"])
341
  def health():
342
+ """Health check endpoint."""
343
+ models_loaded = MODEL is not None and DATA_PREP is not None
344
  return jsonify({
345
  "status": "ok",
346
+ "timestamp": datetime.utcnow().isoformat(),
347
+ "models_loaded": models_loaded,
348
+ "weather_model": WEATHER_MODEL is not None,
349
+ "timeseries_model": TIMESERIES_MODEL is not None,
350
+ })
351
+
352
+
353
+ @app.route("/api/info", methods=["GET"])
354
+ def info():
355
+ """Get system information and available date range."""
356
+ if config is None:
357
+ return jsonify({"error": "Configuration not available"}), 500
358
+
359
+ start_date, end_date = get_date_range()
360
+ crime_types = [crime.lower() for crime in config.CRIME_TYPE]
361
+
362
+ return jsonify({
363
+ "date_range": {
364
+ "start": start_date.strftime('%Y-%m-%d') if start_date else None,
365
+ "end": end_date.strftime('%Y-%m-%d') if end_date else None,
366
+ },
367
+ "crime_types": crime_types,
368
+ "grid_info": {
369
+ "lat_min": float(config.LAT_MIN),
370
+ "lat_max": float(config.LAT_MAX),
371
+ "lon_min": float(config.LON_MIN),
372
+ "lon_max": float(config.LON_MAX),
373
+ "lat_grids": config.LAT_GRIDS,
374
+ "lon_grids": config.LON_GRIDS,
375
+ },
376
+ "model_info": {
377
+ "seq_len": config.SEQ_LEN,
378
+ "hidden_dim": config.HIDDEN_DIM,
379
+ "kernel_size": config.KERNEL_SIZE,
380
+ }
381
+ })
382
+
383
+
384
+ @app.route("/api/crime-types", methods=["GET"])
385
+ def crime_types():
386
+ """Get list of available crime types."""
387
+ if config is None:
388
+ return jsonify({"error": "Configuration not available"}), 500
389
+
390
+ crime_types_list = [crime.lower() for crime in config.CRIME_TYPE]
391
+ return jsonify({
392
+ "crime_types": crime_types_list,
393
+ "count": len(crime_types_list)
394
  })
395
 
396
 
397
+ @app.route("/api/predict", methods=["POST"])
398
+ def predict():
399
+ """
400
+ Advanced prediction endpoint with full temporal factors.
401
+
402
+ Request body:
403
+ {
404
+ "date": "2024-01-15",
405
+ "crime_type": "theft", // or crime_type_index
406
+ "threshold": 0.5,
407
+ "use_temporal_factors": true
408
+ }
409
+ """
410
+ if MODEL is None or DATA_PREP is None:
411
+ # Fallback to mock data
412
+ data = request.get_json() or {}
413
+ city = data.get("city", "bangalore")
414
+ threshold = float(data.get("threshold", 0.5))
415
+ return jsonify({
416
+ "city": city,
417
+ "threshold": threshold,
418
+ "hotspots": get_mock_hotspots(city, threshold),
419
+ "mock": True
420
+ })
421
+
422
+ try:
423
+ data = request.get_json() or {}
424
+
425
+ # Parse parameters
426
+ date = data.get("date")
427
+ if not date:
428
+ return jsonify({"error": "Date parameter required"}), 400
429
+
430
+ # Get crime type index
431
+ crime_type = data.get("crime_type")
432
+ crime_type_index = data.get("crime_type_index")
433
+
434
+ if crime_type is not None:
435
+ crime_types = [crime.lower() for crime in config.CRIME_TYPE]
436
+ try:
437
+ crime_type_index = crime_types.index(crime_type.lower())
438
+ except ValueError:
439
+ return jsonify({"error": f"Invalid crime type. Available: {crime_types}"}), 400
440
+ elif crime_type_index is None:
441
+ crime_type_index = 0 # Default to first crime type
442
+
443
+ threshold = float(data.get("threshold", 0.5))
444
+ use_temporal_factors = data.get("use_temporal_factors", True)
445
+
446
+ # Get prediction data
447
+ pred_result = get_prediction_data_by_date(
448
+ date=date,
449
+ crime_type_index=crime_type_index,
450
+ use_temporal_factors=use_temporal_factors
451
+ )
452
+
453
+ if pred_result is None or "error" in pred_result:
454
+ return jsonify(pred_result or {"error": "Prediction failed"}), 400
455
+
456
+ # Convert to hotspots
457
+ hotspots = get_hexagon_data(
458
+ pred_data=pred_result["prediction"],
459
+ weather_factor=pred_result["weather_factor"],
460
+ timeseries_factors=pred_result["timeseries_factors"],
461
+ crime_type_index=crime_type_index,
462
+ threshold=threshold,
463
+ use_temporal_factors=use_temporal_factors
464
+ )
465
+
466
+ crime_types = [crime.lower() for crime in config.CRIME_TYPE]
467
+
468
+ return jsonify({
469
+ "date": date,
470
+ "crime_type": crime_types[crime_type_index],
471
+ "crime_type_index": crime_type_index,
472
+ "threshold": threshold,
473
+ "use_temporal_factors": use_temporal_factors,
474
+ "temporal_factors": {
475
+ "weather": float(pred_result["weather_factor"]),
476
+ "timeseries": [float(f) for f in pred_result["timeseries_factors"]],
477
+ },
478
+ "count": len(hotspots),
479
+ "hotspots": hotspots,
480
+ })
481
+
482
+ except Exception as e:
483
+ import traceback
484
+ traceback.print_exc()
485
+ return jsonify({"error": str(e)}), 500
486
+
487
+
488
  @app.route("/api/hotspots", methods=["GET"])
489
  def get_hotspots():
490
+ """
491
+ Simple hotspot endpoint (backward compatible).
492
+ Query params: city, threshold
493
+ """
494
  city = request.args.get("city", "bangalore")
495
  threshold = float(request.args.get("threshold", 0.5))
496
+
497
+ if MODEL is None:
498
+ hotspots = get_mock_hotspots(city, threshold)
499
+ else:
500
+ # Use latest available date
501
+ date = datetime.strptime(config.END_DATE[1:-1], '%Y-%m-%d').strftime('%Y-%m-%d')
502
+ pred_result = get_prediction_data_by_date(date=date, crime_type_index=0)
503
+
504
+ if pred_result and "error" not in pred_result:
505
+ hotspots = get_hexagon_data(
506
+ pred_data=pred_result["prediction"],
507
+ weather_factor=pred_result["weather_factor"],
508
+ timeseries_factors=pred_result["timeseries_factors"],
509
+ crime_type_index=0,
510
+ threshold=threshold
511
+ )
512
+ else:
513
+ hotspots = get_mock_hotspots(city, threshold)
514
+
515
  return jsonify({
516
  "city": city,
517
  "threshold": threshold,
 
520
  })
521
 
522
 
523
+ @app.route("/api/cumulative", methods=["GET"])
524
+ def cumulative_heatmap():
525
+ """
526
+ Get cumulative crime data for heatmap visualization.
527
+ This replicates the "Cumulative Heatmap (All Data)" mode from GUI.py.
528
+
529
+ Query params:
530
+ - crime_types: comma-separated list (optional, defaults to all)
531
+ - lat_min, lat_max, lon_min, lon_max: bounding box (optional)
532
+ """
533
+ if CRIME_DATA is None or config is None:
534
+ return jsonify({"error": "Crime data not available"}), 500
535
+
536
+ try:
537
+ # Parse crime type filter
538
+ crime_types_param = request.args.get("crime_types")
539
+ if crime_types_param:
540
+ selected_types = [t.strip() for t in crime_types_param.split(",")]
541
+ else:
542
+ selected_types = CRIME_DATA['TYPE'].unique().tolist()
543
+
544
+ # Parse bounding box
545
+ lat_min = float(request.args.get("lat_min", config.LAT_MIN))
546
+ lat_max = float(request.args.get("lat_max", config.LAT_MAX))
547
+ lon_min = float(request.args.get("lon_min", config.LON_MIN))
548
+ lon_max = float(request.args.get("lon_max", config.LON_MAX))
549
+
550
+ # Filter data
551
+ filtered_data = CRIME_DATA[
552
+ (CRIME_DATA['Longitude'] >= lon_min) &
553
+ (CRIME_DATA['Longitude'] <= lon_max) &
554
+ (CRIME_DATA['Latitude'] >= lat_min) &
555
+ (CRIME_DATA['Latitude'] <= lat_max) &
556
+ (CRIME_DATA['TYPE'].isin(selected_types))
557
+ ]
558
+
559
+ # Convert to list of points
560
+ points = []
561
+ for _, row in filtered_data.iterrows():
562
+ points.append({
563
+ "latitude": float(row['Latitude']),
564
+ "longitude": float(row['Longitude']),
565
+ "type": row['TYPE'],
566
+ "date": row.get('Date', None),
567
+ })
568
+
569
+ return jsonify({
570
+ "crime_types": selected_types,
571
+ "bounds": {
572
+ "lat_min": lat_min,
573
+ "lat_max": lat_max,
574
+ "lon_min": lon_min,
575
+ "lon_max": lon_max,
576
+ },
577
+ "count": len(points),
578
+ "points": points,
579
+ })
580
+
581
+ except Exception as e:
582
+ import traceback
583
+ traceback.print_exc()
584
+ return jsonify({"error": str(e)}), 500
585
+
586
+
587
+ @app.route("/api/temporal-factors", methods=["GET"])
588
+ def temporal_factors():
589
+ """
590
+ Get weather and timeseries factors for a specific date.
591
+
592
+ Query params:
593
+ - date: Date in YYYY-MM-DD format
594
+ """
595
+ if WEATHER_MODEL is None or TIMESERIES_MODEL is None or config is None:
596
+ return jsonify({"error": "Temporal models not available"}), 500
597
+
598
+ date = request.args.get("date")
599
+ if not date:
600
+ return jsonify({"error": "Date parameter required"}), 400
601
+
602
+ # Validate date
603
+ is_valid, dt, error = validate_date(date)
604
+ if not is_valid:
605
+ return jsonify({"error": error}), 400
606
+
607
+ try:
608
+ weather_factor = WEATHER_MODEL.getWeatherFactor(date)
609
+ crime_types = [crime.lower() for crime in config.CRIME_TYPE]
610
+ timeseries_factors = [
611
+ TIMESERIES_MODEL.getTimeseriesFactor(crime_name, date)
612
+ for crime_name in crime_types
613
+ ]
614
+
615
+ return jsonify({
616
+ "date": date,
617
+ "weather_factor": float(weather_factor),
618
+ "timeseries_factors": {
619
+ crime_types[i]: float(timeseries_factors[i])
620
+ for i in range(len(crime_types))
621
+ }
622
+ })
623
+
624
+ except Exception as e:
625
+ return jsonify({"error": str(e)}), 500
626
+
627
+
628
  @app.route("/api/predictions", methods=["GET"])
629
  def predictions():
630
+ """Legacy endpoint for backward compatibility."""
631
  city = request.args.get("city", "bangalore")
632
+
633
+ if MODEL is None:
634
+ data = get_mock_hotspots(city, threshold=0.0)
635
+ else:
636
+ date = datetime.strptime(config.END_DATE[1:-1], '%Y-%m-%d').strftime('%Y-%m-%d')
637
+ pred_result = get_prediction_data_by_date(date=date, crime_type_index=0)
638
+
639
+ if pred_result and "error" not in pred_result:
640
+ data = get_hexagon_data(
641
+ pred_data=pred_result["prediction"],
642
+ weather_factor=pred_result["weather_factor"],
643
+ timeseries_factors=pred_result["timeseries_factors"],
644
+ crime_type_index=0,
645
+ threshold=0.0
646
+ )
647
+ else:
648
+ data = get_mock_hotspots(city, 0.0)
649
+
650
  return jsonify({
651
  "city": city,
652
  "timestamp": datetime.utcnow().isoformat(),
 
656
 
657
  @app.errorhandler(404)
658
  def not_found(_):
659
+ return jsonify({"error": "Not found"}), 404
660
+
661
+
662
+ @app.errorhandler(500)
663
+ def internal_error(error):
664
+ return jsonify({"error": "Internal server error"}), 500
665
+
666
+
667
+ # Initialize system on startup
668
+ print("=" * 60)
669
+ print("Initializing Crime Hotspot Prediction System...")
670
+ print("=" * 60)
671
+ initialization_success = initialize_system()
672
+ if initialization_success:
673
+ print("✓ System ready!")
674
+ else:
675
+ print("⚠ Running in mock mode - some features unavailable")
676
+ print("=" * 60)
677
+
678
 
679
  if __name__ == "__main__":
680
+ app.run(host="0.0.0.0", port=5000, debug=True)