
    2i                    (   d dl Z d dlZd dlmZmZmZmZ d dlZd dlm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 ddlmZ dd	lmZ dd
lmZ ddlmZ  e         ej2                  ej4                          ej6                  e      Z G d d      Zy)    N)DictAnyListOptional)datetime)load_dotenv)CodecOptionsDatetimeConversion   )JobAPIService)OpenSearchService)Embedder)mongo_service)	AIService)levelc                   h   e Zd ZdZd Zdedeeef   fdZdede	e
   deeef   fdZdefdZdede	e
   fd	Zdedeeeef      fd
Zd*deeef   de	e   de	eeef      fdZd*deeef   de	e   de	eeef      fdZdeeef   de	eeef      fdZdeeef   defdZde	e   dede	eeef      fdZdeeef   deeef   deeeef      fdZdedeeeef      fdZdeeef   deeef   deeef   defdZdededeeef   fdZdedeeef   deeef   fdZd edefd!Zdedeeef   deeef   fd"Zd+ded e
d#eeef   d$e
deeef   f
d%Zd*ded e
deeef   d$e
deeef   f
d&Zdeeef   deeef   deeef   deeeef      fd'Z d e
deeef   fd(Z!d#eeef   d e
deeef   fd)Z"y),JobMatchingServicezKService for job matching with candidate screening and background processingc                 |    t               | _        t               | _        t	               | _        t               | _        y N)r   job_api_servicer   opensearch_servicer   embedderr   
ai_service)selfs    A/var/www/html/drjob-dev/drjob-ai/services/job_matching_service.py__init__zJobMatchingService.__init__   s)    ,"3"5 
#+    
job_emp_idreturnc                 
   	 t        j                  | j                  |fd      }|j                          dd| |ddS # t        $ r:}t
        j                  dt        |              dt        |      |dcY d	}~S d	}~ww xY w)
a	  
        Start background job matching process and return immediately
        
        Args:
            job_emp_id: The employer job ID to process
            
        Returns:
            Dict with success status - actual processing happens in background
        Ttargetargsdaemonz)Job matching process started for job ID: processing_started)successmessagejob_idstatusz%Error starting job matching process: Fr&   errorr(   N)	threadingThread_background_job_matchingstart	Exceptionloggerr+   str)r   r   threades       r   process_job_matching_asyncz-JobMatchingService.process_job_matching_async!   s    	%%44 ]F
 LLN  FzlS$.	   	LL@QIJ Q$ 	s   <? 	B/A=7B=Bjobseeker_idsc                     	 t        j                  | j                  ||fd      }|j                          dd| |t	        |      ddS # t
        $ r:}t        j                  dt        |              dt        |      |dcY d	}~S d	}~ww xY w)
aM  
        Start background processing for specific candidate analysis
        
        Args:
            job_emp_id: The employer job ID to process
            jobseeker_ids: List of specific jobseeker IDs to analyze
            
        Returns:
            Dict with success status - actual processing happens in background
        Tr!   z0Specific candidate analysis started for job ID: r%   )r&   r'   r(   jobseeker_countr)   z,Error starting specific candidate analysis: Fr*   N)	r,   r-   (_background_specific_candidates_analysisr/   lenr0   r1   r+   r2   )r   r   r6   r3   r4   s        r   !process_specific_candidates_asyncz4JobMatchingService.process_specific_candidates_asyncC   s    	%%DD -0F
 LLN  Mj\Z$#&}#5.   	LLGAxPQ Q$ 	s   AA
 
	B/BBBc           
      0   	 t         j                  d|        | j                  |      }|st         j                  d|        y| j	                  |      }|st         j                  d|        yd}|D ]  }	 | j                  ||      }|rh|j                  dd      dk\  r| j                  |||       |dz  }n:t         j                  d	|j                  d
       d|j                  d       d        t         j                  d| d| d       y# t        $ r=}t         j                  d|j                  d
       dt        |              Y d}~d}~ww xY w# t        $ r.}t         j                  d| dt        |              Y d}~yd}~ww xY w)z
        Background process for job matching - runs in separate thread
        
        Args:
            job_emp_id: The employer job ID to process
        z-Starting background job matching for job ID: +Could not retrieve job details for job ID: Nz)No matching candidates found for job ID: r   matching_percentage      I@r   z
Candidate 	resume_idz below 50% threshold: %zError processing candidate : z#Job matching completed for job ID: z. Processed z candidates.z,Error in background job matching for job ID )
r1   info_get_job_detailsr+   _search_candidates_generate_candidate_analysisget_store_screening_resultr0   r2   )r   r   job_details
candidatesprocessed_count	candidateanalysisr4   s           r   r.   z+JobMatchingService._background_job_matchingg   s   &	`KKG
|TU //
;KJ:,WX 00=JG
|TU  O' 	#@@iXH#<<(=qATI 88YPXY+q0O"KK*Y]];5O4PPfgogsgs  uJ  hK  gL  LM  )N  O$ KK=j\VeUffrst	 ! LL#>y}}[?Y>ZZ\]`ab]c\d!ef  	`LLG
|SUVYZ[V\U]^__	`sO   AE +E 2E :A<D6E 	E3EE EE 	F'$FFc           
         	 t         j                  d| dt        |              | j                  |      }|st         j	                  d|        yt         j                  d|        |j                  d      r!t         j                  d|d    d       d|d<   d	}d	}|D ]  }	 t        |      }| j                  |      st         j                  d
|        9|dz  }| j                  |      }|st         j                  d|        j|d	d	d	d	d	d}	| j                  ||	      }
|
r| j                  ||	|
       |dz  } t         j                  d| d| d|        y# t        $ r.}t         j	                  d| dt        |              Y d}~d}~ww xY w# t        $ r.}t         j	                  d| dt        |              Y d}~yd}~ww xY w)z
        Background process for specific candidate analysis - runs in separate thread
        
        Args:
            job_emp_id: The employer job ID to process
            jobseeker_ids: List of specific jobseeker IDs to analyze
        z1Starting specific candidate analysis for job ID: z, candidates: r=   Nz>Type 1 specific analysis - country filtering disabled for job country_header_codezOriginal job country: z - will be ignored for type 1r   z)Could not ensure indexing for jobseeker: r   z/Could not get candidate details for jobseeker: r@   keyword_scorekeyword_percentagevector_scorevector_percentagecombined_scorez$Error processing specific candidate rB   z2Specific candidate analysis completed for job ID: z. Indexed: z, Processed: z;Error in background specific candidate analysis for job ID )r1   rC   r:   rD   r+   rG   r2   _ensure_jobseeker_indexedwarning_get_candidate_detailsrF    _store_specific_screening_resultr0   )r   r   r6   rI   rK   indexed_countjobseeker_idjobseeker_id_strcandidate_datarL   rM   r4   s               r   r9   z;JobMatchingService._background_specific_candidates_analysis   s.   >	oKKKJ<Wefijwfxeyz{ //
;KJ:,WX KKXYcXdef454[AV5W4XXuvw5912  OM - &%'*<'8$  99:JK)RScRd'ef !Q&M &*%@%@AQ%RN))XYiXj'kl  &6)*./()-.*+!I  $@@iXH==j)U]^'1,E&P KKLZLXcdqcrr  AP  @Q  R  S	 ! LL#G~UWX[\]X^W_!`a  	oLLVWaVbbdehijekdlmnn	osa   AF AF &4E"F 0E"F 5E" F "	F+$FF FF 	G%$GGc                 	   	 | j                   j                  |      }|j                  d      s(t        j	                  d|j                  d              y|j                  di       }t        |t              rt        j	                  d| d       yt        |t              s&t        j	                  d| dt        |       d	       yd
|v r|d
   }||j                  dd      xs: |j                  dd      xs& |j                  dd      xs |j                  dd      |j                  dd      xs: |j                  dd      xs& |j                  dd      xs |j                  dd      |j                  dd      xs& |j                  dd      xs |j                  dd      |j                  dd      |j                  dd      xs& |j                  dd      xs |j                  dd      |j                  dd      xs& |j                  dd      xs |j                  dd      |j                  dd      xs& |j                  dd      xs |j                  d d      |j                  d!      dd"
}|j                  d!      r	 t        t        j                  #      }t        j                  }|t        j                  j                     j!                  |$      }|d%   }|j#                  d&|d!   id'd'd'd(      }	|	rI|	j                  d)      r8|	d)   j%                         |d*<   t        j'                  d+|d*    d,|d!           nt        j)                  d-|d!           |j                  d*      rt        j'                  d0| d1|d*           nt        j)                  d2| d3       |j                  d      st        j	                  d| d4       yt        j'                  d5|        |S # t*        $ rW}
t        j	                  d.t-        |
              t        j)                  d/|j                  d!              d|d*<   Y d}
~
d}
~
ww xY w# t*        $ r.}
t        j	                  d6| d1t-        |
              Y d}
~
yd}
~
ww xY w)7z
        Get job details from job API and extract required fields
        
        Args:
            job_emp_id: The employer job ID
            
        Returns:
            Dictionary with job details or None if failed
        r&   zFailed to get job details: r+   NdatazInvalid job ID z-: API returned empty list instead of job dataz: API returned z instead of dictionaryjob	job_title titlepositionjob_positionjob_descriptionjob_descdescriptionrI   
