
    B2i<              	          U d Z ddlZddlZddlZddlmZ ddlmZmZ ddlm	Z	m
Z
mZ ddlmZ ddlmZmZ ddlZddlmZ dd	lmZmZmZmZ dd
lmZmZmZ ddlmZ ddlm Z   e	ddd      Z!e!jE                  eg dddgdg        e ejF                  dd      dd      Z$i Z%e&e'ef   e(d<    G d de      Z) G d de      Z* G d d e      Z+ G d! d"e      Z, G d# d$e      Z- G d% d&e      Z. G d' d(e      Z/e!ja                  d)      d*        Z1e!je                  d+e*,      d-e)d.efd/       Z3e!je                  d0e/,      d1e'd.efd2       Z4e!ja                  d3e+,      d1e'fd4       Z5e!je                  d5e.,      	 dMd6e'd-e,d1ee'   fd7       Z6e!je                  d8e.,      	 dMd6e'd-e-d1ee'   fd9       Z7e!ja                  d:      	 	 dNd;ee'   d<ee'   fd=       Z8e!ja                  d>      d?        Z9e!ju                  d@      dA        Z;e!ju                  dB      dC        Z<e=dDk(  rW e> ejF                  dEdF            Z? ejF                  dGdH      Z@ ej                  dIe@e? ejF                  dJdK      dKk(  L       yy)OzW
EventheOdds LangGraph SEO Service
FastAPI server for LangGraph workflow orchestration
    N)datetime)OptionalList)FastAPIHTTPExceptionBackgroundTasks)CORSMiddleware)	BaseModelField)RedisCheckpointer)SEOCampaignStatecreate_initial_stateCampaignPhaseLinkOpportunityStatus)create_seo_campaign_graphrun_campaignresume_campaign)create_zero_click_graph)create_link_approval_graphz!EventheOdds LangGraph SEO ServicezKLangGraph-based SEO campaign orchestration with human-in-the-loop approvals1.0.0)titledescriptionversion)zhttp://localhost:3000zhttp://127.0.0.1:3000zhttps://eventheodds.aiT*)allow_originsallow_credentialsallow_methodsallow_headers	REDIS_URLzredis://localhost:6379/0zlanggraph:seo:i ' )	redis_urlprefixttl_secondscampaign_cachec                   n    e Zd ZU  edd      Zeed<    edd      Zeed<    ed	d
      Ze	e
   ed<   y	)StartCampaignRequest.zSite ID to run campaign forr   site_idzero_click_authorityzECampaign template: zero_click_authority, link_building, full_campaigndefaultr   templateNzOptional campaign configurationconfig)__name__
