
    i*                        d Z ddlZddlZddlZddlZddlmZ ddlmZ ddlm	Z	 ddl
mZ ddlmZ ddlmZ dd	lmZ dd
lmZ  ej(                  e      Zg dZdZdZe G d d             Zdej6                  fdZdedefdZh dddddhdddZdedee   fdZ de!fdZ"dedefdZ#d edefd!Z$d ede%fd"Z&d#ededefd$Z'y)%zB
Article generation using Gemini Flash with web search grounding.
    N)	dataclass)datetime)Path)Optional)genai)types)get_settings)Topic)zgemini-3-pro-previewzgemini-3-flash-previewzgemini-2.5-pro      c                   v    e Zd ZU eed<   eed<   eed<   eed<   eed<   eed<   eed<   dZe	e   ed	<   d
Z
eed<   y)GenerationResultsuccesstitleslugcontent_markdownmeta_description
word_countcost_usdNerrorFgrounding_used)__name__
__module____qualname__bool__annotations__strintfloatr   r   r        ;/var/www/html/content-pipeline/modules/article_generator.pyr   r   !   s?    MJ
IOOE8C= ND r!   r   returnc                  V    t               } t        j                  | j                        S )zInitialize Gemini client.)api_key)r	   r   Clientgemini_api_key)settingss    r"   get_gemini_clientr)   .   s    ~H<< 7 788r!   topicc                 \   t        d      j                  | j                  | j                  | j                  xs d| j
                  | j                  t        j                         j                        }t        | j
                        }|r+|dz   |z   }t        j                  d| j
                   d       	 t               }t        j                  t        j                                }d}d}t"        D ]w  }t%        t&              D ]^  }	 t        j                  d	| d
|dz    dt&         d       |j(                  j+                  ||t        j,                  |gd            } n t|w n |t/        d|       t;        |      }
|
s&t        j3                  d| j                  dd  d       t=        |j>                  |       }|
|_         tC        |      dz  }tC        |j>                        dz  }|dz  |dz  z   dz  |_"        t        jG                  d |j                  dd  d!|jH                   d"|
 d       |S # t.        $ r}	|	}dt1        |	      v sdt1        |	      v rX|t&        dz
  k  rLt        j3                  d| dt4         d|dz    dt&         d	       t7        j8                  t4               Y d}	~	t        j3                  d| d|	        Y d}	~	 vd}	~	ww xY w# t.        $ r>}	t        jK                  d#|	        tM        d$ddddd%d&t1        |	      '      cY d}	~	S d}	~	ww xY w)(z
    Generate a 2000-3000 word article using Gemini Flash with web search.

    CRITICAL: Verifies grounding_metadata.web_search_queries exists to ensure
    the model used actual web search for factual accuracy.
    zarticle_generation.txt )topic_titletarget_keywordssecondary_keywordscontent_clustersearch_intentcurrent_yearz

z'Parketry context appended for cluster '')google_searchNzTrying model z
 (attempt    /)gffffff?)toolstemperature)modelcontentsconfig429RESOURCE_EXHAUSTEDzRate limited on z
