
    ti                     4   d Z ddlZddlZddlZddlZddlmZmZ ddlm	Z	m
Z
mZmZmZmZmZ  ej        dd          ZdZdZd	Zd
ZdZdZddddddddZd@dZed         ZdZd Z G d d          Zedk    r ed            ed            ed            e            Z  ed           e !                                Z" ede"             ed            d!d"d#d$d%d&d'd$gZ#e $                    d(d)e#d*+          Z% ed,e%d-                      ed.e%d/                      ed0e%d1                      ed2e%d3         dd4          d5            ed6           e &                    e%d3                   Z'e'(                                D ]\  Z)Z* ed7e) d8e*             ed9           e $                    d:g ;          Z+ ed<e+d-                      ed=e+d3                      ed>            ed?            ed           dS dS )Au"  
[CYBER-STRAT] Module de synthese NLP — GeoSynthesizer v1.1
Agent responsable : NLP_SYNTHESIZER

Synthese geopolitique : Groq API (priorite 1) + Ollama fallback.
Utilise /api/chat (PAS /api/generate).
Zero API Claude — regle absolue.
Zero import requests — urllib.request uniquement.
    N)datetimedate)
OLLAMA_URLOLLAMA_MODELOLLAMA_FALLBACK_CHAINMAX_SUMMARY_CHARSMIN_SUMMARY_CHARSOLLAMA_TEMPERATUREOLLAMA_NUM_PREDICTGROQ_API_KEY z/https://api.groq.com/openai/v1/chat/completionszllama-3.3-70b-versatile      u  

CONTRAINTES TEMPORELLES (OBLIGATOIRES) :
- La date du jour est : {today}.
- REGLE AGE CRITIQUE : Ne JAMAIS recopier un age provenant des sources tel quel. Si une source datee de l'annee X indique qu'une personne a N ans, son age actuel est approximativement N + ({current_year} - X). Exemple : source de 2017 indiquant "48 ans" → en {current_year} la personne a environ {example_age} ans. Ne PAS mentionner de date de naissance sauf si explicitement donnee dans les sources. Formuler : "agee d'environ X ans en {current_year}" ou omettre l'age si incertain.
- Si une institution, fonction ou structure a une date de fin, de dissolution ou de cessation, utiliser le PASSE.
- Ne JAMAIS affirmer qu'une personne "est actuellement" en poste si la source date de plus de 2 ans et qu'aucune source recente ne le confirme. Utiliser "occupait" ou "a occupe".
- Les sources peuvent etre anciennes : croiser les dates des sources avec la date du jour pour eviter les anachronismes.
a  

CONTRAINTES TEMPORELLES (OBLIGATOIRES) :
- La date du jour est : {today}.
- NE MENTIONNE JAMAIS l'age sauf si une date de naissance EXACTE (jour/mois/annee) est explicitement presente dans les sources. Cela inclut TOUTE formulation : 'N ans', 'a l'age de N ans', 'age de N ans', 'ne en XXXX'. INTERDIT.
- Si une institution, fonction ou structure a une date de fin, de dissolution ou de cessation, utiliser le PASSE.
- Ne JAMAIS affirmer qu'une personne "est actuellement" en poste si la source date de plus de 2 ans et qu'aucune source recente ne le confirme. Utiliser "occupait" ou "a occupe".
- Les sources peuvent etre anciennes : croiser les dates des sources avec la date du jour pour eviter les anachronismes.
a%  Tu es un critique litteraire et analyste culturel.
On te donne des extraits sur une oeuvre de fiction (roman, saga, jeu, etc.).

CONSIGNES :
- Presente l'oeuvre : titre, auteur, genre, univers
- Decris le contexte narratif et les themes principaux
- Si l'oeuvre a un ancrage geopolitique ou philosophique, l'analyser
- Structure en sections : PRESENTATION, UNIVERS, THEMES, RECEPTION
- Termine par : GENRE: [FANTASY|SF|THRILLER|HISTORIQUE|AUTRE]
- Jamais d'opinion personnelle, style analytique
- 800 a 4500 caracteres maximum
- Toujours en francaisu  Tu es un analyste biographique. Synthetise le profil de la personne indiquee dans SUJET.

REGLE ANTI-DERIVE : si les sources parlent d'une personne differente du SUJET, les ignorer.

CONNAISSANCES : tu peux completer avec tes propres connaissances si la personne est publique (Wikipedia, presse, publications). Privilegier les sources fournies, mais ne pas dire "aucune information disponible" si tu connais la personne.

LONGUEUR OBLIGATOIRE : MINIMUM 1500 caracteres, MAXIMUM 4000 caracteres.
Une synthese d'une seule phrase est un echec. Developpe chaque section.
Utilise TOUTES les sources fournies, pas seulement Wikipedia.

STRUCTURE — dans cet ordre, omettre si vide :

