chore(#49): track seed script and geography CSV in git

Allowlist scripts/ in .gitignore (was caught by [Ss]cripts venv rule)
so seed_geography.py and colombia_municipios.csv can be committed.
This simplifies deployment since the files no longer need to be
copied separately to the server.
This commit is contained in:
2026-08-16 21:58:12 -05:00
parent 3a0c76ce69
commit 792ac4367c
3 changed files with 1310 additions and 1 deletions

2
.gitignore vendored
View File

@@ -321,7 +321,7 @@ pyrightconfig.json
[Ll]ib [Ll]ib
[Ll]ib64 [Ll]ib64
[Ll]ocal [Ll]ocal
[Ss]cripts Scripts/
pyvenv.cfg pyvenv.cfg
pip-selfcheck.json pip-selfcheck.json

File diff suppressed because it is too large Load Diff

188
scripts/seed_geography.py Normal file
View File

@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""Seed geografía de Colombia (países, departamentos y municipios).
Requisitos (instalar ad-hoc, NO son dependencia del proyecto):
pip install pycountry requests
Uso:
python scripts/seed_geography.py http://localhost:7000
El script obtiene el país y los departamentos de Colombia desde
`pycountry` (ISO 3166-1/3166-2) y los municipios desde el dataset DANE
incluido en `scripts/data/colombia_municipios.csv`. No consulta internet
en tiempo de ejecución.
"""
import argparse
import csv
import getpass
import os
import sys
import unicodedata
import requests
try:
import pycountry
except ModuleNotFoundError:
print(
"pycountry no está instalado. Ejecuta: pip install pycountry",
file=sys.stderr,
)
sys.exit(1)
TOKEN_URL = "/api/token/"
SEED_GEOGRAPHY_URL = "/don_confiao/api/seed_geography"
MUNICIPALITIES_CSV = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data",
"colombia_municipios.csv",
)
# Nombres DANE que difieren de los nombres oficiales de pycountry.
DEPARTMENT_ALIASES = {
"BOGOTA": "Distrito Capital de Bogotá",
"SAN ANDRES": "San Andrés, Providencia y Santa Catalina",
}
def get_credentials():
username = input("Usuario: ")
password = getpass.getpass("Contraseña: ")
return username, password
def get_token(domain, username, password):
url = domain.rstrip("/") + TOKEN_URL
response = requests.post(
url, json={"username": username, "password": password}
)
if response.status_code != 200:
print(
f"Error al obtener token: {response.status_code} {response.text}",
file=sys.stderr,
)
sys.exit(1)
return response.json()["access"]
def _normalize(name):
return "".join(
char
for char in unicodedata.normalize("NFD", name)
if unicodedata.category(char) != "Mn"
).upper()
def get_department_mapping(subdivisions):
by_normalized = {
_normalize(subdivision.name): subdivision.name
for subdivision in subdivisions
}
csv_department_names = set()
with open(MUNICIPALITIES_CSV, newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
csv_department_names.add(row["department"])
mapping = dict(DEPARTMENT_ALIASES)
for name in csv_department_names:
if name in mapping:
continue
mapped = by_normalized.get(_normalize(name))
if mapped is None:
print(
f"Departamento '{name}' no encontrado en pycountry",
file=sys.stderr,
)
sys.exit(1)
mapping[name] = mapped
return mapping
def read_municipalities(mapping):
municipalities = []
with open(MUNICIPALITIES_CSV, newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
municipality = {
"name": row["municipality"],
"department": mapping[row["department"]],
}
if row.get("latitude"):
municipality["latitude"] = float(row["latitude"])
if row.get("longitude"):
municipality["longitude"] = float(row["longitude"])
municipalities.append(municipality)
return municipalities
def build_payload():
country = pycountry.countries.get(alpha_2="CO")
subdivisions = pycountry.subdivisions.get(country_code="CO")
departments = [
{"name": subdivision.name, "code": subdivision.code}
for subdivision in subdivisions
]
mapping = get_department_mapping(subdivisions)
municipalities = read_municipalities(mapping)
return {
"country": {"name": country.name, "code": country.alpha_2},
"departments": departments,
"municipalities": municipalities,
}
def seed_geography(domain, token, payload):
headers = {"Authorization": f"Bearer {token}"}
full_url = domain.rstrip("/") + SEED_GEOGRAPHY_URL
response = requests.post(full_url, headers=headers, json=payload)
if response.status_code != 200:
print(
f"Error al sembrar geografía: {response.status_code} {response.text}",
file=sys.stderr,
)
sys.exit(1)
return response.json()
def main():
parser = argparse.ArgumentParser(
description=(
"Alimenta países, departamentos y municipios de Colombia "
"usando pycountry y el dataset DANE incluido en scripts/data/."
)
)
parser.add_argument(
"domain", help="Dominio del backend (ej: http://localhost:7000)"
)
args = parser.parse_args()
username, password = get_credentials()
token = get_token(args.domain, username, password)
print("Token obtenido correctamente.")
payload = build_payload()
print(
"Preparando siembra: 1 país, "
f"{len(payload['departments'])} departamentos, "
f"{len(payload['municipalities'])} municipios."
)
print("Enviando geografía...")
result = seed_geography(args.domain, token, payload)
print(
f" [OK] {result['country']}: "
f"{result['total_departments']} departamentos, "
f"{result['total_municipalities']} municipios."
)
print(
f"\nResumen: {result['departments_created']} departamentos y "
f"{result['municipalities_created']} municipios creados "
f"(re-ejecutar es idempotente)."
)
if __name__ == "__main__":
main()