Developpeur Full-Stack
🎯 Contexte et objectifs
- Faire évoluer des applications naturalistes en production sur des flux critiques: import, synthèse, monitoring, taxonomie et gestion des utilisateurs.
- Stabiliser les interfaces backend/frontend via des contrats API plus robustes, du filtrage/pagination, des formulaires dynamiques et une gestion d'erreurs homogène.
- Réduire les corrections manuelles côté métier en améliorant la cohérence des modèles de données, les retours d'administration et les parcours UI.
🛠️ Réalisations
Projets
- GeoNature-citizen — #projet-citizen
- GeoNature — #projet-geonature
- GeoNature-Docker-services — #projet-geonature-docker-services
- gn_module_export — #projet-gn-module-export
- gn_module_monitoring — #projet-gn-module-monitoring
- TaxHub — #projet-taxhub
- UsersHub — #projet-usershub
🧩 Conception
- #projet-citizen : PR mergées : #430 a fait évoluer la récupération TaxHub pour gérer des listes volumineuses, reformater les taxons/médias et adapter le formulaire d'observation à l'autocomplétion et à la présélection ; #438 a ajouté le compteur de sites dans les statistiques de la page d'accueil et propagé les traductions associées ; #436 a complété la couche i18n, notamment en allemand, dans plusieurs écrans d'authentification, d'observation, de sites et de configuration ; #435 a corrigé la redirection vers le backoffice via la configuration Apache. PR ouvertes : #466 ajoute un proxy i18n pour le mode développement côté webpack/server ; #465 introduit un sélecteur de langue et un service de persistance frontend/server ; #393 refond l'affichage détaillé des observations et sites pour gérer des descriptions et des structures JSON imbriquées.
- #projet-geonature : PR mergées : #3583 a ajouté un identifiant UUID dans le framework d'acquisition via backend, schéma de configuration et composants de formulaire ; #3343 a ajouté une modal de confirmation sur l'édition d'import et ajusté les workflows et tests de l'import V3 ; #3303 a étendu les événements et données de médias / marquages du monitoring avec modèles, routes, migrations et tests ; #3277 a introduit un paramètre de contexte dans le field mapping d'import, propagé du frontend vers l'upload backend avec tests associés ; #3212 a corrigé la validation des radio boutons requis dans les formulaires dynamiques ; #3196 a préservé la géométrie dessinée lors de l'édition Leaflet ; #3169 a ajouté un routage dédié vers les onglets du détail d'observation ; #3154 a exposé les dernières discussions sur la page d'accueil avec route backend, configuration et composant Angular ; #3048 a ajouté fixture et test frontend pour la liste d'import ; #2935 a corrigé les libellés statistiques du rapport PDF ; #2920 a ajouté des tests backend sur les erreurs de numéro de ligne ; #2911 a supprimé
taxa_countau profit de statistiques calculées dans le rapport d'import ; #2910 a ajouté l'action d'import de données directement dans les modules ; #2899 a regroupé les champs du rapport par entité ; #2883 a préparé les listes d'import multi-destinations ; #2875 a généralisé l'import vers toutes les destinations ; #2779 a branchéDRAWSTOPpour fiabiliser la conservation de géométrie ; #2768 a introduit une fonction d'auto-validation avec migration, tâche et schéma de configuration ; #2701 a ajouté un filtre par module source dans la synthèse côté requête et formulaire ; #2692 a rendu GeoNature compatible avec le module monitoring via modèles, routes et migrations ; #2658 a corrigé l'affichage du dernier point de géométrie dans OCCTAX ; #2637 a ajoutégroup3_inpndans la vue d'export synthèse avec migration et tests ; #2321 a corrigé la comparaison de dates dans le formulaire OCCTAX. PR ouvertes : #3952 aligne le préfixe d'URL entre exemples de configuration et scripts d'installation ; #3941 découple l'installation dans$HOMEet introduitnvmsur systèmes mutualisés ; #3663 ajoute un filtreadditional_datade synthèse pour le monitoring côté backend et formulaire Angular ; #3478 corrige le widget numérique pour rejeter les lettres et homogénéiser les messages de validation ; #3432 rend le composant datalist explicitement réinitialisable. - #projet-geonature-docker-services : PR ouvertes : #97 met en place une observabilité légère par logs Docker
json-file, rotation, niveaux de logs configurables, labels de services et overrides pour injecter un bundle CA interne dans les conteneurs runtime et utilitaires ; #76 industrialise la stack Compose avec services one-shot pour monitoring, migrations, supergrant et import raster d'altitude, intègre un module GeoNature externe dans les images backend avecgdal, et renforce Makefiles, layouts de données, permissions et exemples d'environnement. - #projet-gn-module-export : PR mergées : #137 a fusionné la vue Flask d'export avec une gestion cohérente des permissions et du token d'API ; #162 a filtré les utilisateurs applicatifs GeoNature côté administration et backend ; #164 a remonté le token jusqu'au frontend pour l'affichage et l'usage dans la liste des exports.
- #projet-gn-module-monitoring : PR mergées : #238 a rebasé la branche métier sur la base courante en réalignant configuration générique, services, routes et documentation ; #310 a corrigé le tri, le filtre et la pagination de la datatable Angular ; #328 a supprimé les duplications de
formValueentre modèle, composant et service ; #329 a normalisé le formatage frontend via Prettier et la documentation technique ; #330 a ajouté un workflow GitHub Actions de lint frontend ; #407 a ajouté des fixtures et tests backend sur les individus, marquages et schémas associés ; #433 a réduit la taille d'une requête sur les sites en réécrivant la route et en couvrant le correctif par tests ; #531 a corrigé la récupération de l'objet parent ; #540 a supprimé un chemin parent codé en dur dansseeDetails. PR ouvertes : #472 introduit des filtres dynamiques de synthèse pilotés par la configuration TOML ; #429 sécurise la suppression de site avec enfants côté backend et formulaire ; #252 ajoute une migration de journalisation sur les tables complémentaires. - #projet-taxhub : PR mergée : #684 a optimisé l'import massif de
cd_nomavec traitement par lots, insertion idempotente et retour d'administration détaillé. - #projet-usershub : aucune PR mergée identifiée. PR ouverte : #241 homogénéise la gestion d'erreurs backend via une couche utilitaire réutilisée par plusieurs routes.
💻 Développement
- Backend :
#projet-citizen : #430 a refactoré l'adaptation des taxons retournés par TaxHub pour agréger
taxref, médias et attributs utiles au formulaire. Source: PR #430.#projet-geonature : #3277 a ajouté la mise à jour incrémentale d'un import via un
fieldmappingenvoyé dans le formulaire, avec possibilité de modifier un import existant sans renvoyer systématiquement le fichier ; #2768, #2701, #2692 et #2637 ont étendu respectivement la validation automatique, les filtres synthèse, la compatibilité monitoring et la vue d'export. Source: PR #3277.input_file = request.files.get("file", None) input_fieldmapping_preset = request.form.get("fieldmapping", None) # file must be set only when new import. otherwise, it's optional. it can also be a request to update the fieldmapping preset if imprt is None and input_file is None: raise BadRequest("File parameter is missig") if imprt is None: imprt = TImports(destination=destination) author = g.current_user imprt.authors.append(author) db.session.add(imprt) if input_fieldmapping_preset: fieldmapping_preset = json.loads(input_fieldmapping_preset) if imprt.fieldmapping is None: imprt.fieldmapping = {} imprt.fieldmapping.update(fieldmapping_preset) if input_file: size = get_file_size(input_file) max_file_size = current_app.config["IMPORT"]["MAX_FILE_SIZE"] * 1024 * 1024 if size > max_file_size: raise BadRequest(description=f"File too big ({size} > {max_file_size}).") if size == 0: raise BadRequest(description="Impossible to upload empty files")#projet-gn-module-export : #137 a fusionné la gestion des rôles autorisés directement dans la vue Flask-Admin des exports, en ajoutant
allowed_rolesau formulaire d'administration ; #162 a restreint cette sélection aux utilisateurs et groupes réellement rattachés à l'application GeoNature. Source: PR #162.def filter_user_app_and_role(): user_and_gp_from_gn_app = ( User.query.outerjoin(CorRole, User.id_role == CorRole.id_role_utilisateur) .outerjoin( UserApplicationRight, or_( UserApplicationRight.id_role == CorRole.id_role_groupe, UserApplicationRight.id_role == User.id_role, ), ) .join( Application, Application.id_application == UserApplicationRight.id_application, ) .filter(Application.code_application == "GN") ) return user_and_gp_from_gn_app.order_by( User.groupe.desc(), User.nom_role ).filter((User.groupe == True) | (User.identifiant.isnot(None)))#projet-gn-module-monitoring : #433 a réduit une requête trop lourde sur les sites de base ; #407 a ajouté une base de tests backend sur les individus et marquages ; #252 ouvre une piste de journalisation métier par migration. Aucun extrait backend supplémentaire n'est retenu ici afin de réserver le budget d'extraits aux PR mergées les plus structurantes.
#projet-taxhub : #684 a optimisé l'import massif de
cd_nompar validation en lots,ON CONFLICT DO NOTHINGet calcul de statistiques de retour pour l'administration. Source: PR #684.def _populate_bib_liste_batch(id_list, input_cd_noms): valid_cd_noms = set() unique_input_cd_noms = sorted(set(input_cd_noms)) for batch in _chunked(unique_input_cd_noms, 5000): query = select(Taxref.cd_nom).where(Taxref.cd_nom.in_(batch)) valid_cd_noms.update(db.session.execute(query).scalars().all()) inserted_count = 0 sorted_valid_cd_noms = sorted(valid_cd_noms) for batch in _chunked(sorted_valid_cd_noms, 5000): values = [{"id_liste": id_list, "cd_nom": cd_nom} for cd_nom in batch] insert_stmt = ( pg_insert(cor_nom_liste) .values(values) .on_conflict_do_nothing( index_elements=[cor_nom_liste.c.id_liste, cor_nom_liste.c.cd_nom] ) .returning(cor_nom_liste.c.cd_nom) ) inserted_count += len(db.session.execute(insert_stmt).scalars().all())#projet-usershub : #241 centralise le traitement des erreurs dans
app/utils/errors.pyet le réinjecte dans plusieurs routes backend ; aucun extrait n'est retenu ici afin de limiter les exemples de code aux PR mergées.
- Frontend :
#projet-citizen : #430 a adapté le formulaire d'observation pour distinguer les listes taxonomiques courtes et longues, charger le référentiel du programme et présélectionner automatiquement l'unique taxon disponible. Source: PR #430.
this.surveySpecies$ = this.programService .getProgramTaxonomyList(this.taxonomyListID) .pipe( tap((species) => { this.taxa = species; }), switchMap((species) => this.programService.getAllProgramTaxonomyList().pipe( map((listsTaxonomy) => { this.taxaCount = listsTaxonomy .filter((lt) => lt.id_liste === this.taxonomyListID) .map((lt) => lt.nb_taxons)[0]; if (this.taxaCount >= this.taxonAutocompleteInputThreshold) { this.inputAutoCompleteSetup(); } else if (this.taxaCount === 1) { this.onTaxonSelected(species[0]); } return species; }) ) ), share() );#projet-geonature : #3154 a ajouté un composant Angular pour récupérer, trier et paginer les dernières discussions ; #3169, #3212, #3196, #3478 et #3432 complètent ce volet sur le routage modal, la validation de formulaires, la géométrie Leaflet et les composants de saisie. Source: PR #3154.
getDiscussions() { const params = this.buildQueryParams(); this.syntheseApi .getReports(params.toString()) .pipe(takeUntil(this.destroy$)) .subscribe((response) => { this.setDiscussions(response); }); } buildQueryParams(): URLSearchParams { const params = new URLSearchParams(); params.set('type', 'discussion'); params.set('sort', this.sort); params.set('orderby', this.orderby); params.set('page', this.currentPage.toString()); params.set('per_page', this.perPage.toString()); params.set('my_reports', this.myReportsOnly.toString()); return params; }#projet-gn-module-export : #164 a branché l'interface Angular pour récupérer les exports, conserver uniquement le premier token autorisé par export et piloter son affichage / sa copie côté composant. Source: PR #164.
this._exportService .getExports() .pipe( map((exports: Export[]) => { exports.forEach((element) => { element.cor_roles_exports.splice(1); }); return exports; }) ) .subscribe((exports: Export[]) => { exports.forEach((element) => { element.cor_roles_exports.length > 0 ? this.objectToken.push({ token: element.cor_roles_exports[0].token, display: false }) : this.objectToken.push({ token: null, display: false }); }); this.exports = exports; });#projet-gn-module-monitoring : #328 a recentré l'initialisation du formulaire sur un schéma unique enrichi dynamiquement, avec tri explicite des champs et préparation d'un
metapartagé ; #310 a corrigé le comportement de la datatable ; #540 et #531 ont fiabilisé la navigation parent/enfant. Source: PR #328.this._configService .init(this.obj.moduleCode) .pipe( mergeMap(() => iif( () => this.obj.objectType == 'site' && this.obj.id != undefined, this._siteService.getTypesSiteByIdSite(this.obj.id), of(null) ) ) ) .subscribe((typesSites) => { this.queryParams = this._route.snapshot.queryParams || {}; this.bChainInput = this._configService.frontendParams()['bChainInput']; this.schemaGeneric = this.obj.schema(); this.obj.objectType == 'site' ? delete this.schemaGeneric['types_site'] : null; this.obj.id != undefined && this.obj.objectType == 'site' ? this.initExtraSchema(typesSites) : null; });
🏗️ Infrastructure et déploiement
#projet-geonature-docker-services : #76 ajoute des services Compose one-shot pour installer des protocoles monitoring, lancer des migrations, accorder des droits et charger un raster d'altitude, tout en intégrant un module externe et
gdaldans les images backend, en fiabilisant les layoutsdata/geonature, les permissions et les.env; #97 structure l'observabilité via le driver Dockerjson-file, la rotation des logs, des labels de services GeoNature, des niveaux de logs backend/worker configurables et des overrides dédiés pour les certificats internes.#projet-geonature : #3941 et #3952 portent sur les scripts d'installation,
nvm, les chemins d'installation et l'alignement des préfixes d'URL.#projet-citizen : #466 ajoute un proxy de traductions en développement ; #465 ajoute un sélecteur de langue réutilisable côté topbar et serveur.
#projet-gn-module-monitoring : #329 homogénéise le formatage frontend ; #330 ajoute un workflow GitHub Actions qui contrôle Black côté backend et Prettier côté frontend. Source: PR #330.
name: Lint on: [push, pull_request] jobs: backend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Backend code formatting check (Black) uses: psf/black@stable with: src: "setup.py ./backend/gn_module_monitoring" frontend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - name: Frontend code formatting check (Prettier) run: npm install prettier@~3.1.0 && npm run format:check
🧭 Organisation / méthodologie
- Travail structuré par PR thématiques, avec granularité volontairement limitée sur les corrections ciblées (#3212, #3196, #531, #540) et PR plus transverses pour les chantiers d'architecture ou de refonte (#3277, #238, #76).
- Couverture qualité visible dans les PR orientées tests et validation : #3048, #2920, #407, #684.
- Approche de convergence inter-repos sur des sujets communs : import et field mapping entre GeoNature et modules, cohérence d'authentification entre export et UsersHub, conventions d'installation et d'observabilité côté Docker / scripts d'installation.
- Animation d'un parcours de formation en présentiel sur la stack GeoNature entre janvier 2026 et juin 2026, représentant environ quinze jours, avec un objectif de montée en compétences du client sur le développement web et la contribution aux applications GeoNature jusqu'à l'autonomie sur les évolutions et correctifs.
📈 Résultats
- J'ai contribué à un périmètre multi-repo représentant 54 PR vérifiées, dont 40 mergées et 14 ouvertes, sur des sujets qui vont de la donnée naturaliste aux composants Angular, aux migrations SQL et aux scripts d'installation. L'ensemble aboutit à une base logicielle plus cohérente pour les imports, la taxonomie, le monitoring, l'administration et l'exploitation locale, avec une meilleure traçabilité des changements par PR et une réduction des corrections manuelles attendues sur les flux métier. Depuis janvier 2026, j'anime en parallèle un parcours de formation en présentiel sur la stack GeoNature, prévu jusqu'en juin 2026 sur une quinzaine de jours, pour rendre le client autonome sur la contribution aux applications. Bénéfice principal : une fiabilisation progressive des parcours de saisie, des contrats d'API et des environnements de livraison, prolongée par un transfert de compétences directement exploitable par le client.
GeoNature-citizen
- Nb PR: 7
- Nb commits: 34
- Nb issues: 3
- Période: 20 novembre 2024 au 17 juin 2025
GeoNature
- Nb PR: 28
- Nb commits: 178
- Nb issues: 26
- Période: 3 février 2023 au 20 février 2026
GeoNature-Docker-services
- Nb PR: 2
- Nb commits: 31 commits portés par les PR ouvertes #76 et #97
- Nb issues: 1
- Période: 1 septembre 2025 au 10 mars 2026
gn_module_export
- Nb PR: 3
- Nb commits: 6
- Nb issues: 2
- Période: 10 mai 2023 au 16 mai 2023
gn_module_monitoring
- Nb PR: 12
- Nb commits: 329
- Nb issues: 6
- Période: 12 décembre 2022 au 23 décembre 2025
TaxHub
- Nb PR: 1
- Nb commits: 1
- Nb issues: 10
- Période: 21 novembre 2024 au 9 mars 2026
UsersHub
- Nb PR: 1
- Nb commits: 2 commits portés par la PR ouverte #241
- Nb issues: 0
- Période: 23 avril 2025 au 17 juillet 2025
🔧 Environnement technique
- Backend observé via les PR retenues : Python, Flask, SQLAlchemy, Alembic, PostgreSQL, routes REST et tests
pytestsur #430, #3277, #137, #433, #684 et #241. - Frontend observé : Angular, TypeScript, RxJS, composants de formulaire dynamiques, datatables, Leaflet, Cypress et i18n sur #430, #3154, #3169, #164, #328, #310, #3478 et #3432.
- Qualité, CI et exploitation observées : GitHub Actions, Prettier, scripts shell d'installation, Docker Compose, Makefiles et observabilité locale sur #3048, #2920, #330, #3941, #3952, #76 et #97.