edgarodriguez commited on
Commit
fed2ea0
·
verified ·
1 Parent(s): 2d90c38

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +36 -27
  2. assistant.R +213 -119
  3. indicator_dictionary.json +1415 -0
Dockerfile CHANGED
@@ -4,8 +4,10 @@
4
  # The `# syntax` line above enables BuildKit features (RUN --mount=type=secret).
5
  FROM rocker/geospatial:4.4
6
 
7
- # DuckDB lives on the edgarodriguez/depth_alpha dataset (public); baked in at
8
- # build. Override with: docker build --build-arg DDB_URL=... -t depth-hf .
 
 
9
  ARG DDB_URL=https://huggingface.co/datasets/edgarodriguez/depth_alpha/resolve/main/depth_mexico.duckdb
10
 
11
  # RAG index (corpus chunks + local embeddings) for the AI assistant. The corpus
@@ -66,35 +68,42 @@ RUN mkdir -p "$OLLAMA_MODELS" \
66
 
67
  WORKDIR /app
68
 
69
- # Pre-bake the DuckDB vss extension under the runtime HOME (/app) so the
70
- # assistant's vector search works offline; the code falls back to core
71
- # list_cosine_distance if it is ever missing.
72
- RUN HOME=/app R -e "library(duckdb); con <- dbConnect(duckdb()); DBI::dbExecute(con, 'INSTALL vss; LOAD vss;'); DBI::dbDisconnect(con, shutdown=TRUE); cat('vss installed\n')"
73
 
74
- COPY depth_interactive_map_alpha.R assistant.R helpers.R styles.css entrypoint.sh ./
 
 
 
75
 
76
- # Bake the DuckDB map data (public dataset, build-time download).
77
- RUN curl -fL "${DDB_URL}" -o /app/depth_mexico.duckdb \
78
- && test -s /app/depth_mexico.duckdb \
79
- && ls -lh /app/depth_mexico.duckdb
80
-
81
- # Bake the private RAG index via the hf_token secret (best-effort; never fails
82
- # the build). The secret is mounted only for this RUN, at /run/secrets/hf_token.
83
- # Reports the HTTP status on failure (401 = bad/expired token, 403 = token has no
84
- # read access to the dataset, 404 = wrong URL/path) and removes a partial file so
85
- # the app correctly shows "offline" rather than loading a broken index.
86
  RUN --mount=type=secret,id=hf_token \
87
- if [ -s /run/secrets/hf_token ]; then \
88
- code=$(curl -sL -o /app/rag_chunks.parquet -w '%{http_code}' \
89
- -H "Authorization: Bearer $(cat /run/secrets/hf_token)" "${RAG_URL}"); \
90
- if [ "$code" = "200" ] && [ -s /app/rag_chunks.parquet ]; then \
91
- echo "[build] RAG index baked: $(ls -lh /app/rag_chunks.parquet | awk '{print $5}')"; \
92
- else \
93
- echo "[build] RAG fetch failed (HTTP $code) -> assistant offline"; \
94
- rm -f /app/rag_chunks.parquet; \
95
- fi; \
 
 
 
 
 
 
 
96
  else \
97
- echo "[build] no hf_token secret found at /run/secrets/hf_token -> assistant offline"; \
 
98
  fi
99
 
100
  # HF Spaces runs the container as a non-root user -- make /app world-readable,
 
4
  # The `# syntax` line above enables BuildKit features (RUN --mount=type=secret).
5
  FROM rocker/geospatial:4.4
6
 
7
+ # DuckDB lives on the edgarodriguez/depth_alpha dataset (now PRIVATE); pulled with
8
+ # the hf_token build secret below and baked in. Set that dataset to private (or move
9
+ # the file to your private dataset and update this URL). Override with:
10
+ # docker build --secret id=hf_token,src=token.txt --build-arg DDB_URL=... -t depth-hf .
11
  ARG DDB_URL=https://huggingface.co/datasets/edgarodriguez/depth_alpha/resolve/main/depth_mexico.duckdb
12
 
13
  # RAG index (corpus chunks + local embeddings) for the AI assistant. The corpus
 
68
 
69
  WORKDIR /app
70
 
71
+ # Pre-bake the DuckDB vss (vector) + fts (BM25 keyword) extensions under the
72
+ # runtime HOME (/app) so the assistant's hybrid retrieval works offline. The code
73
+ # falls back to core list_cosine_distance / dense-only if either is missing.
74
+ RUN HOME=/app R -e "library(duckdb); con <- dbConnect(duckdb()); DBI::dbExecute(con, 'INSTALL vss; LOAD vss; INSTALL fts; LOAD fts;'); DBI::dbDisconnect(con, shutdown=TRUE); cat('vss+fts installed\n')"
75
 
76
+ # indicator_dictionary.json (generated by R/96) grounds the assistant's tools +
77
+ # alias routing; assistant.R loads it from /app/. Regenerate + re-copy when the
78
+ # data dictionary overlay changes.
79
+ COPY depth_interactive_map_alpha.R assistant.R helpers.R styles.css entrypoint.sh indicator_dictionary.json ./
80
 
81
+ # Bake the PRIVATE datasets via the hf_token build secret (mounted only for this
82
+ # RUN at /run/secrets/hf_token). The map DuckDB is MANDATORY -- the app cannot run
83
+ # without it, so a non-200 FAILS the build with the HTTP code (401 = bad/expired
84
+ # token, 403 = token has no read access, 404 = wrong URL/path). The RAG index is
85
+ # best-effort (a partial file is removed so the assistant shows "offline" rather
86
+ # than loading a broken index).
 
 
 
 
87
  RUN --mount=type=secret,id=hf_token \
88
+ if [ ! -s /run/secrets/hf_token ]; then \
89
+ echo "[build] ERROR: hf_token secret missing -- add it as a Space secret"; exit 1; \
90
+ fi; \
91
+ TOKEN="$(cat /run/secrets/hf_token)"; \
92
+ dcode=$(curl -sL -o /app/depth_mexico.duckdb -w '%{http_code}' \
93
+ -H "Authorization: Bearer $TOKEN" "${DDB_URL}"); \
94
+ if [ "$dcode" = "200" ] && [ -s /app/depth_mexico.duckdb ]; then \
95
+ echo "[build] DuckDB baked: $(ls -lh /app/depth_mexico.duckdb | awk '{print $5}')"; \
96
+ else \
97
+ echo "[build] ERROR: DuckDB fetch failed (HTTP $dcode) -- check token / DDB_URL / dataset visibility"; \
98
+ rm -f /app/depth_mexico.duckdb; exit 1; \
99
+ fi; \
100
+ rcode=$(curl -sL -o /app/rag_chunks.parquet -w '%{http_code}' \
101
+ -H "Authorization: Bearer $TOKEN" "${RAG_URL}"); \
102
+ if [ "$rcode" = "200" ] && [ -s /app/rag_chunks.parquet ]; then \
103
+ echo "[build] RAG index baked: $(ls -lh /app/rag_chunks.parquet | awk '{print $5}')"; \
104
  else \
105
+ echo "[build] RAG fetch failed (HTTP $rcode) -> assistant offline"; \
106
+ rm -f /app/rag_chunks.parquet; \
107
  fi
108
 
109
  # HF Spaces runs the container as a non-root user -- make /app world-readable,
assistant.R CHANGED
@@ -60,53 +60,100 @@ ollama_embed <- function(text, cfg = assistant_config()) {
60
  as.numeric(unlist(out$embeddings[[1]]))
61
  }
62
 
63
- # Decide once whether the vss extension (array_cosine_distance) is available;
64
- # fall back to DuckDB's core list_cosine_distance otherwise. Both are exact
65
- # brute-force cosine -- fine for a few thousand chunks.
66
  .assist_cache <- new.env(parent = emptyenv())
67
 
68
- .rag_dist_clause <- function(con, vec_lit, d) {
69
- mode <- .assist_cache$dist_mode
70
- if (is.null(mode)) {
71
- ok <- tryCatch({
72
- DBI::dbExecute(con, "INSTALL vss; LOAD vss;")
73
- DBI::dbGetQuery(con,
74
- "SELECT array_cosine_distance(CAST([1.0,2.0] AS FLOAT[2]), CAST([1.0,2.0] AS FLOAT[2])) AS d")
75
- TRUE
76
- }, error = function(e) FALSE)
77
- mode <- if (isTRUE(ok)) "vss" else "list"
78
- .assist_cache$dist_mode <- mode
79
- message("[assist] vector search mode: ", mode)
80
- }
81
- if (mode == "vss") {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  glue::glue("array_cosine_distance(CAST(embedding AS FLOAT[{d}]), CAST({vec_lit} AS FLOAT[{d}]))")
83
- } else {
84
  glue::glue("list_cosine_distance(embedding, CAST({vec_lit} AS DOUBLE[]))")
85
- }
86
  }
87
 
