Skip to content

feat(ingestion): orchestrate dbt Cloud Run Job from main.py (T9.17, D108) - #81

Merged
GMartin-Data merged 4 commits into
mainfrom
feat/orchestration-dbt-invocation
May 22, 2026
Merged

GMartin-Data merged 4 commits into
mainfrom
feat/orchestration-dbt-invocation

Conversation

@GMartin-Data

Copy link
Copy Markdown
Owner

Contexte

Cette PR met en œuvre T9.17 (US9, Orchestration prod ingestion → dbt) et finalise la chaîne d'orchestration actée en D72. Jusqu'ici, le Cloud Run Job `datatalent-dbt` était provisionné en Terraform (PR #52) et avait son CD branché (PR #54), mais aucun acteur ne le déclenchait. À partir de cette PR, `ingestion/main.py` invoque le Job dbt en fin de pipeline, conditionnellement à la réussite des sources critiques.

La décision D108 (rédigée dans `notes-projet.md` en pré-implémentation) acte les deux sous-arbitrages restants de T9.17 : comportement sur échec partiel, et mode d'invocation.

Décisions techniques

Sous-arbitrage T9.17 Tranché en D108 Pattern
3.5 — Comportement échec partiel Hybride best-effort + liste blanche `CRITICAL_SOURCES = frozenset({"france_travail", "adzuna"})` ; dbt skippé ssi `errors & CRITICAL_SOURCES` non vide
3.6 — Synchrone vs fire-and-forget Fire-and-forget `JobsClient.run_job(name=...)` ; le retour RPC est traité comme un ack, pas comme un verdict final

Justifications synthétiques :

  • Whitelist : les marts métier (`fct_offres` et descendants, D94) reposent matériellement sur France Travail + Adzuna ; les autres sources dégradent l'enrichissement sans casser le mart, donc tolérables best-effort.
  • Fire-and-forget : un mode synchrone forcerait le conteneur ingestion à rester vivant ~5-15 min de plus (facturation, risque timeout D63). Le verdict d'exécution dbt revient à Cloud Monitoring (D105), pas à l'orchestrateur.

Sémantique exit code (D19) préservée : exit 1 sur échec partiel d'ingestion, orthogonal à la décision d'invoquer dbt. L'invocation `invoke_dbt()` est placée avant `sys.exit(1)` pour garantir que les échecs non-critiques (geo, urssaf*) ne court-circuitent pas le déclenchement dbt.

Changements

  • `ingestion/main.py` : ajout de `CRITICAL_SOURCES` (frozenset module-level), des constantes d'environnement `GCP_PROJECT_ID` / `GCP_REGION` / `DBT_JOB_NAME`, et de la fonction privée `_invoke_dbt()` (3 gardes : métier, environnementale, technique). Branchement dans `main()` après la boucle d'ingestion, avant `sys.exit(1)`.
  • `ingestion/pyproject.toml` + `ingestion/uv.lock` : ajout de la dépendance `google-cloud-run>=0.16`.
  • `ingestion/tests/test_main.py` : nouvelle classe `TestDbtInvocation` couvrant les 4 cas critiques (succès complet, échec non-critique, échec critique, exception JobsClient non-propageante). Patches sur `main.run_v2.JobsClient` et `main.GCP_PROJECT_ID` / `main.GCP_REGION` — aucun appel API réel.
  • `.gitignore` : ajout de `CLAUDE.md` (commit héritage d'une PR antérieure sur cette branche, sera consolidé au squash-merge).

Validation

```
$ uv run pytest -v tests/test_main.py
============================= test session starts ==============================
collected 6 items

tests/test_main.py::TestMain::test_calls_all_sources_in_order PASSED [ 16%]
tests/test_main.py::TestMain::test_continues_on_failure PASSED [ 33%]
tests/test_main.py::TestDbtInvocation::test_invokes_dbt_on_full_success PASSED [ 50%]
tests/test_main.py::TestDbtInvocation::test_invokes_dbt_on_non_critical_failure PASSED [ 66%]
tests/test_main.py::TestDbtInvocation::test_skips_dbt_on_critical_failure PASSED [ 83%]
tests/test_main.py::TestDbtInvocation::test_does_not_raise_when_run_job_fails PASSED [100%]

============================== 6 passed in 1.35s ===============================
```

```
$ uv run python -c "import main; print('import OK; CRITICAL_SOURCES=', main.CRITICAL_SOURCES)"
import OK; CRITICAL_SOURCES= frozenset({'france_travail', 'adzuna'})
```

Lint ruff + format : OK (validés à chaque commit via pre-commit).

Articulation avec D105

L'alerte Cloud Monitoring "échec exécution `datatalent-dbt`" (provisionnée par la prochaine PR `feat/monitoring-module`) devient le seul signal d'échec côté dbt. C'est précisément ce qui justifie le `try/except Exception` non-re-raise dans `_invoke_dbt` — la robustesse n'est pas redondante côté code applicatif, elle est déléguée au monitoring (defense in depth par couche, pas par duplication).

Suite

  • Au merge : cocher T9.17 dans `user-stories-datatalent.md` ; le CD `cd-ingestion.yml` (path-scoped sur `ingestion/**`) rebuilde l'image et redéploie `datatalent-ingestion` automatiquement.
  • Pas de test prod observable avant lundi 6h (cron `0 6 * * 1`) : le nouveau code ne s'exécutera qu'au prochain run planifié, sauf déclenchement manuel via `gcloud run jobs execute datatalent-ingestion`. Le test end-to-end est planifié en Phase 4 du chantier inter-bloc.
  • Prochaine PR du chantier : `feat/monitoring-module` (module Terraform `monitoring/`, D105/D106, alertes Cloud Run + notification channel email).

Décisions liées

  • D19 — Orchestrateur séquentiel best-effort, exit 1 sur échec partiel
  • D63 — Dimensionnement Cloud Run Job ingestion (timeout 1800s)
  • D72 — Orchestration ingestion → dbt depuis le code applicatif (pas de Scheduler dbt)
  • D105 — Monitoring Niveau 1, alerte échec `datatalent-dbt` (prochaine PR)
  • D108 — Hybride best-effort + fire-and-forget (cette PR)

Refs : T9.17.

Module-level frozenset listing the ingestion sources whose failure must
prevent the downstream dbt Cloud Run Job invocation. Defined ahead of
its first usage to make the orchestration whitelist reviewable in
isolation (T9.17, D108).
Add fire-and-forget invocation of the datatalent-dbt Cloud Run Job at
the end of main(), skipped iff a critical ingestion source failed. The
RPC return is treated as acknowledgement only; downstream failures are
surfaced by the Cloud Monitoring alert on datatalent-dbt (D105).

Invocation runs before sys.exit(1) so that non-critical source failures
(geo, urssaf_*, sirene, bmo) still trigger dbt while preserving the D19
partial-failure exit code.

Refs: D72, D108, T9.17.
Add four unit tests for the dbt Cloud Run Job invocation logic (D108):
- full success → dbt invoked with the projects/.../jobs/datatalent-dbt name
- non-critical failure (geo) → dbt still invoked, sys.exit(1) preserved
- critical failure (france_travail) → dbt skipped, sys.exit(1) preserved
- run_job raising → main() logs but does not re-raise (fire-and-forget)

Tests patch main.run_v2.JobsClient (where the symbol is looked up) and
main.GCP_PROJECT_ID / main.GCP_REGION (module-level constants, captured
at import time). No real Cloud Run API call.

Refs: T9.17, D108.
@GMartin-Data
GMartin-Data merged commit 0524b22 into main May 22, 2026
6 checks passed
@GMartin-Data
GMartin-Data deleted the feat/orchestration-dbt-invocation branch May 22, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant