MonPortfolio
France août 2022 – Aujourd'hui

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 et des parcours UI.

🛠️ Réalisations

Projets

🧩 Conception

💻 Développement

feature = get_geojson_feature(self.geom)
for k in result_dict:
    if k in OBS_KEYS:
        feature["properties"][k] = (
            result_dict[k].name if isinstance(result_dict[k], Enum) else result_dict[k]
        )
feature["properties"]["photos"] = [
    {
        "url": f"/media/{p.media.filename}",
        "date": result_dict["date"],
        "author": self.obs_txt,
    }
    for p in self.medias
]
query = (
    select(TImports)
    .options(
        contains_eager(TImports.authors),
        contains_eager(TImports.destination).contains_eager(Destination.module),
    )
    .join(TImports.authors, isouter=True)
    .join(Destination)
    .join(TModules)
    .where(TImports.filter_by_scope(scope=scope))
    .where(or_(*filters) if len(filters) > 0 else True)
    .order_by(order_by)
)
imports = db.paginate(query, page=page, error_out=False, max_per_page=limit)
def validate_form(self, form):
    view_name = getattr(form, "view_name", "")
    schema_name = getattr(form, "schema_name", "")
    if is_form_submitted() and view_name and schema_name:
        query = GenericQueryGeo(
            DB,
            view_name.data,
            schema_name.data,
            geometry_field=geometry_field.data,
            filters=[],
        )
        columns = query.view.tableDef.columns.keys()
  • TaxHub: optimisation de la route biblistes et correction de l’expression SQLAlchemy nb_taxons. Source: PR #585, PR #583.
def get_biblistes():
    biblistes_records = db.session.query(
        BibListes.id_liste, BibListes.code_liste, BibListes.nom_liste, BibListes.nb_taxons
    ).all()
    biblistes_infos = {
        "data": biblistes_schema.dump(biblistes_records, many=True),
        "count": len(biblistes_records),
    }
    return biblistes_infos
  • UsersHub: centralisation des handlers d’erreurs SQL et exceptions globales sur les routes CRUD. Source: PR #241.
from sqlalchemy.exc import ProgrammingError, IntegrityError
from app.utils.errors import (
    handle_unauthenticated_request,
    handle_integrity_error,
    handle_general_exception,
)

app.login_manager.unauthorized_handler(handle_unauthenticated_request)
app.register_error_handler(IntegrityError, handle_integrity_error)
app.register_error_handler(Exception, handle_general_exception)
this.surveySpecies$ = this.programService
  .getAllProgramTaxonomyList()
  .pipe(
    map((listsTaxonomy) => {
      this.taxaCount = listsTaxonomy
        .filter((lt) => lt.id_liste === this.taxonomyListID)
        .map((lt) => lt.nb_taxons)[0];
      return this.taxaCount < this.taxonAutocompleteInputThreshold;
    }),
    switchMap((shouldFetchTaxonomyList) => shouldFetchTaxonomyList
      ? this.programService.getProgramTaxonomyList(this.taxonomyListID)
      : []
    ),
    share()
  );
route.params.pipe(takeUntil(this.destroy)).subscribe((params) => {
  this.currentDialog = this.modalService.open(SyntheseInfoObsComponent, {
    size: 'lg',
    windowClass: 'large-modal',
  });
  this.currentDialog.componentInstance.idSynthese = params.id_synthese;
  this.currentDialog.componentInstance.selectedTab = params.tab;
  this.dialogResult = this.currentDialog.result.then(
    () => this.location.back(),
    () => this.location.back()
  );
});
this._configService
  .init(this.obj.moduleCode)
  .pipe(
    switchMap((_) =>
      iif(
        () => this.isSiteObject,
        this.initTypeSiteConfig(this.obj.config['specific'], this.obj['properties'], this.obj.config['types_site'])
          .pipe(concatMap(({ idsTypesSite, typesSiteConfig }) => {
            idsTypesSite.forEach((number) => this.idsTypesSite.add(number));
            this.allTypesSiteConfig = typesSiteConfig;
            return this.initSpecificConfig(this.obj.config['specific'], this.obj.config['types_site']);
          })),
        this.initSpecificConfig(this.obj.config['specific'])
      )
    )
  );