skill_listskillsrequired_skillsskill_list_arworkplace_type	work_typeremote_typejob_typeemployment_typetypecountry_namecountrylocation
country_id)
r   ra   rg   ri   rl   rm   rp   rs   rv   rO   )datetime_conversion)codec_optionscountry_updateidr   )rz   nameheader_coder|   rO   zFound country header code: z for country_id: z%No header_code found for country_id: z#Error getting country header code: z8Could not determine country_header_code for country_id: z"Country filtering enabled for job rB   zNo country filtering for job z - will search all countriesz$: No job title found in API responsez/Successfully retrieved job details for job ID: z%Error getting job details for job ID )r   get_employer_jobrG   r1   r+   
isinstancelistdictrr   r	   r
   DATETIME_AUTOr   clientdbr{   with_optionsfind_onelowerrC   rW   r0   r2   )r   r   resultjob_datarI   rx   r   r   country_collectioncountry_docr4   s              r   rD   z#JobMatchingService._get_job_details   sw   ^	)):::FF::i(:6::g;N:OPQzz&"-H (D)zl:ghi h-zl/$x.IYYopq  #E? )&ll;; =$LL"5=$LLR8= %LL<%\\*;R@ ;#<<
B7;#<<r:; $<<r:'||L"=  A%\\(B7 A%\\*;R@!)or!B#+<<0@"#E $A!)k2!>$A!)mR!@%\\*b9 4#<<(92>4#<<3!)nb!A "<'||Ir:"<'||J;&ll<8'+1K8 |,>$0EWEeEe$fM +11F 0 0 5 56CCR_C`B)+,<)=& #5"="={<89 !A>#K #{}'E=H=W=]=]=_$9:&A+NcBdAeev  xC  DP  xQ  wR  %S  T)N{[gOhNi'jk 45@B{[pOqNrst!>zlJfgh ??;/zl:^_`KKI*VW% ! >LL#Fs1vh!OPNN%]^i^m^mnz^{]|#}~9=K 56	>(  	LL@BsSTvhWX	s^   AQ ;Q 5Q F>Q CO2 $A3Q Q 2	Q;AQQ QQ 	R$RRNrI   c           	          	 | j                  |      }t        j                  d| d       t        j                  d       |rft        j                  dt        |       d|        | j                  j                  |      }t        j                  d|rt        |      nd d       nC| j                  j                  |d	
      }t        j                  d|rt        |      nd d       t        t        j                  dd            }|r&|D cg c]  }|j                  dd      |k\  s| c}ng }t        j                  d| dt        |              |r|D cg c]%  }|j                  d      s|j                  d      ' }}t        j                  dt        |       d       | j                  j                  ||d	
      }	t        j                  d|	rt        |	j                  dg             nd d       | j                  ||	j                  dg       |      }
|
S t        j                  d       g S c c}w c c}w # t        $ r-}t        j                  dt!        |              g cY d}~S d}~ww xY w)aQ  
        Search for candidates without country filtering (for type 1 and type 3)
        
        Args:
            job_details: Job details dictionary
            jobseeker_ids: Optional list of specific jobseeker IDs to analyze
            
        Returns:
            List of matching candidates without country restrictions
        z/Search query built without country filtering: ''z0Country filtering disabled for type 1 and type 3Searching specific  candidates: OpenSearch returned r    specific candidatesd   top_k resultsJOB_MATCHING_ABSOLUTE_MIN_SCORE333333?rQ   OpenSearch results above  threshold: r@   Performing vector search on 4 pre-filtered candidates without country restrictionVector search returned r   ! results from filtered candidatesz+No candidates met minimum quality thresholdz3Error searching candidates without country filter: N_build_search_queryr1   rC   r:   r   search_by_idssearchfloatosgetenvrG   r   search_filtered_candidates_merge_and_rank_resultsr0   r+   r2   r   rI   r6   search_querykeyword_resultsabsolute_min_scorerfiltered_keyword_resultsfiltered_resume_idsvector_resultsfinal_candidatesr4   s               r   )_search_candidates_without_country_filterz<JobMatchingService._search_candidates_without_country_filterH  s   +	33K@LKKI,WXYZ KKJK 1#m2D1E]S`Rabc"&"9"9"G"G"V2?33G`a2bbvwx #'"9"9"@"@UX"@"Y2?33G`a2bbjkl "'ryy1RTW'X!Y {J?'vaaeeO]^F_cuFu'v  PR$KK34F3G|TWXpTqSrst (C[&ra_`_d_dep_qquu['9&r#&r:3?R;S:T  UI  J  K "&!I!I,Xksv!I!w5_mc.:L:LXWY:Z6[st5u  vW  X  Y $(#?#?@XZhZlZlmuwyZz  }H  $I ''IJ	% (w 's  	LLNsSTvhWXI	O   DI H=,H=0/I I6I	BI &I =
I 	I="I82I=8I=c           	          	 | j                  |      }t        j                  d| d       t        j                  d       |rft        j                  dt        |       d|        | j                  j                  |      }t        j                  d|rt        |      nd d       nC| j                  j                  |d	
      }t        j                  d|rt        |      nd d       t        t        j                  dd            }|r&|D cg c]  }|j                  dd      |k\  s| c}ng }t        j                  d| dt        |              |r|D cg c]%  }|j                  d      s|j                  d      ' }}t        j                  dt        |       d       | j                  j                  ||d	
      }	t        j                  d|	rt        |	j                  dg             nd d       | j                  ||	j                  dg       |      }
|
S t        j                  d       g S c c}w c c}w # t        $ r-}t        j                  dt!        |              g cY d}~S d}~ww xY w)aX  
        Search for candidates for specific analysis without country filtering (type 1)
        
        Args:
            job_details: Job details dictionary
            jobseeker_ids: Optional list of specific jobseeker IDs to analyze
            
        Returns:
            List of matching candidates without country restrictions
        z+Search query built for specific analysis: 'r   z9Country filtering disabled for specific analysis (type 1)r   r   r   r   r   r   r   r   r   r   rQ   r   r   r@   r   r   r   r   r   zANo candidates met minimum quality threshold for specific analysisz2Error searching candidates for specific analysis: Nr   r   s               r   (_search_candidates_for_specific_analysisz;JobMatchingService._search_candidates_for_specific_analysis  s   +	33K@LKKEl^STUV KKST 1#m2D1E]S`Rabc"&"9"9"G"G"V2?33G`a2bbvwx #'"9"9"@"@UX"@"Y2?33G`a2bbjkl "'ryy1RTW'X!Y {J?'vaaeeO]^F_cuFu'v  PR$KK34F3G|TWXpTqSrst (C[&ra_`_d_dep_qquu['9&r#&r:3?R;S:T  UI  J  K "&!I!I,Xksv!I!w5_mc.:L:LXWY:Z6[st5u  vW  X  Y $(#?#?@XZhZlZlmuwyZz  }H  $I ''_`	% (w 's  	LLMcRSfXVWI	r   c                    	 | j                  |      }t        j                  d| d       |j                  d      }|rt        j                  d|        nt        j	                  d       |rH| j
                  j                  ||d      }t        j                  d| d	|rt        |      nd
 d       nC| j
                  j                  |d      }t        j                  d|rt        |      nd
 d       t        t        j                  dd            }|r&|D cg c]  }|j                  dd
      |k\  s| c}ng }t        j                  d| dt        |              |r|D cg c]%  }|j                  d      s|j                  d      ' }}t        j                  dt        |       d       |r\d|i}	| j                  j                  ||	d      }
t        j                  d| d	|
rt        |
j                  dg             nd
 d       nn| j                  j                  ||d      }
t        j                  d|
rt        |
j                  dg             nd
 d       nt        j                  d       dg i}
| j                  ||
      }t        j                  dt        |       d|d           |S c c}w c c}w # t         $ r-}t        j#                  dt%        |              g cY d}~S d}~ww xY w)a  
        Search for candidates using both OpenSearch and Vector search with country filtering
        
        Args:
            job_details: Job details dictionary
            
        Returns:
            List of matching candidates with combined scores
        zSearch query built: 'r   rO   z0Country filtering enabled for screening type 2: z@No country_header_code found - search will include all countriesr   r   z OpenSearch with country filter (z) returned r   r   r   r   r   rQ   r   r   r@   r   z pre-filtered candidatesz#Vector search with country filter (r   r   r   zANo candidates passed OpenSearch filtering, skipping vector searchzFound z matching candidates for job: ra   zError searching candidates: N)r   r1   rC   rG   rW   r   search_with_country_filterr:   r   r   r   r   r   search_with_filtersr   _combine_and_filter_resultsr0   r+   r2   )r   rI   r   rO   r   r   r   r   r   vector_filtersr   rJ   r4   s                r   rE   z%JobMatchingService._search_candidates  sQ   5	33K@LKK/~Q?@ #.//2G"H"NObNcdeab #"&"9"9"T"TUacv  B"T  #C>?R>SS^  xG_bcr_s  MN  _O  OW  X  Y"&"9"9"@"@UX"@"Y2?33G`a2bbjkl "'ryy1RTW'X!Y {J?'vaaeeO]^F_cuFu'v  PR$KK34F3G|TWXpTqSrst (C[&ra_`_d_dep_qquu['9&r#&r:3?R;S:TTlmn '&;=P%QN%)]]%F%F|Uckn%F%oNKK"EFYEZZe  P^fijxj|j|  ~F  HJ  kK  gL  de  ff  fn  !o  p &*]]%M%Ml\owz%M%{NKK"9cq#n>P>PQY[]>^:_wx9y  z[  !\  ]_`"*B 99:RTbcJKK&Z 11OP[\gPhOijk7 (w 's.  	LL7Ax@AI	sI   DK K:K>0K .KKD*K 
