Files
jorgecuadros-platform/migration/dbenv.py
T
rmancinasandClaude Opus 4.8 a680ad2bb0 Transform+load: properties/services/trust + env-parameterize migration
migration/transform_properties.py loads properties, property_services and
trust_accounts from staged DATMEX/PROFILE, resolving each property's customer
FK through customer_legacy_refs. Services are derived from DATMEX's own
account/route/meter fields (the authoritative data); PROFILE flags — merged
best-effort on (numer_id,casa,direccion), which matched 1519/1519 — only
refine each service's `active`. Trust accounts are 1:1 from DATMEX trust
fields; TRUSTVENCE (overlapping) deferred to reconciliation; blobs are step 4.

Loaded/validated (dev): 1519 properties (0 orphans, 1 blank id skipped),
3486 services (ELECTRIC 1118 / PROPERTY_TAX 939 / WATER 859 / GAS 335 /
OTHER 115 / FEDERAL_ZONE 76 / CABLE 41 / ALARM 3), 553 trust accounts —
counts track the PROFILE enrollment flags.

Reproducibility (asked: dev must be redoable in prod):
- migration/dbenv.py: single DB-target source = deploy/.env.<env>'s
  DATABASE_URL; connect(env) + env_arg() (--env, default dev).
- transform_customers.py / transform_properties.py now take --env instead of
  hardcoding .env.dev.
- migration/run_all.py: runs every step in dependency order against --env
  (optional --stage re-extracts from Access first). Reproducing dev->prod is
  `run_all.py --env prod` after deploying the prod stack + prisma db push.

All steps are idempotent (truncate+rebuild); re-run yields identical counts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 18:29:31 -07:00

73 lines
2.4 KiB
Python

"""
Environment selection for the migration scripts.
Every transform is environment-agnostic: it reads the staged Parquet (same for
all environments) and writes to whichever database `--env` selects. The target
is defined entirely by `deploy/.env.<env>` (the same file Portainer is fed at
deploy time), whose `DATABASE_URL` is the single source of truth for host /
port / credentials / database.
Reproduce the whole migration in a new environment (e.g. prod) by:
1. deploy the DB stack for that env (deploy/jorgecuadros-db.stack.yml)
2. write deploy/.env.<env> with its DATABASE_URL
3. push the schema: DATABASE_URL=... npx prisma@5 db push --schema=packages/database/prisma/schema.prisma
4. run: ./.venv/bin/python run_all.py --env <env>
Usage in a script:
from dbenv import connect, env_arg
env = env_arg() # --env dev|prod (default dev)
conn = connect(env)
"""
from __future__ import annotations
import argparse
from pathlib import Path
from urllib.parse import unquote, urlparse
import pymysql
REPO = Path(__file__).resolve().parents[1]
def load_env(env: str) -> dict:
f = REPO / "deploy" / f".env.{env}"
if not f.exists():
raise SystemExit(
f"missing {f} — deploy the '{env}' DB stack and write its .env first "
f"(see dbenv.py header)."
)
out = {}
for line in f.read_text().splitlines():
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
out[k] = v
if "DATABASE_URL" not in out:
raise SystemExit(f"{f} has no DATABASE_URL")
return out
def connect(env: str):
url = load_env(env)["DATABASE_URL"]
u = urlparse(url) # mysql://user:pass@host:port/db
return pymysql.connect(
host=u.hostname,
port=u.port or 3306,
user=unquote(u.username or ""),
password=unquote(u.password or ""),
database=(u.path or "/").lstrip("/"),
autocommit=False,
charset="utf8mb4",
)
def env_arg(extra_args=None) -> str:
"""Parse --env (default 'dev') and return it. Scripts that need more args
can pass an argparse parser via extra_args(parser)."""
p = argparse.ArgumentParser()
p.add_argument("--env", default="dev", help="target environment (dev|prod|...): reads deploy/.env.<env>")
if extra_args:
extra_args(p)
return p.parse_args().env