🏗️ Infrastructure et déploiement

frontend:
  steps:
    - uses: actions/setup-node@v3
    - name: Frontend code formatting check (Prettier)
      run: npm install prettier@~3.1.0 && npm run format:check
  • Sur les autres dépôts, les pipelines sont utilisés dans le cycle de livraison, sans attribution de création globale en l’absence de preuve commit directe sur les fichiers de workflow.

🧭 Organisation / méthodologie

  • Cadre de travail Agile/Scrum avec revue de code sur PR.
  • Pair programming sur sujets structurants (refactoring, formulaires complexes, API critiques).
  • Cycle de sprint régulier (daily, planning, review) avec suivi de priorisation.

📈 Résultats

  • Résultats globaux: 547 commits, 76 PR, 45 issues liées sur 6 projets. Les livraisons couvrent API, modèle de données, formulaires UI et qualité CI, avec amélioration de la fiabilité opérationnelle et de la maintenabilité.

Projet GeoNature (core)

  • Nb PR: 39 (5 open / 34 closed / 23 merged / 1 draft)
  • Nb commits: 178
  • Nb issues: 25 (10 open / 15 closed)
  • Période: 2023-02-02 -> 2025-07-29

Projet GeoNature-citizen

  • Nb PR: 9 (3 open / 6 closed / 4 merged / 0 draft)
  • Nb commits: 34
  • Nb issues: 3 (1 open / 2 closed)
  • Période: 2024-11-20 -> 2025-03-24

Projet gn_module_monitoring

  • Nb PR: 19 (3 open / 16 closed / 9 merged / 1 draft)
  • Nb commits: 329
  • Nb issues: 6 (3 open / 3 closed)
  • Période: 2022-12-12 -> 2025-11-14

Projet gn_module_export

  • Nb PR: 5 (0 open / 5 closed / 3 merged / 0 draft)
  • Nb commits: 6
  • Nb issues: 2 (0 open / 2 closed)
  • Période: 2023-05-11 -> 2023-05-16

Projet TaxHub

  • Nb PR: 3 (0 open / 3 closed / 0 merged / 0 draft)
  • Nb commits attribués localement: 0
  • Nb issues: 9 (2 open / 7 closed)
  • Période: attribution locale indisponible (contributions observées via PR)

Projet UsersHub

  • Nb PR: 1 (1 open / 0 closed / 0 merged / 0 draft)
  • Nb commits attribués localement: 0
  • Nb issues: 0
  • Période: attribution locale indisponible (contribution observée via PR)

🔧 Environnement technique

  • Langages: Python, TypeScript, SQL.
  • Backend: Flask, SQLAlchemy, GeoAlchemy2, Marshmallow, Alembic, Celery, Gunicorn.
  • Frontend: Angular, RxJS, Leaflet, Angular Material, Bootstrap.
  • Qualité/tests: Pytest, Coverage, Cypress, Black, Isort, Pylint, ESLint, Prettier.
  • CI/CD & Ops: GitHub Actions, Docker, scripts d’installation/migration, quality gates.
  • Données: PostgreSQL/PostGIS, migrations SQL, optimisation de requêtes, modèles de permissions.

Technologies utilisées

Backend
Alembic
Celery
Flask
Gunicorn
Marshmallow
Python
Frontend
Angular
Angular Material
Bootstrap
Leaflet
RxJS
TypeScript
Qualite / Tests
Cypress
ESLint
Prettier
Pytest
DevOps
Docker
docker-compose
GitHub Actions
GitLab CI/CD
Bases de donnees (SGBD & SQL)
PostGIS
PostgreSQL
Design Patterns & Architecture
SQLAlchemy