Unmeshraj commited on
Commit
6660aa4
·
1 Parent(s): 3ee8a7e
Files changed (3) hide show
  1. .env +0 -0
  2. BestModel.pt +0 -3
  3. app.py +141 -660
.env ADDED
File without changes
BestModel.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:ee571cc7163696ad7b3b4e2ef470d0524e55601975241350659e37ee446c25c5
3
- size 404203145
 
 
 
 
app.py CHANGED
@@ -1,680 +1,161 @@
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:
323
- results.append({
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
331
- else "low"
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,
518
- "count": len(hotspots),
519
- "hotspots": hotspots,
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(),
653
- "data": data
654
- })
655
-
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)
 
1
+ from datetime import datetime
 
 
 
 
2
 
3
+ import joblib
4
  import numpy as np
5
+ import pandas as pd
6
  from flask import Flask, jsonify, request
7
  from flask_cors import CORS
8
 
9
+ # ---------------- CONFIG ----------------
10
+ LAT_MIN = 12.70
11
+ LAT_MAX = 13.30
12
+ LON_MIN = 77.30
13
+ LON_MAX = 78.00
14
+ LAT_GRIDS = 50
15
+ LON_GRIDS = 50
16
+ THRESHOLD = 0.6
17
+
18
+ FEATURES = [
19
+ "grid_x",
20
+ "grid_y",
21
+ "day_of_week",
22
+ "is_weekend",
23
+ "month",
24
+ "crime_lag_1",
25
+ "crime_lag_7",
26
+ "crime_lag_30"
27
+ ]
28
+
29
+ # ---------------- APP ----------------
30
  app = Flask(__name__)
31
  CORS(app)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ # ---------------- LOAD MODELS ----------------
34
+ model1 = joblib.load("model1.pkl")
35
+ model2 = joblib.load("model2.pkl")
36
+ le = joblib.load("label_encoder.pkl")
37
 
38
+ # ---------------- LOAD DATA ----------------
39
+ # Used for lag features + historical plotting
40
+ full = pd.read_csv("crime_grid_daily_features.csv")
41
+ full["FIR_DATE"] = pd.to_datetime(full["FIR_DATE"])
42
+
43
+ raw_points = pd.read_csv("dataset_cleaned.csv")[["Latitude", "Longitude", "FIR_DATE"]]
44
+ raw_points["FIR_DATE"] = pd.to_datetime(raw_points["FIR_DATE"])
45
+
46
+ lat_size = (LAT_MAX - LAT_MIN) / LAT_GRIDS
47
+ lon_size = (LON_MAX - LON_MIN) / LON_GRIDS
48
+
49
+ # ---------------- HELPERS ----------------
50
+ def latlon_to_grid(lat, lon):
51
+ return (
52
+ int((lat - LAT_MIN) / lat_size),
53
+ int((lon - LON_MIN) / lon_size)
54
+ )
55
+
56
+ def get_lags(grid_x, grid_y):
57
+ hist = full[
58
+ (full["grid_x"] == grid_x) &
59
+ (full["grid_y"] == grid_y)
60
+ ].sort_values("FIR_DATE")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ if hist.empty:
63
+ return 0, 0, 0
64
 
65
+ return (
66
+ hist.tail(1)["crime_count"].mean(),
67
+ hist.tail(7)["crime_count"].mean(),
68
+ hist.tail(30)["crime_count"].mean()
69
+ )
70
 
71
+ # ---------------- ROUTES ----------------
72
+
73
+ @app.route("/health", methods=["GET"])
74
  def health():
75
+ return jsonify({"status": "ok"})
76
+
77
+ # ---- Historical crime points (map page 1)
78
+ @app.route("/historical", methods=["GET"])
79
+ def historical():
80
+ points = raw_points.dropna().sample(min(5000, len(raw_points)))
81
+ return jsonify(points.to_dict(orient="records"))
82
+
83
+ # ---- Predict for ONE location + date
84
+ @app.route("/predict", methods=["POST"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def predict():
86
+ data = request.json
87
+
88
+ lat = float(data["latitude"])
89
+ lon = float(data["longitude"])
90
+ date = datetime.strptime(data["date"], "%Y-%m-%d")
91
+
92
+ grid_x, grid_y = latlon_to_grid(lat, lon)
93
+
94
+ crime_lag_1, crime_lag_7, crime_lag_30 = get_lags(grid_x, grid_y)
95
+
96
+ row = pd.DataFrame([{
97
+ "grid_x": grid_x,
98
+ "grid_y": grid_y,
99
+ "day_of_week": date.weekday(),
100
+ "is_weekend": int(date.weekday() >= 5),
101
+ "month": date.month,
102
+ "crime_lag_1": crime_lag_1,
103
+ "crime_lag_7": crime_lag_7,
104
+ "crime_lag_30": crime_lag_30
105
+ }])
106
+
107
+ prob = model1.predict_proba(row[FEATURES])[0][1]
108
+
109
+ response = {
110
+ "crime_probability": float(prob),
111
+ "risk": "UNSAFE" if prob > THRESHOLD else "SAFE"
112
  }
113
+
114
+ if prob > THRESHOLD:
115
+ probs = model2.predict_proba(row[FEATURES])[0]
116
+ top = np.argsort(probs)[-3:][::-1]
117
+
118
+ response["top_crimes"] = [
119
+ {
120
+ "type": le.inverse_transform([i])[0],
121
+ "confidence": float(probs[i])
122
+ }
123
+ for i in top
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  ]
125
+
126
+ return jsonify(response)
127
+
128
+ # ---- Predict whole city for a date (map page 2)
129
+ @app.route("/future-map", methods=["POST"])
130
+ def future_map():
131
+ date = datetime.strptime(request.json["date"], "%Y-%m-%d")
132
+
133
+ rows = []
134
+ for x in range(LAT_GRIDS):
135
+ for y in range(LON_GRIDS):
136
+ l1, l7, l30 = get_lags(x, y)
137
+ rows.append({
138
+ "grid_x": x,
139
+ "grid_y": y,
140
+ "day_of_week": date.weekday(),
141
+ "is_weekend": int(date.weekday() >= 5),
142
+ "month": date.month,
143
+ "crime_lag_1": l1,
144
+ "crime_lag_7": l7,
145
+ "crime_lag_30": l30
146
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
+ df = pd.DataFrame(rows)
149
+ probs = model1.predict_proba(df[FEATURES])[:, 1]
150
+ df["crime_probability"] = probs
151
+
152
+ df = df[df["crime_probability"] > THRESHOLD]
153
+
154
+ df["lat"] = LAT_MIN + (df["grid_x"] + 0.5) * lat_size
155
+ df["lon"] = LON_MIN + (df["grid_y"] + 0.5) * lon_size
156
+
157
+ return jsonify(df[["lat", "lon", "crime_probability"]].to_dict(orient="records"))
158
 
159
+ # ---------------- RUN ----------------
160
  if __name__ == "__main__":
161
+ app.run(host="0.0.0.0", port=5000)