**IDENTITE**
Nom, profession, nationalite. 2-3 phrases.

**PARCOURS**
Formation, postes, institutions. Passe pour ce qui n'est plus actuel.

**TRAVAUX ET POSITIONS**
Expertise, prises de position publiques, tribunes, debats. Ne pas repeter PARCOURS.

**INFLUENCE ET PRESENCE PUBLIQUE**
Medias, emissions, conferences, controverses si pertinent. Omettre si inconnu.

**PUBLICATIONS**
Ouvrages, du plus recent au plus ancien, annee + editeur. Omettre si aucune.

**ACTUALITE RECENTE**
3 dernieres annees uniquement. Omettre si rien de notable.

STATUT : [ACTIF|DISCRET|INACTIF]

REGLES : zero repetition entre sections, distinguer personne/collectif homonyme, jamais d'opinion, toujours en francais. Croiser les sources pour une synthese riche.ae  Tu es un analyste de renseignement open source (OSINT). Tu rediges un portrait factuel et dense a partir des sources fournies.

REGLES ABSOLUES :
- Exploite TOUTES les informations concretes presentes dans les sources.
- Chaque phrase doit contenir une information factuelle verifiable.
- NE DIS JAMAIS "les informations sont limitees" ou "les sources ne permettent pas".
- NE DIS JAMAIS "il est difficile de determiner" ou "les details ne sont pas clairs".
- Si une information n'est pas disponible dans les sources, OMETS la section correspondante silencieusement. Ne signale pas l'absence.
- NE MENTIONNE JAMAIS l'age ou la date de naissance SAUF si une date de naissance EXACTE (jour/mois/annee) est explicitement presente dans les sources. Cela inclut TOUTE formulation : 'N ans', 'a l'age de N ans', 'age de N ans', 'ne en XXXX'. INTERDIT.
- N'INVENTE aucune information. Ne deduis pas, ne suppose pas, ne specule pas.
- Si les sources parlent d'evenements, de prises de position, de publications : CITE-LES avec precision (noms, dates, lieux, titres).

REGLE ANTI-DERIVE : si les sources parlent d'une personne differente du SUJET, les ignorer.

LONGUEUR OBLIGATOIRE : MINIMUM 1000 caracteres, MAXIMUM 3500 caracteres.
Une synthese d'une seule phrase est un echec. Developpe chaque section.

STRUCTURE (inclure uniquement les sections pour lesquelles tu as des informations) :

**IDENTITE**
Nom complet, profession/fonction principale, organisation/entreprise.

**PARCOURS**
Formation, postes occupes, chronologie si disponible.

**ACTIVITE**
Domaines d'expertise, prises de position, roles publics.

**PUBLICATIONS**
Livres, articles, interventions mediatiques.

**PRESENCE PUBLIQUE**
Medias, conferences, reseaux professionnels.

STATUT : [ACTIF|DISCRET|INACTIF]

REGLES : zero repetition entre sections, distinguer personne/collectif homonyme, jamais d'opinion, toujours en francais.a  Tu es un analyste geopolitique senior.
On te donne des sources sur un pays ou une region.

CONSIGNES :
- Produis une synthese analytique de 800 a 4500 caracteres (espaces inclus)
- Structure en sections :
  PRESENTATION : position geographique, regime, population
  CONTEXTE : situation politique, economique, diplomatique
  ENJEUX : enjeux strategiques actuels, tensions, alliances
  POSITIONNEMENT : positionnement international, zones d'influence
  DEVELOPPEMENTS RECENTS : faits marquants des 12 derniers mois si disponibles
- Ton : analytique, factuel, dense, style rapport de veille strategique
- Termine par : STATUT: [STABLE|INSTABLE|CRITIQUE|CONFLIT]
- JAMAIS d'opinion, JAMAIS de jugement moral
- Si les sources sont insuffisantes, dis-le explicitement en 2 lignes
- Toujours en francaisa  Tu es un analyste en intelligence strategique.
On te donne des sources sur une organisation (institution, entreprise, alliance).

CONSIGNES :
- Produis une synthese analytique de 800 a 4500 caracteres (espaces inclus)
- Structure en sections :
  PRESENTATION : nature, siege, dirigeants, domaine d'activite
  CONTEXTE : historique, evolution recente
  ENJEUX : enjeux strategiques, economiques, influence
  POSITIONNEMENT : alliances, rivalites, zone d'action
  DEVELOPPEMENTS RECENTS : faits marquants des 12 derniers mois si disponibles
- Ton : analytique, factuel, dense, style rapport de veille strategique
- Termine par : STATUT: [ACTIF|EMERGENT|ETABLI|DISCRET|CONFIDENTIEL]
- JAMAIS d'opinion
- Si les sources sont insuffisantes, dis-le explicitement en 2 lignes
- Toujours en francaisu  Tu es un analyste specialise en gouvernance locale francaise.
On te donne des informations sur un ELU LOCAL (maire, adjoint, conseiller municipal) issu du Repertoire National des Elus.