K 	L"K>8L>Lc                    g }|j                  d      r|j                  |d          |j                  d      r|j                  |d          |j                  d      r|d   dd }|j                  |       dj                  |      S )z
        Build search query from job details
        
        Args:
            job_details: Job details dictionary
            
        Returns:
            Combined search query string
        ra   ri   rg   N    )rG   appendjoin)r   rI   query_partsrg   s       r   r   z&JobMatchingService._build_search_query  s      ??;'{;78 ??<({<89 ??:&":.t4Hx(xx$$r   r   r   c                    i }|r't         j                  dt        |       d       |rt        |D cg c]  }|j	                  dd       c}      }t         j                  d|        |D ]r  }|j	                  d      }|j	                  dd      }|dkD  r||z  dz  nd}	t        t        j                  dd	            }
|	|
k\  sZ|s]||t        |	d
      dd|	d||<   t t         j                  d
 dt        |j                         D cg c]  }|d   |
k\  s| c}              nt         j                  d       |r|j	                  dg       ng }|rt         j                  dt        |       d       |r}t        |D cg c]  }|j	                  dd       c}      }t         j                  d|        |D ]  }|j	                  di       j	                  d      }|j	                  dd      }|dkD  r||z  dz  nd}	t        t        j                  dd	            }
|	|
k\  sj|sm||v r9|||   d<   t        |	d
      ||   d<   t        ||   d   |	z   d
z  d
      ||   d<   t         j                  d| d       |dd|t        |	d
      |	d||<    t         j                  d
 dt        |j                         D cg c]  }|d   |
k\  s| c}              nt         j                  d       t        |j                               }|j                  d d       t         j                  dt        j                  dd        d!t        |       d"       |S c c}w c c}w c c}w c c}w )#a  
        Combine pre-filtered OpenSearch and Vector search results, apply percentage threshold
        
        Args:
            keyword_results: Pre-filtered results from OpenSearch (already above absolute minimum)
            vector_results: Results from Qdrant vector search on filtered candidates
            
        Returns:
            List of filtered candidates with combined scores
        zProcessing z  pre-filtered OpenSearch resultsrQ   r   zMax OpenSearch score: r@   r   JOB_MATCHING_MIN_PERCENTAGEr?      rP   zOpenSearch candidates above z% threshold: rR   z+No pre-filtered OpenSearch results receivedr   z( vector results from filtered candidatesscorezMax vector score: payloadrS   rT   rU   z"Vector result found for resume_id z not in OpenSearch resultszVector candidates above z3No vector results received from filtered candidatesc                     | d   S )NrU    )xs    r   <lambda>z@JobMatchingService._combine_and_filter_results.<locals>.<lambda>h  s    !,<*= r   T)keyreversez#After double filtering (absolute + z50.0z%): z candidates)r1   rC   r:   maxrG   r   r   r   roundvaluesrW   r   sort)r   r   r   rJ   r   max_keyword_scorer   r@   r   match_percentagemin_thresholdcvector_datamax_vector_scorecandidate_lists                  r   r   z.JobMatchingService._combine_and_filter_results  s    
 KK+c/&:%;;[\]$'O(\q)B(\$]!45F4GHI- F &

; 7I"JJ:EL]`aLa0A(AS'Hgh$ %*"))4QSW*X$YM'=8Y)2-2278H!2L,-12.>1
9-  :=/WZgqgxgxgz  \hbc~  AU  V  Zg  g\]  \h  Xi  Wj  k  lKKEF ;In((26bKK+c+&6%77_`a #&;'Oagq(9'O#P 01A0BCD) F &

9b 9 = =k JI"JJw2EK[^_K_0@(@C'Gef$ %*"))4QSW*X$YM'=8Y$
2DIJy1.AINO_abIcJy12EFFK!+I!67K!LO_!_cd dfgGJy12BC
 #NN-OPY{Zt+uv-61267055:;KQ5O2B5Jy1'6 6}o]SVcmctctcv  Xc^_z{  }P  {Q  Ub  {bXY  Xc  Td  Se  f  gKKMN j//12 =tL9"))Daci:j9kkops  uC  qD  pE  EP  Q  	RK )]& \h (P< Xcs#   M%M*M*	M/M4M4rL   c           	         	 | j                  |d         }|st        j                  d|d    d       y| j                  |||      }d}| j                  j                  ||d      }| j                  ||d         }|S # t        $ r=}t        j                  d	|j                  d       d
t        |              Y d}~yd}~ww xY w)a9  
        Generate AI analysis for a candidate including strengths, weaknesses, and justification
        
        Args:
            job_details: Job details dictionary
            candidate: Candidate information with scores
            
        Returns:
            Analysis dictionary or None if failed
        r@   z%No database candidate data found for z!, will use direct analysis methodNtYou are an expert HR analyst. Analyze the candidate's fit for the job and provide detailed, professional assessment.  questionsystem_prompt
max_tokensrU   z(Error generating candidate analysis for rB   )rX   r1   rC   _create_analysis_promptr   ai_response_parse_ai_analysisr0   r+   rG   r2   )	r   rI   rL   r]   promptr   analysis_textparsed_analysisr4   s	            r   rF   z/JobMatchingService._generate_candidate_analysism  s    	!88;9OPN "CIkDZC[[|}~ 11+~yYF SM OO77+ 8 M #55mYO_E`aO"" 	LLCIMMR]D^C__abefgbhaijk	s   2A? A	A? ?	C3C  Cr@   c           	          	 t        j                  |      }|S # t        $ r.}t        j	                  d| dt        |              Y d}~yd}~ww xY w)z
        Get detailed candidate information from MongoDB
        
        Args:
            resume_id: Resume/jobseeker ID
            
        Returns:
            Candidate details or None if not found
        z$Error getting candidate details for rB   N)r   get_jobseeker_complete_datar0   r1   r+   r2   )r   r@   r]   r4   s       r   rX   z)JobMatchingService._get_candidate_details  sO    	*FFyQN!! 	LL?	{"SQRVHUV	s    	A$AAr]   candidate_scoresc                    	 |r|j                  di       ni }|r|j                  dg       ng }|r|j                  dg       ng }d}|rt        |t              r|dd D ]  }t        |t              r5|j                  d      r$|j                  d      r|d	|d    d
|d    dz  }Ht        |t              sY|j                  d      sk|j                  d      s}|d	|d    d
|d    dz  } d}	|rat        |t              rQ|dd D ]I  }
t        |
t              s|
j                  d      s&|
j                  d      s8|	d	|
d    d|
d    dz  }	K |r|j                  dd      nd}|r|j                  dd      nd}|r|j                  dd      nd}|r|j                  dd      nd}|r|j                  dd      nd}|r|j                  dd      nd}|r|j                  dd      nd}d}|rt        |t              r|D ]  }t        |t              s|j                  dd      }t        |t        t
        f      r||z  }n>t        |t              r.|j                  dd      j                         r|t        |      z  }|j                  d|j                  dd            }|j                  d|j                  dd            }|j                  d|j                  dd            dd }|d | d
| d!| d"z  } d#| d$| d%| d&| d'| d(|dk7  r|dd) nd d*|j                  d+d       d,|j                  d-d       d.| d/|j                  d0d       d1|j                  d2      r|j                  d2d      dd3 nd d4|r|nd5 d6|	r|	nd7 d8}|S # t        $ rp}t        j                  d9t        |              t        j                  d:|        t        j                  d;|        t        j                  d<|        |d}~ww xY w)=a  
        Create prompt for AI analysis
        
        Args:
            job_details: Job requirements
            candidate_data: Candidate information
            candidate_scores: Matching scores
            
        Returns:
            Formatted prompt string
        basic_detailsemployment_detailseducation_detailsrb   N   rolecompanyz- z at 
designationcompany_namer   degreeinstitutionz from ra   zN/Ari   rp   rm   rs   rg   rU   r   job_experience_duration.rf   zNo descriptionr   u   • z
  Responsibilities: z...

z
You are an expert HR analyst. Analyze this candidate for the job position with PRECISE percentage scoring.

JOB REQUIREMENTS:
- Position: z
- Required Skills: z
- Job Type: z
- Workplace: z
- Location: z
- Description: i  z

CANDIDATE PROFILE:
- Name: 
first_namer   	last_namez
- Total Experience: ~z years
- Key Skills: 
key_skillsz
- Profile Summary: profile_summaryi,  z

WORK EXPERIENCE:
z%No detailed experience data availablez

EDUCATION:
zNo education data availablea  