__module____qualname__r   r'   str__annotations__r+   r,   r   dict     &/var/www/html/langgraph-service/app.pyr%   r%   A   sC    *GHGSH&[Hc  #4=^_FHTN_r4   r%   c                   @    e Zd ZU eed<   eed<   eed<   eed<   eed<   y)StartCampaignResponsecampaign_idr'   statuscurrent_phasemessageNr-   r.   r/   r0   r1   r3   r4   r5   r7   r7   J   s    LKLr4   r7   c                   x    e Zd ZU eed<   eed<   eed<   eed<   eed<   ee   ed<   eed<   eed<   eed	<   eed
<   y)CampaignStateResponser8   r'   campaign_typer:   r9   pending_approvalsrequires_human_reviewmetrics
started_at
updated_atN)r-   r.   r/   r0   r1   r   r2   boolr3   r4   r5   r>   r>   R   s=    LKDz!MOOr4   r>   c                   2    e Zd ZU  edd      Zee   ed<   y)ApprovalRequestNzOptional edited pitch textr)   edited_pitch)r-   r.   r/   r   rH   r   r0   r1   r3   r4   r5   rG   rG   _   s    "'B^"_L(3-_r4   rG   c                   ,    e Zd ZU  edd      Zeed<   y)RejectRequest.zReason for rejectionr&   reasonN)r-   r.   r/   r   rK   r0   r1   r3   r4   r5   rJ   rJ   c   s    )?@FC@r4   rJ   c                   ,    e Zd ZU eed<   eed<   eed<   y)ApprovalResponseopportunity_idr9   r;   Nr<   r3   r4   r5   rM   rM   g   s    KLr4   rM   c                   6    e Zd ZU eed<   eed<   eed<   eed<   y)ResumeCampaignResponser8   r9   r:   r;   Nr<   r3   r4   r5   rP   rP   m   s    KLr4   rP   z/healthc                  X   K   dddt        j                         j                         dS w)Nhealthyzlanggraph-seor   )r9   servicer   	timestamp)r   utcnow	isoformatr3   r4   r5   health_checkrW   u   s.      "__&002	 s   (*z/api/campaigns/start)response_modelrequestbackground_tasksc           	      n   K   t        t        j                               t         j                   j
                   j                  xs i       t        <    fd}|j                  |       t         j                  dt        j                  j                  d j
                   d      S w)z
    Start a new SEO campaign

    Templates:
    - zero_click_authority: Focus on SERP features and AI Overviews
    - link_building: Focus on safe link acquisition with human approval
    - full_campaign: Complete SEO workflow with all phases
    )r'   r8   r?   r,   c                    K   	 j                   dk(  rt        t              } n.j                   dk(  rt        t              } nt	        t              } ddii}t        d dj                    d       | j                  |       d {   }|t        <   t        d d|j                  d	              y 7 .# t        $ r}t        |      rt        |      n
t        |      }t        d
 d|        t        d
 dt        j                                 t        j                  j                   d	<   j                  dg       d|t#        j$                         j'                         dgz   d<   t        <   Y d }~y d }~ww xY ww)Nr(   link_buildingconfigurable	thread_idz[Campaign] z): Starting graph execution for template ''z: Completed - phase: r:   z[CampaignError] : z: Traceback:
errors	execution)phaser;   rT   )r+   r   checkpointerr   r   printainvoker#   get	Exceptionr0   repr	traceback
format_excr   FAILEDvaluer   rU   rV   )graphthread_configfinal_statee	error_msgr8   initial_staterY   s        r5   run_campaign_taskz)start_campaign.<locals>.run_campaign_task   sz    	8#99/=!!_42<@1,?+k;-GHMK},UV]VfVfUgghij %m] KKK*5N;'K},A+//RaBbAcde L  
	8"%a&Ad1gI$[MI;?@$[M	@T@T@V?WXY-:-A-A-G-GM/*&3&7&7"&E$$%__.88:J I 'M(#
 +8N;'
	8sB   FBB: 	B8
-B: 7F8B: :	FB8F ;F FFstartedz Campaign started with template 'r`   )r8   r'   r9   r:   r;   )r0   uuiduuid4r   r'   r+   r,   r#   add_taskr7   r   RESEARCHrn   )rY   rZ   ru   r8   rt   s   `  @@r5   start_campaignr{      s      djjl#K )&&~~#	M #0N;86 /0 #,,22273C3C2DAF s   B0B5z#/api/campaigns/{campaign_id}/resumer8   c           	      6   K   t         j                         st        dd      j                  d      t        j                  j
                  k(  rt        dd      j                  dg       }|D cg c]1  }|j                  d      t        j                  j
                  k(  s0|3 }}|r,t         d	j                  dd
      dt        |       d      S  fd}|j                  |       t         dj                  dd
      d      S c c}w w)z
    Resume a paused campaign after human approval

    Call this after approving/rejecting link opportunities to continue the workflow.
      Campaign not foundstatus_codedetailr:   i  zCampaign already completedr@   r9   pendingunknownzStill waiting for z
 approvals)r8   r9   r:   r;   c                     K   	 t        t              } ddii}| j                  |       d {   }|t        <   y 7 # t        $ r}t        d d|        Y d }~y d }~ww xY ww)Nr^   r_   z[ResumeError] ra   )r   re   rg   r#   ri   rf   )ro   rp   rq   rr   r8   states       r5   resume_taskz-resume_campaign_endpoint.<locals>.resume_task   so     	7-l;E+k;-GHM %e] CCK*5N;' D 	7N;-r!566	7s;   A'*A  >A  A'A   	A$	AA'A$$A'resumingz Campaign resuming after approval)r#   rh   r   r   	COMPLETEDrn   r   PENDINGrP   lenry   )r8   rZ   r   ostill_pendingr   r   s   `     @r5   resume_campaign_endpointr      s     {+E4HIIyy!]%<%<%B%BB4PQQ ii+R0G 'b1155?>S>[>[>a>a+aQbMb%#))OY?(]);(<JG	
 	