STRUCTURE OBLIGATOIRE — dans cet ordre exact :

**IDENTITE ET MANDAT**
Nom complet, fonction (maire, adjoint...), commune, departement, code INSEE si disponible. Date de debut de mandat et date de debut de fonction. Categorie socio-professionnelle.

**COMMUNE**
Presentation de la commune : localisation geographique, population si connue, caractere (rural/urbain/periurbain), intercommunalite si mentionnee. Si un extrait Wikipedia de la commune est fourni, utiliser ces informations.

**CONTEXTE LOCAL**
Enjeux locaux si disponibles dans les sources : amenagement, developpement economique, services publics, environnement. Si aucune source locale disponible, l'indiquer brievement.

**INFORMATIONS COMPLEMENTAIRES**
Toute information additionnelle sur la personne issue des sources (parcours, profession, engagements). Si aucune info : omettre.

STATUT : [EN FONCTION|MANDAT EXPIRE|INCONNU]

REGLES :
- Source OFFICIELLE : les donnees RNE (Repertoire National des Elus) sont la source primaire et font autorite
- Ne PAS inventer d'informations non presentes dans les sources
- Si peu d'informations disponibles, produire une synthese courte mais factuelle (minimum 400 caracteres)
- Maximum 3000 caracteres au total
- Toujours en francaisa  Tu es un analyste en intelligence strategique et geopolitique.
On te donne des extraits d'articles de presse et de medias sur un sujet.

CONSIGNES :
- Produis une synthese analytique de 800 a 4500 caracteres (espaces inclus)
- Structure ta synthese en sections clairement identifiees :
  PRESENTATION : identite, fonction, rattachement
  CONTEXTE : cadre geopolitique, historique recent, situation actuelle
  ENJEUX : enjeux strategiques, economiques, diplomatiques ou securitaires
  POSITIONNEMENT : positionnement de l'entite, alliances, rivalites, influence
  DEVELOPPEMENTS RECENTS : faits marquants des 12 derniers mois si disponibles
- Ton : analytique, factuel, dense, style rapport de veille strategique
- Termine par : STATUT: [ACTIF|EMERGENT|ETABLI|DISCRET|CONFIDENTIEL]
- JAMAIS d'opinion, JAMAIS "il est excellent/brillant/controverse"
- Si les sources sont insuffisantes, dis-le explicitement en 2 lignes
- Toujours en francais)fictionpersonperson_notorietecountryorganization	elu_localdefaultr   c                 b   t                               | t           d                   }t          j                                        d          }t          j                    j        }d|dz
  z   }| dk    rt                              |          }nt                              |||          }||z   S )zSelectionner le prompt systeme adapte au type de contenu.
    Injecte automatiquement la date du jour pour la coherence temporelle.
    v4.9f : person_notoriete utilise TEMPORAL_INSTRUCTION_NO_AGE
    (pas d'estimation d'age sans date de naissance).
    r   z%Y-%m-%d0   i  r   )today)r   current_yearexample_age)	CONTENT_PROMPTSgetr   r   strftimeyearTEMPORAL_INSTRUCTION_NO_AGEformatTEMPORAL_INSTRUCTION)content_typebase_prompt	today_strr   r   temporals         1/var/www/cyber-strat/agents_python/synthesizer.pyget_system_promptr(   !  s     "%%lOI4NOOK
%%j11I:<<$Lt+,K ))).55 6   (..%# / % %
 !!    ud  Ta synthese precedente ne fait que {char_count} caracteres. C'est insuffisant — une seule phrase n'est PAS une synthese. Developpe OBLIGATOIREMENT chaque section pour atteindre au minimum 1500 caracteres. Ajoute des details, du contexte historique, des enjeux et des developpements recents. Utilise TOUTES les sources fournies, pas seulement la premiere.c                 0    t          d|  d|            dS )z&Log prefixe pour le module synthesizerz[CYBER-STRAT][SYNTHESIZER][] N)print)levelmessages     r'   _logr/   G  s'    	
:
:
:
:
:;;;;;r)   c                   z    e Zd ZdZdZdZdZd Z	 	 	 dd
ZdZ	dZ
dZ	 	 ddZd Zd Zd Zd Zd Zd Zd Zd ZdS )GeoSynthesizeraQ  Synthetiseur geopolitique multi-sources.

    Pipeline :
    1. Construction du prompt utilisateur a partir des sources
    2. Tentative de synthese via Ollama (fallback chain : mistral > gemma2 > llama3.2)
    3. Si Ollama indisponible : synthese heuristique locale (extraction de phrases)
    4. Validation de la synthese produite
    Nr   x   c                     t          dd           t          r#t          ddt          dd          d           dS t          dd           dS )	zInitialisation du synthetiseurINFOzGeoSynthesizer initialisezGroq configure: Nr   ...ERRORuB   GROQ_API_KEY absente — verifier override.conf. Ollama uniquement)r/   r   )selfs    r'   __init__zGeoSynthesizer.__init__[  s^    V0111 	`AL!,<AAABBBBB^_____r)   conceptfrr   r   c                 	   t          dd d| d| d| d|rdnd 
           ||fd
