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
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
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