Orquestar Refactorización de Código

orch-refine-code · MetriQ Analytics SaaS · Sesión 2026-06-18

orch-refine-code v1.0
MetriQ Analytics — Extracción GA4HttpClient + Transformer
Refactorización behavior-neutral: dividir poller.py (450 líneas, 12 métodos) en tres módulos de responsabilidad única.
23 tests en verde
Python 3.11 + FastAPI
Cobertura inicial: 68%
Target: ≥75%
Antes
poller.py
68%
Después
ga4_client.py
91%
Después
transformer.py
87%
Pipeline de Refactorización
0
Pre-vuelo — Confirmar tests en verde
✓ completado

Antes de tocar una línea de código, se confirma que toda la suite existente está en verde.

$ pytest tests/ -v --tb=short --no-header

============ test session starts ============
platform linux -- Python 3.11.9, pytest-8.3.2

PASSED tests/test_poller.py::test_poll_returns_events
PASSED tests/test_poller.py::test_poll_handles_empty_response
PASSED tests/test_poller.py::test_poll_retries_on_503
PASSED tests/test_poller.py::test_transform_dimensions_mapped
PASSED tests/test_poller.py::test_transform_metrics_aggregated
... 18 more ...

23 passed in 1.42s

Coverage report:
src/analytics/poller.py 68% (cobertura baja — se añadirán tests de caracterización)

Cobertura insuficiente detectada (<75%). Se crean tests de caracterización antes de proceder.

$ touch tests/test_ga4_client.py
# Caracterización: 8 tests del cliente HTTP (mocking httpx.AsyncClient)
PASSED tests/test_ga4_client.py8 tests added, 31 total
2
Plan de Extracción — Diseño de módulos
✓ Gate 1 aprobado

El agente propone la estructura de módulos resultante antes de mover código:

src/analytics/
├── ga4_client.pyNUEVO # GA4HttpClient: auth, request, retry
├── transformer.pyNUEVO # transform_ga4_response() → list[MetricEvent]
├── poller.pyREDUCIDO # Orquestador delgado, ~80 líneas
└── __init__.pyACTUALIZADO
🔒 GATE 1 — Aprobar plan de extracción
El equipo de MetriQ revisó el plan de extracción y verificó:
  • GA4HttpClient encapsula solo I/O: headers de auth, retry con exponential backoff, raise_for_status
  • transform_ga4_response es función pura: sin side effects, testeable en aislamiento
  • poller.py queda como orquestador: inyecta GA4HttpClient, llama transformer, dispara APScheduler
  • Ningún endpoint externo cambia su interfaz pública
  • Los imports en los endpoints afectados (src/api/routes/analytics.py) solo añaden un nivel de indirección
✓ GATE 1 APROBADO — 2026-06-18 09:47:22 por @tech-lead-metriQ
4
Ejecución — Movimiento en pasos pequeños
✓ tests verdes en cada paso

La extracción se realiza en 3 micro-pasos, ejecutando pytest tras cada uno:

Paso 4.1 — Extraer GA4HttpClient a ga4_client.py

# Mover clase GA4HttpClient (métodos: build_headers, fetch_report, _retry)
$ pytest tests/ -q
31 passed in 1.58s

Paso 4.2 — Extraer transform_ga4_response a transformer.py

# Mover función pura transform_ga4_response + tipos auxiliares
$ pytest tests/ -q
31 passed in 1.61s

Paso 4.3 — Delegar limpieza a refactor-cleaner

$ refactor-cleaner --target src/analytics/ --check knip,dead-code

knip: 0 unused exports
dead-code: 0 unreachable branches
NOTE: _legacy_format_date() marcado como muerto — eliminado

$ pytest tests/ -q
31 passed in 1.63s

Diff final de poller.py — de 450 a 82 líneas:

src/analytics/poller.py +8 -368
diff --git a/src/analytics/poller.py b/src/analytics/poller.py
@@ -1,31 +1,12 @@
-import httpx
-import backoff
-from google.oauth2 import service_account
+from analytics.ga4_client import GA4HttpClient
+from analytics.transformer import transform_ga4_response
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from analytics.models import MetricEvent
@@ -45,180 +16,12 @@
-class GAPoller:
- def __init__(self, credentials_path: str, property_id: str,
- interval_minutes: int = 15):
- self._creds = service_account.Credentials.from_service_account_file(
- credentials_path, scopes=SCOPES
- )
- ...
- def _build_headers(self) -> dict:
- """Build auth headers. (NOW in ga4_client.py)"""
- ...
- @backoff.on_exception(backoff.expo, httpx.HTTPStatusError, max_tries=3)
- async def fetch_report(self, start_date: str, end_date: str) -> dict:
- ...
- def _transform_dimensions(self, row: dict) -> dict:
- """Map GA4 dims. (NOW in transformer.py)"""
- ...
+class GAPoller:
+ def __init__(self, credentials_path: str, property_id: str,
+ interval_minutes: int = 15):
+ self._client = GA4HttpClient(credentials_path, property_id)
+ self._interval = interval_minutes
+ self._scheduler = AsyncIOScheduler()
+
+ async def poll_once(self) -> list[MetricEvent]:
+ raw = await self._client.fetch_report("7daysAgo", "today")
+ return transform_ga4_response(raw)
5
Code Review — Verificación behavior-neutral
✓ diff aprobado

Revisión automática del diff para confirmar que no hay cambios de comportamiento observable:

Revisando diff semántico...

Todos los endpoints públicos mantienen su firma
GAPoller.__init__ acepta los mismos parámetros
poll_once() devuelve list[MetricEvent] — sin cambios
No se introducen nuevas excepciones no controladas
APScheduler configurado con los mismos parámetros
src/api/routes/analytics.py — imports actualizados, sin ruptura

─────────────────────────────────────────
Veredicto: diff BEHAVIOR-NEUTRAL ✓

Tests finales tras todos los cambios:

PASStest_poll_returns_events
PASStest_poll_empty_resp
PASStest_poll_retries_503
PASStest_transform_dims
PASStest_transform_metrics
PASStest_ga4_auth_headers
PASStest_ga4_retry_logic
PASStest_ga4_404_raises
PASStest_scheduler_interval
PASStest_client_inject
PASStest_transform_pure
PASStest_transform_nulls
31 passed, 0 warnings in 1.63s
6
Commit refactor: — Gate 2 pre-commit
✓ commit creado
🔒 GATE 2 — Confirmar pre-commit
Última verificación antes del commit:
  • 31/31 tests en verde
  • Cobertura: ga4_client.py 91%, transformer.py 87%, poller.py 79% (todas ≥75%)
  • Diff behavior-neutral confirmado por revisión semántica automática
  • Sin dependencias circulares introducidas
  • Mensaje de commit sigue convención refactor:
✓ GATE 2 APROBADO — commit autorizado
commit a3f7b2e
refactor: extract GA4HttpClient and transformer from poller
Autor: orch-refine-code agent · 2026-06-18 10:15:04 +0200
4 files changed +312 insertions(+) -368 deletions(-)
$ git show --stat HEAD

src/analytics/ga4_client.py | 198 +++++++++++++++++++
src/analytics/transformer.py | 114 +++++++++++
src/analytics/poller.py | 82 +++-------- (era 450)
src/analytics/__init__.py | 12 +++--
4 files changed, 312 insertions(+), 368 deletions(-)
31
Tests en verde
3
Módulos resultantes
91%
Cobertura máx.
82→
Líneas poller.py (era 450)