PRECISE SCORING GUIDELINES (Use EXACT percentages):
95-100%: Perfect match - ALL required skills + exceeds experience + perfect education + additional value
85-94%:  Excellent match - Most required skills + strong experience + good education + some extras
75-84%:  Very good match - Core skills present + adequate experience + meets education requirements
65-74%:  Good match - Some key skills + reasonable experience + acceptable education background
55-64%:  Fair match - Few required skills + limited relevant experience + basic education
45-54%:  Below average - Missing many skills + insufficient experience + education gaps
35-44%:  Poor match - Wrong skill set + little relevant experience + education mismatch
25-34%:  Very poor match - No relevant skills + unrelated background + major gaps
15-24%:  Not suitable - Completely different field + no matching qualifications
0-14%:   Totally unsuitable - No relevance to job requirements whatsoever

CRITICAL INSTRUCTIONS:
1. Be PRECISE - use specific percentages like 73%, 82%, 91%, not round numbers like 70%, 80%, 90%
2. Consider SKILL OVERLAP percentage between job requirements and candidate skills
3. Factor in EXPERIENCE RELEVANCE and years of experience
4. Evaluate EDUCATION ALIGNMENT with job requirements
5. Avoid generic 60-65% range - be specific based on actual qualifications

Please provide analysis in this EXACT format:

STRENGTHS:
[List 3-5 specific strengths with examples from their background]

WEAKNESSES:
[List 3-5 specific gaps compared to job requirements]

MATCHING_PERCENTAGE:
[Provide ONE precise percentage (0-100) - be specific like 78% or 86%, NOT generic like 65%]

MATCH_JUSTIFICATION:
[2-3 detailed sentences explaining the EXACT percentage with specific skill/experience examples]
z Error creating analysis prompt: zJob details: zCandidate data: zCandidate scores: )rG   r~   r   r   intr   r2   replaceisdigitr0   r1   r+   )r   rI   r]   r   r   r   r   experience_textempeducation_textedura   ri   rp   rm   rs   rg   rU   total_experience_yearsdurationr   r   r   r4   s                           r   r   z*JobMatchingService._create_analysis_prompt  s   x	GUN..C[]MQ_!3!34H"!MegO] 2 23F Kce !O!j1CT&J-bq1 `C!#t,SWWYEW'RF}DY@PPR+SS#C.377=3IcggVdNe'RM0B/C4NH[G\\^+__	`  N Z0A4%H,Ra0 [C!#t,1Bsww}G]&Bs8}oVCDVCWWY*ZZ[
 @KU;PUIALu=RWJ=H{z59eHIT[__-=uEZ_NEP;??>5AV[L=H{z59eH K[-112BAF`aN &'"!j1CT&J- tC!#t,#&77+Da#H%he=2h>2'#68;K;KCQS;T;\;\;^2eHoE2 #&''.#'')U:S"T&)ggmSWWVU=S&T#&77:sww?PRb7c#deifi#j'T+d7)Kabjakkr+sst" K <  J  N "*e"3#? @	 
		<	,-Q}/@/@b/Q.R S,- .  u56 7IVIZIZ[lImM%%&7?Esxy z $)P Q R "'D E "F+7Fp M 	LL;CF8DELL=67LL+N+;<=LL-.>-?@AG	sF   B3N 6N N A N N -N ?CN E9N 	PA+PPr   fallback_percentagec                 <   	 g g |dd}|j                  d      }|D ]^  }|j                         }|j                  d      r|j                  dd      j                         }|j                  d      D cg c]  }|j                         s|j                         j                  d      r4|j                         j	                  d      rT|j                         j                  d      j                          }}||d	<   |j                  d
      r|j                  d
d      j                         }	|	j                  d      D 
cg c]  }
|
j                         s|
j                         j                  d      r4|
j                         j	                  d      rT|
j                         j                  d      j                          }}
||d<   |j                  d      ra|j                  dd      j                         }ddl}|j                  d|      }|st        dt        dt        |d                     |d<   )|j                  d      s<|j                  dd      j                         |d<   a |d	   rB|d   r=|d   r8t        |d	         dk(  s't        |d         dk(  s|d   j                         dk(  rt        j                  d       y|S c c}w c c}
w # t        $ r+}t        j                  dt!        |              Y d}~yd}~ww xY w)a  
        Parse AI analysis response into structured data
        
        Args:
            analysis_text: Raw AI response
            fallback_percentage: Fallback percentage if parsing fails
            
        Returns:
            Structured analysis dictionary
        rb   )	strengths
weaknessesr>   match_justificationz

z
STRENGTHS:r   []u   -•r  zWEAKNESSES:r  zMATCHING_PERCENTAGE:r   Nz	\d+\.?\d*g      Y@g        r>   zMATCH_JUSTIFICATION:r  z+AI analysis incomplete - skipping candidatezError parsing AI analysis: )splitstrip
startswithr   endswithlstriprefindallminr   r   r:   r1   rW   r0   r+   r2   )r   r   r   rM   sectionssectionstrengths_textsstrengths_listweaknesses_textwweaknesses_listpercentage_textr  numbersr4   s                   r   r   z%JobMatchingService._parse_ai_analysis/  s^   1	 ':')	H %**62H# j!--/%%l3%,__\2%F%L%L%NNP^PdPdeiPj  &{1nonununw  AB  AH  AH  AJ  AU  AU  VY  AZ  cd  cj  cj  cl  cu  cu  vy  czaggi&6&6v&>&D&D&F  &{N  &{,:H[)''6&-oomR&H&N&N&POQ`QfQfgkQl  '}Apqpwpwpy  CD  CJ  CJ  CL  CW  CW  X[  C\  ef  el  el  en  ew  ew  x{  e|qwwy'7'7'?'E'E'G  '}O  '}-<H\*''(>?&-oo6Lb&Q&W&W&YO jjGG:=eSeT[\]T^N_E`:a!67''(>?6=ooF\^`6a6g6g6iH23+j0 [)\*23H[)*a/H\*+q0./5572=LMO7 &{
 '}0  	LL6s1vh?@	so   A2K' 4K
K*K
/K9AK' K"K";K"/K"
AK' 8K' BK' K' 
K' '	L0!LLrM   c                    	 ddl m}  |       }|d   }|j                  |t        |d         dd      }|r t        j                  d|d    d| d	       y
| j                  |d         }g }	|r|j                  di       }
|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }g }|
r1i d|
j                  dd       d|
j                  dd       j                         d|
j                  dd      d|
j                  dd      d|j                  dd      xs |
j                  dd      d|j                  dd      xs |
j                  dd      d|j                  dd      d|
j                  dd      d|
j                  dd      d |
j                  d d      d!|
j                  d!d      d"|
j                  d"d      d#|
j                  d$d      xs |
j                  d#d      d%|
j                  d%d      d&|
j                  d&d      d'|
j                  d'd      d(|
j                  d&d       d|
j                  d'd       j                         d)|
j                  d)d      i d*|
j                  d*d      d+|
j                  d+d      d,|
j                  d,      r!|
j                  d,d      j                  d-      ng d.|
j                  d.g       d/|
j                  d/d      d0|
j                  d0d      d1|
j                  d1d      d2|
j                  d2d      d3|
j                  d3d      d4|
j                  d4d      d5|
j                  d5d      d6|
j                  d6d      d7|
j                  d7d      d8|
j                  d8d      d9|
j                  d9d      d:|
j                  d:d      d;|
j                  d;d      i d<|
j                  d<d      d=|
j                  d=d      d>|
j                  d>d      d?|
j                  d?d      d@|
j                  d@d      dA|
j                  dAd      dB|
j                  dBd      dC|
j                  dCd      dD|
j                  dDd      dE|
j                  dEd      dF|
j                  dFd      dG|
j                  dGd      dH|
j                  dHg       dI|
j                  dId      dJ|
j                  dJd      dK|
j                  dKd      dL|
j                  dLd      |
j                  dMd      |
j                  dNd      |
j                  dOd      |
j                  dPd      |
j                  dQd      |
j                  dRd      |
j                  dSd      |
j                  dTd      |
j                  dUd      |
j                  dVd      dW
}|j                  |       i d|dX|r|ng dY|r|ng dZ|r|ng d[|r|ng d\|r|ng d]|r|ng d^|r|ng d_|r|ng d`|r|ng da|r|ng d|j                  dd      d|j                  dd      db|j                  dbd      dc|j                  dcd      dR|j                  dRd      dd|j                  ddd      |j                  d9d      |j                  ded      |j                         D ci c]  \  }}|dfvr|| c}}dg}|	j                  |       |t        |d         d|dh   |di   t        |dj   d      |dk   |	t        j                         t        j                         dl
}|j                  |      }t        j                  dm|d    d|        y
c c}}w # t         $ r+}t        j#                  dnt%        |              Y do}~ypdo}~ww xY w)qa  
        Store screening result in MongoDB screenings collection
        Check for duplicates before inserting
        
        Args:
            job_emp_id: Job employer ID
            candidate: Candidate information
            analysis: AI analysis result
        r   get_database
screeningsr@   r   r(   r[   rr   z'Screening already exists for candidate 	 and job , skipping insertTr   r   r   certification_detailscourse_detailsproject_detailslanguage_detailshobby_detailsreference_detailsportfolio_detailssocial_media_detailsr{   r   rb   r   r   emailphonemobile_number
birth_date	gender_idmarital_statusnationality_idreligion_idcurrent_designationcurrent_desigationcurrent_companytotal_experience_yeartotal_experience_monthtotal_experienceindustry_idfunctional_area_idprofile_type_idr   ,key_skills_idr   cv_headlinecurrent_salarycurrent_salary_periodcurrent_salary_currencyexpected_salaryexpected_salary_periodexpected_salary_currencycurrent_locationprefered_locationrv   com_addressvisa_status_idvisa_residence_locationvisa_validity_monthvisa_validity_yearlicense_optprofile_completionprofile_updated_datesystem_original_resumesystem_resume_fileoriginal_file_nameresume_fileresume_added_dateprofile_picknown_language_idknown_languagescv_push
cv_push_idcv_download_countapplied_job_countconnection_countprofile_uploadresume_uploades_pushr)   
is_deleted
created_at
updated_atrz   
rV  rW  rX  rY  rZ  r)   r[  r\  r]  rz   
experience	educationcertificationscoursesprojects	languageshobbies
references	portfoliosocial_mediars   rO   indexedis_subscribedr   r   r   r  r   r!  r"  r#  r$  r%  r&  rv   rj  additional_infor  r  r>   r  
r(   r[   rr   r  r  r>   r  jobseeker_detailsr\  processed_atz7Successfully stored new screening result for candidate z Error storing screening result: NFservices.databaser  r   r   r1   rC   rX   rG   r  r  r   itemsr   r   utcnow
insert_oner0   r+   r2   r   r   rL   rM   r  r   screenings_collectionexisting_screeningr]   ro  r   r   r   r  r   r!  r"  r#  r$  r%  r&  basic_details_arraybasic_detailkvjobseeker_detailscreening_datar   r4   s                                r   rH   z*JobMatchingService._store_screening_resultm  sQ   r	6B$&|$4! "7!?!?$ #Ik$: ;A " "EiP[F\E]]fgqfr  sD  E  F "88;9OPN " . 2 2?B G%3%7%78Lb%Q"$2$6$67JB$O!(6(:(:;RTV(W%!/!3!34Db!I"0"4"45F"K#1#5#56H"#M  . 2 2?B G$2$6$67JB$O!$2$6$67JB$O!'5'9'9:PRT'U$ ')# O$=#4#4\2#F"GqIZIZ[fhjIkHl m s s uO$ %m&7&7b&IO$ $]%6%6{B%G	O$
  !3!3GR!@!bMDUDUV]_aDbO$  !3!3OR!H!jML]L]^egiLjO$ (););OR)PO$ %m&7&7b&IO$ $]%6%6{B%GO$ )-*;*;<Lb*QO$ )-*;*;<Lb*QO$ &}'8'8'KO$ .}/@/@AUWY/Z  0K^k^o^o  qF  HJ  _KO$  *=+<+<=NPR+S!O$" 01B1BCZ\^1_#O$$ 1-2C2CD\^`2a%O$& +}/@/@AXZ\/].^^_`m`q`q  sK  MO  aP  `Q  -R  -X  -X  -Z'O$( &}'8'8'K)O$* -m.?.?@TVX.Y+O$, *=+<+<=NPR+S-O$2 %XeXiXijvXwm&7&7b&I&O&OPS&T}3O$4 ():):?B)O5O$6 *=+<+<=NPR+S7O$8 &}'8'8'K9O$> )-*;*;<Lb*Q?O$@ 01B1BCZ\^1_AO$B 2=3D3DE^`b3cCO$D *=+<+<=NPR+SEO$F 1-2C2CD\^`2aGO$H 3M4E4EF`bd4eIO$N +M,=,=>PRT,UOO$P ,]->->?RTV-WQO$R %m&7&7b&ISO$T &}'8'8'KUO$Z )-*;*;<Lb*Q[O$\ 2=3D3DE^`b3c]O$^ .}/@/@AVXZ/[_O$` -m.?.?@TVX.YaO$b &}'8'8'KcO$h -m.?.?@TVX.YiO$j /0A0ABXZ\0]kO$l 1-2C2CD\^`2amO$n -m.?.?@TVX.YoO$p -m.?.?@TVX.YqO$r &}'8'8'KsO$t ,]->->?RTV-WuO$v &}'8'8'KwO$| ,]->->?RTV-W}O$~ *=+<+<=NPR+SO$D "=#4#4Y#CEO$F %m&7&7b&IGO$H ,]->->?RTV-WIO$J .;->->?RTV-W,9,=,=>PRT,U*7*;*;<Lb*Q)6):):?B)O#0#4#4Y#C"/"3"3Hb"A&3&7&7b&I&3&7&7b&I&3&7&7b&I+//b9]O$L` (..|<$#%8$ 8J"4PR$  6G!2R$ %?T&;Z\	$
 ~R$ ?$  5E!12$ }2$ !7H"3b$  6G!2R$ #<P$8VX$ ^//<$ $^%7%7%L$  #N$6$6~r$J!$" *>+=+=>SUW+X#$$ n002>%$& ~11)R@'$( #1"4"4\2"F%3%7%7%K *8)=)=)?(!%A %P P 1(1$ @ "(()9: % #Ik$: ;%k2&|4',X6K-La'P'/0E'F%6&oo/ ( 1N +55nEFKKQR[\gRhQiirs}r~  A9(<  	LL;CF8DE	2   A` \` `	.B` 	` 	a!`>>ar[   c           	         	 t        |      }t        j                  d      j                  |dd      }|st        j                  d| d       y|j                  d      dk(  rt        j                  d| d       y	| j                  j                  |      }|rt        j                  d