7 k*!ii;2	 - cs   A4D81D*D.A+Dz"/api/campaigns/{campaign_id}/statec                 $  K   t         j                  |       }|st        dd      |j                  dd      |j                  dd      |j                  dd      |j                  dd      |j                  d	d      t        |j                  d
g             t        |j                  dg             d}|j                  dd      }|t        j
                  j                  k(  rd}n6|t        j                  j                  k(  rd}n|j                  d      rd}nd}t        | |j                  dd      |j                  dd      |||j                  dg       |j                  dd      ||j                  dd      |j                  dd      
      S w)z:
    Get current campaign state and pending approvals
    r}   r~   r   pages_generatedr   pages_optimizedsnippets_capturedlinks_approvedlinks_acquiredrb   warnings)r   r   r   r   r   rb   r   r:   r   	completedfailedrA   awaiting_approvalrunningr'    r?   r@   FrC   rD   )
r8   r'   r?   r:   r9   r@   rA   rB   rC   rD   )	r#   rh   r   r   r   r   rn   rm   r>   )r8   r   rB   r:   r9   s        r5   get_campaign_stater      sm    
 {+E4HII !99%6: 99%6:"YY':A>))$4a8))$4a8eii"-.		*b12G IIoy9M//555	-..44	4	*	+$ 		)R(ii4#))$7<#ii(?G99\2.99\2. s   FFz'/api/approvals/{opportunity_id}/approverN   c                   K   |}d}|rGt         j                  |      }|r|j                  dg       D ]  }|j                  d      | k(  s|} nS nQt         j                         D ]:  \  }}|j                  dg       D ]  }|j                  d      | k(  s|}|} n |s: n |st        dd      t        j
                  j                  |d<   t        j                         j                         |d<   |j                  r|j                  |d	<   |rbt         |   }|j                  d
g       D cg c]  }|j                  d      | k7  r| c}|d
<   |j                  dd      dz   |d<   |d
   sd|d<   t        | dd      S c c}w w)zi
    Approve a link opportunity

    Optionally provide an edited pitch to use instead of the draft.
    Nlink_opportunitiesidr}   Opportunity not foundr   r9   approved_atpitch_approvedr@   r   r      FrA   approvedzLink opportunity approvedrN   r9   r;   )r#   rh   itemsr   r   APPROVEDrn   r   rU   rV   rH   rM   	rN   rY   r8   found_campaign_id	found_oppr   oppcidr   s	            r5   approve_linkr   !  s     $I"";/yy!5r: 774=N2 #I )..0 	JCyy!5r: 774=N2 #I(+%	
 	 4KLL 088>>Ih'0::<Im&-&:&:	"# 01yy!4b9&
uuT{n, &
!" #()),<a"@1"D ()-2E)*%+ &
s&   AF
A	F	FBF,E?	;Fz&/api/approvals/{opportunity_id}/rejectc                   K   |}d}|rGt         j                  |      }|r|j                  dg       D ]  }|j                  d      | k(  s|} nS nQt         j                         D ]:  \  }}|j                  dg       D ]  }|j                  d      | k(  s|}|} n |s: n |st        dd      t        j
                  j                  |d<   |j                  |d<   |rJt         |   }|j                  d	g       D cg c]  }|j                  d      | k7  r| c}|d	<   |d	   sd