, waiting zs (attempt zModel z	 failed: zAll models failed. Last error: z&No web search grounding detected for '2   z...'   g333333?g333333?i@B zGenerated article: 'z...' (z words, grounding: zArticle generation failed: Fr           r   r   r   r   r   r   r   r   )'_load_promptformatr   r.   r/   r0   r1   r   nowyear_get_parketry_contextloggerdebugr)   r   ToolGoogleSearchTEXT_MODELSrangeMAX_RETRIESmodelsgenerate_contentGenerateContentConfig	Exceptionr   warningRETRY_DELAYtimesleep_check_grounding_parse_article_responsetextr   lenr   infor   r   r   )r*   promptparketry_contextclientgoogle_search_toolresponse
last_errorr:   attempter   resultinput_tokensoutput_tokenss                 r"   generate_articlerg   4   sU    23::KK-- 339r--))\\^(( ; F -U-B-BC&#33>u?T?T>UUVWXK
"$ #ZZe6H6H6JK 
  	E - LL=z'A+aP[}\]!^_%}}==#!'$::#5"6(+   >  H 0 #7	: =j\JKK *(3NN8Sb9I8J$O
 )> . 6{QHMM*Q.'%/-$2FF)S"6<<#4"5 6!!""5n5EQH	
 O ! 	!"JA*>#a&*H"[1_4"NN-=eWJ{m[fgnqrgrfsst  vA  uB  BC  ,D  E JJ{3$NNVE7)A3#?@	R  
21#67a&	
 		

s^   6AK$ AH="K$ )CK$ =	K!A.K4K$ :KK$ K!!K$ $	L+-3L& L+&L+>   kostenprobleme	vergleichu   Integriere die Parketry-Fakten als 1-2 Absätze im Artikel. Ordne Parketry als konkrete Marktalternative zu teuren klassischen Lösungen ein. Platziere dies im Kontext von Kosten, Vergleich oder Problemlösung — je nach Thema.)clustersinstructionbranchenhowtou   Erwähne in einem einzelnen Satz, dass es auch kostenlose Full-Service-Modelle wie Parketry gibt. Nicht mehr als ein Satz — der Fokus bleibt auf dem Kernthema.)stronglightr0   c                     t         j                         D ]+  }| |d   v st        d      }|j                  |d         c S  y)u   
    Return Parketry context prompt fragment based on content cluster tier.

    Strong (kosten, vergleich, probleme): 1-2 paragraphs as market alternative
    Light (branchen, howto): single sentence mention
    None (technik, recht, trends): no mention — stay editorially neutral
    rk   zparketry_context.txtrl   )integration_instructionN)_PARKETRY_INTEGRATIONvaluesrC   rD   )r0   tiertemplates      r"   rG   rG      sQ     &,,. Pd:..#$:;H??4;N?OOP r!   c                 ^   	 t        | d      rr| j                  rf| j                  d   }t        |d      rK|j                  }t        |d      r|j                  ryt        |d      r|j                  ryt        |d      ryy# t
        $ r"}t        j                  d	|        Y d
}~yd
}~ww xY w)z,Check if response used web search grounding.
candidatesr   grounding_metadataweb_search_queriesTgrounding_chunkssearch_entry_pointFzCould not check grounding: N)hasattrrx   ry   rz   r{   rR   rH   rI   )r`   	candidatemetadatarc   s       r"   rW   rW      s    8\*x/B/B ++A.Iy"67$778%9:x?Z?Z8%78X=V=V8%9: 21#67s$   AB B 3B 	B,
B''B,filenamec                     t        t              j                  j                  dz  | z  }|j                         st	        d|       |j                         S )z,Load prompt template from prompts directory.promptszPrompt file not found: )r   __file__parentexistsFileNotFoundError	read_text)r   prompt_paths     r"   rC   rC      sO    x.''..:XEK"9+ GHH  ""r!   rY   c                 .   | j                         } t        j                  dd|       } t        j                  dd|       } t        j                  dd|       } t        j                  dd|       } t        j                  d	d
|       } | j                  d
      } | S )z"Convert text to URL-friendly slug.u   [äÄ]aeu   [öÖ]oeu   [üÜ]ueu   [ß]ssz
[^a-z0-9]+-)lowerresubstrip)rY   s    r"   _slugifyr      sy    ::<D66)T4(D66)T4(D66)T4(D66'4&D66-d+D::c?DKr!   c                     t        j                  dd|       } t        j                  dd|       } t        j                  dd|       } | j                         }t        |      S )z@Count words in markdown text, excluding code blocks and headers.z```[\s\S]*?```r,   z`[^`]+`z[#*_\[\]()] )r   r   splitrZ   )rY   wordss     r"   _count_wordsr      sP     66#R.D66*b$'D66.#t,DJJLEu:r!   response_textc                    	 | j                         }d}d|v r5|j                  d      d   j                  d      d   j                         }n|}d|v rQ	 |j                  d      d   j                  d      d   }t        j                  |      }|j	                  dd      d	d
 }d|v rt        j                  dd|      }|j                  d      }|j                  }d}	t        |      D ]-  \  }
}|j                  d      s|dd	 j                         }|
}	 n |s||	dz   d	 D ]  }|j                         }|s|j                  d      r(|j                  d      r:|j                  d      rLt        j                  dd|      }t        j                  dd|      }|d	d
 } n t        |      }t        d|t        |      |||d      S # t        j
                  t        f$ r Y Cw xY w# t        $ rJ}t         j#                  d|        t        ddd| dt        |       ddt%        |             cY d	}~S d	}~ww xY w)z,Parse Gemini response into GenerationResult.r,   z```markdownr5   z```r   z```jsonr   N   z```[a-z]*\n[\s\S]*?```
z#    #r   *z\*\*([^*]+)\*\*z\1z\[([^\]]+)\]\([^)]+\)TrA   )r   r   r   r   r   r   r   z"Failed to parse article response: FzParse error: rB   )r   r   jsonloadsgetJSONDecodeError
IndexErrorr   r   r   	enumerate
startswithr   r   r   rR   rH   r   r   )r   r*   rY   r   markdown_content	json_textdatalinesr   content_startiliner   rc   s                 r"   rX   rX      sm   F
""$ D #zz-8;AA%HKQQS  $  JJy1"5;;EB1E	zz),#'88,>#CDS#I 
 $$!vv&?EUV !&&t, ' 	GAtt$QR( !		  ma/01 zz| 4T__S=QZ^ZiZijmZn')vv.@%'N$')vv.FO_'`$'7'=$ ""23
%--!
 	
A ((*5 T  
9!=>*#M2!#a&*	
 		

sc   AG+ AG $AG+ 7G+ 9G+ G+ G+ /AG+ G($G+ 'G((G+ +	H>4?H93H>9H>)(__doc__r   loggingr   rU   dataclassesr   r   pathlibr   typingr   googler   google.genair   config.settingsr	   database.modelsr
   	getLoggerr   rH   rL   rN   rT   r   r&   r)   rg   rs   r   rG   r   rW   rC   r   r   r   rX   r    r!   r"   <module>r      s>     	  !      ( !			8	$  	! 	! 	!95<< 9a
E a
&6 a
L 8e  )a &3 8C= $ (#3 #3 #	3 	3 	
s 
s 
H
3 H
u H
AQ H
r!   