JobLess
  • Accueil
  • Architecture
  • GitHub

Architecture du projet


Structure des dépôts

Le projet est réparti sur deux dépôts GitHub avec des responsabilités distinctes :

Dépôt applicatif — Mise_en_production_DS

Mise_en_production_DS/
├── .github/workflows/           # CI/CD : build Docker, déploiement Pages
├── app/                         # API FastAPI + Dockerfile backend
├── frontend/                    # Application Streamlit + Dockerfile frontend
├── src/                         # Modules Python (extraction, classification, LLM)
├── notebooks/                   # Expérimentations
├── website/                     # Ce site (GitHub Pages)
├── pyproject.toml               # Gestion des dépendances (uv)
└── LICENSE

Dépôt GitOps — Jobless_deployment

Jobless_deployment/
├── application_backend.yaml     # Application ArgoCD — backend
├── application_frontend.yaml    # Application ArgoCD — frontend
├── deployment/                  # Manifestes Kubernetes — Backend (FastAPI)
│   ├── deployment.yaml
│   ├── service.yaml
│   └── ingress.yaml
└── deployment_front/            # Manifestes Kubernetes — Frontend (Streamlit)
    ├── deployment.yaml
    ├── service.yaml
    └── ingress.yaml


Architecture technique


graph LR
    classDef plain fill:#fff,stroke:#3b82f6,stroke-width:1.5px,rx:5,ry:5,color:#333;
    classDef store fill:#f0f9ff,stroke:#3b82f6,stroke-width:1.5px,rx:5,ry:5,color:#333;
    classDef gitops fill:#f0fdf4,stroke:#22c55e,stroke-width:1.5px,rx:5,ry:5,color:#333;

    subgraph Stockage ["Stockage (S3 / MinIO)"]
        direction TB
        NER("Modèle NER<br/>(spaCy)")
        HIST("Historique<br/>classifications")
        PROMPTS("Prompts LLM")
    end

    subgraph Backend ["Backend (FastAPI)"]
        direction TB
        EXT("Extraction NER")
        CACHE("Cache DuckDB")
        LLM("Appels LLM")
        CLASSIF("Classification<br/>4 niveaux")
    end

    subgraph Frontend ["Frontend (Streamlit)"]
        UI("Interface<br/>utilisateur")
    end

    U("Utilisateur")

    NER --> EXT
    HIST --> CACHE
    PROMPTS --> LLM
    EXT --> CACHE
    CACHE --> LLM
    LLM --> CLASSIF
    CLASSIF --> UI
    UI --> U

    class U,UI,EXT,CACHE,LLM,CLASSIF plain;
    class NER,HIST,PROMPTS store;
    linkStyle default stroke:#3b82f6,stroke-width:1.5px;
    style Stockage fill:none,stroke:#94a3b8,stroke-width:1.5px,rx:10,ry:10
    style Backend fill:none,stroke:#3b82f6,stroke-width:1.5px,rx:10,ry:10
    style Frontend fill:none,stroke:#3b82f6,stroke-width:1.5px,rx:10,ry:10


Pipeline de classification

Les compétences sont d’abord extraites par le modèle NER (CamemBERTa-v2 via spaCy), puis classifiées en plusieurs niveaux par LLM. Un cache DuckDB alimenté par l’historique JOCAS 2019–2025 évite les appels redondants pour des compétences déjà connues.

Niveau Description Catégories
1er Type de compétence Soft Skill, Compétence numérique, Compétence non numérique, Domaine / Secteur, Certification / Formation
Si Compétence numérique :
2e Thématique numérique Données, Analytics & IA · Développement applicatif · Infrastructure, Systèmes & Réseaux · Bureautique & compétences générales · …
3e Niveau Basique · Intermédiaire · Avancé
4e Catégorie IA Machine Learning · IA générative · …


API

L’API FastAPI expose un endpoint principal :

GET /analyze?desc_offre=<texte de l'offre>

Elle retourne une liste d’objets JSON, un par compétence extraite, avec l’ensemble des niveaux de classification. La documentation interactive est accessible sur /docs.

Le backend utilise run_in_threadpool de Starlette pour ne pas bloquer la boucle d’événements FastAPI lors des appels synchrones au modèle spaCy et au LLM.


CI/CD

Le projet utilise GitHub Actions pour trois workflows automatisés :

Workflow Déclencheur Action
deploy_pages.yml Push sur main Build Quarto + déploiement GitHub Pages
docker_back.yml Push sur main, branches actives, tags v* Build & push image Docker backend sur Docker Hub
docker_front.yml Push sur main, branches actives, tags v* Build & push image Docker frontend sur Docker Hub

Les images Docker sont construites avec Docker Buildx et publiées sur Docker Hub. Les variables de configuration (URL LLM, chemin S3, nom de modèle) sont injectées comme build-args lors du build du backend.


Déploiement GitOps

L’infrastructure de production est gérée selon une approche GitOps : le dépôt Jobless_deployment est la source de vérité des manifestes Kubernetes, et ArgoCD se charge de maintenir le cluster SSP Cloud en conformité avec cet état.

graph LR
    classDef plain fill:#fff,stroke:#3b82f6,stroke-width:1.5px,rx:5,ry:5,color:#333;
    classDef green fill:#f0fdf4,stroke:#22c55e,stroke-width:1.5px,rx:5,ry:5,color:#333;

    DEV("Push code<br/>Mise_en_production_DS")
    CI("GitHub Actions<br/>Build & push Docker")
    HUB("Docker Hub<br/>arthurleroudier/jobless<br/>arthurleroudier/jobless_front")
    GITOPS("Jobless_deployment<br/>manifestes K8s")
    ARGO("ArgoCD<br/>selfHeal: true")
    K8S("Cluster K8s<br/>SSP Cloud")

    DEV --> CI --> HUB
    HUB -.->|"Mise à jour<br/>du tag d'image"| GITOPS
    GITOPS --> ARGO --> K8S

    class DEV,CI,HUB plain;
    class GITOPS,ARGO green;
    class K8S plain;
    linkStyle default stroke:#3b82f6,stroke-width:1.5px;

Deux applications ArgoCD

Application Composant Image Namespace
jobless-backend API FastAPI (port 8000) arthurleroudier/jobless user-aleroudier
jobless-frontend Streamlit (port 8501) arthurleroudier/jobless_front user-aleroudier

Les deux applications ont syncPolicy.automated.selfHeal: true : si l’état du cluster dérive des manifestes (redémarrage, modification manuelle…), ArgoCD corrige automatiquement sans intervention humaine.

Ingress et accès public

Le frontend est exposé via un Ingress NGINX avec :

  • TLS activé sur jobless-website.lab.sspcloud.fr
  • CORS configuré pour autoriser les appels cross-origin depuis le frontend vers l’API

Secrets Kubernetes

Un secret doit être présent dans le namespace avant le premier déploiement :

```bash kubectl create secret generic api-jeton
–from-literal=API_KEY=‘votre_clé_api_llm’
-n user-aleroudier