|        y	t        j                  d|        y# t        $ r.}t        j                  d| dt        |              Y d}~yd}~ww xY w)z
        Ensure jobseeker is indexed in OpenSearch, index if needed
        
        Args:
            jobseeker_id: Jobseeker ID to check/index
            
        Returns:
            bool: True if indexed successfully, False otherwise
        
jobseekersr   )rz   r)   z
Jobseeker z not found or inactiveFri  z already indexedTzSuccessfully indexed jobseeker zFailed to index jobseeker zError ensuring jobseeker z is indexed: N)r   r   get_collectionr   r1   rW   rG   rC   r   
add_resumer+   r0   r2   )r   r[   jobseeker_id_int	jobseekerr&   r4   s         r   rV   z,JobMatchingService._ensure_jobseeker_indexed+  s    	"<0%44\BKK'15I L>9OPQ }}Y'1,j6FGH --88FG=l^LM9,HI 	LL4\N-PSTUPVxXY	s*   AC -C >5C 4C 	D$C??Dc                    	 ddl m}  |       }|d   }|j                  |t        |d         dd      }|r t        j                  d|d    d| d	       y
| j                  |d         }g }	|r|j                  di       }
|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }g }|
r1i d|
j                  dd       d|
j                  dd       j                         d|
j                  dd      d|
j                  dd      d|j                  dd      xs |
j                  dd      d|j                  dd      xs |
j                  dd      d|j                  dd      d|
j                  dd      d|
j                  dd      d |
j                  d d      d!|
j                  d!d      d"|
j                  d"d      d#|
j                  d$d      xs |
j                  d#d      d%|
j                  d%d      d&|
j                  d&d      d'|
j                  d'd      d(|
j                  d&d       d|
j                  d'd       j                         d)|
j                  d)d      i d*|
j                  d*d      d+|
j                  d+d      d,|
j                  d,      r!|
j                  d,d      j                  d-      ng d.|
j                  d.g       d/|
j                  d/d      d0|
j                  d0d      d1|
j                  d1d      d2|
j                  d2d      d3|
j                  d3d      d4|
j                  d4d      d5|
j                  d5d      d6|
j                  d6d      d7|
j                  d7d      d8|
j                  d8d      d9|
j                  d9d      d:|
j                  d:d      d;|
j                  d;d      i d<|
j                  d<d      d=|
j                  d=d      d>|
j                  d>d      d?|
j                  d?d      d@|
j                  d@d      dA|
j                  dAd      dB|
j                  dBd      dC|
j                  dCd      dD|
j                  dDd      dE|
j                  dEd      dF|
j                  dFd      dG|
j                  dGd      dH|
j                  dHg       dI|
j                  dId      dJ|
j                  dJd      dK|
j                  dKd      dL|
j                  dLd      |
j                  dMd      |
j                  dNd      |
j                  dOd      |
j                  dPd      |
j                  dQd      |
j                  dRd      |
j                  dSd      |
j                  dTd      |
j                  dUd      |
j                  dVd      dW
}|j                  |       i d|dX|r|ng dY|r|ng dZ|r|ng d[|r|ng d\|r|ng d]|r|ng d^|r|ng d_|r|ng d`|r|ng da|r|ng d|j                  dd      d|j                  dd      db|j                  dbd      dc|j                  dcd      dR|j                  dRd      dd|j                  ddd      |j                  d9d      |j                  ded      |j                         D ci c]  \  }}|dfvr|| c}}dg}|	j                  |       |t        |d         d|dh   |di   t        |dj   dk      |dl   |	t        j                         t        j                         dm
}|j                  |      }t        j                  dn|d    d|        y
c c}}w # t         $ r+}t        j#                  dot%        |              Y dp}~yqdp}~ww xY w)ra  
        Store screening result for specific candidate analysis with type=1
        Check for duplicates before inserting
        
        Args:
            job_emp_id: Job employer ID
            candidate: Candidate information
            analysis: AI analysis result
        r   r  r  r@   r   r  z0Specific screening already exists for candidate r  r  Tr   r   r   r  r   r!  r"  r#  r$  r%  r&  r{   r   rb   r   r   r'  r(  r)  r*  r+  r,  r-  r.  r/  r0  r1  r2  r3  r4  r5  r6  r7  r   r8  r9  r   r:  r;  r<  r=  r>  r?  r@  rA  rB  rv   rC  rD  rE  rF  rG  rH  rI  rJ  rK  rL  rM  rN  rO  rP  rQ  rR  rS  rT  rU  rV  rW  rX  rY  rZ  r)   r[  r\  r]  rz   r^  r_  r`  ra  rb  rc  rd  re  rf  rg  rh  rs   rO   ri  rj  rk  rl  r  r  r>   r   r  rn  z<Successfully stored specific screening result for candidate z)Error storing specific screening result: NFrq  rv  s                                r   rY   z3JobMatchingService._store_specific_screening_resultR  s\   r	6B$&|$4! "7!?!?$ #Ik$: ;A " "NyYdOeNffopzo{  |M  N  O "88;9OPN " . 2 2?B G%3%7%78Lb%Q"$2$6$67JB$O!(6(:(:;RTV(W%!/!3!34Db!I"0"4"45F"K#1#5#56H"#M  . 2 2?B G$2$6$67JB$O!$2$6$67JB$O!'5'9'9:PRT'U$ ')# O$=#4#4\2#F"GqIZIZ[fhjIkHl m s s uO$ %m&7&7b&IO$ $]%6%6{B%G	O$
  !3!3GR!@!bMDUDUV]_aDbO$  !3!3OR!H!jML]L]^egiLjO$ (););OR)PO$ %m&7&7b&IO$ $]%6%6{B%GO$ )-*;*;<Lb*QO$ )-*;*;<Lb*QO$ &}'8'8'KO$ .}/@/@AUWY/Z  0K^k^o^o  qF  HJ  _KO$  *=+<+<=NPR+S!O$" 01B1BCZ\^1_#O$$ 1-2C2CD\^`2a%O$& +}/@/@AXZ\/].^^_`m`q`q  sK  MO  aP  `Q  -R  -X  -X  -Z'O$( &}'8'8'K)O$* -m.?.?@TVX.Y+O$, *=+<+<=NPR+S-O$2 %XeXiXijvXwm&7&7b&I&O&OPS&T}3O$4 ():):?B)O5O$6 *=+<+<=NPR+S7O$8 &}'8'8'K9O$> )-*;*;<Lb*Q?O$@ 01B1BCZ\^1_AO$B 2=3D3DE^`b3cCO$D *=+<+<=NPR+SEO$F 1-2C2CD\^`2aGO$H 3M4E4EF`bd4eIO$N +M,=,=>PRT,UOO$P ,]->->?RTV-WQO$R %m&7&7b&ISO$T &}'8'8'KUO$Z )-*;*;<Lb*Q[O$\ 2=3D3DE^`b3c]O$^ .}/@/@AVXZ/[_O$` -m.?.?@TVX.YaO$b &}'8'8'KcO$h -m.?.?@TVX.YiO$j /0A0ABXZ\0]kO$l 1-2C2CD\^`2amO$n -m.?.?@TVX.YoO$p -m.?.?@TVX.YqO$r &}'8'8'KsO$t ,]->->?RTV-WuO$v &}'8'8'KwO$| ,]->->?RTV-W}O$~ *=+<+<=NPR+SO$D "=#4#4Y#CEO$F %m&7&7b&IGO$H ,]->->?RTV-WIO$J .;->->?RTV-W,9,=,=>PRT,U*7*;*;<Lb*Q)6):):?B)O#0#4#4Y#C"/"3"3Hb"A&3&7&7b&I&3&7&7b&I&3&7&7b&I+//b9]O$L` (..|<$#%8$ 8J"4PR$  6G!2R$ %?T&;Z\	$
 ~R$ ?$  5E!12$ }2$ !7H"3b$  6G!2R$ #<P$8VX$ ^//<$ $^%7%7%L$  #N$6$6~r$J!$" *>+=+=>SUW+X#$$ n002>%$& ~11)R@'$( #1"4"4\2"F%3%7%7%K *8)=)=)?(!%A %P P 1(1$ @ "(()9: % #Ik$: ;%k2&|4',X6K-La'P'/0E'F%6&oo/ ( 1N +55nEFKKVW`alWmVnnw  yC  xD  E  F9(<  	LLDSVHMN	r  parsed_dataanalysis_typec                 B   	 | j                  |      }|sdd| dS t        |      dddddd}| j                  |      }|s+t        j	                  d| d       | j                  ||      }| j                  ||      }|s| j                  |||      }|rA| j                  |||||      }	|	r$t        j                  d	| d
