diff --git a/assets/script.js b/assets/script.js index 2462384..d30c8f3 100644 --- a/assets/script.js +++ b/assets/script.js @@ -509,6 +509,159 @@ function load_top_anio(data, cfg) { }); } +function load_top_colonia(data, cfg) { + let input_fini = document.getElementById(`afini-${cfg['num']}`); + let input_init = document.getElementById(`ainit-${cfg['num']}`); + + if (input_fini != undefined && input_fini != undefined) { + let err = false; + + if (data['annio_inicio'] < 2016 || data['annio_inicio'] > 2023) { + input_init.classList.add("text-rose-300") + err = true + } else { + input_init.classList.remove("text-rose-300") + }; + + if (data['annio_final'] < 2016 || data['annio_final'] > 2023) { + input_fini.classList.add("text-rose-300") + err = true + } else { + input_fini.classList.remove("text-rose-300") + }; + + if (err) return; + } + + console.log('Fetching') + console.log(JSON.stringify(data)) + + fetch(cfg.endpoint, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data) + } + ) + .then((response) => response.json()) + .then((json) => { + const ctx = document.getElementById(`pinned-${cfg.num}`); + + if( window.myBar6 === undefined) { + window.myBar6 = new Chart(ctx, { + type: 'bar', + data: { + labels: json["nombres"], + datasets: [{ + // label: `Top delitos`, + data: json["valores"], + borderWidth: 1, + pointRadius: 0, + fill: false, + backgroundColor: [ + 'rgba(255, 99, 132, 0.2)', + 'rgba(255, 159, 64, 0.2)', + 'rgba(255, 205, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(201, 203, 207, 0.2)', + 'rgba(255, 99, 132, 0.2)', + 'rgba(255, 159, 64, 0.2)', + 'rgba(255, 205, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(201, 203, 207, 0.2)', + 'rgba(255, 99, 132, 0.2)', + ], + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(255, 159, 64)', + 'rgb(255, 205, 86)', + 'rgb(75, 192, 192)', + 'rgb(54, 162, 235)', + 'rgb(153, 102, 255)', + 'rgb(201, 203, 207)', + 'rgb(255, 99, 132)', + 'rgb(255, 159, 64)', + 'rgb(255, 205, 86)', + 'rgb(75, 192, 192)', + 'rgb(54, 162, 235)', + 'rgb(153, 102, 255)', + 'rgb(201, 203, 207)', + 'rgb(255, 99, 132)', + ], + }], + }, + options: { + indexAxis: 'y', + scales: { + y: { + beginAtZero: true + } + }, + plugins: { + title: { + display: false + }, + legend: { + display: false + } + } + } + }); + } else { + window.myBar6.data.datasets = [{ + label: `Top delitos ${data.annio}`, + data: json["valores"], + borderWidth: 1, + pointRadius: 0, + fill: false, + backgroundColor: [ + 'rgba(255, 99, 132, 0.2)', + 'rgba(255, 159, 64, 0.2)', + 'rgba(255, 205, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(201, 203, 207, 0.2)', + 'rgba(255, 99, 132, 0.2)', + 'rgba(255, 159, 64, 0.2)', + 'rgba(255, 205, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(201, 203, 207, 0.2)', + 'rgba(255, 99, 132, 0.2)', + ], + borderColor: [ + 'rgb(255, 99, 132)', + 'rgb(255, 159, 64)', + 'rgb(255, 205, 86)', + 'rgb(75, 192, 192)', + 'rgb(54, 162, 235)', + 'rgb(153, 102, 255)', + 'rgb(201, 203, 207)', + 'rgb(255, 99, 132)', + 'rgb(255, 159, 64)', + 'rgb(255, 205, 86)', + 'rgb(75, 192, 192)', + 'rgb(54, 162, 235)', + 'rgb(153, 102, 255)', + 'rgb(201, 203, 207)', + 'rgb(255, 99, 132)', + ], + }]; + window.myBar6.data.labels = json["nombres"]; + window.myBar6.update(); + } + }); +} + + function load_all_anio(data, cfg) { let input_fini = document.getElementById(`afini-${cfg['num']}`); let input_init = document.getElementById(`ainit-${cfg['num']}`); diff --git a/src/bin/api.rs b/src/bin/api.rs index 5c5220d..0f3b341 100644 --- a/src/bin/api.rs +++ b/src/bin/api.rs @@ -338,10 +338,11 @@ async fn mapa_porcentajes( categorias.sort(); - let annio_inicio = annio_inicio - OFFSET; - let annio_final = annio_final - OFFSET; - - let (total,): (i64,) = sqlx::query_as(&format!("SELECT COUNT(1) FROM delitos WHERE {}id_anio_hecho BETWEEN {annio_inicio} AND {annio_final};", + let (total,): (i64,) = sqlx::query_as(&format!( + "SELECT COUNT(1) + FROM delitos + WHERE {}id_anio_hecho BETWEEN ? AND ?; + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { @@ -355,11 +356,20 @@ async fn mapa_porcentajes( ) } )) + .bind(&(annio_inicio - OFFSET)) + .bind(&(annio_final - OFFSET)) .fetch_one(&state.db) .await .unwrap(); - let resultados: Vec<(i64,)> = sqlx::query_as(&format!("SELECT COUNT(1) FROM delitos WHERE {}id_anio_hecho BETWEEN {annio_inicio} AND {annio_final} AND delitos.id_alcaldia_hecho IS NOT NULL GROUP BY delitos.id_alcaldia_hecho ORDER BY delitos.id_alcaldia_hecho;", + let resultados: Vec<(i64,)> = sqlx::query_as(&format!( + "SELECT COUNT(1) + FROM delitos + WHERE {}id_anio_hecho BETWEEN ? AND ? + AND delitos.id_alcaldia_hecho IS NOT NULL + GROUP BY delitos.id_alcaldia_hecho + ORDER BY delitos.id_alcaldia_hecho; + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { @@ -373,6 +383,8 @@ async fn mapa_porcentajes( ) } )) + .bind(&(annio_inicio - OFFSET)) + .bind(&(annio_final - OFFSET)) .fetch_all(&state.db) .await .unwrap(); @@ -409,11 +421,16 @@ async fn cantidades_por_mes( categorias.sort(); alcaldias.sort(); - let annio_inicio = annio_inicio - OFFSET; - let annio_final = annio_final - OFFSET; - let resultados: Vec<(u64, u64, u64, i64)> = sqlx::query_as(&format!( - "SELECT id_anio_hecho + 1947, id_mes_hecho, id_alcaldia_hecho, COUNT(1) FROM delitos WHERE id_anio_hecho BETWEEN {annio_inicio} AND {annio_final} AND {}id_alcaldia_hecho IN ({a}) GROUP BY id_anio_hecho, id_mes_hecho, id_alcaldia_hecho;", + "SELECT id_anio_hecho + ?, + id_mes_hecho, + id_alcaldia_hecho, + COUNT(1) + FROM delitos + WHERE id_anio_hecho BETWEEN ? AND ? + AND {}id_alcaldia_hecho IN ({a}) + GROUP BY id_anio_hecho, id_mes_hecho, id_alcaldia_hecho; + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { @@ -432,6 +449,9 @@ async fn cantidades_por_mes( .collect::>() .join(",") )) + .bind(OFFSET) + .bind(&(annio_inicio - OFFSET)) + .bind(&(annio_final - OFFSET)) .fetch_all(&state.db) .await .unwrap(); @@ -528,7 +548,14 @@ async fn dias_porcentajes( .await .unwrap(); - let resultados: Vec<(i16, i64)> = sqlx::query_as(&format!("SELECT WEEKDAY(fecha_hecho), COUNT(1) FROM delitos WHERE {}id_anio_hecho BETWEEN ? AND ? AND WEEKDAY(fecha_hecho) IS NOT NULL GROUP BY WEEKDAY(fecha_hecho) ORDER BY WEEKDAY(fecha_hecho);", + let resultados: Vec<(i16, i64)> = sqlx::query_as(&format!( + "SELECT WEEKDAY(fecha_hecho), + COUNT(1) FROM delitos + WHERE {}id_anio_hecho BETWEEN ? AND ? + AND WEEKDAY(fecha_hecho) IS NOT NULL + GROUP BY WEEKDAY(fecha_hecho) + ORDER BY WEEKDAY(fecha_hecho); + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { @@ -577,7 +604,12 @@ async fn horas_porcentajes( dias.sort(); categorias.sort(); - let (total,): (i64,) = sqlx::query_as(&format!("SELECT COUNT(1) FROM delitos WHERE {}id_anio_hecho BETWEEN ? AND ? AND WEEKDAY(fecha_hecho) IN ({d});", + let (total,): (i64,) = sqlx::query_as(&format!( + "SELECT COUNT(1) + FROM delitos + WHERE {}id_anio_hecho BETWEEN ? AND ? + AND WEEKDAY(fecha_hecho) IN ({d}); + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { @@ -602,7 +634,15 @@ async fn horas_porcentajes( .await .unwrap(); - let resultados: Vec<(i16, i64)> = sqlx::query_as(&format!("SELECT HOUR(hora_hecho), COUNT(1) FROM delitos WHERE {}id_anio_hecho BETWEEN ? AND ? AND WEEKDAY(fecha_hecho) IS NOT NULL AND WEEKDAY(fecha_hecho) IN ({d}) GROUP BY HOUR(hora_hecho);", + let resultados: Vec<(i16, i64)> = sqlx::query_as(&format!( + "SELECT HOUR(hora_hecho), + COUNT(1) + FROM delitos + WHERE {}id_anio_hecho BETWEEN ? AND ? + AND WEEKDAY(fecha_hecho) IS NOT NULL + AND WEEKDAY(fecha_hecho) IN ({d}) + GROUP BY HOUR(hora_hecho); + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { @@ -671,7 +711,13 @@ async fn anio_porcentajes( .await .unwrap(); - let resultados: Vec<(i64,)> = sqlx::query_as(&format!("SELECT COUNT(1) FROM delitos WHERE {}id_anio_hecho BETWEEN ? AND ? GROUP BY id_anio_hecho ORDER BY id_anio_hecho;", + let resultados: Vec<(i64,)> = sqlx::query_as(&format!( + "SELECT COUNT(1) + FROM delitos + WHERE {}id_anio_hecho BETWEEN ? AND ? + GROUP BY id_anio_hecho + ORDER BY id_anio_hecho; + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { @@ -731,16 +777,23 @@ async fn mes_porcentajes( .await .unwrap(); - let resultados: Vec<(u16, i64)> = sqlx::query_as(&format!("SELECT id_mes_hecho, COUNT(1) FROM delitos WHERE {}id_anio_hecho = ? GROUP BY id_mes_hecho;", + let resultados: Vec<(u16, i64)> = sqlx::query_as(&format!( + "SELECT id_mes_hecho, + COUNT(1) FROM delitos + WHERE {}id_anio_hecho = ? + GROUP BY id_mes_hecho; + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { - format!("id_categoria IN ({0}) AND ", - categorias - .iter() - .map(|id| format!("{id}")) - .collect::>() - .join(",")) + format!( + "id_categoria IN ({0}) AND ", + categorias + .iter() + .map(|id| format!("{id}")) + .collect::>() + .join(",") + ) } )) .bind(&(anio - OFFSET)) @@ -794,18 +847,25 @@ async fn delitos_por_anio( categorias.sort(); let mut resultados: Vec<(String, i64)> = sqlx::query_as(&format!( - "SELECT categoria, COUNT(*) AS fre FROM delitos JOIN categoria USING(id_categoria) WHERE {}id_anio_hecho BETWEEN ? AND ? GROUP BY id_categoria;", + "SELECT categoria, + COUNT(*) AS fre + FROM delitos + JOIN categoria USING(id_categoria) + WHERE {}id_anio_hecho BETWEEN ? AND ? + GROUP BY id_categoria; + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { - format!("id_categoria IN ({0}) AND ", - categorias - .iter() - .map(|id| format!("{id}")) - .collect::>() - .join(",")) + format!( + "id_categoria IN ({0}) AND ", + categorias + .iter() + .map(|id| format!("{id}")) + .collect::>() + .join(",") + ) } - )) .bind(&(annio_inicio - OFFSET)) .bind(&(annio_final - OFFSET)) @@ -839,18 +899,84 @@ async fn top_por_anio( categorias.sort(); let mut resultados: Vec<(String, i64)> = sqlx::query_as(&format!( - "SELECT delito, COUNT(*) AS fre FROM delitos JOIN delito USING(id_delito) WHERE {}id_anio_hecho BETWEEN ? AND ? GROUP BY delito;", + "SELECT delito, + COUNT(*) AS fre + FROM delitos + JOIN delito USING(id_delito) + WHERE {}id_anio_hecho BETWEEN ? AND ? + GROUP BY delito + ORDER BY fre DESC + LIMIT 15; + ", if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { format!("") } else { - format!("id_categoria IN ({0}) AND ", - categorias - .iter() - .map(|id| format!("{id}")) - .collect::>() - .join(",")) + format!( + "id_categoria IN ({0}) AND ", + categorias + .iter() + .map(|id| format!("{id}")) + .collect::>() + .join(",") + ) } + )) + .bind(&(annio_inicio - OFFSET)) + .bind(&(annio_final - OFFSET)) + .fetch_all(&state.db) + .await + .unwrap(); + + resultados.sort_unstable_by_key(|(_, v)| *v); + resultados = resultados + .into_iter() + .rev() + .take(15) + .into_iter() + .collect::>(); + + TopPorAnio { + valores: resultados.iter().map(|(_, n)| *n as u64).collect(), + nombres: resultados + .into_iter() + .map(|(n, _)| uppercase_first_letter(n.to_lowercase().as_str())) + .collect(), + } + .into() +} + +async fn top_por_colonia( + State(state): State, + Json(sol): Json, +) -> Json { + let SolicitudTopPorAnio { + annio_inicio, + annio_final, + mut categorias, + } = dbg!(sol); + + categorias.sort(); + let mut resultados: Vec<(String, i64)> = sqlx::query_as(&format!( + "SELECT IFNULL(colonia_hecho, 'Desconocido'), + COUNT(*) AS fre + FROM delitos + WHERE {}id_anio_hecho BETWEEN ? AND ? GROUP BY colonia_hecho + ORDER BY fre DESC + LIMIT 15; + ", + if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { + format!("") + } else { + format!( + "id_categoria IN ({0}) AND ", + categorias + .iter() + .map(|id| format!("{id}")) + .collect::>() + .join(",") + ) + } )) .bind(&(annio_inicio - OFFSET)) .bind(&(annio_final - OFFSET)) @@ -887,7 +1013,13 @@ async fn cantidad_alto_y_bajo( } = dbg!(sol); let mut resultados: Vec<(String, i64)> = sqlx::query_as(&format!( - "SELECT delito, COUNT(*) AS fre FROM delitos JOIN delito USING(id_delito) WHERE id_anio_hecho BETWEEN ? AND ? AND id_categoria = 1 GROUP BY delito;", + "SELECT delito, + COUNT(*) AS fre + FROM delitos JOIN delito USING(id_delito) + WHERE id_anio_hecho BETWEEN ? AND ? + AND id_categoria = 1 + GROUP BY delito; + ", )) .bind(&(annio_inicio - OFFSET)) .bind(&(annio_final - OFFSET)) @@ -927,19 +1059,33 @@ async fn ultimo_dato(State(state): State) -> String { } async fn numero_anio(State(state): State, Path(anio): Path) -> String { - let (resultado,): (String,) = - sqlx::query_as("SELECT FORMAT(COUNT(*), 0) FROM delitos JOIN categoria USING(id_categoria) WHERE id_anio_hecho = ? AND categoria = 'HOMICIDIO DOLOSO' LIMIT 1;") - .bind(&(anio - OFFSET)) - .fetch_one(&state.db) - .await - .unwrap(); + let (resultado,): (String,) = sqlx::query_as( + "SELECT FORMAT(COUNT(*), 0) + FROM delitos + JOIN categoria USING(id_categoria) + WHERE id_anio_hecho = ? + AND categoria = 'HOMICIDIO DOLOSO' + LIMIT 1; + ", + ) + .bind(&(anio - OFFSET)) + .fetch_one(&state.db) + .await + .unwrap(); format!("+{resultado}") } async fn numero_robo(State(state): State, Path(anio): Path) -> String { let (resultado,): (String,) = - sqlx::query_as("SELECT FORMAT(COUNT(*), 0) FROM delitos JOIN categoria USING(id_categoria) WHERE id_anio_hecho = ? AND id_categoria IN (SELECT id_categoria FROM categoria WHERE categoria LIKE '%ROBO%') LIMIT 1;") + sqlx::query_as( + "SELECT FORMAT(COUNT(*), 0) + FROM delitos + JOIN categoria USING(id_categoria) + WHERE id_anio_hecho = ? + AND id_categoria IN (SELECT id_categoria FROM categoria WHERE categoria LIKE '%ROBO%') + LIMIT 1; + ") .bind(&(anio - OFFSET)) .fetch_one(&state.db) .await @@ -958,12 +1104,18 @@ async fn cantidad_alto_y_bajo2( .. } = dbg!(sol); - let annio_inicio = annio_inicio - OFFSET; - let annio_final = annio_final - OFFSET; - let mut resultados: Vec<(String, i64)> = sqlx::query_as(&format!( - "SELECT delito, COUNT(*) AS fre FROM delitos JOIN delito USING(id_delito) WHERE id_anio_hecho BETWEEN {annio_inicio} AND {annio_final} AND id_categoria != 1 GROUP BY delito;", + "SELECT delito, + COUNT(*) AS fre + FROM delitos + JOIN delito USING(id_delito) + WHERE id_anio_hecho BETWEEN ? AND ? + AND id_categoria != 1 + GROUP BY delito; + ", )) + .bind(&(annio_inicio - OFFSET)) + .bind(&(annio_final - OFFSET)) .fetch_all(&state.db) .await .unwrap(); @@ -1053,6 +1205,7 @@ async fn main() -> anyhow::Result<()> { .route("/dias_percent", post(dias_porcentajes)) .route("/horas_percent", post(horas_porcentajes)) .route("/top_por_anio", post(top_por_anio)) + .route("/top_por_colonia", post(top_por_colonia)) .route("/delitos_por_anio", post(delitos_por_anio)) .route("/alto_y_bajo", post(cantidad_alto_y_bajo)) .route("/alto_y_bajo2", post(cantidad_alto_y_bajo2)) diff --git a/templates/hello.html b/templates/hello.html index 2ca9131..4b7a062 100644 --- a/templates/hello.html +++ b/templates/hello.html @@ -291,6 +291,69 @@

Delitos de alto impacto +
+
+
+

Top 15 colonias por tiempo

+
+ + +
+
+ + +
+
+
+
+ + + + +
+ +
+
+
+ +
+