|d<   t        | dd|j                         S c c}w w)z#
    Reject a link opportunity
    Nr   r   r}   r   r   r9   rejection_reasonr@   FrA   rejectedzLink opportunity rejected: r   )	r#   rh   r   r   r   REJECTEDrn   rK   rM   r   s	            r5   reject_linkr   `  s|     $I"";/yy!5r: 774=N2 #I
 )..0 	JCyy!5r: 774=N2 #I(+%	
 	 4KLL 088>>Ih$+NNI ! 01yy!4b9&
uuT{n, &
!" ()-2E)*%-gnn-=> &
s&   AE
A	E	EAE;E0Ez/api/campaignsr9   r'   c                 :  K   g }t         j                         D ]  \  }}|r|j                  d      |k7  r|j                  dd      }|t        j                  j
                  k(  rd}n6|t        j                  j
                  k(  rd}n|j                  d      rd}nd}| r|| k7  r|j                  ||j                  d      |j                  d	      ||t        |j                  d
g             |j                  d      |j                  d      d        d|iS w)zC
    List all campaigns, optionally filtered by status or site
    r'   r:   r   r   r   rA   r   r   r?   r@   rC   rD   )r8   r'   r?   r:   r9   r@   rC   rD   	campaigns)	r#   r   rh   r   r   rn   rm   appendr   )r9   r'   r   r   r   r:   camp_statuss          r5   list_campaignsr     s     I$**, 
Uuyy+w6 		/9=M33999%Km22888"KYY./-K#K kV+yy+"YY7*!!$UYY/BB%G!H))L1))L1	
 		)> ##s   DDz/api/approvals/pendingc                  4  K   g } t         j                         D ]n  \  }}|j                  dg       D ]T  }|j                  d      t        j                  j
                  k(  s0| j                  ||j                  d      d|       V p | t        |       dS w)z=
    Get all pending link approvals across all campaigns
    r@   r9   r'   )r8   r'   )r@   total)r#   r   rh   r   r   rn   r   r   )r   r   r   r   s       r5   get_pending_approvalsr     s     
 G$**, 
U990"5 	Cwwx $9$A$A$G$GG#&$yy3    	 ")3w<@@s   AB!7Bstartupc                     K   t        d       	 t        j                          d {    t        d       y 7 # t        $ r} t        d|         Y d } ~ y d } ~ ww xY ww)Nz&[LangGraph SEO Service] Starting up...z4[LangGraph SEO Service] Redis connection establishedz1[LangGraph SEO Service] Redis connection failed: )rf   re   _get_clientri   )rr   s    r5   r   r     sY     	
23G&&(((DE 	) GA!EFFGs7   A9 79 A9 	AAAAAshutdownc                  ^   K   t        d       t        j                          d {    y 7 w)Nz([LangGraph SEO Service] Shutting down...)rf   re   closer3   r4   r5   r   r     s!     	
45



s   #-+-__main__LANGGRAPH_PORT8001LANGGRAPH_HOSTz	127.0.0.1zapp:appENVdevelopment)hostportreload)N)NN)B__doc__osrw   rk   r   typingr   r   fastapir   r   r   fastapi.middleware.corsr	   pydanticr
   r   uvicorncheckpointer.redis_checkpointerr   state.campaign_stater   r   r   r   graphs.seo_campaign_graphr   r   r   graphs.zero_click_graphr   graphs.link_approval_graphr   appadd_middlewaregetenvre   r#   r2   r0   r1   r%   r7   r>   rG   rJ   rM   rP   rh   rW   postr{   r   r   r   r   r   r   on_eventr   r   r-   intr   r   runr3   r4   r5   <module>r      s`  
 
    ! ; ; 2 %  =  
 < A 
-]   
 %%  
 !bii%?@ /1S**+ 0`9 `I 
I 
`i `AI Ay Y    
 1FG=!=%= H=@ 
/@VW..%. X.b 	->ST)# ) U)Z 
3DTU "&;;; #; V;| 
2CST "&444 #4 U4p 	 !($SM($c]($ ($X 	!"A #A& iG G j  zyryy)623D299%{3DGKKryy.-?		 r4   