From ba5aecaee19cfdbfe45814673866391753116090 Mon Sep 17 00:00:00 2001 From: Alejandro Osornio <50227494+AOx0@users.noreply.github.com> Date: Sun, 12 Nov 2023 20:11:24 -0600 Subject: [PATCH] Working hour heat map --- assets/script.js | 67 ++++++++++++++++++++++ src/bin/api.rs | 134 +++++++++++++++++++++++++++++++++++++++++-- templates/hello.html | 26 +++++---- 3 files changed, 209 insertions(+), 18 deletions(-) diff --git a/assets/script.js b/assets/script.js index fbc01fd..4d7dd7f 100644 --- a/assets/script.js +++ b/assets/script.js @@ -319,6 +319,56 @@ function load_dias_data(data, cfg, valores) { }); } +function load_hours_data(data, cfg, valores) { + 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 hours') + console.log(JSON.stringify(data)) + + for (let i = 1; i <= 24; i++) { + document.getElementById(`hora-${i}`).style.opacity = 0.2; + } + + if (data['pinned'].length == 0) { + return; + } + + fetch(cfg.endpoint, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data) + } + ) + .then((response) => response.json()) + .then((json) => { + update_hours_data(cfg.num, json, valores); + }); +} + function load_month_data(data, cfg, valores) { console.log('Fetching months') console.log(JSON.stringify(data)) @@ -427,6 +477,23 @@ function update_dias_data(n, data, valores) { } } +function update_hours_data(n, data, valores) { + // console.log(`Updating ${n} with ${data.total} y ${data.valores}`) + let vals = data.valores.map((v) => v / data.total); + console.log(valores); + valores['horas'] = data.valores; + + let r = calculateStandardDeviation(vals); + let r2 = calculateMean(vals); + + for (let i = 0; i < 24; i++) { + // There's no map zone for undefined areas and outside the city + let color2 = calculateProbabilityLessThan(vals[i], r2, r); + document.getElementById(`hora-${i + 1}`).style.backgroundColor = MAIN_COLOR; + document.getElementById(`hora-${i + 1}`).style.opacity = color2 + 0.1; + } +} + function update_mes_data(n, data, valores) { // console.log(`Updating ${n} with ${data.total} y ${data.valores}`) let vals = data.valores.map((v) => v / data.total); diff --git a/src/bin/api.rs b/src/bin/api.rs index 1be0512..9831a13 100644 --- a/src/bin/api.rs +++ b/src/bin/api.rs @@ -224,6 +224,18 @@ struct SolicitudPorcentajePorDia { categorias: Vec, } +#[derive(Debug, Deserialize)] +struct SolicitudPorcentajePorHora { + #[serde(default = "min_year")] + annio_inicio: u16, + #[serde(default = "max_year")] + annio_final: u16, + #[serde(default)] + categorias: Vec, + #[serde(default, rename = "pinned")] + dias: Vec, +} + #[derive(Debug, Deserialize)] struct SolicitudPorcentajePorAnio { #[serde(default)] @@ -250,6 +262,12 @@ struct DiaPorcentajes { valores: Vec, } +#[derive(Serialize, Debug, Default)] +struct HoraPorcentajes { + total: u64, + valores: Vec, +} + #[derive(Serialize, Debug, Default)] struct AnioPorcetajes { total: u64, @@ -285,9 +303,11 @@ async fn mapa_porcentajes( let SolicitudMapaPorcentajes { annio_inicio, annio_final, - categorias, + mut categorias, } = sol; + categorias.sort(); + let annio_inicio = annio_inicio - OFFSET; let annio_final = annio_final - OFFSET; @@ -353,10 +373,13 @@ async fn cantidades_por_mes( let SolicitudCantidadesPorMes { annio_inicio, annio_final, - categorias, - alcaldias, + mut categorias, + mut alcaldias, } = sol; + categorias.sort(); + alcaldias.sort(); + let annio_inicio = annio_inicio - OFFSET; let annio_final = annio_final - OFFSET; @@ -458,12 +481,14 @@ async fn dias_porcentajes( let SolicitudPorcentajePorDia { annio_inicio, annio_final, - categorias, + mut categorias, } = sol; let annio_inicio = annio_inicio - OFFSET; let annio_final = annio_final - OFFSET; + categorias.sort(); + let (total,): (i64,) = if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { sqlx::query_as(&format!("SELECT COUNT(1) FROM delitos WHERE id_anio_hecho BETWEEN {annio_inicio} AND {annio_final};")) .fetch_one(&state.db) @@ -517,11 +542,105 @@ async fn dias_porcentajes( .into() } +async fn horas_porcentajes( + State(state): State, + Json(sol): Json, +) -> Json { + let SolicitudPorcentajePorHora { + annio_inicio, + annio_final, + mut categorias, + mut dias, + } = dbg!(sol); + + let annio_inicio = annio_inicio - OFFSET; + let annio_final = annio_final - OFFSET; + + dias.sort(); + categorias.sort(); + + let (total,): (i64,) = if categorias.is_empty() || categorias.len() >= ACTUAL_CATEGORIES { + sqlx::query_as(&format!("SELECT COUNT(1) FROM delitos WHERE id_anio_hecho BETWEEN {annio_inicio} AND {annio_final} AND WEEKDAY(fecha_hecho) IN ({0});", + dias + .iter() + .map(|id| format!("{}", id - 1)) + .collect::>() + .join(",") + )) + .fetch_one(&state.db) + .await + .unwrap() + } else { + sqlx::query_as(&format!("SELECT COUNT(1) FROM delitos WHERE id_categoria IN ({0}) AND id_anio_hecho BETWEEN {annio_inicio} AND {annio_final} AND WEEKDAY(fecha_hecho) IN ({1});", + categorias + .iter() + .map(|id| format!("{id}")) + .collect::>() + .join(","), + dias + .iter() + .map(|id| format!("{}", id - 1)) + .collect::>() + .join(",") + )) + .fetch_one(&state.db) + .await + .unwrap() + }; + + let resultados: Vec<(i16, i64)> = if categorias.is_empty() + || categorias.len() >= ACTUAL_CATEGORIES + { + sqlx::query_as(&format!("SELECT HOUR(hora_hecho), COUNT(1) FROM delitos WHERE id_anio_hecho BETWEEN {annio_inicio} AND {annio_final} AND WEEKDAY(fecha_hecho) IS NOT NULL AND WEEKDAY(fecha_hecho) IN ({0}) GROUP BY HOUR(hora_hecho);", + dias + .iter() + .map(|id| format!("{}", id - 1)) + .collect::>() + .join(",") + )) + .fetch_all(&state.db) + .await + .unwrap() + } else { + sqlx::query_as(&format!("SELECT HOUR(hora_hecho), COUNT(1) FROM delitos WHERE id_categoria IN ({0}) AND id_anio_hecho BETWEEN {annio_inicio} AND {annio_final} AND WEEKDAY(fecha_hecho) IS NOT NULL AND WEEKDAY(fecha_hecho) IN ({1}) GROUP BY HOUR(hora_hecho);", + categorias + .iter() + .map(|id| format!("{id}")) + .collect::>() + .join(","), + dias + .iter() + .map(|id| format!("{}", id - 1)) + .collect::>() + .join(",") + )) + .fetch_all(&state.db) + .await + .unwrap() + }; + + let mut res = vec![0; 24]; + + for (i, v) in resultados { + res[i as usize] = v as u64; + } + + println!("Horas: {res:?}"); + + HoraPorcentajes { + total: u64::try_from(total).unwrap(), + valores: res, + } + .into() +} + async fn anio_porcentajes( State(state): State, Json(sol): Json, ) -> Json { - let SolicitudPorcentajePorAnio { categorias } = sol; + let SolicitudPorcentajePorAnio { mut categorias } = sol; + + categorias.sort(); let annio_inicio = 2016 - OFFSET; let annio_final = 2023 - OFFSET; @@ -576,7 +695,9 @@ async fn mes_porcentajes( State(state): State, Json(sol): Json, ) -> Json { - let SolicitudPorcentajePorMesDeAnio { categorias, anio } = sol; + let SolicitudPorcentajePorMesDeAnio { mut categorias, anio } = sol; + + categorias.sort(); let anio = anio - OFFSET; @@ -709,6 +830,7 @@ async fn main() -> anyhow::Result<()> { .route("/mes_percent", post(mes_porcentajes)) .route("/anio_percent", post(anio_porcentajes)) .route("/dias_percent", post(dias_porcentajes)) + .route("/horas_percent", post(horas_porcentajes)) .route("/c_por_mes", post(cantidades_por_mes)) .route("/date/upnow", get(untilnow)) .with_state(state) diff --git a/templates/hello.html b/templates/hello.html index a7db03a..85f869f 100644 --- a/templates/hello.html +++ b/templates/hello.html @@ -275,28 +275,30 @@

Mapas de calor por Mes y Año

@@ -330,7 +332,7 @@

Mapas de calor por Dia y Hora

All
- {% for i in (1..=2) %} + {% for i in (0..2) %}
{% for hora in (1..=12) %} - {% set hora = hora * i %} + {% set hora = hora + (i * 12) %}
@@ -385,7 +387,7 @@

Mapas de calor por Dia y Hora

{{ hora - 1 }}

-