Skip to content

Sync API Entreprise/API Particulier fiches to data.gouv.fr - #264

Open
Samuelfaure wants to merge 20 commits into
developfrom
feat/datagouv_auto_fill
Open

Sync API Entreprise/API Particulier fiches to data.gouv.fr#264
Samuelfaure wants to merge 20 commits into
developfrom
feat/datagouv_auto_fill

Conversation

@Samuelfaure

@Samuelfaure Samuelfaure commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Syncs endpoint metadata from commons/endpoints/*.yml to data.gouv.fr's dataservices API, triggered by a GitHub Actions workflow when fiche files change (push to main, i.e. the develop→main CI force-push, filtered to commons/endpoints/**).
  • Creates or updates a dataservice per endpoint; deletes it when the endpoint is deprecated. The yml is the single source of truth — title/description/tags/URLs are always regenerated, never hand-edited on the data.gouv.fr side.
  • Newly-assigned datagouv_uid values are written back into the yml automatically, committed to develop via the GitHub Contents API so the commit is GitHub-signed (required by develop's branch-protection ruleset).
  • sync_with_datagouv: false added to all endpoint entries that share a datagouv_uid with a sibling (deprecated/current pairs, grouped INSEE Sirene/DJEPVA entries), so the shared dataservice is never deleted out from under a still-live sibling.
  • Currently points at demo.data.gouv.fr via the DATAGOUV_HOST GitHub variable — swapping it to data.gouv.fr later works identically, since the client has no hardcoded demo reference (also need to change DATAGOUV_API_TOKEN)

Test plan

  • bundle exec rspec spec/services/datagouv/ spec/clients/datagouv_api_client_spec.rb spec/models/abstract_endpoint_spec.rb spec/lib/tasks/datagouv_spec.rb — 62 examples, 0 failures
  • bundle exec rubocop on all touched files — clean (one pre-existing, unrelated offense in inflections.rb)
  • Full catalog audit confirming no two unrelated endpoints share a datagouv_uid without sync_with_datagouv: false
  • Live sync against demo.data.gouv.fr once the bot identity is added to the "Secure contain protek" ruleset's bypass list on develop (required for the write-back commit's pull_request rule, separate from the signing fix already in place)

@Samuelfaure
Samuelfaure marked this pull request as draft July 14, 2026 13:23
@Samuelfaure
Samuelfaure force-pushed the feat/datagouv_auto_fill branch from 93ba634 to beea280 Compare July 14, 2026 13:25
@Samuelfaure

Copy link
Copy Markdown
Contributor Author

Problème: il faut sync back le datagouv_uid depuis l'action github 🤔 mais on a des règles de protection de commits et de PR reviews, faut que je trouve comment bypass pour les actions github

@skelz0r

skelz0r commented Jul 14, 2026

Copy link
Copy Markdown
Member

On ne peut pas passer par un moissonneur ?

@skelz0r

skelz0r commented Jul 14, 2026

Copy link
Copy Markdown
Member

Ce datagouv_uid c'est un peu de la boue en réalité.. ça devrait être nous la source de vérité 🤷

@skelz0r

skelz0r commented Jul 15, 2026

Copy link
Copy Markdown
Member

Faire faire des commits à travers une API c'est une très mauvaise idée btw. Faut trouver une autre solution.

Vu qu'il y a l'air d'avoir un search ( https://guides.data.gouv.fr/api-de-data.gouv.fr/reference/dataservices ) je pense qu'il faut explorer cette piste. Ça simplifiera.

Par ailleurs, je vois qu'il y a des exclus du style DGFIP/URSSAF.. c'est voulu ?
Aussi, on fait des destroy de deprecated, est-ce nécessaire ?

@Samuelfaure

Samuelfaure commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Imo destroy de deprecated bonne idée

datagouv_uid ça simplifie la sync mais effectivement je suis pas fan de la solution actuelle, le search sera peut-être bien meilleur si ça marche (imo ça devrait passer, faut juste faire un search à chaque sync, un peu lourd mais plus simple)

Je crois pas qu'on puisse être source de vérité sur datagouv_uid vu que c'est set par datagouv (à explorer si on peut pas le set nous-même, juste au cas où ça soit possible) - mais avec le search on peut peut-être juste jarter tout ça

Le moissoneur j'ai reçu l'avis de @nicolaskempf57 qui m'a tldr dit "ne fait pas ça" (la team a l'air assez terrifiée du code relatif au moissonnage, la complexité semble +++)

@skelz0r

skelz0r commented Jul 15, 2026

Copy link
Copy Markdown
Member

Imo destroy de deprecated bonne idée

Pourquoi ? Je ne vois pas de use case en fait, vu qu'un bump de version on garde le même uid et on udpate la donnée.

Je crois pas qu'on puisse être source de vérité sur datagouv_uid vu que c'est set par datagouv

C'est évident oui, mais ça ne change pas le fait qu'on devrait être le seul "pusher" de la donnée et qu'on ne devrait pas avoir besoin de faire des itérations techniques avec les ids dans notre codebase (c'est clairement une béquille actuellement), d'où ma proposition de faire des search (limite sur le champ de l'url qui est imo une clé valide).

@Samuelfaure

Copy link
Copy Markdown
Contributor Author

dans les cas comme DGFIP-TVA vaudrait mieux suppr la fiche non?

J'ai du mal à voir pourquoi tu veux garder des fiches d'endpoints depreciés sur datagouv

@skelz0r

skelz0r commented Jul 16, 2026

Copy link
Copy Markdown
Member

dans les cas comme DGFIP-TVA vaudrait mieux suppr la fiche non?

J'ai du mal à voir pourquoi tu veux garder des fiches d'endpoints depreciés sur datagouv

Ces cas se comptent sur les doigts d'une main (voir c'est le seul ..?)

@Samuelfaure

Copy link
Copy Markdown
Contributor Author

OKAY U GOT A POINT lol

@skelz0r

skelz0r commented Jul 16, 2026

Copy link
Copy Markdown
Member

Cela étant dit c'est une balance code/logique métier à maintenir VS confort de supprimer les dépréciés sur datagouv. Vu qu'actuellement ce n'est de toute manière par réellement maintenu un j'aurais tendance à ne pas le gérer mais j'ai aucun strong belief la dessus

@Samuelfaure

Copy link
Copy Markdown
Contributor Author

je serais partisan de le garder si t'as pas d'opinion forte du coup, flemme de me rappeller de delete manuellement si le cas repop

@Samuelfaure
Samuelfaure force-pushed the feat/datagouv_auto_fill branch 2 times, most recently from a9503c7 to c6fc014 Compare July 24, 2026 09:43
@Samuelfaure
Samuelfaure marked this pull request as ready for review July 24, 2026 11:48
@Samuelfaure

Copy link
Copy Markdown
Contributor Author

C'est techniquement prêt mais je suis pas sûr d'être satisfait du résultat, + faut tester en sandbox sur demo.

Je demande pas encore les reviews du coup, je me laisse un peu de temps pour voir si je trouve mieux

@nicolaskempf57

Copy link
Copy Markdown

Le moissoneur j'ai reçu l'avis de @nicolaskempf57 qui m'a tldr dit "ne fait pas ça" (la team a l'air assez terrifiée du code relatif au moissonnage, la complexité semble +++)

Le problème du moissonnage c'est que ça s'appuie sur un standard (DCAT principalement) et le format RDF. Donc ça veut dire faire un endpoint dédié de zero, avoir la même correspondance pour les champs qui n'ont pas toujours un mapping claire entre le standard et les données sur data.gouv, ce qui semble plus complexe à faire à maintenir par rapport à faire des appels d'API.

Concernant la récupération de la "bonne" fiche à mettre à jour, si vous ne voulez pas de datagouv_uid, le plus simple serait d'utiliser le slug (ex: https://www.data.gouv.fr/api/1/dataservices/api-donnees-du-repertoire-sirene-insee-bouquet-api-entreprise/). Sinon, il faut partir de l'organisation DINUM avec son id ou avec le token API il est possible d'avoir les orga associées à l'utilisateur (via /api/1/me/). Et avec l'id, il est possible de faire une recherche sur les API de l'orga (ex: https://www.data.gouv.fr/api/2/dataservices/search/?organization=57fe2a35c751df21e179df72)

@Samuelfaure

Samuelfaure commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@nicolaskempf57 Big thanks pour ces conseils (c'est déjà ce qu'on fait en vrai avec l'ID DINUM)

@Samuelfaure
Samuelfaure requested review from Un3x and skelz0r and removed request for Un3x and skelz0r July 29, 2026 09:28
@Samuelfaure
Samuelfaure force-pushed the feat/datagouv_auto_fill branch from c6fc014 to 2f39345 Compare July 30, 2026 11:35
@Samuelfaure
Samuelfaure requested review from Un3x and skelz0r July 30, 2026 11:36
@Samuelfaure

Copy link
Copy Markdown
Contributor Author

Okay j'ai enfin trouvé le focus pour vérifier le taff ici, c'est prêt pour review du coup

@Un3x Un3x left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moi ca me convient dans l'ensemble niveau qualité du code.

J'ai une questions cependants, pourquoi avoir fait des services et clients and co et pas avoir suivi le "pattern coc" qu'on utilise partout et qui ferait le taff ici aussi ?

En vrai, techniquement je trouve ca tout aussi bien, c'est plus par convention dans la team

Sinon, le corps de la PR ne décrit plus trop les changements que tu as fais, j'imagine que ca correspond à une précédente opération.

Faudrait voir ce qu'il se passe si un fichier _swagger_shared change. J'ai l'impression que ca peut faire planter la tache et du coup tout le deploy et pour le coup je serait assez contre avoir des step bloquantes.

datagouv:sync takes uids via the SYNC_UIDS env var (comma-separated) --
the mechanism the GitHub Actions workflow uses, since Rake's bracket-arg
CLI syntax (`datagouv:sync[a,b,c]`) splits on commas into separate
positional arguments before the task runs and silently drops all but the
first uid. The bracket argument remains a fallback for a single uid
passed manually from a shell. datagouv:sync_all syncs every endpoint.
Runs on push to main (the auto-generated force-push from develop after
CI goes green) when commons/endpoints/** changed, and computes the
touched fiche uids from the push's before/after SHAs to sync only what
changed; workflow_dispatch runs a full sync instead. DATAGOUV_HOST is a
plain vars entry (swappable to point at production later) and
DATAGOUV_API_TOKEN a secret.

Sync is search-based (Datagouv::DataserviceIndex lists data.gouv.fr's
own records and matches them by an embedded marker) rather than relying
on a locally-stored id, so the workflow never writes back to the repo --
no repo write permissions needed, no branch-protection bypass required.
@Samuelfaure
Samuelfaure force-pushed the feat/datagouv_auto_fill branch from 2f39345 to dab9481 Compare August 12, 2026 14:37
…ument sync_with_datagouv

Three shared swagger yml files (cnous, france_travail, mesri) contain
unquoted Date literals that Psych::safe_load rejects, so touching any
of them would crash the "compute changed uids" workflow step and fail
the whole sync job. _swagger_shared files never carry fiche entries
anyway, so exclude them from the changed-file glob.

Also documents datagouv_uid/sync_with_datagouv in the entreprise fiche
template per review feedback on PR #264.
…nstead of a marker

Per review on PR #264, the HTML-comment marker embedded in the
description (with a title-match fallback) was fragile and more
complex than needed: business_documentation_url is already unique
per endpoint uid and comes back verbatim on every dataservice, so it
works as the match key on its own. Drops DataserviceIndex's title
fallback and by_marker/by_title split down to a single lookup, which
also lets SyncFiche use #find for both the update and the
deprecated-delete paths instead of a separate #marker_match.
@Samuelfaure

Copy link
Copy Markdown
Contributor Author

@skelz0r C'est supposé être run avec un workflow github, donc pas possible de faire ça en job, github a pas accès à notre queue (à moins qu'on mette en place un systeme de call complexe maybe?) - mais ça fait sens d'être un job synchrone du coup

Samuelfaure added a commit that referenced this pull request Aug 12, 2026
Per review on PR #264, punchline embeds the endpoint's raw technical
swagger description as bold Markdown text verbatim. Surveyed all 74
current endpoints -- none are actually unsafe today (max 264 chars,
single-line, no markdown-special characters) -- but nothing enforced
that going forward. Strips HTML tags and markdown emphasis/code/link
characters, collapses newlines to spaces, and caps length so a future
endpoint description can't break the fiche's rendering.
@skelz0r

skelz0r commented Aug 14, 2026

Copy link
Copy Markdown
Member

C'est supposé être run avec un workflow github

Pourquoi ?

Tu n'es pas passé sur mes autres points btw.

@Samuelfaure

Copy link
Copy Markdown
Contributor Author

@skelz0r C'est juste plus pratique non? Surtout qu'on ne touche qu'aux fiches qui ont changées donc ça ne ralentis pas les autres déploiements

Tu voudrais remplacer ça par un cron job toutes les nuits? Ça me semble un poil plus relou

(oui les autres points je vais passer dessus)

@skelz0r

skelz0r commented Aug 17, 2026

Copy link
Copy Markdown
Member

Plus pratique cela m'étonne, lancer un job au démarrage c'est 1 ligne dans un initializer, et on s'assure que ça run à chaque boot d'app VS un workflow github qui est moins déterministe et plus compliqué à maintenir/comprendre.

Garder le workflow github why not, mais dans ce cas là je ferai tout en ruby pour simplifier ces quelques lignes de bash (qui sont peu lisibles) je lançerais à chaque déploiement sans faire de disjonction (comme ça c'est sûr que ça soit toujours à jour) et j'utiliserai un PORO et non une tâche rake.

…iption

Per further review on PR #264: the ad hoc <[^>]*> regex used to strip
HTML out of the swagger description lived in FichePayloadBuilder and was
weaker than what Rails already ships. Moves HTML-tag stripping into
AbstractEndpoint#description_plain_text (via ActionView::Base.full_sanitizer,
already a transitive dependency through rails-html-sanitizer/Loofah, no new
gem), and renames the leftover punchline method/constant to summary_line
since it now only handles the datagouv-specific markdown escaping and
truncation.
@Samuelfaure
Samuelfaure force-pushed the feat/datagouv_auto_fill branch from 6e62cde to dd1e94b Compare August 17, 2026 09:17
@Samuelfaure

Copy link
Copy Markdown
Contributor Author

Je trouve le workflow github plus safe (on a une meilleure vue dessus si ça foire VS lancer le job au boot) donc je vais faire les modifs dans ce sens

@skelz0r

skelz0r commented Aug 17, 2026

Copy link
Copy Markdown
Member

Je trouve le workflow github plus safe (on a une meilleure vue dessus si ça foire VS lancer le job au boot) donc je vais faire les modifs dans ce sens

Le job qui fail lève une erreur sentry et est résilient (si erreur réseau temporaire ça retry) contrairement à github qui va juste planter. Le fait d'utiliser du job async via notre pipe donne toute la stack by design pour gérer exactement ce genre de use cases (qui est de keep sync le code en prod avec des ressources externes).
On met à jour les status pages hyperping comme ça par exemple.

Après, le cas du fail n'existe pas encore, mais clairement si ça arrive je vais mécaniquement migrer vers du job car évident pour moi 😅 (vs des dizaines de lignes de yaml en dehors de la codebase rails).

@Samuelfaure

Copy link
Copy Markdown
Contributor Author

bon tu as de bons arguments je vais migrer en job 😅

Per review on PR #264: GitHub Actions had no reason left to own this
sync once matching moved off datagouv_uid (no more write-back commit
needed) -- replaces the site-sync-datagouv.yml workflow and the
datagouv:sync/:sync_all rake tasks with Datagouv::SyncFichesRemoteService,
wired up in an initializer the same way siade's
SyncPingsWithMonitorsRemoteService already is: a Rails.cache-locked
service, spawned in a Thread from after_initialize, production only.

Every boot now does a full sync instead of only syncing uids changed
since the last deploy; Datagouv::SyncFiche#unchanged? already skips
no-op API calls, so this stays cheap. No manual rake-task escape hatch
is kept, matching the mirrored pattern.

Needs DATAGOUV_HOST/DATAGOUV_API_TOKEN added to site's own production
environment before deploy -- today they only exist as GitHub Actions
secrets.
site's Redis cache namespace bakes in Time.now.to_i at process boot
(config/application.rb), so two dynos booting seconds apart in a
rolling deploy get different namespaces and Rails.cache's
unless_exist lock never actually crosses processes -- both would run
the full sync concurrently and race create_dataservice calls for the
same endpoint. Passing an explicit namespace: on the lock's
write/delete/exist? calls overrides the store default for just this
key, without touching the global cache config other features may
depend on.
DatagouvAPIClient read DATAGOUV_HOST/DATAGOUV_API_TOKEN straight from
ENV, unlike every sibling client (AbstractHubeeAPIClient,
AbstractINSEEAPIClient) which goes through
AdminApientreprise.credentials -- the only place site's own runtime
actually provisions these secrets, since nothing in the app's
environment config defined them (they only ever existed as GitHub
Actions secrets for the now-removed workflow).
perform() ran unguarded inside its boot-time Thread: SyncRunner
already rescues Faraday::Error internally, but anything else (a
missing credential, a bug) died silently -- Thread.new swallows
unhandled exceptions by default, and Sentry has no hook into raw
threads the way it does for requests/jobs. Wraps the sync in rescue
StandardError => e; Sentry.capture_exception(e), matching the
DatapassWebhook::CreateFormulaireQFResources convention.

Moves release_lock! into ensure as part of the same change -- adding
the rescue without it would leave the lock held for the full hour on
every reported failure, silently blocking retries with no error to
explain why.
@Samuelfaure
Samuelfaure requested a review from skelz0r August 18, 2026 08:57
@Samuelfaure

Copy link
Copy Markdown
Contributor Author

Pour info on peut tester en sandbox sans risque (ça envoie sur demo.data.gouv)

@skelz0r skelz0r left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 gros points:

  1. Transformer le service en job pour avoir toute la stack de retry/tracking etc..
  2. Le summary_line

Comment thread site/app/models/abstract_endpoint.rb Outdated
Comment thread site/app/services/datagouv/fiche_payload_builder.rb
Comment thread site/app/services/datagouv/fiche_payload_builder.rb Outdated
Comment thread site/config/initializers/sync_fiches_with_datagouv_remote_service.rb Outdated
Comment thread site/app/services/datagouv/sync_fiches_remote_service.rb Outdated
…the first

Per review on PR #264: an endpoint can have several provider_uids, so
picking providers.first silently dropped the rest from the fiche title
and description.

Claude-Session: https://claude.ai/code/session_01FraV2QQbbbGpUQnUiL8etP
Per review on PR #264: the swagger-description-derived summary line
was hacky and the sanitizer it depended on (description_plain_text)
had no reason to live on AbstractEndpoint since only the datagouv
sync used it.

Adds an optional, authored datagouv_summary attribute -- documented in
both commons/endpoints/template.*.yml.example -- that FichePayloadBuilder
prefers verbatim over the derived summary line when present. None of the
existing fiches have it yet, so the derivation stays as a fallback;
backfilling is editorial work for whoever owns each fiche.

Also moves description_plain_text out of AbstractEndpoint into
FichePayloadBuilder, where it's now only used by that fallback path.

Claude-Session: https://claude.ai/code/session_01FraV2QQbbbGpUQnUiL8etP
Per review on PR #264: SyncFichesRemoteService mirrored siade's
SyncPingsWithMonitorsRemoteService (Thread-spawned from an initializer,
manual rescue + Sentry.capture_exception), but that pattern exists in
siade because siade has no ActiveJob/GoodJob stack -- this app does.

Datagouv::SyncFichesJob replaces it, enqueued via perform_later with
retry_on so genuinely unexpected errors actually retry instead of being
swallowed into a single Sentry report; sentry-rails already reports
ActiveJob failures automatically once retries are exhausted, matching
every other job in app/jobs/.

Claude-Session: https://claude.ai/code/session_01FraV2QQbbbGpUQnUiL8etP
Backfills the datagouv_summary field added in a prior commit for all
55 non-deprecated, datagouv-synced fiches across both catalogs, so the
data.gouv.fr description no longer depends on the swagger-description
derivation for any of them.

Also strips a trailing period from the authored summary the same way
the derived one already was -- the markdown template appends its own
period, so an authored sentence (naturally written with one) produced
a double period otherwise. Folded the two summary_line code paths into
one shared formatting step while at it.

Claude-Session: https://claude.ai/code/session_01FraV2QQbbbGpUQnUiL8etP
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.

4 participants