|
|
from flask import render_template, jsonify, request |
|
|
from app.map_py import create_map |
|
|
from app.map_py_heatmap import create_heatmap_interactive |
|
|
from app.table_summary import table_summary |
|
|
from app.forecast import forecast |
|
|
from app import app |
|
|
import subprocess |
|
|
import sys |
|
|
import os |
|
|
import pandas as pd |
|
|
from collections import Counter |
|
|
import warnings |
|
|
warnings.filterwarnings("ignore") |
|
|
|
|
|
|
|
|
@app.route('/') |
|
|
def home(): |
|
|
return render_template('index.html') |
|
|
|
|
|
|
|
|
@app.route('/map') |
|
|
def map_view(): |
|
|
"""Display interactive heatmap with choropleth (click to see case details)""" |
|
|
|
|
|
filter_year = request.args.get('year', 'all') |
|
|
filter_crime = request.args.get('crime', 'all') |
|
|
filter_city = request.args.get('city', 'all') |
|
|
|
|
|
map_html = create_heatmap_interactive(filter_year=filter_year, filter_crime=filter_crime, filter_city=filter_city) |
|
|
return render_template('map.html', map_html=map_html) |
|
|
|
|
|
|
|
|
@app.route('/map-folium') |
|
|
def map_folium_view(): |
|
|
"""Old Folium polygon map (backup)""" |
|
|
map_html = create_map() |
|
|
return render_template('map.html', map_html=map_html) |
|
|
|
|
|
|
|
|
@app.route('/heatmap') |
|
|
def heatmap_view(): |
|
|
"""Display GeoPandas-generated heatmap""" |
|
|
return render_template('heatmap.html') |
|
|
|
|
|
|
|
|
@app.route('/generate-heatmap', methods=['POST']) |
|
|
def generate_heatmap(): |
|
|
"""API endpoint to regenerate heatmap""" |
|
|
try: |
|
|
|
|
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
|
|
script_path = os.path.join(project_root, 'scripts', 'generate_heatmap_geopandas.py') |
|
|
|
|
|
|
|
|
result = subprocess.run( |
|
|
[sys.executable, script_path], |
|
|
cwd=project_root, |
|
|
capture_output=True, |
|
|
text=True, |
|
|
timeout=60 |
|
|
) |
|
|
|
|
|
if result.returncode == 0: |
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'message': 'Heatmap generated successfully', |
|
|
'image_url': '/static/img/heatmap_jatim.png' |
|
|
}) |
|
|
else: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': result.stderr or 'Unknown error' |
|
|
}), 500 |
|
|
|
|
|
except subprocess.TimeoutExpired: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Script timeout (lebih dari 60 detik)' |
|
|
}), 500 |
|
|
except Exception as e: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': str(e) |
|
|
}), 500 |
|
|
|
|
|
|
|
|
@app.route('/api/statistics') |
|
|
def get_statistics(): |
|
|
"""API endpoint untuk mendapatkan data statistik dari CSV""" |
|
|
|
|
|
filter_year = request.args.get('year', 'all') |
|
|
filter_crime = request.args.get('crime', 'all') |
|
|
filter_city = request.args.get('city', 'all') |
|
|
|
|
|
|
|
|
cleaned_csv_path = 'cleaned_data.csv' |
|
|
print(os.getcwd()) |
|
|
|
|
|
if os.path.exists(cleaned_csv_path): |
|
|
|
|
|
print(f"Loading data from {cleaned_csv_path}") |
|
|
data = pd.read_csv(cleaned_csv_path, on_bad_lines='skip') |
|
|
data_lower = data.map(lambda x: x.lower().strip() if isinstance(x, str) and x.strip() != "" else x) |
|
|
|
|
|
|
|
|
if 'tahun' in data_lower.columns: |
|
|
data_lower['tahun_putusan'] = data_lower['tahun'].astype('Int64') |
|
|
|
|
|
|
|
|
exist_pn = data_lower['lembaga_peradilan'].nunique() |
|
|
|
|
|
|
|
|
filtered_data = data_lower.copy() |
|
|
filtered = False |
|
|
|
|
|
|
|
|
if filter_year != 'all': |
|
|
try: |
|
|
year_val = int(filter_year) |
|
|
filtered_data = filtered_data[filtered_data['tahun_putusan'] == year_val] |
|
|
filtered = True |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
if filter_crime != 'all': |
|
|
filtered_data = filtered_data[filtered_data['kata_kunci'] == filter_crime.lower()] |
|
|
filtered = True |
|
|
|
|
|
|
|
|
if filter_city != 'all': |
|
|
filtered_data = filtered_data[ |
|
|
filtered_data['lembaga_peradilan'].str.contains(filter_city.lower(), case=False, na=False) |
|
|
] |
|
|
filtered = True |
|
|
|
|
|
city_names = ( |
|
|
data_lower['lembaga_peradilan'] |
|
|
.str.replace(r'^pn\s+', '', regex=True) |
|
|
.str.strip() |
|
|
.str.title() |
|
|
.unique() |
|
|
) |
|
|
|
|
|
|
|
|
city_names = sorted(city_names) |
|
|
|
|
|
city_options = [ |
|
|
{'value': city.lower(), 'label': city} |
|
|
for city in city_names |
|
|
] |
|
|
|
|
|
|
|
|
if (filtered): |
|
|
data_lower = filtered_data |
|
|
|
|
|
|
|
|
|
|
|
all_crimes = data_lower['kata_kunci'].value_counts() |
|
|
crime_types = [ |
|
|
{'value': crime, 'label': crime.title(), 'count': int(count)} |
|
|
for crime, count in all_crimes.items() |
|
|
] |
|
|
|
|
|
|
|
|
all_years = data_lower['tahun_putusan'].dropna().astype(int).value_counts().sort_index(ascending=False) |
|
|
year_options = [ |
|
|
{'value': str(year), 'label': str(year), 'count': int(count)} |
|
|
for year, count in all_years.items() |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
if 'tanggal' in data_lower.columns: |
|
|
try: |
|
|
data_lower['tanggal'] = pd.to_datetime( |
|
|
data_lower['tanggal'], errors='coerce' |
|
|
) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
data_lower['bulan_putusan'] = data_lower['tanggal'].dt.month |
|
|
data_lower['tahun_putusan'] = data_lower['tanggal'].dt.year |
|
|
|
|
|
|
|
|
grouped = ( |
|
|
data_lower |
|
|
.groupby(['tahun_putusan', 'bulan_putusan']) |
|
|
.size() |
|
|
.reset_index(name='count') |
|
|
) |
|
|
|
|
|
|
|
|
tahun_list = sorted(grouped['tahun_putusan'].unique()) |
|
|
|
|
|
forecast_data = [] |
|
|
|
|
|
for tahun in tahun_list: |
|
|
for bulan in range(1, 12+1): |
|
|
row = grouped[ |
|
|
(grouped['tahun_putusan'] == tahun) & |
|
|
(grouped['bulan_putusan'] == bulan) |
|
|
] |
|
|
|
|
|
jumlah = int(row['count'].iloc[0]) if not row.empty else 0 |
|
|
|
|
|
forecast_data.append({ |
|
|
'tahun': tahun, |
|
|
'bulan': bulan, |
|
|
'count': jumlah |
|
|
}) |
|
|
|
|
|
forecast_result = forecast(forecast_data) |
|
|
|
|
|
|
|
|
monthly_counts_raw = ( |
|
|
data_lower['bulan_putusan'] |
|
|
.dropna() |
|
|
.astype(int) |
|
|
.value_counts() |
|
|
.to_dict() |
|
|
) |
|
|
|
|
|
|
|
|
monthly_data = [ |
|
|
{'month': m, 'count': int(monthly_counts_raw.get(m, 0))} |
|
|
for m in range(1, 13) |
|
|
] |
|
|
|
|
|
|
|
|
tabel = table_summary(data_lower) |
|
|
|
|
|
|
|
|
tabel = tabel.fillna(0) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tabel.columns = ( |
|
|
tabel.columns |
|
|
.str.strip() |
|
|
.str.replace(" ", "_") |
|
|
.str.lower() |
|
|
) |
|
|
|
|
|
return jsonify({ |
|
|
'total_cases': len(filtered_data), |
|
|
'total_pn': exist_pn, |
|
|
'seasonal_data': monthly_data, |
|
|
'kasus_percentage': tabel.to_dict(orient="records"), |
|
|
'crime_types': crime_types, |
|
|
'year_options': year_options, |
|
|
'city_options': city_options, |
|
|
'forecast_result': forecast_result, |
|
|
'filter_active': filter_year != 'all' or filter_crime != 'all' or filter_city != 'all' |
|
|
}) |
|
|
|