| d|        d||dS dddS dddS # t        $ r9}
t        j                  dt        |
              dt        |
      dcY d}
~
S d}
~
ww xY w)a  
        Analyze a single candidate for upload with AI analysis and store with specified type
        
        Args:
            job_emp_id: The employer job ID
            jobseeker_id: The jobseeker ID from jobseekers_bulk_upload_cv.id
            parsed_data: Parsed resume data
            analysis_type: Type of analysis (3 for upload analysis)
            
        Returns:
            Dict: Analysis result with success status
        FzFailed to get job details for )r&   r+   r   rP   z6Could not fetch complete candidate data for jobseeker z, using parsed data only)r   r[   rM   r  r]   z2Successfully analyzed and stored upload candidate z	 for job  with type T)r&   r[   rM   zFailed to store analysis resultzFailed to generate AI analysisz-Error analyzing single candidate for upload: N)rD   r2   (_get_complete_bulk_upload_candidate_datar1   rW   _format_parsed_data_for_storagerF   #_generate_candidate_analysis_direct_store_upload_screening_resultrC   r0   r+   )r   r   r[   r  r  rI   rL   complete_candidate_dataanalysis_resultstorage_successr4   s              r   #analyze_single_candidate_for_uploadz6JobMatchingService.analyze_single_candidate_for_upload  s   C	//
;K$=j\J  !.!"&' !%&"#I '+&S&ST`&a#*!WXdWee}~*.*N*N{\h*i' #??YWO #"&"J"J;Xoqz"{"&"E"E)!-,"/#: #F # #KK"TUaTbbklvkw  xC  DQ  CR  !S  T#'(4$3  $)!B   %= 
  	LLHQQR Q 	s/   C B4C C C 	D%.DDDc                    	 ddl m}  |       }|d   }|j                  |||d      }	|	r t        j	                  d| d| d| d       y	g }