88
- # Embed the question (with the nomic query prefix) and return the top-k corpus
89
- # passages as a single context string with [source] tags for citation. k kept
90
- # small (2) so the injected context stays short -> cheaper CPU prefill.
91
- rag_search <- function(con, query, cfg = assistant_config(), k = 2) {
92
- if (is.null(con) || !file.exists(cfg$rag_path)) return("")
93
- qvec <- ollama_embed(paste0("search_query: ", query), cfg)
 
 
94
  if (length(qvec) == 0) return("")
95
  vec_lit <- paste0("[", paste(format(qvec, scientific = FALSE, trim = TRUE),
96
  collapse = ","), "]")
97
- dist <- .rag_dist_clause(con, vec_lit, length(qvec))
98
- sql <- glue::glue(
99
- "SELECT source_title, folder, chunk_text, {dist} AS dist
100
- FROM read_parquet('{path}')
101
- ORDER BY dist ASC
102
- LIMIT {k}",
103
- path = cfg$rag_path, k = as.integer(k)
104
- )
105
- res <- tryCatch(DBI::dbGetQuery(con, sql), error = function(e) {
106
- message("[assist] rag_search: ", conditionMessage(e)); NULL
107
- })
108
- if (is.null(res) || nrow(res) == 0) return("")
109
- paste(sprintf("[%s] %s", res$source_title, res$chunk_text), collapse = "\n\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
 
112
  # --- Grounding text from WEB_SPEC --------------------------------------------
@@ -156,18 +203,94 @@ assistant_user_turn <- function(question, context_txt = "", scope_txt = "") {
156
  # `state` is a non-reactive environment the server keeps in sync with rv, so
157
  # tools can be called outside a reactive context during async streaming.
158
 
159
- .IND_LABELS <- c(
160
- homicidio = "homicide rate (per 100k)",
161
- robo = "robbery rate (per 100k)",
162
- secuestro = "kidnapping rate (per 100k)",
163
- trafico_menores = "child-trafficking rate (per 100k)",
164
- trata_personas = "human-trafficking rate (per 100k)",
165
- desap_n = "disappeared and not located (count)",
166
- pam_n = "irregular-migration (PAM) events (count)"
 
 
 
 
 
 
 
 
 
 
 
167
  )
168
  .POINT_TO_COUNT <- c(migrants = "n_incidents", graves = "n_graves",
169
  infra = "n_facilities", ocved = "ocved_n", ged = "n_ged")
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  assistant_selection_summary <- function(state) {
172
  cnt <- state$counts %||% list()
173
  scope <- if (isTRUE(state$is_national)) {
@@ -200,83 +323,51 @@ assistant_count_points <- function(state, layer) {
200
  }
201
 
202
  assistant_aggregate <- function(con, state, indicator, statistic = "mean") {
203
- if (!indicator %in% names(.IND_LABELS)) return(paste0("Unknown indicator: ", indicator))
204
- if (!statistic %in% c("mean", "sum", "max", "min")) statistic <- "mean"
205
- lab <- .IND_LABELS[[indicator]]
206
-
207
- # Selection path: aggregate the in-memory municipalities (matches the sidebar).
208
- if (!isTRUE(state$is_national) && !is.null(state$mun_df) &&
209
- nrow(state$mun_df) > 0 && indicator %in% names(state$mun_df)) {
210
- vals <- suppressWarnings(as.numeric(state$mun_df[[indicator]]))
211
- vals <- vals[is.finite(vals)]
212
- if (length(vals) == 0) return(glue::glue("No {lab} values in the current selection."))
213
- res <- switch(statistic, mean = mean(vals), sum = sum(vals),
214
- max = max(vals), min = min(vals))
215
- return(glue::glue(
216
- "{statistic} of {lab} across {nrow(state$mun_df)} selected municipalities: {round(res, 2)}."))
217
- }
218
-
219
- # National path: a single bounded aggregate query (indicator is allow-listed).
220
  if (is.null(con)) return("No database connection available.")
221
- if (indicator %in% c("desap_n", "pam_n")) {
222
- inner_tbl <- if (indicator == "desap_n") "dim_desap_mun" else "dim_pam_mun"
223
- inner_col <- if (indicator == "desap_n") "final_desap_nl" else "pam_n"
224
- from_clause <- glue::glue("(SELECT CVEGEO, SUM({inner_col}) AS x FROM {inner_tbl} GROUP BY CVEGEO)")
225
- col_expr <- "x"
226
- } else {
227
- from_clause <- "crime_mun"
228
- col_expr <- glue::glue("CAST({indicator} AS DOUBLE)")
229
- }
230
- agg <- switch(statistic,
231
- mean = glue::glue("AVG({col_expr})"), sum = glue::glue("SUM({col_expr})"),
232
- max = glue::glue("MAX({col_expr})"), min = glue::glue("MIN({col_expr})"))
233
- sql <- glue::glue("SELECT {agg} AS v, COUNT(*) AS n FROM {from_clause}")
234
- r <- tryCatch(DBI::dbGetQuery(con, sql), error = function(e) NULL)
235
- if (is.null(r) || nrow(r) == 0 || is.na(r$v[1]))
236
- return(glue::glue("Could not compute {lab} nationally."))
237
- glue::glue("{statistic} of {lab} across all {r$n[1]} municipalities in Mexico: {round(r$v[1], 2)}.")
238
  }
239
 
240
  assistant_top_municipalities <- function(con, state, indicator, n = 5) {
241
- if (!indicator %in% names(.IND_LABELS)) return(paste0("Unknown indicator: ", indicator))
242
- n <- max(1L, min(20L, as.integer(n)))
243
- lab <- .IND_LABELS[[indicator]]
244
-
245
- # Selection path: rank the in-memory municipalities.
246
- if (!isTRUE(state$is_national) && !is.null(state$mun_df) &&
247
- nrow(state$mun_df) > 0 && indicator %in% names(state$mun_df)) {
248
- d <- state$mun_df
249
- d$.v <- suppressWarnings(as.numeric(d[[indicator]]))
250
- d <- d[is.finite(d$.v), , drop = FALSE]
251
- if (nrow(d) == 0) return(glue::glue("No {lab} data in the current selection."))
252
- d <- utils::head(d[order(-d$.v), , drop = FALSE], n)
253
- items <- sprintf("%d. %s (%s): %s", seq_len(nrow(d)), d$municipio, d$entidad,
254
- round(d$.v, 2))
255
- return(paste0("Top municipalities by ", lab, " (current selection):\n",
256
- paste(items, collapse = "\n")))
257
- }
258
-
259
- # National path.
260
  if (is.null(con)) return("No database connection available.")
261
- if (indicator %in% c("homicidio", "robo", "secuestro", "trafico_menores", "trata_personas")) {
262
- sql <- glue::glue(
263
- "SELECT municipio, entidad, CAST({indicator} AS DOUBLE) AS v
264
- FROM crime_mun WHERE {indicator} IS NOT NULL
265
- ORDER BY v DESC NULLS LAST LIMIT {n}")
266
- } else {
267
- inner_tbl <- if (indicator == "desap_n") "dim_desap_mun" else "dim_pam_mun"
268
- inner_col <- if (indicator == "desap_n") "final_desap_nl" else "pam_n"
269
- sql <- glue::glue(
270
- "SELECT c.municipio, c.entidad, s.v AS v
271
- FROM crime_mun c
272
- JOIN (SELECT CVEGEO, SUM({inner_col}) AS v FROM {inner_tbl} GROUP BY CVEGEO) s
273
- ON c.CVEGEO = s.CVEGEO
274
- ORDER BY v DESC NULLS LAST LIMIT {n}")
275
- }
276
- r <- tryCatch(DBI::dbGetQuery(con, sql), error = function(e) NULL)
277
- if (is.null(r) || nrow(r) == 0) return(glue::glue("Could not rank municipalities by {lab}."))
278
  items <- sprintf("%d. %s (%s): %s", seq_len(nrow(r)), r$municipio, r$entidad, round(r$v, 2))
279
- paste0("Top municipalities by ", lab, " (Mexico):\n", paste(items, collapse = "\n"))
280
  }
281
 
282
  # --- Assemble the per-session chat -------------------------------------------
@@ -300,7 +391,10 @@ make_assistant <- function(con, state, cfg = assistant_config()) {
300
  )
301
  if (is.null(chat)) return(NULL)
302
 
303
- ind_enum <- ellmer::type_enum(names(.IND_LABELS), "Indicator column.")
 
 
 
304
 
305
  # Only the 3 data tools are registered. The indicator catalog lives in the
306
  # system prompt and the current scope/totals are injected as <scope> each turn,
@@ -313,7 +407,7 @@ make_assistant <- function(con, state, cfg = assistant_config()) {
313
  count_points <- function(layer) assistant_count_points(state, layer)
314
 
315
  chat$register_tool(ellmer::tool(aggregate_indicator,
316
- "Aggregate one municipality indicator over the current selection (or nationally if nothing is selected). Use 'mean' for rate indicators and 'sum' for count indicators.",
317
  arguments = list(
318
  indicator = ind_enum,
319
  statistic = ellmer::type_enum(c("mean", "sum", "max", "min"),
 
60
  as.numeric(unlist(out$embeddings[[1]]))
61
  }
62
 
 
 
 
63
  .assist_cache <- new.env(parent = emptyenv())
64
 
65
+ # Model-aware query text: nomic-embed-text needs the "search_query: " task prefix;
66
+ # bge-m3 and most other embedders do not.
67
+ .embed_query_text <- function(query, cfg) {
68
+ if (grepl("nomic", cfg$embed_model, ignore.case = TRUE)) paste0("search_query: ", query)
69
+ else query
70
+ }
71
+
72
+ # Build (once per process) a writable in-memory DuckDB holding the rag chunks, with
73
+ # the vss (vector) extension and an fts (BM25) index for hybrid search. Returns NULL
74
+ # if the parquet is missing or it can't be built. Cached in .assist_cache.
75
+ .get_rag_con <- function(cfg) {
76
+ if (!is.null(.assist_cache$rag_con)) return(.assist_cache$rag_con)
77
+ if (!file.exists(cfg$rag_path)) return(NULL)
78
+ rc <- tryCatch({
79
+ c2 <- DBI::dbConnect(duckdb::duckdb())
80
+ tryCatch(DBI::dbExecute(c2, "INSTALL vss; LOAD vss;"), error = function(e) NULL)
81
+ DBI::dbExecute(c2, sprintf("CREATE TABLE rag AS SELECT * FROM read_parquet(%s)",
82
+ DBI::dbQuoteString(c2, cfg$rag_path)))
83
+ c2
84
+ }, error = function(e) { message("[assist] rag_con: ", conditionMessage(e)); NULL })
85
+ if (is.null(rc)) return(NULL)
86
+ .assist_cache$vss_ok <- tryCatch({
87
+ DBI::dbGetQuery(rc, "SELECT array_cosine_distance(CAST([1.0] AS FLOAT[1]), CAST([1.0] AS FLOAT[1]))")
88
+ TRUE
89
+ }, error = function(e) FALSE)
90
+ .assist_cache$fts_ok <- tryCatch({
91
+ DBI::dbExecute(rc, "INSTALL fts; LOAD fts;")
92
+ DBI::dbExecute(rc,
93
+ "PRAGMA create_fts_index('rag','chunk_id','chunk_text', stemmer='none', strip_accents=1, lower=1, overwrite=1)")
94
+ TRUE
95
+ }, error = function(e) { message("[assist] fts: ", conditionMessage(e)); FALSE })
96
+ message("[assist] rag index ready (vss=", .assist_cache$vss_ok,
97
+ " fts=", .assist_cache$fts_ok, ")")
98
+ .assist_cache$rag_con <- rc
99
+ rc
100
+ }
101
+
102
+ .dist_clause <- function(vss_ok, vec_lit, d) {
103
+ if (isTRUE(vss_ok))
104
  glue::glue("array_cosine_distance(CAST(embedding AS FLOAT[{d}]), CAST({vec_lit} AS FLOAT[{d}]))")
105
+ else
106
  glue::glue("list_cosine_distance(embedding, CAST({vec_lit} AS DOUBLE[]))")
 
107
  }
108
 
109
+ # Hybrid retrieval: dense cosine + BM25 keyword, fused with Reciprocal Rank Fusion,
110
+ # returning the top-k passages as one context string with [source] tags. Degrades to
111
+ # dense-only if fts is unavailable. k kept small so injected context stays cheap on
112
+ # CPU prefill. `con` is unused (kept for signature stability).
113
+ rag_search <- function(con, query, cfg = assistant_config(), k = 2, n_cand = 20) {
114
+ rc <- .get_rag_con(cfg)
115
+ if (is.null(rc)) return("")
116
+ qvec <- ollama_embed(.embed_query_text(query, cfg), cfg)
117
  if (length(qvec) == 0) return("")
118
  vec_lit <- paste0("[", paste(format(qvec, scientific = FALSE, trim = TRUE),
119
  collapse = ","), "]")
120
+ dist <- .dist_clause(.assist_cache$vss_ok, vec_lit, length(qvec))
121
+
122
+ dense <- tryCatch(DBI::dbGetQuery(rc, glue::glue(
123
+ "SELECT chunk_id, {dist} AS dist FROM rag ORDER BY dist ASC LIMIT {n_cand}")),
124
+ error = function(e) { message("[assist] dense: ", conditionMessage(e)); NULL })
125
+ if (is.null(dense) || nrow(dense) == 0) return("")
126
+ dense$rank <- seq_len(nrow(dense))
127
+
128
+ sparse <- NULL
129
+ if (isTRUE(.assist_cache$fts_ok)) {
130
+ qx <- DBI::dbQuoteString(rc, query)
131
+ sparse <- tryCatch(DBI::dbGetQuery(rc, glue::glue(
132
+ "SELECT chunk_id, score FROM (
133
+ SELECT chunk_id, fts_main_rag.match_bm25(chunk_id, {qx}) AS score FROM rag
134
+ ) WHERE score IS NOT NULL ORDER BY score DESC LIMIT {n_cand}")),
135
+ error = function(e) { message("[assist] bm25: ", conditionMessage(e)); NULL })
136
+ }
137
+
138
+ # Reciprocal Rank Fusion (k_const = 60).
139
+ scores <- setNames(1 / (60 + dense$rank), as.character(dense$chunk_id))
140
+ if (!is.null(sparse) && nrow(sparse) > 0) {
141
+ sr <- 1 / (60 + seq_len(nrow(sparse)))
142
+ for (i in seq_len(nrow(sparse))) {
143
+ id <- as.character(sparse$chunk_id[i])
144
+ scores[id] <- (if (is.na(scores[id])) 0 else scores[id]) + sr[i]
145
+ }
146
+ }
147
+ top_ids <- as.integer(names(sort(scores, decreasing = TRUE)))[seq_len(min(k, length(scores)))]
148
+ top_ids <- top_ids[!is.na(top_ids)]
149
+ if (length(top_ids) == 0) return("")
150
+ txt <- tryCatch(DBI::dbGetQuery(rc, glue::glue(
151
+ "SELECT chunk_id, source_title, chunk_text FROM rag WHERE chunk_id IN ({paste(top_ids, collapse = ',')})")),
152
+ error = function(e) NULL)
153
+ if (is.null(txt) || nrow(txt) == 0) return("")
154
+ txt <- txt[match(top_ids, txt$chunk_id), ] # preserve fused order
155
+ txt <- txt[!is.na(txt$chunk_id), ]
156
+ paste(sprintf("[%s] %s", txt$source_title, txt$chunk_text), collapse = "\n\n")
157
  }
158
 
159
  # --- Grounding text from WEB_SPEC --------------------------------------------
 
203
  # `state` is a non-reactive environment the server keeps in sync with rv, so
204
  # tools can be called outside a reactive context during async streaming.
205
 
206
+ # Queryable municipality-level indicators. Each maps to a DuckDB table + value
207
+ # expression, joined by CVEGEO. `per_muni=TRUE` means the table has many rows per
208
+ # municipality (sum them first); `kind` sets the sensible default statistic
209
+ # (rate -> mean across munis, count -> sum). All join to crime_mun for names.
210
+ .IND_SPEC <- list(
211
+ homicidio = list(table = "crime_mun", expr = "CAST(homicidio AS DOUBLE)", per_muni = FALSE, kind = "rate", label = "homicide rate", unit = "per 100k"),
212
+ robo = list(table = "crime_mun", expr = "CAST(robo AS DOUBLE)", per_muni = FALSE, kind = "rate", label = "robbery rate", unit = "per 100k"),
213
+ secuestro = list(table = "crime_mun", expr = "CAST(secuestro AS DOUBLE)", per_muni = FALSE, kind = "rate", label = "kidnapping rate", unit = "per 100k"),
214
+ lesiones = list(table = "crime_mun", expr = "CAST(lesiones AS DOUBLE)", per_muni = FALSE, kind = "rate", label = "intentional-injury rate", unit = "per 100k"),
215
+ trafico_menores = list(table = "crime_mun", expr = "CAST(trafico_menores AS DOUBLE)", per_muni = FALSE, kind = "rate", label = "child-trafficking rate", unit = "per 100k"),
216
+ trata_personas = list(table = "crime_mun", expr = "CAST(trata_personas AS DOUBLE)", per_muni = FALSE, kind = "rate", label = "human-trafficking rate", unit = "per 100k"),
217
+ poblacion = list(table = "crime_mun", expr = "CAST(poblacion AS DOUBLE)", per_muni = FALSE, kind = "count", label = "population", unit = "people"),
218
+ desap_n = list(table = "dim_desap_mun", expr = "final_desap_nl", per_muni = TRUE, kind = "count", label = "disappeared and not located", unit = "count"),
219
+ pam_n = list(table = "dim_pam_mun", expr = "pam_n", per_muni = TRUE, kind = "count", label = "irregular-migration (PAM) events", unit = "count"),
220
+ fosas_total = list(table = "dim_fosas_mun", expr = "fosas_total", per_muni = TRUE, kind = "count", label = "clandestine graves", unit = "count"),
221
+ cuerpos_total = list(table = "dim_fosas_mun", expr = "cuerpos_total", per_muni = TRUE, kind = "count", label = "bodies recovered", unit = "count"),
222
+ pobreza_pct = list(table = "dim_pobreza_mun", expr = "TRY_CAST(pobreza_pct AS DOUBLE)", per_muni = FALSE, kind = "rate", label = "poverty", unit = "%"),
223
+ pobreza_ext_pct = list(table = "dim_pobreza_mun", expr = "TRY_CAST(pobreza_ext_pct AS DOUBLE)", per_muni = FALSE, kind = "rate", label = "extreme poverty", unit = "%"),
224
+ remesas_2024 = list(table = "dim_remesas_mun", expr = "TRY_CAST(remesas_2024 AS DOUBLE)", per_muni = FALSE, kind = "count", label = "remittances 2024", unit = "MXN")
225
  )
226
  .POINT_TO_COUNT <- c(migrants = "n_incidents", graves = "n_graves",
227
  infra = "n_facilities", ocved = "ocved_n", ged = "n_ged")
228
 
229
+ # Optional enrichment: load the generated indicator_dictionary.json (labels, units,
230
+ # aliases) to refine tool descriptions + alias routing. Absent -> use .IND_SPEC.
231
+ .load_ind_dict <- function() {
232
+ if (!is.null(.assist_cache$ind_dict)) return(.assist_cache$ind_dict)
233
+ cands <- c(Sys.getenv("DEPTH_INDICATOR_DICT", ""), "/app/indicator_dictionary.json",
234
+ "data/export/web/indicator_dictionary.json")
235
+ cands <- cands[nzchar(cands) & file.exists(cands)]
236
+ d <- if (length(cands) > 0)
237
+ tryCatch(jsonlite::fromJSON(cands[1], simplifyVector = FALSE)$indicators,
238
+ error = function(e) NULL) else NULL
239
+ .assist_cache$ind_dict <- d %||% list()
240
+ .assist_cache$ind_dict
241
+ }
242
+
243
+ # alias (and label) -> canonical .IND_SPEC key, so "asesinatos"/"murders" -> homicidio.
244
+ .alias_map <- function() {
245
+ if (!is.null(.assist_cache$alias_map)) return(.assist_cache$alias_map)
246
+ m <- list()
247
+ for (k in names(.IND_SPEC)) m[[tolower(k)]] <- k
248
+ for (it in .load_ind_dict()) {
249
+ key <- it$key
250
+ if (is.null(key) || is.null(.IND_SPEC[[key]])) next
251
+ for (a in unlist(it$aliases)) if (nzchar(a)) m[[tolower(trimws(a))]] <- key
252
+ if (nzchar(it$label_en %||% "")) m[[tolower(it$label_en)]] <- key
253
+ }
254
+ .assist_cache$alias_map <- m
255
+ m
256
+ }
257
+
258
+ .resolve_indicator <- function(x) {
259
+ if (is.null(x) || !nzchar(x)) return(NA_character_)
260
+ if (!is.null(.IND_SPEC[[x]])) return(x)
261
+ am <- .alias_map(); xl <- tolower(trimws(x))
262
+ if (!is.null(am[[xl]])) return(am[[xl]])
263
+ NA_character_
264
+ }
265
+
266
+ # Human label "<label> (<unit>)", preferring the dictionary's wording.
267
+ .ind_label <- function(key) {
268
+ spec <- .IND_SPEC[[key]]
269
+ de <- Filter(function(it) identical(it$key, key), .load_ind_dict())
270
+ lab <- if (length(de) > 0 && nzchar(de[[1]]$label_en %||% "")) de[[1]]$label_en else spec$label
271
+ unit <- if (length(de) > 0 && nzchar(de[[1]]$unit %||% "")) de[[1]]$unit else spec$unit
272
+ if (nzchar(unit)) paste0(lab, " (", unit, ")") else lab
273
+ }
274
+
275
+ # CVEGEO list of the current selection (NULL when national / nothing selected).
276
+ .cvegeo_filter <- function(state) {
277
+ if (isTRUE(state$is_national) || is.null(state$mun_df) ||
278
+ is.null(state$mun_df$CVEGEO) || nrow(state$mun_df) == 0) return(NULL)
279
+ cv <- unique(state$mun_df$CVEGEO)
280
+ cv <- cv[!is.na(cv) & nzchar(cv)]
281
+ if (length(cv) == 0) NULL else cv
282
+ }
283
+
284
+ # Per-municipality (CVEGEO, value) subquery for an indicator spec.
285
+ .base_subquery <- function(spec) {
286
+ if (isTRUE(spec$per_muni))
287
+ glue::glue("(SELECT CVEGEO AS k, SUM({spec$expr}) AS v FROM {spec$table} GROUP BY CVEGEO)")
288
+ else
289
+ glue::glue("(SELECT CVEGEO AS k, {spec$expr} AS v FROM {spec$table})")
290
+ }
291
+
292
+ .quote_cvegeos <- function(cv) paste0("'", gsub("'", "''", cv), "'", collapse = ",")
293
+
294
  assistant_selection_summary <- function(state) {
295
  cnt <- state$counts %||% list()
296
  scope <- if (isTRUE(state$is_national)) {
 
323
  }
324
 
325
  assistant_aggregate <- function(con, state, indicator, statistic = "mean") {
326
+ key <- .resolve_indicator(indicator)
327
+ if (is.na(key)) return(paste0("Unknown indicator: ", indicator,
328
+ ". Available: ", paste(names(.IND_SPEC), collapse = ", ")))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  if (is.null(con)) return("No database connection available.")
330
+ spec <- .IND_SPEC[[key]]
331
+ if (!statistic %in% c("mean", "sum", "max", "min"))
332
+ statistic <- if (spec$kind == "rate") "mean" else "sum"
333
+ lab <- .ind_label(key)
334
+ base <- .base_subquery(spec)
335
+ cv <- .cvegeo_filter(state)
336
+ where <- if (!is.null(cv)) glue::glue("WHERE k IN ({.quote_cvegeos(cv)})") else ""
337
+ agg <- switch(statistic, mean = "AVG(v)", sum = "SUM(v)", max = "MAX(v)", min = "MIN(v)")
338
+ sql <- glue::glue("SELECT {agg} AS res, COUNT(v) AS n FROM {base} {where}")
339
+ r <- tryCatch(DBI::dbGetQuery(con, sql),
340
+ error = function(e) { message("[assist] agg: ", conditionMessage(e)); NULL })
341
+ if (is.null(r) || nrow(r) == 0 || is.na(r$res[1]))
342
+ return(glue::glue("No {lab} data for that scope."))
343
+ scope <- if (!is.null(cv)) glue::glue("{r$n[1]} selected municipalities")
344
+ else glue::glue("all {r$n[1]} municipalities in Mexico")
345
+ glue::glue("{statistic} of {lab} across {scope}: {round(r$res[1], 2)}.")
 
346
  }
347
 
348
  assistant_top_municipalities <- function(con, state, indicator, n = 5) {
349
+ key <- .resolve_indicator(indicator)
350
+ if (is.na(key)) return(paste0("Unknown indicator: ", indicator,
351
+ ". Available: ", paste(names(.IND_SPEC), collapse = ", ")))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  if (is.null(con)) return("No database connection available.")
353
+ n <- suppressWarnings(as.integer(n)); if (is.na(n)) n <- 5L
354
+ n <- max(1L, min(20L, n))
355
+ spec <- .IND_SPEC[[key]]; lab <- .ind_label(key)
356
+ base <- .base_subquery(spec)
357
+ cv <- .cvegeo_filter(state)
358
+ conds <- "b.v IS NOT NULL"
359
+ if (!is.null(cv)) conds <- paste0(conds, " AND b.k IN (", .quote_cvegeos(cv), ")")
360
+ sql <- glue::glue(
361
+ "SELECT c.municipio, c.entidad, b.v AS v
362
+ FROM {base} b JOIN crime_mun c ON c.CVEGEO = b.k
363
+ WHERE {conds}
364
+ ORDER BY v DESC NULLS LAST LIMIT {n}")
365
+ r <- tryCatch(DBI::dbGetQuery(con, sql),
366
+ error = function(e) { message("[assist] top: ", conditionMessage(e)); NULL })
367
+ if (is.null(r) || nrow(r) == 0) return(glue::glue("No {lab} data for that scope."))
368
+ scope <- if (!is.null(cv)) "current selection" else "Mexico"
 
369
  items <- sprintf("%d. %s (%s): %s", seq_len(nrow(r)), r$municipio, r$entidad, round(r$v, 2))
370
+ paste0("Top municipalities by ", lab, " (", scope, "):\n", paste(items, collapse = "\n"))
371
  }
372
 
373
  # --- Assemble the per-session chat -------------------------------------------
 
391
  )
392
  if (is.null(chat)) return(NULL)
393
 
394
+ ind_desc <- paste0("Indicator key. Options -- ",
395
+ paste(vapply(names(.IND_SPEC), \(k) paste0(k, " (", .ind_label(k), ")"), character(1)),
396
+ collapse = "; "))
397
+ ind_enum <- ellmer::type_enum(names(.IND_SPEC), ind_desc)
398
 
399
  # Only the 3 data tools are registered. The indicator catalog lives in the
400
  # system prompt and the current scope/totals are injected as <scope> each turn,
 
407
  count_points <- function(layer) assistant_count_points(state, layer)
408
 
409
  chat$register_tool(ellmer::tool(aggregate_indicator,
410
+ "Aggregate one municipality indicator over the current map selection (or nationally if nothing is selected). Use 'mean' for rate indicators (rates, %) and 'sum' for counts.",
411
  arguments = list(
412
  indicator = ind_enum,
413
  statistic = ellmer::type_enum(c("mean", "sum", "max", "min"),
indicator_dictionary.json ADDED
@@ -0,0 +1,1415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generated": "2026-06-22",
3
+ "source": "data/export/duckdb/depth_mexico.duckdb",
4
+ "n_indicators": 128,
5
+ "indicators": [
6
+ {
7
+ "key": "poblacion",
8
+ "table": "crime_mun",
9
+ "type": "DOUBLE",
10
+ "label_en": "Population",
11
+ "label_es": "Población",
12
+ "unit": "people",
13
+ "source": "SESNSP (Sistema Nacional de Seguridad Publica), 2022-2025",
14
+ "range": [282, 7351266],
15
+ "aliases": ["population", "poblacion", "habitantes"]
16
+ },
17
+ {
18
+ "key": "homicidio",
19
+ "table": "crime_mun",
20
+ "type": "DOUBLE",
21
+ "label_en": "Homicides",
22
+ "label_es": "Tasa de homicidios dolosos",
23
+ "unit": "per 100k",
24
+ "source": "SESNSP (Sistema Nacional de Seguridad Publica), 2022-2025",
25
+ "range": [0, 783.73],
26
+ "aliases": ["homicide", "murders", "homicidios", "asesinatos"]
27
+ },
28
+ {
29
+ "key": "lesiones",
30
+ "table": "crime_mun",
31
+ "type": "DOUBLE",
32
+ "label_en": "Intentional injury rate",
33
+ "label_es": "Tasa de lesiones dolosas",
34
+ "unit": "per 100k",
35
+ "source": "SESNSP (Sistema Nacional de Seguridad Publica), 2022-2025",
36
+ "range": [0, 768.27],
37
+ "aliases": ["injuries", "assault", "lesiones"]
38
+ },
39
+ {
40
+ "key": "rapto",
41
+ "table": "crime_mun",
42
+ "type": "DOUBLE",
43
+ "label_en": "Abduction (non-extortion) rate",
44
+ "label_es": "Tasa de rapto",
45
+ "unit": "per 100k",
46
+ "source": "SESNSP (Sistema Nacional de Seguridad Publica), 2022-2025",
47
+ "range": [0, 9.63],
48
+ "aliases": ["abduction", "rapto"]
49
+ },
50
+ {
51
+ "key": "robo",
52
+ "table": "crime_mun",
53
+ "type": "DOUBLE",
54
+ "label_en": "Robberies",
55
+ "label_es": "Tasa de robo",
56
+ "unit": "per 100k",
57
+ "source": "SESNSP (Sistema Nacional de Seguridad Publica), 2022-2025",
58
+ "range": [0, 2189.24],
59
+ "aliases": ["robbery", "theft", "robo", "asalto"]
60
+ },
61
+ {
62
+ "key": "secuestro",
63
+ "table": "crime_mun",
64
+ "type": "DOUBLE",
65
+ "label_en": "Kidnapping",
66
+ "label_es": "Tasa de secuestro",
67
+ "unit": "per 100k",
68
+ "source": "SESNSP (Sistema Nacional de Seguridad Publica), 2022-2025",
69
+ "range": [0, 26.47],
70
+ "aliases": ["kidnapping", "secuestro", "abduction"]
71
+ },
72
+ {
73
+ "key": "trafico_menores",
74
+ "table": "crime_mun",
75
+ "type": "DOUBLE",
76
+ "label_en": "child trafficking",
77
+ "label_es": "Tasa de trafico de menores",
78
+ "unit": "per 100k",
79
+ "source": "SESNSP (Sistema Nacional de Seguridad Publica), 2022-2025",
80
+ "range": [0, 0.86],
81
+ "aliases": ["child trafficking", "trafico de menores", "menores"]
82
+ },
83
+ {
84
+ "key": "trata_personas",
85
+ "table": "crime_mun",
86
+ "type": "DOUBLE",
87
+ "label_en": "human trafficking",
88
+ "label_es": "Tasa de trata de personas",
89
+ "unit": "per 100k",
90
+ "source": "SESNSP (Sistema Nacional de Seguridad Publica), 2022-2025",
91
+ "range": [0, 26.09],
92
+ "aliases": ["human trafficking", "trata", "trafficking"]
93
+ },
94
+ {
95
+ "key": "final_desap_nl",
96
+ "table": "dim_desap_mun",
97
+ "type": "DOUBLE",
98
+ "label_en": "disappearance and not located people",
99
+ "label_es": "Desaparecidos y no localizados",
100
+ "unit": "count",
101
+ "source": "RNPDNO (Registro Nacional de Personas Desaparecidas), 2014-2023",
102
+ "range": [1.0001, 1827.1838],
103
+ "aliases": ["disappearances", "missing", "desaparecidos", "no localizados"]
104
+ },
105
+ {
106
+ "key": "final_desap_nl_per_100k",
107
+ "table": "dim_desap_mun",
108
+ "type": "DOUBLE",
109
+ "label_en": "disappearance and not located per 100k inhabitants",
110
+ "label_es": "Desaparecidos no localizados por 100 mil habitantes",
111
+ "unit": "per 100k",
112
+ "source": "RNPDNO (Registro Nacional de Personas Desaparecidas), 2014-2023",
113
+ "range": [0.051, 5653.7652],
114
+ "aliases": ["disappearances rate", "desaparecidos por 100k"]
115
+ },
116
+ {
117
+ "key": "dark_figures_perc",
118
+ "table": "dim_envipe_state",
119
+ "type": "DOUBLE",
120
+ "label_en": "Dark figures (unreported crime)",
121
+ "label_es": "Cifra negra (delitos no denunciados)",
122
+ "unit": "%",
123
+ "source": "ENVIPE (Encuesta Nacional de Victimizacion y Percepcion sobre Seguridad Publica), 2022-2023",
124
+ "range": [88.2671, 97.1171],
125
+ "aliases": ["dark figure", "unreported crime", "cifra negra"]
126
+ },
127
+ {
128
+ "key": "perception_insecurity_perc",
129
+ "table": "dim_envipe_state",
130
+ "type": "DOUBLE",
131
+ "label_en": "Insecurity perspection",
132
+ "label_es": "Percepcion de inseguridad",
133
+ "unit": "%",
134
+ "source": "ENVIPE (Encuesta Nacional de Victimizacion y Percepcion sobre Seguridad Publica), 2022-2023",
135
+ "range": [20.419, 57.993],
136
+ "aliases": ["insecurity perception", "percepcion de inseguridad", "inseguridad"]
137
+ },
138
+ {
139
+ "key": "year",
140
+ "table": "dim_fosas_mun",
141
+ "type": "INTEGER",
142
+ "label_en": "Year",
143
+ "label_es": "Año",
144
+ "unit": "",
145
+ "source": "Plataforma Ciudadana de Fosas, 2006-2024",
146
+ "range": [2007, 2024],
147
+ "aliases": []
148
+ },
149
+ {
150
+ "key": "fosas_total",
151
+ "table": "dim_fosas_mun",
152
+ "type": "DOUBLE",
153
+ "label_en": "Clandestine graves",
154
+ "label_es": "Fosas clandestinas",
155
+ "unit": "count",
156
+ "source": "Plataforma Ciudadana de Fosas, 2006-2024",
157
+ "range": [1, 102.5],
158
+ "aliases": ["graves", "clandestine graves", "fosas", "fosas clandestinas"]
159
+ },
160
+ {
161
+ "key": "cuerpos_total",
162
+ "table": "dim_fosas_mun",
163
+ "type": "DOUBLE",
164
+ "label_en": "Bodies founded in clandestine graves",
165
+ "label_es": "Cuerpos / restos recuperados",
166
+ "unit": "count",
167
+ "source": "Plataforma Ciudadana de Fosas, 2006-2024",
168
+ "range": [0, 154],
169
+ "aliases": ["bodies", "remains", "cuerpos", "restos"]
170
+ },
171
+ {
172
+ "key": "pam_n",
173
+ "table": "dim_pam_mun",
174
+ "type": "INTEGER",
175
+ "label_en": "Irregular migration events (PAM)",
176
+ "label_es": "Eventos de migracion irregular (PAM)",
177
+ "unit": "count",
178
+ "source": "Unidad de Politica Migratoria (PAM), Gobierno de Mexico",
179
+ "range": [1, 17464],
180
+ "aliases": ["irregular migration", "pam", "migracion irregular", "presentados"]
181
+ },
182
+ {
183
+ "key": "pam_presentados_n",
184
+ "table": "dim_pam_mun",
185
+ "type": "INTEGER",
186
+ "label_en": "Irregular migration cases",
187
+ "label_es": "PAM personas presentadas",
188
+ "unit": "count",
189
+ "source": "Unidad de Politica Migratoria (PAM), Gobierno de Mexico",
190
+ "range": [0, 17357],
191
+ "aliases": "presentados"
192
+ },
193
+ {
194
+ "key": "pam_canalizados_n",
195
+ "table": "dim_pam_mun",
196
+ "type": "INTEGER",
197
+ "label_en": "Irregular migration deported",
198
+ "label_es": "PAM personas canalizadas",
199
+ "unit": "count",
200
+ "source": "Unidad de Politica Migratoria (PAM), Gobierno de Mexico",
201
+ "range": [0, 5440],
202
+ "aliases": "canalizados"
203
+ },
204
+ {
205
+ "key": "pam_hombre_n",
206
+ "table": "dim_pam_mun",
207
+ "type": "INTEGER",
208
+ "label_en": "Irregular migrates cases (male)",
209
+ "label_es": "PAM hombres",
210
+ "unit": "count",
211
+ "source": "Unidad de Politica Migratoria (PAM), Gobierno de Mexico",
212
+ "range": [0, 13708],
213
+ "aliases": ["men", "hombres"]
214
+ },
215
+ {
216
+ "key": "pam_mujer_n",
217
+ "table": "dim_pam_mun",
218
+ "type": "INTEGER",
219
+ "label_en": "Irregular migrates cases (female)",
220
+ "label_es": "PAM mujeres",
221
+ "unit": "count",
222
+ "source": "Unidad de Politica Migratoria (PAM), Gobierno de Mexico",
223
+ "range": [0, 3967],
224
+ "aliases": ["women", "mujeres"]
225
+ },
226
+ {
227
+ "key": "pob_muj",
228
+ "table": "dim_patp_mun",
229
+ "type": "DOUBLE",
230
+ "label_en": "",
231
+ "label_es": "",
232
+ "unit": "",
233
+ "source": "CONEVAL - PATP territorial analysis, 2020",
234
+ "range": [41.4818, 59.4937],
235
+ "aliases": []
236
+ },
237
+ {
238
+ "key": "pob_hom",
239
+ "table": "dim_patp_mun",
240
+ "type": "DOUBLE",
241
+ "label_en": "",
242
+ "label_es": "",
243
+ "unit": "",
244
+ "source": "CONEVAL - PATP territorial analysis, 2020",
245
+ "range": [40.5063, 58.5182],
246
+ "aliases": []
247
+ },
248
+ {
249
+ "key": "men_5",
250
+ "table": "dim_patp_mun",
251
+ "type": "DOUBLE",
252
+ "label_en": "",
253
+ "label_es": "",
254
+ "unit": "",
255
+ "source": "CONEVAL - PATP territorial analysis, 2020",
256
+ "range": [2.596, 18.3688],
257
+ "aliases": []
258
+ },
259
+ {
260
+ "key": "edad_5_14",
261
+ "table": "dim_patp_mun",
262
+ "type": "DOUBLE",
263
+ "label_en": "",
264
+ "label_es": "",
265
+ "unit": "",
266
+ "source": "CONEVAL - PATP territorial analysis, 2020",
267
+ "range": [5.8824, 31.3135],
268
+ "aliases": []
269
+ },
270
+ {
271
+ "key": "edad_15_59",
272
+ "table": "dim_patp_mun",
273
+ "type": "DOUBLE",
274
+ "label_en": "",
275
+ "label_es": "",
276
+ "unit": "",
277
+ "source": "CONEVAL - PATP territorial analysis, 2020",
278
+ "range": [42.3464, 70.7873],
279
+ "aliases": []
280
+ },
281
+ {
282
+ "key": "edad_60_74",
283
+ "table": "dim_patp_mun",
284
+ "type": "DOUBLE",
285
+ "label_en": "",
286
+ "label_es": "",
287
+ "unit": "",
288
+ "source": "CONEVAL - PATP territorial analysis, 2020",
289
+ "range": [2.218, 26.9634],
290
+ "aliases": []
291
+ },
292
+ {
293
+ "key": "edad_75",
294
+ "table": "dim_patp_mun",
295
+ "type": "DOUBLE",
296
+ "label_en": "",
297
+ "label_es": "",
298
+ "unit": "",
299
+ "source": "CONEVAL - PATP territorial analysis, 2020",
300
+ "range": [0.587, 19.4656],
301
+ "aliases": []
302
+ },
303
+ {
304
+ "key": "pob_afro",
305
+ "table": "dim_patp_mun",
306
+ "type": "DOUBLE",
307
+ "label_en": "",
308
+ "label_es": "",
309
+ "unit": "",
310
+ "source": "CONEVAL - PATP territorial analysis, 2020",
311
+ "range": [0, 95.6911],
312
+ "aliases": []
313
+ },
314
+ {
315
+ "key": "decl_g",
316
+ "table": "dim_patp_mun",
317
+ "type": "DOUBLE",
318
+ "label_en": "",
319
+ "label_es": "",
320
+ "unit": "",
321
+ "source": "CONEVAL - PATP territorial analysis, 2020",
322
+ "range": [0, 4],
323
+ "aliases": []
324
+ },
325
+ {
326
+ "key": "decl_h",
327
+ "table": "dim_patp_mun",
328
+ "type": "DOUBLE",
329
+ "label_en": "",
330
+ "label_es": "",
331
+ "unit": "",
332
+ "source": "CONEVAL - PATP territorial analysis, 2020",
333
+ "range": [0, 11],
334
+ "aliases": []
335
+ },
336
+ {
337
+ "key": "prc_anp",
338
+ "table": "dim_patp_mun",
339
+ "type": "DOUBLE",
340
+ "label_en": "",
341
+ "label_es": "",
342
+ "unit": "",
343
+ "source": "CONEVAL - PATP territorial analysis, 2020",
344
+ "range": [0, 100],
345
+ "aliases": []
346
+ },
347
+ {
348
+ "key": "pr_p_1hmas_bbienestar_auto",
349
+ "table": "dim_patp_mun",
350
+ "type": "DOUBLE",
351
+ "label_en": "",
352
+ "label_es": "",
353
+ "unit": "",
354
+ "source": "CONEVAL - PATP territorial analysis, 2020",
355
+ "range": [0, 100],
356
+ "aliases": []
357
+ },
358
+ {
359
+ "key": "pr_p_1hmas_bbienestar_pie",
360
+ "table": "dim_patp_mun",
361
+ "type": "DOUBLE",
362
+ "label_en": "",
363
+ "label_es": "",
364
+ "unit": "",
365
+ "source": "CONEVAL - PATP territorial analysis, 2020",
366
+ "range": [0, 100],
367
+ "aliases": []
368
+ },
369
+ {
370
+ "key": "ind_conec",
371
+ "table": "dim_patp_mun",
372
+ "type": "DOUBLE",
373
+ "label_en": "Roads connectivity index",
374
+ "label_es": "",
375
+ "unit": "",
376
+ "source": "CONEVAL - PATP territorial analysis, 2020",
377
+ "range": [0.0112, 0.9313],
378
+ "aliases": []
379
+ },
380
+ {
381
+ "key": "pob_2020",
382
+ "table": "dim_pobreza_mun",
383
+ "type": "INTEGER",
384
+ "label_en": "poverty",
385
+ "label_es": "pobreza",
386
+ "unit": "",
387
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
388
+ "range": [81, 1913345],
389
+ "aliases": []
390
+ },
391
+ {
392
+ "key": "pobreza_pct",
393
+ "table": "dim_pobreza_mun",
394
+ "type": "DOUBLE",
395
+ "label_en": "Share population in poverty",
396
+ "label_es": "Porcentaje de población en pobreza",
397
+ "unit": "%",
398
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
399
+ "range": [5.45, 99.65],
400
+ "aliases": []
401
+ },
402
+ {
403
+ "key": "pobreza_pers",
404
+ "table": "dim_pobreza_mun",
405
+ "type": "DOUBLE",
406
+ "label_en": "",
407
+ "label_es": "",
408
+ "unit": "people",
409
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
410
+ "range": [55, 816934],
411
+ "aliases": []
412
+ },
413
+ {
414
+ "key": "pobreza_carencias_prom",
415
+ "table": "dim_pobreza_mun",
416
+ "type": "DOUBLE",
417
+ "label_en": "",
418
+ "label_es": "",
419
+ "unit": "avg deprivations",
420
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
421
+ "range": [1.21, 3.93],
422
+ "aliases": []
423
+ },
424
+ {
425
+ "key": "pobreza_ext_pct",
426
+ "table": "dim_pobreza_mun",
427
+ "type": "DOUBLE",
428
+ "label_en": "Share population in extreme poverty",
429
+ "label_es": "Porcentaje de población en pobreza extrema",
430
+ "unit": "%",
431
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
432
+ "range": [0, 84.45],
433
+ "aliases": []
434
+ },
435
+ {
436
+ "key": "pobreza_ext_pers",
437
+ "table": "dim_pobreza_mun",
438
+ "type": "DOUBLE",
439
+ "label_en": "",
440
+ "label_es": "",
441
+ "unit": "people",
442
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
443
+ "range": [0, 126672],
444
+ "aliases": []
445
+ },
446
+ {
447
+ "key": "pobreza_ext_carencias_prom",
448
+ "table": "dim_pobreza_mun",
449
+ "type": "DOUBLE",
450
+ "label_en": "",
451
+ "label_es": "",
452
+ "unit": "avg deprivations",
453
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
454
+ "range": [3.03, 4.26],
455
+ "aliases": []
456
+ },
457
+ {
458
+ "key": "pobreza_mod_pct",
459
+ "table": "dim_pobreza_mun",
460
+ "type": "DOUBLE",
461
+ "label_en": "",
462
+ "label_es": "",
463
+ "unit": "%",
464
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
465
+ "range": [5.17, 85.04],
466
+ "aliases": []
467
+ },
468
+ {
469
+ "key": "pobreza_mod_pers",
470
+ "table": "dim_pobreza_mun",
471
+ "type": "DOUBLE",
472
+ "label_en": "",
473
+ "label_es": "",
474
+ "unit": "people",
475
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
476
+ "range": [47, 700991],
477
+ "aliases": []
478
+ },
479
+ {
480
+ "key": "pobreza_mod_carencias_prom",
481
+ "table": "dim_pobreza_mun",
482
+ "type": "DOUBLE",
483
+ "label_en": "",
484
+ "label_es": "",
485
+ "unit": "avg deprivations",
486
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
487
+ "range": [1.2, 3.59],
488
+ "aliases": []
489
+ },
490
+ {
491
+ "key": "vul_carencia_pct",
492
+ "table": "dim_pobreza_mun",
493
+ "type": "DOUBLE",
494
+ "label_en": "",
495
+ "label_es": "",
496
+ "unit": "%",
497
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
498
+ "range": [0, 77.58],
499
+ "aliases": []
500
+ },
501
+ {
502
+ "key": "vul_carencia_pers",
503
+ "table": "dim_pobreza_mun",
504
+ "type": "DOUBLE",
505
+ "label_en": "",
506
+ "label_es": "",
507
+ "unit": "people",
508
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
509
+ "range": [0, 728225],
510
+ "aliases": []
511
+ },
512
+ {
513
+ "key": "vul_carencia_carencias_prom",
514
+ "table": "dim_pobreza_mun",
515
+ "type": "DOUBLE",
516
+ "label_en": "",
517
+ "label_es": "",
518
+ "unit": "avg deprivations",
519
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
520
+ "range": [1.17, 3.39],
521
+ "aliases": []
522
+ },
523
+ {
524
+ "key": "vul_ingreso_pct",
525
+ "table": "dim_pobreza_mun",
526
+ "type": "DOUBLE",
527
+ "label_en": "",
528
+ "label_es": "",
529
+ "unit": "%",
530
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
531
+ "range": [0, 23.61],
532
+ "aliases": []
533
+ },
534
+ {
535
+ "key": "vul_ingreso_pers",
536
+ "table": "dim_pobreza_mun",
537
+ "type": "DOUBLE",
538
+ "label_en": "",
539
+ "label_es": "",
540
+ "unit": "people",
541
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
542
+ "range": [0, 215576],
543
+ "aliases": []
544
+ },
545
+ {
546
+ "key": "no_pobre_no_vul_pct",
547
+ "table": "dim_pobreza_mun",
548
+ "type": "DOUBLE",
549
+ "label_en": "",
550
+ "label_es": "",
551
+ "unit": "%",
552
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
553
+ "range": [0, 57.43],
554
+ "aliases": []
555
+ },
556
+ {
557
+ "key": "no_pobre_no_vul_pers",
558
+ "table": "dim_pobreza_mun",
559
+ "type": "DOUBLE",
560
+ "label_en": "",
561
+ "label_es": "",
562
+ "unit": "people",
563
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
564
+ "range": [0, 615131],
565
+ "aliases": []
566
+ },
567
+ {
568
+ "key": "rezago_edu_pct",
569
+ "table": "dim_pobreza_mun",
570
+ "type": "DOUBLE",
571
+ "label_en": "",
572
+ "label_es": "",
573
+ "unit": "%",
574
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
575
+ "range": [2.87, 61.39],
576
+ "aliases": []
577
+ },
578
+ {
579
+ "key": "rezago_edu_pers",
580
+ "table": "dim_pobreza_mun",
581
+ "type": "DOUBLE",
582
+ "label_en": "",
583
+ "label_es": "",
584
+ "unit": "people",
585
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
586
+ "range": [16, 319095],
587
+ "aliases": []
588
+ },
589
+ {
590
+ "key": "rezago_edu_carencias_prom",
591
+ "table": "dim_pobreza_mun",
592
+ "type": "DOUBLE",
593
+ "label_en": "",
594
+ "label_es": "",
595
+ "unit": "avg deprivations",
596
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
597
+ "range": [1.36, 4.4],
598
+ "aliases": []
599
+ },
600
+ {
601
+ "key": "caren_salud_pct",
602
+ "table": "dim_pobreza_mun",
603
+ "type": "DOUBLE",
604
+ "label_en": "",
605
+ "label_es": "",
606
+ "unit": "%",
607
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
608
+ "range": [1.05, 83.86],
609
+ "aliases": []
610
+ },
611
+ {
612
+ "key": "caren_salud_pers",
613
+ "table": "dim_pobreza_mun",
614
+ "type": "DOUBLE",
615
+ "label_en": "",
616
+ "label_es": "",
617
+ "unit": "people",
618
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
619
+ "range": [12, 637666],
620
+ "aliases": []
621
+ },
622
+ {
623
+ "key": "caren_salud_carencias_prom",
624
+ "table": "dim_pobreza_mun",
625
+ "type": "DOUBLE",
626
+ "label_en": "",
627
+ "label_es": "",
628
+ "unit": "avg deprivations",
629
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
630
+ "range": [1.93, 4.77],
631
+ "aliases": []
632
+ },
633
+ {
634
+ "key": "caren_seg_social_pct",
635
+ "table": "dim_pobreza_mun",
636
+ "type": "DOUBLE",
637
+ "label_en": "",
638
+ "label_es": "",
639
+ "unit": "%",
640
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
641
+ "range": [22.03, 96.99],
642
+ "aliases": []
643
+ },
644
+ {
645
+ "key": "caren_seg_social_pers",
646
+ "table": "dim_pobreza_mun",
647
+ "type": "DOUBLE",
648
+ "label_en": "",
649
+ "label_es": "",
650
+ "unit": "people",
651
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
652
+ "range": [52, 955642],
653
+ "aliases": []
654
+ },
655
+ {
656
+ "key": "caren_seg_social_carencias_prom",
657
+ "table": "dim_pobreza_mun",
658
+ "type": "DOUBLE",
659
+ "label_en": "",
660
+ "label_es": "",
661
+ "unit": "avg deprivations",
662
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
663
+ "range": [1.23, 3.96],
664
+ "aliases": []
665
+ },
666
+ {
667
+ "key": "caren_vivienda_pct",
668
+ "table": "dim_pobreza_mun",
669
+ "type": "DOUBLE",
670
+ "label_en": "",
671
+ "label_es": "",
672
+ "unit": "%",
673
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
674
+ "range": [0.78, 76.68],
675
+ "aliases": []
676
+ },
677
+ {
678
+ "key": "caren_vivienda_pers",
679
+ "table": "dim_pobreza_mun",
680
+ "type": "DOUBLE",
681
+ "label_en": "",
682
+ "label_es": "",
683
+ "unit": "people",
684
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
685
+ "range": [5, 130963],
686
+ "aliases": []
687
+ },
688
+ {
689
+ "key": "caren_vivienda_carencias_prom",
690
+ "table": "dim_pobreza_mun",
691
+ "type": "DOUBLE",
692
+ "label_en": "",
693
+ "label_es": "",
694
+ "unit": "avg deprivations",
695
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
696
+ "range": [1.76, 4.62],
697
+ "aliases": []
698
+ },
699
+ {
700
+ "key": "caren_serv_basicos_pct",
701
+ "table": "dim_pobreza_mun",
702
+ "type": "DOUBLE",
703
+ "label_en": "",
704
+ "label_es": "",
705
+ "unit": "%",
706
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
707
+ "range": [0.08, 100],
708
+ "aliases": []
709
+ },
710
+ {
711
+ "key": "caren_serv_basicos_pers",
712
+ "table": "dim_pobreza_mun",
713
+ "type": "DOUBLE",
714
+ "label_en": "",
715
+ "label_es": "",
716
+ "unit": "people",
717
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
718
+ "range": [1, 220919],
719
+ "aliases": []
720
+ },
721
+ {
722
+ "key": "caren_serv_basicos_carencias_prom",
723
+ "table": "dim_pobreza_mun",
724
+ "type": "DOUBLE",
725
+ "label_en": "",
726
+ "label_es": "",
727
+ "unit": "avg deprivations",
728
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
729
+ "range": [1.51, 4.38],
730
+ "aliases": []
731
+ },
732
+ {
733
+ "key": "caren_alimentacion_pct",
734
+ "table": "dim_pobreza_mun",
735
+ "type": "DOUBLE",
736
+ "label_en": "",
737
+ "label_es": "",
738
+ "unit": "%",
739
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
740
+ "range": [0, 75.66],
741
+ "aliases": []
742
+ },
743
+ {
744
+ "key": "caren_alimentacion_pers",
745
+ "table": "dim_pobreza_mun",
746
+ "type": "DOUBLE",
747
+ "label_en": "",
748
+ "label_es": "",
749
+ "unit": "people",
750
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
751
+ "range": [0, 515767],
752
+ "aliases": []
753
+ },
754
+ {
755
+ "key": "caren_alimentacion_carencias_prom",
756
+ "table": "dim_pobreza_mun",
757
+ "type": "DOUBLE",
758
+ "label_en": "",
759
+ "label_es": "",
760
+ "unit": "avg deprivations",
761
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
762
+ "range": [1.73, 4.58],
763
+ "aliases": []
764
+ },
765
+ {
766
+ "key": "min_1_carencia_pct",
767
+ "table": "dim_pobreza_mun",
768
+ "type": "DOUBLE",
769
+ "label_en": "",
770
+ "label_es": "",
771
+ "unit": "%",
772
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
773
+ "range": [38.85, 100],
774
+ "aliases": []
775
+ },
776
+ {
777
+ "key": "min_1_carencia_pers",
778
+ "table": "dim_pobreza_mun",
779
+ "type": "DOUBLE",
780
+ "label_en": "",
781
+ "label_es": "",
782
+ "unit": "people",
783
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
784
+ "range": [75, 1220637],
785
+ "aliases": []
786
+ },
787
+ {
788
+ "key": "min_1_carencia_carencias_prom",
789
+ "table": "dim_pobreza_mun",
790
+ "type": "DOUBLE",
791
+ "label_en": "",
792
+ "label_es": "",
793
+ "unit": "avg deprivations",
794
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
795
+ "range": [1.19, 3.92],
796
+ "aliases": []
797
+ },
798
+ {
799
+ "key": "min_3_carencias_pct",
800
+ "table": "dim_pobreza_mun",
801
+ "type": "DOUBLE",
802
+ "label_en": "",
803
+ "label_es": "",
804
+ "unit": "%",
805
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
806
+ "range": [1.2, 89.89],
807
+ "aliases": []
808
+ },
809
+ {
810
+ "key": "min_3_carencias_pers",
811
+ "table": "dim_pobreza_mun",
812
+ "type": "DOUBLE",
813
+ "label_en": "",
814
+ "label_es": "",
815
+ "unit": "people",
816
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
817
+ "range": [6, 303656],
818
+ "aliases": []
819
+ },
820
+ {
821
+ "key": "min_3_carencias_carencias_prom",
822
+ "table": "dim_pobreza_mun",
823
+ "type": "DOUBLE",
824
+ "label_en": "",
825
+ "label_es": "",
826
+ "unit": "avg deprivations",
827
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
828
+ "range": [3.04, 4.23],
829
+ "aliases": []
830
+ },
831
+ {
832
+ "key": "ing_inf_lp_pct",
833
+ "table": "dim_pobreza_mun",
834
+ "type": "DOUBLE",
835
+ "label_en": "",
836
+ "label_es": "",
837
+ "unit": "%",
838
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
839
+ "range": [7.25, 99.89],
840
+ "aliases": []
841
+ },
842
+ {
843
+ "key": "ing_inf_lp_pers",
844
+ "table": "dim_pobreza_mun",
845
+ "type": "DOUBLE",
846
+ "label_en": "",
847
+ "label_es": "",
848
+ "unit": "people",
849
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
850
+ "range": [56, 1020408],
851
+ "aliases": []
852
+ },
853
+ {
854
+ "key": "ing_inf_lp_carencias_prom",
855
+ "table": "dim_pobreza_mun",
856
+ "type": "DOUBLE",
857
+ "label_en": "",
858
+ "label_es": "",
859
+ "unit": "avg deprivations",
860
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
861
+ "range": [0.72, 3.93],
862
+ "aliases": []
863
+ },
864
+ {
865
+ "key": "ing_inf_lpe_pct",
866
+ "table": "dim_pobreza_mun",
867
+ "type": "DOUBLE",
868
+ "label_en": "",
869
+ "label_es": "",
870
+ "unit": "%",
871
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
872
+ "range": [1.09, 97.55],
873
+ "aliases": []
874
+ },
875
+ {
876
+ "key": "ing_inf_lpe_pers",
877
+ "table": "dim_pobreza_mun",
878
+ "type": "DOUBLE",
879
+ "label_en": "",
880
+ "label_es": "",
881
+ "unit": "people",
882
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
883
+ "range": [11, 362871],
884
+ "aliases": []
885
+ },
886
+ {
887
+ "key": "ing_inf_lpe_carencias_prom",
888
+ "table": "dim_pobreza_mun",
889
+ "type": "DOUBLE",
890
+ "label_en": "",
891
+ "label_es": "",
892
+ "unit": "avg deprivations",
893
+ "source": "CONEVAL - Municipal poverty measurement, 2020",
894
+ "range": [0.71, 3.95],
895
+ "aliases": []
896
+ },
897
+ {
898
+ "key": "pobtot",
899
+ "table": "dim_pop_cpv_grid",
900
+ "type": "DOUBLE",
901
+ "label_en": "Population",
902
+ "label_es": "Población",
903
+ "unit": "people",
904
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
905
+ "range": [0, 24585342],
906
+ "aliases": ["total population", "poblacion total", "pobtot"]
907
+ },
908
+ {
909
+ "key": "pnacop",
910
+ "table": "dim_pop_cpv_grid",
911
+ "type": "DOUBLE",
912
+ "label_en": "Population born abroad (Census 2020)",
913
+ "label_es": "Poblacion nacida en el extranjero",
914
+ "unit": "people",
915
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
916
+ "range": [-7, 170217],
917
+ "aliases": ["born abroad", "foreign born", "nacida en el extranjero", "pnacop"]
918
+ },
919
+ {
920
+ "key": "presop2015",
921
+ "table": "dim_pop_cpv_grid",
922
+ "type": "DOUBLE",
923
+ "label_en": "Resided abroad in 2015 (Census 2020)",
924
+ "label_es": "Residian en otro pais en 2015",
925
+ "unit": "people",
926
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
927
+ "range": [-7, 77184],
928
+ "aliases": ["resided abroad 2015", "presop2015"]
929
+ },
930
+ {
931
+ "key": "remesas_2024",
932
+ "table": "dim_remesas_mun",
933
+ "type": "DOUBLE",
934
+ "label_en": "Remittances 2024",
935
+ "label_es": "Remesas 2024",
936
+ "unit": "MXN",
937
+ "source": "CONAPO - migration intensity / remittances",
938
+ "range": [0, 908.6767],
939
+ "aliases": ["remittances", "remesas"]
940
+ },
941
+ {
942
+ "key": "year",
943
+ "table": "ged_events",
944
+ "type": "DOUBLE",
945
+ "label_en": "",
946
+ "label_es": "",
947
+ "unit": "",
948
+ "source": "UCDP Georeferenced Event Dataset (GED), 2019-2024",
949
+ "range": [2019, 2024],
950
+ "aliases": []
951
+ },
952
+ {
953
+ "key": "priogrid_gid",
954
+ "table": "ged_events",
955
+ "type": "DOUBLE",
956
+ "label_en": "",
957
+ "label_es": "",
958
+ "unit": "",
959
+ "source": "UCDP Georeferenced Event Dataset (GED), 2019-2024",
960
+ "range": [150656, 176531],
961
+ "aliases": []
962
+ },
963
+ {
964
+ "key": "best",
965
+ "table": "ged_events",
966
+ "type": "DOUBLE",
967
+ "label_en": "GED conflict deaths (best estimate)",
968
+ "label_es": "Muertes por conflicto GED (mejor estim.)",
969
+ "unit": "count",
970
+ "source": "UCDP Georeferenced Event Dataset (GED), 2019-2024",
971
+ "range": [0, 145],
972
+ "aliases": ["ged", "ucdp", "conflict deaths", "conflicto"]
973
+ },
974
+ {
975
+ "key": "TiempoViaje",
976
+ "table": "graves",
977
+ "type": "DOUBLE",
978
+ "label_en": "",
979
+ "label_es": "",
980
+ "unit": "",
981
+ "source": "Geospatial analysis of clandestine graves (Reinforcement Learning)",
982
+ "range": [0, 165.6634],
983
+ "aliases": []
984
+ },
985
+ {
986
+ "key": "Visibilidad",
987
+ "table": "graves",
988
+ "type": "DOUBLE",
989
+ "label_en": "",
990
+ "label_es": "",
991
+ "unit": "",
992
+ "source": "Geospatial analysis of clandestine graves (Reinforcement Learning)",
993
+ "range": [0.4, 56.62],
994
+ "aliases": []
995
+ },
996
+ {
997
+ "key": "pobtot",
998
+ "table": "grid_nivel4",
999
+ "type": "DOUBLE",
1000
+ "label_en": "Population",
1001
+ "label_es": "Población",
1002
+ "unit": "people",
1003
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1004
+ "range": [0, 24585342],
1005
+ "aliases": ["total population", "poblacion total", "pobtot"]
1006
+ },
1007
+ {
1008
+ "key": "pnacop",
1009
+ "table": "grid_nivel4",
1010
+ "type": "DOUBLE",
1011
+ "label_en": "Population born abroad (Census 2020)",
1012
+ "label_es": "Poblacion nacida en el extranjero",
1013
+ "unit": "people",
1014
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1015
+ "range": [-6, 170217],
1016
+ "aliases": ["born abroad", "foreign born", "nacida en el extranjero", "pnacop"]
1017
+ },
1018
+ {
1019
+ "key": "presop2015",
1020
+ "table": "grid_nivel4",
1021
+ "type": "DOUBLE",
1022
+ "label_en": "Resided abroad in 2015 (Census 2020)",
1023
+ "label_es": "Residian en otro pais en 2015",
1024
+ "unit": "people",
1025
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1026
+ "range": [-6, 77184],
1027
+ "aliases": ["resided abroad 2015", "presop2015"]
1028
+ },
1029
+ {
1030
+ "key": "pobtot",
1031
+ "table": "grid_nivel5",
1032
+ "type": "DOUBLE",
1033
+ "label_en": "Population",
1034
+ "label_es": "Población",
1035
+ "unit": "people",
1036
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1037
+ "range": [0, 9029039],
1038
+ "aliases": ["total population", "poblacion total", "pobtot"]
1039
+ },
1040
+ {
1041
+ "key": "pnacop",
1042
+ "table": "grid_nivel5",
1043
+ "type": "DOUBLE",
1044
+ "label_en": "Population born abroad (Census 2020)",
1045
+ "label_es": "Poblacion nacida en el extranjero",
1046
+ "unit": "people",
1047
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1048
+ "range": [-7, 72380],
1049
+ "aliases": ["born abroad", "foreign born", "nacida en el extranjero", "pnacop"]
1050
+ },
1051
+ {
1052
+ "key": "presop2015",
1053
+ "table": "grid_nivel5",
1054
+ "type": "DOUBLE",
1055
+ "label_en": "Resided abroad in 2015 (Census 2020)",
1056
+ "label_es": "Residian en otro pais en 2015",
1057
+ "unit": "people",
1058
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1059
+ "range": [-7, 32847],
1060
+ "aliases": ["resided abroad 2015", "presop2015"]
1061
+ },
1062
+ {
1063
+ "key": "pobtot",
1064
+ "table": "grid_nivel6",
1065
+ "type": "DOUBLE",
1066
+ "label_en": "Population",
1067
+ "label_es": "Población",
1068
+ "unit": "people",
1069
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1070
+ "range": [0, 2622896],
1071
+ "aliases": ["total population", "poblacion total", "pobtot"]
1072
+ },
1073
+ {
1074
+ "key": "pnacop",
1075
+ "table": "grid_nivel6",
1076
+ "type": "DOUBLE",
1077
+ "label_en": "Population born abroad (Census 2020)",
1078
+ "label_es": "Poblacion nacida en el extranjero",
1079
+ "unit": "people",
1080
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1081
+ "range": [-7, 44895],
1082
+ "aliases": ["born abroad", "foreign born", "nacida en el extranjero", "pnacop"]
1083
+ },
1084
+ {
1085
+ "key": "presop2015",
1086
+ "table": "grid_nivel6",
1087
+ "type": "DOUBLE",
1088
+ "label_en": "Resided abroad in 2015 (Census 2020)",
1089
+ "label_es": "Residian en otro pais en 2015",
1090
+ "unit": "people",
1091
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1092
+ "range": [-7, 20696],
1093
+ "aliases": ["resided abroad 2015", "presop2015"]
1094
+ },
1095
+ {
1096
+ "key": "pobtot",
1097
+ "table": "grid_nivel7",
1098
+ "type": "DOUBLE",
1099
+ "label_en": "Population",
1100
+ "label_es": "Población",
1101
+ "unit": "people",
1102
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1103
+ "range": [0, 367487],
1104
+ "aliases": ["total population", "poblacion total", "pobtot"]
1105
+ },
1106
+ {
1107
+ "key": "pnacop",
1108
+ "table": "grid_nivel7",
1109
+ "type": "DOUBLE",
1110
+ "label_en": "Population born abroad (Census 2020)",
1111
+ "label_es": "Poblacion nacida en el extranjero",
1112
+ "unit": "people",
1113
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1114
+ "range": [-7, 17987],
1115
+ "aliases": ["born abroad", "foreign born", "nacida en el extranjero", "pnacop"]
1116
+ },
1117
+ {
1118
+ "key": "presop2015",
1119
+ "table": "grid_nivel7",
1120
+ "type": "DOUBLE",
1121
+ "label_en": "Resided abroad in 2015 (Census 2020)",
1122
+ "label_es": "Residian en otro pais en 2015",
1123
+ "unit": "people",
1124
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1125
+ "range": [-7, 9024],
1126
+ "aliases": ["resided abroad 2015", "presop2015"]
1127
+ },
1128
+ {
1129
+ "key": "pobtot",
1130
+ "table": "grid_nivel8",
1131
+ "type": "DOUBLE",
1132
+ "label_en": "Population",
1133
+ "label_es": "Población",
1134
+ "unit": "people",
1135
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1136
+ "range": [0, 65588],
1137
+ "aliases": ["total population", "poblacion total", "pobtot"]
1138
+ },
1139
+ {
1140
+ "key": "pnacop",
1141
+ "table": "grid_nivel8",
1142
+ "type": "DOUBLE",
1143
+ "label_en": "Population born abroad (Census 2020)",
1144
+ "label_es": "Poblacion nacida en el extranjero",
1145
+ "unit": "people",
1146
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1147
+ "range": [-7, 4702],
1148
+ "aliases": ["born abroad", "foreign born", "nacida en el extranjero", "pnacop"]
1149
+ },
1150
+ {
1151
+ "key": "presop2015",
1152
+ "table": "grid_nivel8",
1153
+ "type": "DOUBLE",
1154
+ "label_en": "Resided abroad in 2015 (Census 2020)",
1155
+ "label_es": "Residian en otro pais en 2015",
1156
+ "unit": "people",
1157
+ "source": "Censo de Poblacion y Vivienda (INEGI), 2020",
1158
+ "range": [-7, 2526],
1159
+ "aliases": ["resided abroad 2015", "presop2015"]
1160
+ },
1161
+ {
1162
+ "key": "ALTITUD",
1163
+ "table": "localities",
1164
+ "type": "DOUBLE",
1165
+ "label_en": "",
1166
+ "label_es": "",
1167
+ "unit": "",
1168
+ "source": "Censo de Poblacion y Vivienda (INEGI) - localities, 2020",
1169
+ "range": [-11, 3498],
1170
+ "aliases": []
1171
+ },
1172
+ {
1173
+ "key": "POB_TOTAL",
1174
+ "table": "localities",
1175
+ "type": "INTEGER",
1176
+ "label_en": "Population",
1177
+ "label_es": "Población",
1178
+ "unit": "people",
1179
+ "source": "Censo de Poblacion y Vivienda (INEGI) - localities, 2020",
1180
+ "range": [1, 1835486],
1181
+ "aliases": ["locality population", "poblacion localidad"]
1182
+ },
1183
+ {
1184
+ "key": "PNACOE",
1185
+ "table": "localities",
1186
+ "type": "DOUBLE",
1187
+ "label_en": "",
1188
+ "label_es": "",
1189
+ "unit": "",
1190
+ "source": "Censo de Poblacion y Vivienda (INEGI) - localities, 2020",
1191
+ "range": [0, 789327],
1192
+ "aliases": []
1193
+ },
1194
+ {
1195
+ "key": "PRES2015",
1196
+ "table": "localities",
1197
+ "type": "DOUBLE",
1198
+ "label_en": "",
1199
+ "label_es": "",
1200
+ "unit": "",
1201
+ "source": "Censo de Poblacion y Vivienda (INEGI) - localities, 2020",
1202
+ "range": [1, 1673257],
1203
+ "aliases": []
1204
+ },
1205
+ {
1206
+ "key": "source_quality",
1207
+ "table": "migrants",
1208
+ "type": "DOUBLE",
1209
+ "label_en": "",
1210
+ "label_es": "",
1211
+ "unit": "",
1212
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1213
+ "range": [1, 5],
1214
+ "aliases": []
1215
+ },
1216
+ {
1217
+ "key": "n_missing",
1218
+ "table": "migrants",
1219
+ "type": "DOUBLE",
1220
+ "label_en": "Migrants missing",
1221
+ "label_es": "Migrantes desaparecidos",
1222
+ "unit": "count",
1223
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1224
+ "range": [-2, 60],
1225
+ "aliases": ["missing migrants", "migrantes desaparecidos"]
1226
+ },
1227
+ {
1228
+ "key": "n_dead",
1229
+ "table": "migrants",
1230
+ "type": "DOUBLE",
1231
+ "label_en": "Migrant deaths",
1232
+ "label_es": "Migrantes fallecidos",
1233
+ "unit": "count",
1234
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1235
+ "range": [1, 123],
1236
+ "aliases": ["migrant deaths", "fallecidos", "muertes"]
1237
+ },
1238
+ {
1239
+ "key": "n_total",
1240
+ "table": "migrants",
1241
+ "type": "DOUBLE",
1242
+ "label_en": "Death during migration",
1243
+ "label_es": "Migrantes muertos o desaparecidos (total)",
1244
+ "unit": "count",
1245
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1246
+ "range": [1, 123],
1247
+ "aliases": ["dead or missing", "muertos o desaparecidos"]
1248
+ },
1249
+ {
1250
+ "key": "n_children",
1251
+ "table": "migrants",
1252
+ "type": "DOUBLE",
1253
+ "label_en": "Migrant child casualties",
1254
+ "label_es": "Menores migrantes (victimas)",
1255
+ "unit": "count",
1256
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1257
+ "range": [1, 10],
1258
+ "aliases": ["children", "menores"]
1259
+ },
1260
+ {
1261
+ "key": "n_females",
1262
+ "table": "migrants",
1263
+ "type": "DOUBLE",
1264
+ "label_en": "Migrant female casualties",
1265
+ "label_es": "Mujeres migrantes (victimas)",
1266
+ "unit": "count",
1267
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1268
+ "range": [0, 15],
1269
+ "aliases": ["females", "mujeres"]
1270
+ },
1271
+ {
1272
+ "key": "n_males",
1273
+ "table": "migrants",
1274
+ "type": "DOUBLE",
1275
+ "label_en": "Migrant male casualties",
1276
+ "label_es": "Hombres migrantes (victimas)",
1277
+ "unit": "count",
1278
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1279
+ "range": [0, 50],
1280
+ "aliases": ["males", "hombres"]
1281
+ },
1282
+ {
1283
+ "key": "n_survivors",
1284
+ "table": "migrants",
1285
+ "type": "DOUBLE",
1286
+ "label_en": "Migrant survivors",
1287
+ "label_es": "Migrantes sobrevivientes",
1288
+ "unit": "count",
1289
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1290
+ "range": [0, 346],
1291
+ "aliases": ["survivors", "sobrevivientes"]
1292
+ },
1293
+ {
1294
+ "key": "incident_year",
1295
+ "table": "migrants",
1296
+ "type": "INTEGER",
1297
+ "label_en": "",
1298
+ "label_es": "",
1299
+ "unit": "",
1300
+ "source": "Missing Migrants Project (IOM), 2014-2025",
1301
+ "range": [2014, 2026],
1302
+ "aliases": []
1303
+ },
1304
+ {
1305
+ "key": "year",
1306
+ "table": "ocved_events",
1307
+ "type": "INTEGER",
1308
+ "label_en": "",
1309
+ "label_es": "",
1310
+ "unit": "",
1311
+ "source": "OCVED (Organized Criminal Violence Event Data), 2010-2018",
1312
+ "range": [2010, 2018],
1313
+ "aliases": []
1314
+ },
1315
+ {
1316
+ "key": "month",
1317
+ "table": "ocved_events",
1318
+ "type": "INTEGER",
1319
+ "label_en": "",
1320
+ "label_es": "",
1321
+ "unit": "",
1322
+ "source": "OCVED (Organized Criminal Violence Event Data), 2010-2018",
1323
+ "range": [0, 12],
1324
+ "aliases": []
1325
+ },
1326
+ {
1327
+ "key": "day",
1328
+ "table": "ocved_events",
1329
+ "type": "INTEGER",
1330
+ "label_en": "",
1331
+ "label_es": "",
1332
+ "unit": "",
1333
+ "source": "OCVED (Organized Criminal Violence Event Data), 2010-2018",
1334
+ "range": [0, 31],
1335
+ "aliases": []
1336
+ },
1337
+ {
1338
+ "key": "state",
1339
+ "table": "ocved_events",
1340
+ "type": "INTEGER",
1341
+ "label_en": "",
1342
+ "label_es": "",
1343
+ "unit": "",
1344
+ "source": "OCVED (Organized Criminal Violence Event Data), 2010-2018",
1345
+ "range": [1, 32],
1346
+ "aliases": []
1347
+ },
1348
+ {
1349
+ "key": "mun",
1350
+ "table": "ocved_events",
1351
+ "type": "INTEGER",
1352
+ "label_en": "",
1353
+ "label_es": "",
1354
+ "unit": "",
1355
+ "source": "OCVED (Organized Criminal Violence Event Data), 2010-2018",
1356
+ "range": [1001, 32058],
1357
+ "aliases": []
1358
+ },
1359
+ {
1360
+ "key": "counter",
1361
+ "table": "ocved_events",
1362
+ "type": "INTEGER",
1363
+ "label_en": "Organised crime events",
1364
+ "label_es": "Eventos de violencia OCVED",
1365
+ "unit": "count",
1366
+ "source": "OCVED (Organized Criminal Violence Event Data), 2010-2018",
1367
+ "range": [1, 81],
1368
+ "aliases": ["ocved", "organized crime violence", "violencia organizada"]
1369
+ },
1370
+ {
1371
+ "key": "length_m",
1372
+ "table": "roads",
1373
+ "type": "DOUBLE",
1374
+ "label_en": "",
1375
+ "label_es": "",
1376
+ "unit": "",
1377
+ "source": "Road network (INEGI / maps)",
1378
+ "range": [0.0167, 215977.7978],
1379
+ "aliases": []
1380
+ },
1381
+ {
1382
+ "key": "dark_figures_perc",
1383
+ "table": "state_polygons",
1384
+ "type": "DOUBLE",
1385
+ "label_en": "Dark figure (unreported crime)",
1386
+ "label_es": "Cifra negra (delitos no denunciados)",
1387
+ "unit": "%",
1388
+ "source": "ENVIPE, 2022-2023",
1389
+ "range": [88.2671, 97.1171],
1390
+ "aliases": ["dark figure", "unreported crime", "cifra negra"]
1391
+ },
1392
+ {
1393
+ "key": "perception_insecurity_perc",
1394
+ "table": "state_polygons",
1395
+ "type": "DOUBLE",
1396
+ "label_en": "Perceived insecurity",
1397
+ "label_es": "Percepcion de inseguridad",
1398
+ "unit": "%",
1399
+ "source": "ENVIPE, 2022-2023",
1400
+ "range": [20.419, 57.993],
1401
+ "aliases": ["insecurity perception", "percepcion de inseguridad", "inseguridad"]
1402
+ },
1403
+ {
1404
+ "key": "SHAPE_len",
1405
+ "table": "viaferrea",
1406
+ "type": "DOUBLE",
1407
+ "label_en": "",
1408
+ "label_es": "",
1409
+ "unit": "",
1410
+ "source": "Railway network (INEGI / maps)",
1411
+ "range": [1.2041, 167105.2372],
1412
+ "aliases": []
1413
+ }
1414
+ ]
1415
+ }