# backend/schemas/france_travail.py from datetime import datetime from typing import List, Optional, Dict, Any, Union from pydantic import BaseModel, Field, field_validator, computed_field # Modèles de données pour les structures communes (Lieu, Entreprise, etc.) class LieuTravail(BaseModel): libelle: Optional[str] = Field(None, example="Paris") codePostal: Optional[str] = Field(None, example="75001") commune: Optional[str] = Field(None, example="Paris") class TypeContrat(BaseModel): code: Optional[str] = Field(None, example="CDI") libelle: Optional[str] = Field(None, example="Contrat à durée indéterminée") class Appellation(BaseModel): code: Optional[str] = Field(None, example="10034") libelle: Optional[str] = Field(None, example="Développeur informatique") class OrigineOffre(BaseModel): url: Optional[str] = Field(None, example="https://candidat.francetravail.fr/candidature/offre/1234567") typeOrigine: Optional[str] = Field(None, example="ONLINE") class Entreprise(BaseModel): nom: Optional[str] = Field(None, example="Ma Super Entreprise") description: Optional[str] = None url: Optional[str] = None id: Optional[str] = None class Salaire(BaseModel): libelle: Optional[str] = Field(None, example="2500 EUR brut/mois") commentaire: Optional[str] = None typeForfait: Optional[str] = None periode: Optional[str] = None min: Optional[float] = None max: Optional[float] = None class Competence(BaseModel): code: Optional[str] = None libelle: Optional[str] = None description: Optional[str] = None exigence: Optional[str] = None class Experience(BaseModel): libelle: Optional[str] = Field(None, example="Débutant accepté") code: Optional[str] = None class Formation(BaseModel): domaineLibelle: Optional[str] = None niveaulibelle: Optional[str] = None codeFormation: Optional[str] = None class Permis(BaseModel): libelle: Optional[str] = None code: Optional[str] = None # Modèle pour une offre individuelle class Offre(BaseModel): id: str = Field(..., example="1234567") intitule: str = Field(..., example="Développeur Full Stack") description: Optional[str] = None dateCreation: datetime dateActualisation: datetime lieuTravail: Optional[LieuTravail] = None typeContrat: Optional[Union[TypeContrat, str]] = None romeCode: Optional[str] = None romeLibelle: Optional[str] = None appellationLibelle: Optional[str] = None entreprise: Optional[Entreprise] = None origineOffre: Optional[OrigineOffre] = None nbPostes: Optional[int] = None nbResultats: Optional[int] = None @field_validator('typeContrat', mode='before') @classmethod def validate_type_contrat(cls, v: Any) -> Any: if isinstance(v, str): return TypeContrat(code=v, libelle=None) return v class Config: from_attributes = True # AJOUTEZ CETTE PROPRIÉTÉ CALCULÉE @computed_field def url_francetravail(self) -> str: """Génère l'URL de l'offre sur le site candidat.francetravail.fr.""" return f"https://candidat.francetravail.fr/offres/recherche/detail/{self.id}" # Modèle pour les détails complets d'une offre class OffreDetail(Offre): # OffreDetail hérite de Offre, donc il aura automatiquement la propriété url_francetravail description: str = Field(..., example="Description détaillée du poste...") complementExercice: Optional[str] = None urlDossierCandidature: Optional[str] = None # Ce champ vient directement de l'API s'il est fourni qualification: Optional[str] = None appellations: Optional[List[Appellation]] = None competences: Optional[List[Competence]] = None entreprise: Optional[Entreprise] = None formations: Optional[List[Formation]] = None langues: Optional[List[Dict[str, Any]]] = None permis: Optional[List[Permis]] = None class Config: from_attributes = True class FranceTravailSearchResponse(BaseModel): resultats: List[Offre] = Field(default_factory=list) totalResults: Optional[int] = Field(None, description="Nombre total d'offres correspondant aux critères") range: Optional[str] = Field(None, description="Plage des résultats actuels, ex: '0-14/100'")