|r|d
k(  r|j                  di       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }|j                  dg       }d}d}	 ddl m}  |       }|d   j                  d|i      }|rC|j                  dd      }|j                  dd      }t        j	                  d| d| d|        nt        j                  d|        |s&|j                  dd      xs |j                  dd      }|s&|j                  dd      xs |j                  d"d      }g }|s|rL|r|j                  d#d      nd}|r|j                  d$d      nd}|s0|s-|r*|j                  d%i       }|j                  d&d      }|r|j                  d'      s|j                  d(d      j                  d)d      j                  d*d      j                  d+d,      j                  d-d,      j                         }|j                         } t        |       d.k\  r| d   }d,j                  | d/d!       }|j                  di       }!|s#|!j                  d#      r|!j                  d#d      }|s#|!j                  d$      r|!j                  d$d      }|}"|}#|"s&|r$|j                  di       }!|!j                  dd      }"|#s&|r$|j                  di       }!|!j                  d"d      }#i d0| d,| j                         d#|d$|d|"d"|#d|#d1|j                  d1d      d2|j                  d2d      d3|j                  d3d      d4|j                  d4d      d5|j                  d5d      d6|j                  d7d      xs |j                  d6d      d8|j                  d8d      d9|j                  d9d      d:|j                  d:d      d;|j                  d9d       d,|j                  d:d       j                         d<|j                  d<d      i d=|j                  d=d      d>|j                  d>d      d?|j                  d?      r!|j                  d?d      j                  d@      ng dA|j                  dAg       dB|j                  dBd      dC|j                  dCd      dD|j                  dDd      dE|j                  dEd      dF|j                  dFd      dG|j                  dGd      dH|j                  dHd      dI|j                  dId      dJ|j                  dJd      dK|j                  dKd      dL|j                  dLd      dM|j                  dMd      dN|j                  dNd      i dO|j                  dOd      dP|j                  dPd      dQ|j                  dQd      dR|j                  dRd      dS|j                  dSd      dT|j                  dTd      dU|j                  dUd      dV|j                  dVd      dW|j                  dWd      dX|j                  dXd      dY|j                  dYd      dZ|j                  dZd      d[|j                  d[g       d\|j                  d\d      d]|j                  d]d      d^|j                  d^d      d_|j                  d_d      |j                  d`d      |j                  dad      |j                  dbd      |j                  dcd      |j                  ddd      |j                  ded      |j                  dfd      |j                  dgd      |j                  dhd      |j                  dd      di
}$|j!                  |$       i d|dj|r|ng dk|r|ng dl|r|ng dm|r|ng dn|r|ng do|r|ng dp|r|ng dq|r|ng dr|r|ng ds|r|ng d"d#dt|j                  dtd      du|j                  dud      de|j                  ded      dv|j                  dvd      |j                  dwd      |j                  dLd      |j#                         D %&ci c]  \  }%}&|%dxvr|%|& c}&}%dy}'|
j!                  |'       ||||dz   |d{   t%        |d|   d.      |d}   t'        j(                         t'        j(                         d~	}(|d
k(  r|r|
|(d<   n
|d
k(  rg |(d<   |j+                  |(      })t        j	                  d| d| d|        y	# t        $ r/}t        j                  d| d t        |              Y d!}~]d!}~ww xY wc c}&}%w # t        $ r+}t        j                  dt        |              Y d!}~yd!}~ww xY w)a  
        Store screening result for upload analysis with jobseeker_details for type 3
        Check for duplicates before inserting
        
        Args:
            job_emp_id: Job employer ID
            jobseeker_id: Jobseeker ID from bulk upload collection
            analysis: AI analysis result
            analysis_type: Type of analysis (3 for upload)
            candidate_data: Candidate data to store in jobseeker_details array
        r   r  r  r  z.Upload screening already exists for candidate r  r  r  Tr   r   r   r   r  r   r!  r"  r#  r$  r%  r&  rb   jobseekers_bulk_upload_cvrz   r'  r)  z%Found bulk upload data for jobseeker z: email=z	, mobile=z(No bulk upload data found for jobseeker z-Error getting bulk upload data for jobseeker rB   Nr(  r   r   rm  cv_filenamezuploads/.pdfz.docz.docx_r   -r   r   r{   r*  r+  r,  r-  r.  r/  r0  r1  r2  r3  r4  r5  r6  r7  r   r8  r9  r   r:  r;  r<  r=  r>  r?  r@  rA  rB  rv   rC  rD  rE  rF  rG  rH  rI  rJ  rK  rL  rM  rN  rO  rP  rQ  rR  rS  rT  rU  rV  rW  rX  rY  rZ  r)   r[  r\  r]  r^  r_  r`  ra  rb  rc  rd  re  rf  rg  rh  rs   rO   rj  ri  rk  )ri  rv   rm  r  r  r>   r  )	r(   r[   rr   r  r  r>   r  r\  rp  ro  z:Successfully stored upload screening result for candidate z'Error storing upload screening result: F)rr  r  r   r1   rC   rG   rW   r0   r+   r2   r  r   r  r  r:   r   r   rs  r   r   rt  ru  )*r   r   r[   rM   r  r]   r  r   rw  rx  ro  r   r   r   r  r   r!  r"  r#  r$  r%  r&  r'  mobilebulk_upload_datar4   ry  r   r   rm  r  name_from_file
name_partsparsed_basicfinal_emailfinal_mobilerz  r{  r|  r}  r~  r   s*                                             r   r  z1JobMatchingService._store_upload_screening_resultb  s   p	6B$&|$4! "7!?!?$ ,%A " "L\NZcdncooz  |I  {J  J[  \  ] !#-1"4 . 2 2?B G%3%7%78Lb%Q"$2$6$67JB$O!(6(:(:;RTV(W%!/!3!34Db!I"0"4"45F"K#1#5#56H"#M  . 2 2?B G$2$6$67JB$O!$2$6$67JB$O!'5'9'9:PRT'U$ k>%B')*E'F'O'OQUWcPd'e$' 0 4 4Wb A!1!5!5or!J&KL>Yabgahhqrxqy$z{)QR^Q_'`a
 *..w;]}?P?PQXZ\?]E+//DfHYHYZaceHfF ')# NHU!2!2<!D[]JFS 1 1+r BY[I &iN*8*<*<=NPR*S&5&9&9-&L '{/E/Ej/Q-8-@-@-L-T-TU[]_-`-h-hiprt-u-}-}  B  DG  .H  .P  .P  QT  VY  .Z  .`  .`  .bN)7)=)=)?J":!3-7]
,/HHZ^,D	 (6'9'9/2'N)l.>.>|.L)5)9)9,)KJ(\-=-=k-J(4(8(8b(II #(K#)L&>'5'9'9/2'N&2&6&6w&C'N'5'9'9/2'N'3'7'7'DO$:,a	{ ; A A CO$ %jO$ $Y	O$
  O$  O$ (O$ %m&7&7b&IO$ $]%6%6{B%GO$ )-*;*;<Lb*QO$ )-*;*;<Lb*QO$ &}'8'8'KO$ .}/@/@AUWY/Z  0K^k^o^o  qF  HJ  _KO$  *=+<+<=NPR+S!O$" 01B1BCZ\^1_#O$$ 1-2C2CD\^`2a%O$& +}/@/@AXZ\/].^^_`m`q`q  sK  MO  aP  `Q  -R  -X  -X  -Z'O$( &}'8'8'K)O$* -m.?.?@TVX.Y+O$, *=+<+<=NPR+S-O$2 %XeXiXijvXwm&7&7b&I&O&OPS&T}3O$4 ():):?B)O5O$6 *=+<+<=NPR+S7O$8 &}'8'8'K9O$> )-*;*;<Lb*Q?O$@ 01B1BCZ\^1_AO$B 2=3D3DE^`b3cCO$D *=+<+<=NPR+SEO$F 1-2C2CD\^`2aGO$H 3M4E4EF`bd4eIO$N +M,=,=>PRT,UOO$P ,]->->?RTV-WQO$R %m&7&7b&ISO$T &}'8'8'KUO$Z )-*;*;<Lb*Q[O$\ 2=3D3DE^`b3c]O$^ .}/@/@AVXZ/[_O$` -m.?.?@TVX.YaO$b &}'8'8'KcO$h -m.?.?@TVX.YiO$j /0A0ABXZ\0]kO$l 1-2C2CD\^`2amO$n -m.?.?@TVX.YoO$p -m.?.?@TVX.YqO$r &}'8'8'KsO$t ,]->->?RTV-WuO$v &}'8'8'KwO$| ,]->->?RTV-W}O$~ *=+<+<=NPR+SO$D "=#4#4Y#CEO$F %m&7&7b&IGO$H ,]->->?RTV-WIO$J .;->->?RTV-W,9,=,=>PRT,U*7*;*;<Lb*Q)6):):?B)O#0#4#4Y#C"/"3"3Hb"A&3&7&7b&I&3&7&7b&I&3&7&7b&I+//b9]O$L` (..|<$#%8$ 8J"4PR$  6G!2R$ %?T&;Z\	$
 ~R$ ?$  5E!12$ }2$ !7H"3b$  6G!2R$ #<P$8VX$ [$ $\$  #N$6$6~r$J!$" *>+=+=>SUW+X#$$ n002>%$& $^%7%7%K'$(  .11)R@"0"4"4\2"F *8)=)=)?(!%A %P P 1(1$ @ "(()9: % ,%%k2&|4',X6K-La'P'/0E'F&oo/ ( 1
N !n6G23!#6823 +55nEFKKTUaTbbklvkw  xC  DQ  CR  S  T} ! kLL#PQ]P^^`adefag`h!ijjkx(H  	LLB3q6(KL	sX   Ah Ch !B g !]4h h&B$h 	h$g>8h >h	h 	i !h;;i c           	         	 t         j                  d|r|j                  dd      nd        t         j                  dt        |              t         j                  dt        |              t         j                  d|        | j	                  |||      }t         j                  d|j                  dd              d	}| j
                  j                  ||d
      }| j                  |d      }|S # t        $ rD}|r|j                  dd      nd}	t         j                  d|	 dt        |              Y d}~yd}~ww xY w)aa  
        Generate AI analysis for a candidate using direct data (for upload scenarios)
        
        Args:
            job_details: Job details dictionary
            candidate_data: Direct candidate data
            candidate: Candidate information with scores
            
        Returns:
            Analysis dictionary or None if failed
        z#Starting AI analysis for candidate r@   unknownzNone candidatezJob details available: zCandidate data available: zCandidate object: z(Generated analysis prompt for candidate r   r   r   r?   z/Error generating direct candidate analysis for rB   N)r1   rC   rG   boolr   r   r   r   r0   r+   r2   )
r   rI   r]   rL   r   r   r   r   r4   candidate_ids
             r   r  z6JobMatchingService._generate_candidate_analysis_direct`  sL   	KK=gpimmKYb>c  wG  >H  I  JKK1${2C1DEFKK4T.5I4JKLKK,YK89 11+~yYF KKB9==Q\^gChBijk SM OO77+ 8 M #55mTJO"" 	DM9==i@S\LLLJ<.XZ[^_`[aZbcd		s   C4C7 7	E :D??Ec                    	 ddl m}  |       }i g g g g g g g g g g d}|d   j                  d|i      }|ri|j                  dd      |d<   |j                  dd      |d<   |j                  d	d      |d	<   |j                  d
