# agent_v0/server_v1/domain_context.py """ Contexte métier pour les appels VLM — rend Léa experte du domaine. Chaque workflow est associé à un domaine métier (médical, comptable, etc.) qui enrichit TOUS les prompts VLM (Observer, Critic, acteur, enrichissement). Un gemma4 qui sait qu'il regarde un DPI et que l'utilisateur fait du codage CIM-10 prend des décisions bien meilleures qu'un VLM générique. Premier domaine : TIM (Technicien d'Information Médicale) - Logiciels DPI/DMS (dossier patient informatisé) - Codage CIM-10 / CCAM / GHM - Lecture de comptes rendus médicaux - Validation des séjours / RSS / RSA Usage : ctx = get_domain_context("tim_codage") prompt = f"{ctx.system_prompt}\n\n{user_prompt}" """ import logging from dataclasses import dataclass, field from typing import Any, Dict, List, Optional logger = logging.getLogger(__name__) @dataclass class DomainContext: """Contexte métier pour un domaine spécifique.""" domain_id: str # Identifiant unique (tim_codage, comptabilite, etc.) name: str # Nom lisible (Codage médical TIM) description: str # Description courte du métier # Prompt système injecté dans TOUS les appels VLM system_prompt: str = "" # Vocabulaire métier (termes que le VLM doit connaître) vocabulary: List[str] = field(default_factory=list) # Applications connues (noms de logiciels que le VLM peut rencontrer) known_apps: List[str] = field(default_factory=list) # Écrans types (descriptions des écrans courants du métier) screen_patterns: Dict[str, str] = field(default_factory=dict) def enrich_prompt(self, prompt: str, role: str = "") -> str: """Enrichir un prompt avec le contexte métier. Args: prompt: Le prompt original role: Le rôle du VLM (observer, critic, actor, enrichment) """ parts = [] if self.system_prompt: parts.append(self.system_prompt) if role: role_hint = _ROLE_HINTS.get(role, "") if role_hint: parts.append(role_hint.format(domain=self.name)) parts.append(prompt) return "\n\n".join(parts) def to_dict(self) -> Dict[str, Any]: return { "domain_id": self.domain_id, "name": self.name, "description": self.description, "known_apps": self.known_apps, "vocabulary_count": len(self.vocabulary), } # Hints par rôle VLM — adaptés au contexte métier _ROLE_HINTS = { "observer": ( "Tu observes un écran utilisé dans le domaine '{domain}'. " "Cherche les popups, erreurs, ou états incohérents avec ce métier." ), "critic": ( "Tu vérifies qu'une action dans le domaine '{domain}' a produit " "le bon résultat. Sois précis sur ce que tu vois à l'écran." ), "actor": ( "Tu décides si une action est nécessaire dans le contexte '{domain}'. " "Utilise ta connaissance du métier pour juger si l'état est cohérent." ), "enrichment": ( "Tu analyses un enregistrement de workflow dans le domaine '{domain}'. " "Décris les intentions métier, pas juste les clics." ), } # ========================================================================= # Domaines pré-configurés # ========================================================================= _TIM_CODAGE = DomainContext( domain_id="tim_codage", name="Codage médical TIM", description=( "Technicien d'Information Médicale : lecture de comptes rendus médicaux, " "codage des diagnostics en CIM-10, codage des actes en CCAM, " "validation des groupes homogènes de malades (GHM), " "gestion des résumés de sortie standardisés (RSS/RSA)." ), system_prompt=( "Tu es un assistant expert en codage médical hospitalier. " "L'utilisateur est un TIM (Technicien d'Information Médicale) qui utilise " "un logiciel DPI (Dossier Patient Informatisé) ou DIM (Département d'Information Médicale). " "Son travail : lire les comptes rendus médicaux des patients et coder les diagnostics " "en CIM-10, les actes en CCAM, et valider les séjours pour le PMSI.\n\n" "Vocabulaire du métier :\n" "- DPI/DMS : logiciel de dossier patient (ex: Orbis, DxCare, Crossway, Easily, Hopital Manager)\n" "- CIM-10 : Classification Internationale des Maladies, 10ème révision (codes diagnostics)\n" "- CCAM : Classification Commune des Actes Médicaux (codes actes chirurgicaux/médicaux)\n" "- GHM : Groupe Homogène de Malades (regroupement tarifaire)\n" "- RSS : Résumé de Sortie Standardisé (données du séjour)\n" "- RSA : Résumé de Sortie Anonyme (RSS anonymisé pour la T2A)\n" "- DP : Diagnostic Principal (le code CIM-10 principal du séjour)\n" "- DAS : Diagnostics Associés Significatifs\n" "- CMA : Complication ou Morbidité Associée (augmente la sévérité)\n" "- T2A : Tarification À l'Activité (financement des hôpitaux)\n" "- PMSI : Programme de Médicalisation des Systèmes d'Information\n" "- UM : Unité Médicale (service hospitalier)\n" "- CR : Compte Rendu (document médical)\n\n" "Écrans courants :\n" "- Liste de patients / dossiers à coder\n" "- Fiche patient (identité, séjour, UM)\n" "- Écran de codage CIM-10 (recherche de codes, saisie DP/DAS)\n" "- Visualiseur de comptes rendus médicaux\n" "- Écran de validation / groupage GHM\n" "- Recherche de codes (arborescence CIM-10 ou recherche textuelle)" ), vocabulary=[ "CIM-10", "CCAM", "GHM", "RSS", "RSA", "PMSI", "T2A", "diagnostic principal", "DAS", "CMA", "compte rendu", "dossier patient", "séjour", "unité médicale", "codage", "groupage", "valorisation", "exhaustivité", ], known_apps=[ "Orbis", "DxCare", "Crossway", "Easily", "Hopital Manager", "CORA", "AGFA", "Dedalus", "Maincare", "Softway Medical", "WebPIMS", "CEPAGE", "Medimust", ], screen_patterns={ "liste_patients": "Liste de dossiers patients avec colonnes (nom, prénom, date entrée, UM, statut codage)", "fiche_patient": "Fiche d'identité patient avec numéro IPP, séjour, dates, UM", "codage_cim10": "Écran de saisie des codes CIM-10 avec diagnostic principal et DAS", "compte_rendu": "Visualiseur de compte rendu médical (texte libre, souvent PDF intégré)", "recherche_code": "Recherche de code CIM-10 ou CCAM (champ de recherche + arborescence)", "validation_ghm": "Écran de validation du groupage avec GHM calculé et valorisation", }, ) _GENERIC = DomainContext( domain_id="generic", name="Bureautique générale", description="Automatisation bureautique générale (Office, navigateur, etc.)", system_prompt=( "Tu es un assistant RPA qui observe des applications bureautiques. " "Décris précisément ce que tu vois à l'écran." ), ) # Registre des domaines disponibles _DOMAINS: Dict[str, DomainContext] = { "tim_codage": _TIM_CODAGE, "generic": _GENERIC, } def get_domain_context(domain_id: str = "generic") -> DomainContext: """Récupérer le contexte métier par ID. Args: domain_id: Identifiant du domaine (tim_codage, generic, etc.) Returns: DomainContext correspondant, ou generic si non trouvé. """ ctx = _DOMAINS.get(domain_id, _GENERIC) if ctx is _GENERIC and domain_id != "generic": logger.warning(f"Domaine '{domain_id}' non trouvé, utilisation de 'generic'") return ctx def register_domain(context: DomainContext) -> None: """Enregistrer un nouveau domaine métier.""" _DOMAINS[context.domain_id] = context logger.info(f"Domaine '{context.domain_id}' enregistré ({context.name})") def list_domains() -> List[Dict[str, Any]]: """Lister tous les domaines disponibles.""" return [ctx.to_dict() for ctx in _DOMAINS.values()]