|D             }|g }|st          dd d           d ddd	ddS t          |          }t          dd|            |                     ||||          }	t          r|                     ||	          }
|
r|
}t          |          t          k     rt          ddt          |          t          fz             t                              t          |                    dz   |z   dz   |	z   }|                     ||          }|rBt          |          t          |          k    r"|}t          ddt          |          z             t          |          t          k    rv|d	t                   }t          |                    d          |                    d                    }|t          dz  k    r|d	|dz            }n|d	t          dz
           dz   }dt           }t          dd t          |           d!           |                     |          }t          dd"|d#          d$|d%         rd&nd' d(|d)         rd&nd*            ||t          t          |          dS |                     ||	          \  }}|r|rt          |          t          k     rt          dd+t          |           d,t           d-           t                              t          |                    dz   |z   dz   |	z   }|                     |||          }|rCt          |          t          |          k    r#|}t          dd.t          |           d/           t          |          t          k    rv|d	t                   }t          |                    d          |                    d                    }|t          dz  k    r|d	|dz            }n|d	t          dz
           dz   }d0| }t          dd1| d2t          |           d!           n*t          dd3           |                     |          }d4}d	}|                     |          }t          dd"|d#          d$|d%         rd&nd' d(|d)         rd&nd*            |||t          |          dS )5u=  Synthetiser un resume depuis des sources multiples.

        Parametres :
            query         : str   — le mot-clef recherche
            entity_type   : str   — "person", "country", "organization", "concept"
            sources       : list[dict] — chaque source a les cles "domain", "text", "title"
            source_texts  : list[str]  — alternative simplifiee (liste de textes bruts)
            lang          : str   — langue de sortie ("fr" par defaut)
            content_type  : str   — type de contenu detecte (fiction, person, country, etc.)
            extra_context : str   — contexte supplementaire injecte dans le USER prompt
                                    (ex: activite editoriale Reflets.info)

        Retourne :
            dict avec "summary", "method", "model", "char_count"
        r4   zSynthese demandee pour: 'z	' (type: z, content: z, lang: )z [extra_context]r   Nc                      g | ]
}|d |dS )source)domaintexttitle ).0tquerys     r'   
<listcomp>z-GeoSynthesizer.synthesize.<locals>.<listcomp>  s)    bbb1`ab(AFFbbbr)   WARNzAucune source fournie pour ''z@Aucune source disponible pour synthetiser des informations sur "z".noner   )summarymethodmodel
char_countzPrompt systeme selectionne: uN   Synthese Groq trop courte (%d chars < %d) — retry avec instruction renforcee)rM   z

Ta synthese precedente :
z

Sources originales :
zRetry Groq reussi: %d chars. z.
g?   r   r5   zgroq:zSynthese Groq reussie (z chars)zValidation: rM   z chars, limite=within_limitOKDEPASSEz	, statut=has_status_lineABSENTzSynthese trop courte (z	 chars < z), retry...zRetry reussi: z charszollama:zSynthese Ollama reussie via z (z3Fallback synthese heuristique (Ollama indisponible)	heuristic)r/   r(   _build_user_promptr   
_call_groqlenr	   RETRY_PROMPTr!   r   maxrfind
GROQ_MODELvalidate_summary_synthesize_ollama_call_ollama_heuristic_synthesis)r7   rE   entity_typesourcessource_textslangr#   extra_contextsystem_promptuser_promptgroq_resultrJ   
retry_userretry_result	truncatedlast_periodrK   
validation
model_usedretry_summarys    `                  r'   
synthesizezGeoSynthesizer.synthesizeg  sF   $ 	V= = =!= =.:= =DH= =%2:!!= =	> 	> 	> ?|7bbbbP\bbbG?G 	@@@@AAAi_diii 	   *,77VBLBBCCC --;}> >  0	//-EEK .% w<<"333?w<<):;<= = =
 %++s7||+DD:;=DE679DE 
 $(??%z$3 $3L$ , #L 1 1CLL @ @".V9!'ll+, , ,
 w<<"333 '(:):(: ;I"%iood&;&;Y__U=S=S"T"TK"%6%<<<"+,<[1_,<"="+,B->-B,B"Ce"K---VLs7||LLLMMM!227;;
VT:l#; T T&0&@OddiT T&01B&CQddT TU U U  '$'"%g,,	   #55m[QQ 	 Hc'll->>>Vkc'llkkM^kkklll)00CLL0IILllovv  zV  V  Yd  d
 $ 1 1*mZ X X  HS%7%7#g,,%F%F+G!F#g,,!F!F!FGGG 7||///#$6%6$67	!)//$"7"79O9OPP!2S!888'(8q(89GG'(>):Q)>(>?%GG+z++F[
[[c'll[[[\\\\ NOOO//w??G FJ **733
VL:l3 L L(8GddiL L():;IddL L	M 	M 	M g,,	
 
 	
r)   i.  i     c           	         g }|                     d|dd         z             |r4|                     |          }|r|                     |dd                    |rd|v r|                    d          }|                    d|          }	|	dk     rt          |          }	|||	                                         }
|
                    dd	          }|                     |dd
                    |rd|v r|                    d          }|                    d|          }|dk     r t          |dz   t          |                    }|||                                         }|                     |dd                    |rd|v r|                    d          }||d         }g }|                    d          D ]t}|                                }|                    d          rIt          |          dk     r6d|v r|                    d          d         n|}|                     |           u|r+|                     dd	                    |          z              |rg }|d| j
                 D ]}|                    d          p+|                    d          p|                    d          pd}|r|                                sZ|                    dd          }|                     d|d|d| j                            |r+|                     dd	                    |          z              d	                    |          }t          |          | j        k    r'|d| j                 }t          dd| j        z             t          dd t          |          z             t          dd!|dd"                             dd	          z             |S )#u  Construire le USER prompt — donnees uniquement, zero instruction.

        v4.0 : budget 6000 chars. 5 blocs, ordre strict :
        1. SUJET : {query}                             (80c max)
        2. IDENTITE compacte                           (300c max)
        3. ELU officiel si present                     (200c max)
        4. Reflets si present                          (150c max)
        5. Sources : 6 x 1500c max                     (~9000c brut, tronque)

        Les instructions sont UNIQUEMENT dans system_prompt.
        z