d      |d
<   |j                  dd      |d<   |d   j                  d|i      }|r||d<   t	        |d   j                  d|i            }||d<   t	        |d   j                  d|i            }||d<   t	        |d   j                  d|i            }	|	|d<   t	        |d   j                  d|i            }
|
|d<   t	        |d   j                  d|i            }||d<   t        j                  d|        |S # t        $ r.}t        j                  d| dt        |              Y d}~yd}~ww xY w)z
        Get complete candidate data from all bulk upload related tables
        
        Args:
            jobseeker_id: The jobseeker ID from bulk upload collection
            
        Returns:
            Complete candidate data dictionary
        r   r  rk  r  rz   r'  rb   r)  r  cvfile_store_pathlast_login_date&jobseeker_basic_details_bulk_upload_cvr[   r   +jobseeker_employment_details_bulk_upload_cvr   *jobseeker_education_details_bulk_upload_cvr   'jobseeker_course_details_bulk_upload_cvr   (jobseeker_project_details_bulk_upload_cvr!  .jobseeker_certification_details_bulk_upload_cvr  z;Successfully fetched complete candidate data for jobseeker z5Error fetching complete candidate data for jobseeker rB   N)rr  r  r   rG   r   findr1   rC   r0   r+   r2   )r   r[   r  r   r]   main_cv_datar   r   r   r   r!  r  r4   s                r   r  z;JobMatchingService._get_complete_bulk_upload_candidate_data  sR   :	6B "$&(%')+"$#%$&!#%'%'(*N 9:CCT<DXYL*6*:*:7B*Gw'2>2B2B?TV2W/0<0@0@PR0S}-6B6F6FGZ\^6_234@4D4DEVXZ4[01 GHQQSacoRpqM2?/ "&b)V&W&\&\^lnz]{&|!}3EN/0 !%R(T%U%Z%Z\jlx[y%z {2CN./ ""%N"O"T"TVdfrUs"tuN/=N+, #2&P#Q#V#VXfhtWu#vwO0?N,- %),\)])b)bdr  uA  dB  *C  %D!6KN23KKUVbUcde!! 	LLPQ]P^^`adefag`hij	s   FF	 		G $F;;G c                    	 i d||j                  d      r#|j                  dd      j                         d   nd|j                  d      r^t        |j                  dd      j                               dkD  r2dj                  |j                  dd      j                         dd       nd|j                  dd      |j                  dd      |j                  d	d      |j                  d
      r!dj                  |j                  d
g             nd|j                  d      r|j                  dd      nd|j                  d      |j                  d      |j                  dg       dd|j                  dd      |j                  dd      dd|j                  dd      d|j                  d	d      dg dg dg dg dg d|j                  dd      |j                  d	d      d|j                  dd       dd| d|j                  dd      ddg d g d!g d"g d#g d$g d|j                  dd      d|j                  d	d      }|j                  d%      rt	        |d%   t
              r|d%   D ]  }t	        |t              s||j                  d&d      xs |j                  d'd      |j                  d(d      |j                  d)      |j                  d*      |j                  d+d      xs |j                  d,d      |j                  d-g       d.}|d   j                  |        |j                  d/      rt	        |d/   t
              r|d/   D ]  }t	        |t              s||j                  d0d      |j                  d1d      |j                  d2d      |j                  d)      |j                  d*      |j                  d3      |j                  d4      d5}|d   j                  |        |j                  d6      rt	        |d6   t
              r|d6   D ]  }t	        |t              s||j                  dd      xs |j                  d7d      |j                  d8d      xs |j                  d9d      |j                  d:      |j                  d;      |j                  d<      |j                  d=      d>}	|d   j                  |	        |j                  d?      rt	        |d?   t
              r|d?   D ]  }
t	        |
t              s||
j                  d7d      xs |
j                  dd      |
j                  d9d      xs |
j                  d(d      |
j                  d,d      |
j                  d=d      |
j                  d)      |
j                  d*      |
j                  d@g       dA}|d   j                  |        t        j                  dB|        |S # t        $ r^}t        j                  dC| dDt        |              i g g g g g g g g g g |j                  dd      |j                  d	d      dEcY d}~S d}~ww xY w)Fa  
        Format parsed data to match the expected storage structure
        
        Args:
            parsed_data: The parsed resume data
            jobseeker_id: The jobseeker ID
            
        Returns:
            Formatted candidate data dictionary
        r   r{   rb   r   r   r   Nr'  r(  rj   r8  r   date_of_birthnationalityrd  r\  r]  )r[   r   r   	full_namer'  r(  r   r   r  r  rd  r)   r[  r\  r]  r)  r   r   r  r   r!  rm  zParsed Resume - Unknownr  zuploads/parsed/z/resume.pdf)r'  r)  r  r  r  r"  r#  r$  r%  r&  r_  r   rd   r   
start_dateend_daterf   rh   responsibilities)r[   r   r   r  r  rf   r  r`  r   r   field_of_studycompletion_yeargrade)r[   r   institute_namer  r  r  r  r  ra  rc   issuerorganization
issue_dateexpiry_datecredential_idurl)r[   certificate_namer  r  r  r  r  rc  technologies)r[   project_titleorganization_nameproject_descriptionproject_urlr  r  r  z1Successfully formatted parsed data for jobseeker z+Error formatting parsed data for jobseeker rB   )r   r   r   r  r   r!  r"  r#  r$  r%  r&  r'  r)  )rG   r  r:   r   r~   r   r   r   r1   rC   r0   r+   r2   )r   r  r[   formatted_candidate_dataexpformatted_expr   formatted_educertformatted_certprojformatted_projr4   s                r   r  z2JobMatchingService._format_parsed_data_for_storage  s   ~	)($0LWOO\bLc+//&""="C"C"Ea"HikVaVeVeflVmru  wB  wF  wF  GM  OQ  wR  wX  wX  wZ  s[  ^_  s_+//&"*E*K*K*Mab*Q!R  eg!,!<(__Wb9(__Wb9MX__]eMf#((;??8R+H"IlnQ\Q`Q`arQs{7H"'My{%0___%E#.??=#A!,b!A"#"-//,"C"-//,"C")($ "5%)(&  "!=')(( %b))(* $R+)(, (-)(. !"/)(0 "21)(2 "(__Wb9%0__Wb%A%5koofi6X5YY]#^+:<.)T'2|R'H$3)(B "2C)(D #BE)(F  G)(H $RI)(J $RK)(L 'M)(N "5O)(P  "!=Q)($X |,K<UW[1\&|4 ]C!#t,,8+.7762+>+Y#''*VXBY,/GGIr,B*-'',*?(+
(;/2ww7H"/M/kQTQXQXYfhjQk038JB0O) 11EFMMm\] {+
;{;SUY0Z&{3 \C!#t,,8&)ggh&;.1ggmR.H.1gg6F.K*-'',*?(+
(;/2ww7H/I%(WWW%5	) 11DELL][\ /0ZL\@]_c5d'(89 aD!$-,8040D0]QXZ\H].2hhx.D.dQ_acHd*.((<*@+/88M+B-1XXo-F#'88E?* 11HIPPQ_`a z*z+j:QSW/X'
3 [D!$-,8-1XXgr-B-ZdhhvWYFZ15."1M1hQUQYQYZcegQh3788M23N+/88E2+>*.((<*@(,(<,0HH^R,H	* 11BCJJ>Z[ KKKL>Z[++ 	LLF|nTVWZ[\W]V^_`!#&(%')+"$#%$&!#%'%'(*$"5!,"!= 	s;   JW' C"W' +C	W' 5C W' CW' '	Y0AY	Y	Yr   )r   )#__name__
__module____qualname____doc__r   r2   r   r   r5   r   r   r;   r.   r9   r   rD   r   r   rE   r   r   rF   rX   r   r   r   rH   r  rV   rY   r  r  r  r  r  r   r   r   r   r      s   U& S  T#s(^  D"C "PTUXPY "^bcfhkck^l "H-`3 -`^Fo3 FoW[\_W` FoPh3 h8DcN3K hT6T#s(^ 6dhildm 6y}  C  DG  IL  DL  M  zN 6p6DcN 6cghkcl 6x|  ~B  CF  HK  CK  ~L  yM 6p?d38n ?d3PS8nAU ?B%tCH~ %# %6W4: WW[ W`deijmorjres`t Wr'S#X 'SWX[]`X`Sa 'fnostwy|t|o}f~ 'R c3h8P &D4S> DSWX[]`X`Sa Duyz}  @C  {C  vD D  IL DL< <% <TXY\^aYaTb <||# |$sCx. |\`adfiai\j ||%c %d %N|3 |4PSUXPX> |eijmorjres ||Pc PQT Pcghkmphpcq P  CF P  OS  TW  Y\  T\  O] Pd| |C |[_`ceh`h[i |z} |  PT  UX  Z]  U]  P^ ||+tCH~ +_cdgildl_m +z~  @C  EH  @H  {I +  NV  W[  \_  ad  \d  We  Nf +ZDS DTRUWZRZ^ DLI4S> IY\ Iaefiknfnao Ir   r   )asyncior,   typingr   r   r   r   loggingr   r   pymongodotenvr   bson.codec_optionsr	   r
   r   r   r   r   
embeddingsr   r   r   r   basicConfigINFO	getLoggerr  r1   r   r   r   r   <module>r     sm      , ,   	   ? * 1   ( !    ',, '			8	$D Dr   