SUJET : %sNP   i,  zDONNEES OFFICIELLES (RNE

r   
z |    z
WIKIDATA :   ACTIVITE EDITORIALEz- r   u    — z	REFLETS: z ; r@   extractrJ   r   r?   ?[r+   z	SOURCES:
z
---
rG   zUSER prompt tronque a %d charsr4   USER_PROMPT_CHARS: %dzUSER_PROMPT_PREVIEW: %s...  )append_extract_identity_linefindrX   stripreplaceminsplit
startswithjoinMAX_SOURCESr   MAX_SOURCE_CHARSMAX_USER_CHARSr/   )r7   rE   ra   rb   rd   re   partsid_line	elu_startelu_end	elu_blockelu_compactwd_startwd_endwd_block	ref_startref_texttitleslinerD   	src_partssrcr@   r?   user_contents                            r'   rV   z!GeoSynthesizer._build_user_prompt  s3     	\E#2#J.///  	,11-@@G ,WTcT]+++  	,7=HH%**+EFFI#((;;G{{m,,%i&78>>@@I#++D%88KLLTcT*+++  	)\]::$)),77H"''99FzzX^S-?-?@@$Xf_5;;==HLL$3$(((  
	?2mCC%**+@AAI$YZZ0HF t,, % %zz||??4(( %S[[1__29T//

7++A..tAMM!$$$ ?[5::f+=+==>>>  
	GI0 001 U U 43779+=+= 4779--413  4::<< 3//   fffd;QD<Q;Q6R6R!STTTT G\INN9,E,EEFFFyy'' |t222'(<)<(<=L9D<OOPPPV,s</@/@@AAAV(4C4 ((u556	7 	7 	7 r)   c                    g }|                     d          D ]                                st          fddD                       r6                    d          r1|                    ddd                                                    |                    d          rG|                    d	v r-                     d	          d
                                         nd           ؉                    d          rK|                    dd	v r-                     d	          d
                                         ndz             8                    d          rSd	v r.                     d	d
          d
                                         nd}|r|                    |dd                    |r"dd                    d |D                       z   S dS )zzExtraire une ligne d'identite compacte depuis extra_context.
        Format : IDENTITE : Nom -- Profession (Lieu)
        ru   c              3   B   K   | ]}                     |          V  d S N)r   )rC   skipr   s     r'   	<genexpr>z8GeoSynthesizer._extract_identity_line.<locals>.<genexpr>Y  sG       A AT4??4(( A A A A A Ar)   )zREGLE ABSOLUEzNE PAS DERIVERzSUJET DE LAzIDENTITE CONFIRMEEzDONNEES OFFICIELLESrx   z->zLa personnez- Nom :r      Nz- Profession:rO   r   z- Localisationz(%s)z
- Contexters   zIDENTITE : z -- c              3      K   | ]}||V  	d S r   rB   rC   ps     r'   r   z8GeoSynthesizer._extract_identity_line.<locals>.<genexpr>j  s'      .L.LQ!.Lq.L.L.L.L.L.Lr)   )r   r   anyr   insertr~   r   )r7   re   result_partsctxr   s       @r'   r   z%GeoSynthesizer._extract_identity_lineO  s    !''-- 	2 	2D::<<D  A A A A 6@ A A A A A  y)) 	2##AtABBx~~'7'7888800 2###++DJJsOOA$6$<$<$>$>$>SUVVVV!122 2##FCSWKKdjjooa.@.F.F.H.H.H]_$`aaaa.. 27:d{{djja((+11333 2 ''CRC111 	M 6;;.L.L,.L.L.L#L#LLLrr)   c                    ddl }t          dd           t          ddt          |          z             t          ddt          |          z             |                                 }	 t          j        t
          d|dd	|dgd
t          d                              d          }t          j	        
                    t          |ddt          z  dd          }t          j	                            |t                    5 }t          |                                 |z
  dz            }t          j        |                                                    d                    }	|	                    dg           }
|
r|
d                             di                               dd          }t          dd|z             |rSt          |          dk    r@t          ddt          |          z             |                                cddd           S t          ddt          |          z             	 ddd           dS t          dd           	 ddd           dS # 1 swxY w Y   dS # t          j        j        $ r~}t          |                                 |z
  dz            }|                                                    dd          dd          }t          dd!|j        ||fz             Y d}~dS d}~wt0          $ rG}t          |                                 |z
  dz            }t          dd"||fz             Y d}~dS d}~ww xY w)#u   Appeler Groq API via urllib.request.

        v4.0 : max_tokens=2000, timeout=8s, log GROQ_RESPONSE_MS.
        Zero import requests — urllib.request uniquement.
        Retourne le texte de synthese ou None si echec.
        r   Nr4   z"Tentative synthese via Groq API...zSYSTEM_PROMPT_CHARS: %dr|   systemrolecontentuser  )rL   messages
max_tokenstemperatureutf-8application/jsonz	Bearer %szCyberStrat/1.0)Content-TypeAuthorizationz
User-Agentdataheaderstimeout  choicesr.   r   r   zGROQ_RESPONSE_MS: %dd   zGroq: reponse recue (%d chars)rG   z$Groq: reponse trop courte (%d chars)z$Groq: pas de choices dans la reponser   )errorsrv   zGroq HTTP %d (%dms): %su)   Echec Groq (%dms): %s — fallback Ollama)timer/   rX   jsondumpsr\   r
   encodeurllibrequestRequestGROQ_URLr   urlopenGROQ_TIMEOUTintloadsreaddecoder   r   error	HTTPErrorcode	Exception)r7   rf   rg   _timet0payloadreqresp
elapsed_msr   r   r   ebodys                 r'   rW   zGeoSynthesizer._call_groqq  s    	V9:::V.]1C1CCDDDV,s;/?/??@@@ZZ\\.	j#%-@@#<< #1" "   vg  .(($6%0<%?"2  )  C ''\'BB  d %**,,"3t!;<<
z$))++"4"4W"="=>>((9b11  %ajnnY;;??	2NNG!7*!DEEE $3w<<##5#5V%EG%TUUU&}}                V%KcRYll%Z[[[#                !GHHH                                   " |% 	 	 	ejjllR/4788J6688??79?==dsdCD2afj$5OOPPP44444 	 	 	ejjllR/4788JD
TUVWWW44444	sc   *BI+ DII+ !I3I+  II+ I""I+ %I"&I+ +M?A3K88M<MMc           	      \   ddl }|                                 }|                                 s<t          |                                 |z
  dz            }t          dd|z             dS t          D ]}	 t          dd|z             |                     |||          }|rUt          |          d	k    rBt          |                                 |z
  dz            }t          dd
||fz             ||fc S t          dd||rt          |          ndfz             # t          $ r }t          dd|d|           Y d}~d}~ww xY wt          |                                 |z
  dz            }dt          _	        |                                 t          _
        t          dd|z             dS )zTenter chaque modele de la fallback chain.

        Ordre : gemma2:2b (defini dans ollama_config.py)
        v3.2 fix : timeout=3s, cache disponibilite 120s.
        Retourne (texte, nom_du_modele) ou (None, None) si tous echouent.
        r   Nr   rG   z1Ollama non accessible (%dms), skip fallback chain)NNr4   zTentative synthese avec %s...r   z!OLLAMA_RESPONSE_MS: %d (model=%s)z#Reponse trop courte de %s: %d charszEchec : Fu6   Tous les modeles echoue (%dms) — cache invalide 120s)r   _ollama_availabler   r/   r   r_   rX   r   r1   _ollama_available_cache_ollama_cache_ts)	r7   rf   rg   r   r   r   rL   resultr   s	            r'   r^   z!GeoSynthesizer._synthesize_ollama  s    	ZZ\\%%'' 	ejjllR/4788JLzYZZZ:* 	 	EV<uDEEE**5-MM >c&kkC//!$ejjllR&74%?!@!@J!D
TYGZ!Z[[[!5=(((!Ff;s6{{{!J= "= > > > >   VVuuuaa8999 %**,,+t344
16.*/**,,'VMPZZ[[[zs   3A>D4&D
E%E  Ec                 &   t          j        |d|dd|dgdt          t          dd                              d          }t
          j                            t           d|d	d
i          }t
          j        	                    |t                    5 }t          j        |                                                    d                    }|                    di           }|                    dd          cddd           S # 1 swxY w Y   dS )u   Appeler Ollama via /api/chat (PAS /api/generate).

        Zero API Claude — regle absolue.
        Utilise urllib.request (zero import requests).
        r   r   r   F)r   num_predict)rL   r   streamoptionsr   z	/api/chatr   r   r   r   r.   r   r   N)r   r   r
   r   r   r   r   r   r   r   OLLAMA_TIMEOUTr   r   r   r   )	r7   rL   rf   rg   r   r   r   r   r.   s	            r'   r_   zGeoSynthesizer._call_ollama  s_    *!m<<K88 11 
 
   6'?? 	 n$$$$$#%78 % 
 
 ^##C#@@ 	.D:diikk0099::Dhhy"--G;;y"--		. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	.s   A%DD
D
c                    ddl }|                                 }t          j        $|t          j        z
  | j        k     rt          j        S 	 t
          j                            dt          z            }t
          j        	                    |d          5 }|j
        dk    }ddd           n# 1 swxY w Y   n# t          $ r d}Y nw xY w|t          _        |t          _        |st          dd	           |S )
zVerifier si Ollama est accessible sur le port configure.

        v3.2 fix : cache le resultat pendant 120s pour eviter de
        re-tester a chaque requete quand Ollama est down.
        r   Nz%s/api/tags   r   rv   Fr4   u)   Ollama indisponible — cache 120s active)r   r1   r   r   _OLLAMA_CACHE_TTLr   r   r   r   r   statusr   r/   )r7   r   nowr   r   	availables         r'   r   z GeoSynthesizer._ollama_available  s7    	jjll2>>::() )!99	.(()CDDC''Q'77 /4 K3.	/ / / / / / / / / / / / / / / 	 	 	III	1:.*-' 	FDEEEs7   
AB6 B*B6 *B..B6 1B.2B6 6CCc                    dg}|                                 }d |                                D             }|dd         D ]y}|                    dd          }|r|                                s0|                    dd                              d	          }d
 |D             }	g }
|	D ]w}|                                 }||v r|
                    |           n2t          |          dk    r|d         |v r|
                    |           t          |
          dk    r nx|
rFd	                    |
          }|                    d          s|dz  }|                    |           )|	dd         }|rDd	                    |          }|                    d          s|dz  }|                    |           {d                    |          }|dt                   S )a  Synthese sans LLM : phrases pertinentes extraites des sources.

        Prefixe : [SYNTHESE AUTO -- LLM INDISPONIBLE]
        Utilise quand Ollama est down ou que tous les modeles echouent.

        Strategie d'extraction intelligente :
        1. Priorise les phrases contenant le nom recherche
        2. Fallback sur les premieres phrases si aucune mention directe
        Tronque a MAX_SUMMARY_CHARS (4500) caracteres.
        z#[SYNTHESE AUTO -- LLM INDISPONIBLE]c                 \    g | ])}t          |          d k    |                                *S )r   )rX   lowerr   s     r'   rF   z7GeoSynthesizer._heuristic_synthesis.<locals>.<listcomp>!  s+    EEEA#a&&1**aggii***r)   N   r@   r   ru    rN   c                     g | ];}t          |                                          d k    '|                                <S )   )rX   r   )rC   ss     r'   rF   z7GeoSynthesizer._heuristic_synthesis.<locals>.<listcomp>)  s8    QQQqS^^b=P=P=P=P=Pr)   r   r   .rt   )
r   r   r   r   r   r~   rX   r   endswithr   )r7   rE   rb   r   query_lower
name_partsr   r@   raw_sentences	sentencesrelevantr   s_lowerfragmentfallbackr   s                   r'   r`   z#GeoSynthesizer._heuristic_synthesis  s    77kkmmEEEEE
2A2; 	+ 	+C7762&&D tzz||  LLs3399$??MQQMQQQI H  ''))'))OOA&&&&__))jn.G.GOOA&&&x==A%%E &  +99X..((-- $OHX&&&& %RaR= +#yy22H#,,S11 ( CLL***U##((())r)   c                     t          |          t          |          t          k    d|v t          |          t          k    dS )aC  Verifier la conformite de la synthese.

        Retourne un dict avec :
            char_count   : nombre de caracteres
            within_limit : True si <= 4500 chars (MAX_SUMMARY_CHARS)
            has_status_line : True si contient "STATUT:"
            min_density  : True si >= 2500 chars (MIN_SUMMARY_CHARS)
        zSTATUT:)rM   rP   rS   min_density)rX   r   r	   )r7   rJ   s     r'   r]   zGeoSynthesizer.validate_summaryK  sB     g,,LL,==(G3w<<+<<	
 
 	
r)   c                 8   |r|                                 sg S d}d|dd          }|                     ||          \  }}|st          dd           g S 	 |                    d          }|                    d          d	z   }|d
k    rI||k    rC|||         }t          j        |          }	t          ddt          |	           d|            |	S t          dd           g S # t
          j        t          f$ r}
t          dd|
            g cY d}
~
S d}
~
ww xY w)zExtraction d'entites nommees via Ollama.

        Utilise le LLM pour identifier personnes, organisations, lieux, evenements.
        Retourne une liste de dicts {"entity", "type", "context"}.
        Fallback : liste vide si Ollama indisponible.
        a  Tu es un extracteur d'entites nommees specialise en geopolitique.
Extrais les entites du texte fourni.
Reponds UNIQUEMENT en JSON valide, sous la forme d'une liste :
[{"entity": "nom", "type": "PERSON|ORG|GPE|EVENT", "context": "breve description"}]
Types : PERSON (personne), ORG (organisation), GPE (pays/ville), EVENT (evenement)
Maximum 10 entites. Pas de commentaire, uniquement le JSON.zTexte a analyser :

Nr   rG   z+NER: Ollama indisponible, retour liste vider{   ]rO   r   r4   zNER: z entites extraites via z+NER: Pas de JSON valide dans la reponse LLMzNER: Echec parsing JSON: )
r   r^   r/   r   r[   r   r   rX   JSONDecodeError
ValueError)r7   r@   rf   rg   result_textrn   
json_startjson_endjson_strentitiesr   s              r'   extract_entitieszGeoSynthesizer.extract_entities_  sn     	4::<< 	IJ 	 =tETE{<< #'"9"9-"U"UZ 	FGGGI	$))#..J"((--1HQ8j#8#8&z(':;:h//VWS]]WW:WWXXXVJKKK	$j1 	 	 	8Q88999IIIIII	s%   A;C$ C$ $D:DDD)r9   NNr:   r   r   )r:   r   )__name__
__module____qualname____doc__r   r   r   r8   rp   r   r   r   rV   r   rW   r^   r_   r   r`   r]   r  rB   r)   r'   r1   r1   L  s         #` ` ` @D>G!#K
 K
 K
 K
d NKCG)+P P P Pd  D: : :@% % %N. . .>  :1* 1* 1*n
 
 
(+ + + + +r)   r1   __main__z<============================================================z+[CYBER-STRAT] Test du module synthesizer.pyz&
--- Test 1 : Disponibilite Ollama ---zOllama accessible : z-
--- Test 2 : Synthese (sources simulees) ---z
lemonde.frzArticle test Le MondezEmmanuel Macron a prononce un discours sur la defense europeenne. Le president francais a insiste sur la necessite d'une autonomie strategique. Cette declaration intervient dans un contexte de tensions internationales accrues.)r?   rA   r@   zrfi.frzArticle test RFIzLa France renforce sa position diplomatique en Afrique de l'Ouest. Les relations franco-africaines traversent une periode de redefinition. Plusieurs pays du Sahel ont pris leurs distances avec Paris.zEmmanuel Macronr   r:   )rE   ra   rb   rd   zMethode  : rK   zModele   : rL   zChars    : rM   zResume   :
rJ   r}   r5   z
--- Test 3 : Validation ---z  r   z'
--- Test 4 : Synthese sans sources ---zEntite inconnue)rE   rb   z
Methode : z
Resume  : z=
============================================================z[CYBER-STRAT] Tests termines)r   ),r  r   osurllib.requestr   urllib.errorr   r   ollama_configr   r   r   r   r	   r
   r   getenvr   r   r\   r   r   r"   r    r   r(   SYSTEM_PROMPTrY   r/   r1   r	  r,   synthr   r   test_sourcesrp   r   r]   rm   itemskeyvalueresult_emptyrB   r)   r'   <module>r     s     				         # # # # # # # #                  ry,,<&

 H .H *	!#	_N/	2f	!&	!&#	!N	!qI IX" " " "2  	*F < < <
~ ~ ~ ~ ~ ~ ~ ~J z	E(OOO	E
7888	E(OOONE 
E
3444''))I	E
,
,
,--- 
E
:;;; #,i	
 	
 'S	
 	
L" 	   F 
E
*x(
*
*+++	E
)w
)
)***	E
.|,
.
.///	E
5	*4C40
5
5
5666 
E
)***''y(9::J &&(( # #
U!3!!%!!"""" 
E
4555##*;R#HHL	E
/|H-
/
/000	E
0|I.
0
0111	E/	E
()))	E(OOOOOs r)   