
    u2i                        d dl mZmZmZmZmZ d dlmZ d dlm	Z	 d dl
mZmZmZmZmZmZ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 d d
lmZ d dlm Z  d dl!m"Z" d dl#m$Z$ d dl%m&Z& d dl'm(Z( d dl)m*Z*m+Z+ d dl,m-Z- d dl.m.Z. d dl/Z/d dl0Z0d dl1Z1d dl2Z2 e2jf                  e4      Z5d Z6 G d de	      Z7 e       Z8 e        Z9 e"       Z: e       Z; e       Z< e       Z= e       Z> e       Z? e       Z@ e$       ZA e&       ZB e(       ZCe8j                  de      defd       ZEe8j                  de      defd       ZFe8j                  de       ed       ed      fde*e   d eGfd!       ZHe8j                  d"      dId#eIfd$       ZJe8j                  d%      dJd&e+eI   fd'       ZLe8j                  d(      d)eGfd*       ZMe8j                  d+      d)eGfd,       ZOe8j                  d-      d)eGfd.       ZQe8j                  d/      d0        ZRe8j                  d1       ed       ed       ed       ed       ed       ed2      fd3eGd4eGd5eGd6eGd7eId8eIfd9       ZSe8j                  d:      dKd;eGd<eId=eTfd>       ZUe8j                  d?      dKd;eGd<eId=eTfd@       ZVe8j                  dA      d eGfdB       ZWe8j                  dC      d eGde7fdD       ZXe8j                  dEe      defdF       ZYe8j                  dG      dH        ZZy)L    )FastAPI
UploadFileFileHTTPExceptionForm)JSONResponse)	BaseModel)JobQueryRequestSearchResponseChatRequestChatResponseResumeParseResponseResumeParseRequestOfferLetterRequestOfferLetterResponseEmbedder)QdrantSearchService)OpenSearchService)MongoResumeService)HybridRanker)ExplainService)	AIService)ResumeParserService)JobMatchingService)ResumeUploadService)OfferLetterService)ListOptional)ObjectId)datetimeNc                 ^   t        | t              rt        |       S t        | t              r| j	                         S t        | t
              r-| j                         D ci c]  \  }}|t        |       c}}S t        | t              r| D cg c]  }t        |       c}S | S c c}}w c c}w )zJConvert MongoDB ObjectIds and datetime objects to JSON serializable format)	
isinstancer    strr!   	isoformatdictitemsserialize_documentlist)objkvitems       (/var/www/html/drjob-dev/drjob-ai/main.pyr(   r(      s    #x 3x	C	"}}	C	58YY[ATQ%a((AA	C	589T"4(99
	 B9s   B$B*c                       e Zd ZU ee   ed<   y)SpecificCandidatesRequestjobseeker_idsN)__name__
__module____qualname__r   int__annotations__     r.   r0   r0   (   s    9r8   r0   z/search)response_modelrequestc                    t         j                  | j                        }t        j	                  |d      }t
        j	                  | j                  d      }t        j                  ||      }t        j                  |D cg c]  }|j                   c}      }t        j                  ||| j                        }t        |      S c c}w )N2   top_k)results)embedderembedjob_descriptionqdrant_servicesearchopensearch_servicerankercombinemongo_serviceget_resumes	resume_idexplainadd_explanationsr   )r:   query_vectorvector_resultskeyword_resultsrankedrresumesenricheds           r.   search_resumesrT   :   s     >>'"9"9:L $**<r*BN )//0G0Gr/RO ^^NO<F ''f(E(EFG ''9P9PQH(++ )Fs   Cz/ai-chatc                     d}t         j                  | j                  | j                  |      }t	        | j                  | j                  |      S )Ni  )questionsystem_promptanswer)
ai_serviceai_responserV   rW   r   )r:   
max_tokensresponses      r.   chat_with_air]   Q   sK     J%%g&6&68M8MzZH!!++ r8   z/parse-resume.files
job_emp_idc                    K   | st        dd      |r|j                         dk(  rt        dd      t        j                  |j                               }|j	                  d      st        d|j	                  d            g }| D ]7  }|j                          d{   }|j                  ||j                  f       9 t        j                  |      }|j	                  d      st        d|j	                  d            t        j                  ||j                               }|j	                  d	      st        d
d|j	                  d             t        dt        |       |D 	cg c]  \  }}	|	ddd c}	}      S 7 c c}	}w w)an  
    Upload, parse and store resume files with background AI analysis
    
    This endpoint:
    1. Validates job_emp_id immediately
    2. Validates file types immediately 
    3. Returns success immediately if validation passes
    4. Processes AI analysis in background (strengths, weaknesses, matching_percentage)
    5. Stores results in screenings collection with type=3
    
    Args:
        files: List of uploaded files (PDF or Word documents)
        job_emp_id: The employer job ID (form field)
    
    Returns:
        ResumeParseResponse: Immediate success response - AI analysis happens in background
    i  zNo files uploaded)status_codedetail *job_emp_id is required and cannot be emptyvaliderrorNsuccessi  zError starting processing: Tprocessing_startedz:File validation passed. AI analysis started in background.)filenamestatusmessage)rg   total_filesr?   )r   stripresume_upload_servicevalidate_job_emp_idgetreadappendri   validate_file_typesprocess_resume_uploads_asyncr   len)
r^   r_   job_validation
files_datafilefile_contentfile_validationprocessing_result_ri   s
             r.   parse_resume_filesr}   ]   s    & 4GHH))+r14`aa +>>z?O?O?QRNg&N4F4Fw4OPP J 9!YY[(<789 ,??
KOw'O4G4G4PQQ .JJ:WaWgWgWij  +6QRcRgRghoRpQq4rss J  *
 8	 %.W
  )"
s%   BFFCF-F=
FFz/index/reset-for-testingcountc                    K   	 ddl m} |j                  d      j                  ddddddii      }dd	|j                  | d
S # t
        $ r}dt        |      dcY d}~S d}~ww xY ww)z
    Reset indexed status for a few jobseekers for testing purposes
    
    Args:
        count: Number of jobseekers to reset (default: 10)
    
    Returns:
        dict: Reset operation results
    r   rH   
jobseekers   rj   indexedz$setr   Tz Reset indexed status for testing)rg   rk   reset_countrequested_countFrg   rf   N)services.mongo_servicerH   get_collectionupdate_manymodified_count	Exceptionr$   )r~   rH   resultes       r.    reset_indexed_status_for_testingr      s     
8 --l;GGQ'i^$
 9!00$	
 	
  
V
 	

s4   A'>A A'	A$AA$A'A$$A'z/index/bulk	max_totalc                     K   	 t           fd}t        j                  |d      j                          dd du ddS # t        $ r}dt        |      d	cY d}~S d}~ww xY ww)
a  
    Bulk index all unindexed jobseekers (status=1, indexed=0) to OpenSearch
    Runs in background to avoid blocking other API calls
    
    Args:
        max_total: Maximum number of jobseekers to index 
                  - If None: Process ALL unindexed jobseekers (suitable for cron)
                  - If number: Limit processing for testing
    
    Returns:
        dict: Immediate response - indexing runs in background
    c                     	 t         j                  d        t        j                        } t         j                  d|         y # t        $ r+}t         j                  dt        |              Y d }~y d }~ww xY w)Nz1Starting background bulk indexing with max_total=)r   z$Background bulk indexing completed: z!Background bulk indexing failed: )loggerinforE   bulk_index_jobseekersr   rf   r$   )r   r   r   s     r.   background_bulk_indexingz4bulk_index_resumes.<locals>.background_bulk_indexing   sr    KOPY{[\+AAIAVB6(KL K@QIJJKs   AA
 
	A>!A99A>T)targetdaemonz#Bulk indexing started in backgroundNz-Check logs for progress and completion status)rg   rk   processed_allnoteFr   )y	threadingThreadstartr   r$   )r   r   r   s   `  r.   bulk_index_resumesr      ss     
		K 	 8FLLN <&$.C	
 	
  
V
 	

s1   A"8> A"	AAAA"AA"z/index/single/{jobseeker_id}jobseeker_idc                    K   	 t         j                  |       }|rdd|  dS dd|  dS # t        $ r}dt        |      dcY d}~S d}~ww xY ww)z
    Index a single jobseeker resume to OpenSearch
    
    Args:
        jobseeker_id: The jobseeker ID to index
        
    Returns:
        dict: Success status and message
    Tz Successfully indexed jobseeker: rg   rk   FzFailed to index jobseeker: r   N)rE   
add_resumer   r$   r   rg   r   s      r.   index_single_resumer      ss     
$//==l^L  !6|nE   
V
 	

9   A+ A+ A	AAAAAAz/index/reindex/{jobseeker_id}c                    K   	 t         j                  |       }|rdd|  dS dd|  dS # t        $ r}dt        |      dcY d}~S d}~ww xY ww)z
    Reindex a specific jobseeker resume (reset indexed status and re-index)
    
    Args:
        jobseeker_id: The jobseeker ID to reindex
        
    Returns:
        dict: Success status and message
    Tz"Successfully reindexed jobseeker: r   FzFailed to reindex jobseeker: r   N)rE   reindex_jobseekerr   r$   r   s      r.   reindex_resumer     ss     
$66|D?~N  !8G   
V
 	

r   z/index/{jobseeker_id}c                    K   	 t         j                  |       }|rdd|  dS dd|  dS # t        $ r}dt        |      dcY d}~S d}~ww xY ww)z
    Delete a jobseeker resume from OpenSearch index
    
    Args:
        jobseeker_id: The jobseeker ID to delete from index
        
    Returns:
        dict: Success status and message
    Tz!Successfully deleted from index: r   FzFailed to delete from index: r   N)rE   delete_resumer   r$   r   s      r.   delete_from_indexr   !  ss     
$22<@>|nM  !8G   
V
 	

r   z/index/statusc                  L  K   	 ddl m}  | j                  d      }|j                  ddi      }|j                  ddd      }|j                  dddd	iidd
iddigd      }|dkD  r||z  dz  nd}|||t	        |d      dS # t
        $ r}dt        |      icY d
}~S d
}~ww xY ww)zl
    Get current OpenSearch indexing status
    
    Returns:
        dict: Indexing status with counts
    r   r   r   rj   r   r   r   z$existsFN)rj   z$ord      )total_active_jobseekersindexed_countunindexed_countcompletion_percentagerf   )r   rH   r   count_documentsroundr   r$   )rH   jobseeker_collectiontotal_activer   r   r   r   s          r.   get_indexing_statusr   ?  s     
8,;;LI+;;XqMJ,<<VW=XY />>Y./D!A@
  IUWXHX!=!C^_ (4*.%*+@!%D	
 	
  
SV
 	

s5   B$A<B  B$	B!
BB!B$B!!B$z/search/advancedr<   querycountry_codeskills	educationmin_experiencer>   c                 R  K   	 ddl m} i }|r!|j                         j                         |d<   |rA|j	                  d      D cg c]#  }|j                         s|j                         % c}|d<   |r|j                         |d<   |r
|dkD  r||d<    |       }	|	j                  | ||      }
g }|
j                  dg       D ](  }|j                  d	i       }|j                  |j                  d
      |j                  dd      |j                  dd      |j                  dd      |j                  dd      |j                  dd      |j                  dd      |j                  dg       |j                  dd      |j                  dd      |j                  dd      t        |j                  dd            dkD  r|j                  dd      dd dz   n|j                  dd      d       + dt        |      ||dS c c}w # t        $ r}dt        |      g dcY d}~S d}~ww xY ww)a  
    Advanced resume search with filtering by country, skills, education, experience
    
    Args:
        query: Job description or search query
        country_code: Country header code to filter by (e.g. 'ae', 'bd', 'in')
        skills: Comma-separated list of skills to filter by
        education: Education level to filter by
        min_experience: Minimum years of experience
        top_k: Number of results to return (default: 50)
    
    Returns:
        SearchResponse: Filtered search results with detailed candidate info
    r   r   country_header_code,r   r   min_experience_yearsr   payloadrJ   score
first_namerc   	last_nameemailcountry_namecurrent_designationhighest_educationexperience_yearsprofile_summary   N...)rJ   r   r   r   r   r   r   r   r   r   r   r   T)rg   total_resultsfilters_appliedr?   F)rg   rf   r?   )services.embeddingsr   rm   lowersplitsearch_with_filtersrp   rr   ru   r   r$   )r   r   r   r   r   r>   r   filtersskillr@   r?   formatted_resultsr   r   r   s                  r.   search_resumes_advancedr   h  sF    .1
0 -9-?-?-A-G-G-IG)*<BLL<M _5QVQ\Q\Q^ _GH#,??#4GK nq0.<G*+ :..uguE kk(B/ 	FjjB/G$$$[[5GQ/%kk,;$[[b9 Wb1 'NB ?'.{{3H"'M!++h3'.{{3H"'M%,[[1Db%I$+KK0BA$FWZ[b[f[fgxz|[}W~  BE  XE7;;/@"#Eds#Ke#S  KR  KV  KV  Wh  jl  Km& 	$  !23&(	
 	
; !`H  
V
 	

sN   H'A H G=G=,FH <H'=H 	H$HH$H'H$$H'z/quick-search/{search_text}search_textlimitmin_thresholdc           	        K   	 t         j                  | |      }|sd| dg dS d}|D cg c]  }|d   |k\  s| }}|sd| ddg dS t        |D cg c]  }|d   	 c}      }g }|D ]E  }|d   |z  d	z  }	|	|d	z  k\  s|j                  |d
   t	        |d   d      t	        |	d      d       G d| |d	z   dt        |      |dS c c}w c c}w # t        $ r}
dt        |
      dcY d}
~
S d}
~
ww xY ww)aX  
    Quick search endpoint - just provide search text in URL with minimum matching threshold
    
    Args:
        search_text: Text to search for
        limit: Number of results (default: 10)
        min_threshold: Minimum match threshold (default: 0.5 = 50%)
    
    Returns:
        dict: Simple search results with match percentages
    r=   Tr   )rg   r   total_foundr?   333333?keyword_scorez4No relevant matches found. Search terms too generic.)rg   r   rk   r   r?   r   rJ      r   )rJ   r   match_percentage%)rg   r   threshold_appliedr   r?   Fr   N)rE   rD   maxrr   r   ru   r   r$   )r   r   r   r?   absolute_min_scorerQ   relevant_results	max_scorefiltered_resultsr   r   s              r.   quick_searchr     si    5
$++Ku+E$ 	  ! (/[!!O2DHZ2ZA[[$Q   5EF?+FG	 ! 	A !/ 2Y >#EMC$78 ''!";"1_#5q9(-.>(B) 		  $1#$5#6a!8/0'
 	
5 \ G,  
V
 	

sr   C5C C5C CCC C5	C C!C ?AC C5
C 	C2C-'C2(C5-C22C5z#/vector-search-simple/{search_text}c           
        K   	 t         j                  | |      }|j                  d      sd| ddg dS |d   }d}|D cg c]  }|j                  dd      |k\  r| }}|s	d| dd	dg d
S t        |D cg c]  }|j                  dd       c}      }g }	|D ]  }|j                  di       j                  d      }
|j                  dd      }||z  dz  }|
sA||dz  k\  sJ|	j	                  |
t        |d      t        |d      |j                  di       j                  dd      dd dz   d        d| d|dz   dt        |	      |	dS c c}w c c}w # t        $ r}dt        |      dcY d}~S d}~ww xY ww)a  
    Simple vector search endpoint - provide search text in URL for semantic search with minimum matching threshold
    
    Args:
        search_text: Text to search semantically
        limit: Number of results (default: 10)
        min_threshold: Minimum similarity threshold (default: 0.5 = 50%)
    
    Returns:
        dict: Simple vector search results with match percentages
    r=   r   Tvector_semanticr   )rg   r   search_typer   r?   r   r   z:No semantically relevant matches found. Query too generic.)rg   r   r   rk   r   r?   r   rJ   r   r   r   textrc   Nr   r   )rJ   similarity_scorer   matched_textr   )rg   r   r   r   r   r?   Fr   )	r@   search_with_qdrantrp   r   rr   r   ru   r   r$   )r   r   r   rN   raw_resultsabsolute_min_similarityr   r   max_similarityr?   rJ   r   r   r   s                 r.   vector_search_simpler   <  s    B
!44[4N!!(+$0   %X. #& "-
zz'1%)@@ 
 

  $0W   CSTfjj!4TU& 	F

9b155kBI%zz'15 !1> ASH--#2EF!*(-.>(B(-.>(B$*JJy"$=$A$A&"$Mds$SV[$[	  	   ,$1#$5#6a!8w<
 	
E
  U6  
V
 	

su   E9/E E9E EE )E9*	E 3EAE E !A)E 
E9
E 	E6E1+E6,E91E66E9z/job-matching/{job_emp_id}c                    K   	 | r| j                         sdddS t        j                  | j                               }|S # t        $ r}dt	        |      | dcY d}~S d}~ww xY ww)ab  
    Start job matching process for a specific job ID
    
    This endpoint immediately returns success and processes job matching in background:
    1. Fetches job details from third-party API
    2. Searches candidates using both OpenSearch and Vector search
    3. Filters candidates with 50%+ match threshold
    4. Generates AI analysis (strengths, weaknesses, match justification)
    5. Stores results in screenings collection
    
    Args:
        job_emp_id: The employer job ID to process
        
    Returns:
        dict: Immediate success response - actual processing happens in background
    Frd   r   rg   rf   job_idN)rm   job_matching_serviceprocess_job_matching_asyncr   r$   )r_   r   r   s      r.   start_job_matchingr     so     $
!1!1!3 E  &@@AQAQAST 
V 
 	

s=   A%A  A%$A  A% 	A"	AA"A%A""A%z./job-matching/specific-candidates/{job_emp_id}c                   K   	 | r| j                         sdddS |j                  rt        |j                        dk(  rdddS g }|j                  D ]$  }	 t        |      }|dkD  r|j	                  |       & |sdddS t        j                  | j                         |      }dd|  | t        |      |d	d
S # t
        t        f$ r Y xw xY w# t        $ r}dt        |      | dcY d}~S d}~ww xY ww)a  
    Analyze specific jobseeker IDs for a job position with background processing
    
    This endpoint:
    1. Takes a job ID and array of specific jobseeker IDs 
    2. Checks if jobseekers are indexed, indexes them if needed
    3. Generates AI analysis (strengths, weaknesses, match justification)
    4. Stores results in screenings collection with type=1
    5. Processes in background and returns immediately
    
    Args:
        job_emp_id: The employer job ID to process
        jobseeker_ids: Array of jobseeker IDs to analyze
        
    Returns:
        dict: Immediate success response - actual processing happens in background
    Frd   r   r   z3jobseeker_ids array is required and cannot be emptyzNo valid jobseeker IDs providedTz0Specific candidate analysis started for job ID: rh   )rg   rk   r   jobseeker_countr1   rj   r   N)rm   r1   ru   r5   rr   
ValueError	TypeErrorr   !process_specific_candidates_asyncr   r$   )r_   r:   	valid_idsjidvalid_idr   r   s          r.   analyze_specific_candidatesr    s2    &/
!1!1!3 E 
 $$G,A,A(Ba(G N  	(( 	Cs8a<$$X.		  :  &GG
HXHXHZ\ef I*V "9~&*
 	
 	* ,  
V 
 	

sz   C3C C3(C C3C !B97C ?C3 8C 8C39CC 
CC 	C0C+%C0&C3+C00C3z/generate-offer-letterc           	        K   	 | j                   | j                  | j                  | j                  | j                  | j
                  | j                  | j                  d}t        j                  |      }t        |d   |j                  d      |j                  d            S # t        $ r#}t        ddt        |             cY d	}~S d	}~ww xY ww)
a  
    Generate an offer letter using AI based on provided candidate and job information
    
    This endpoint:
    1. Takes candidate information (name, job title, department, etc.)
    2. Uses OfferLetterService to generate a professional offer letter
    3. Returns the generated offer letter text
    
    Args:
        request: OfferLetterRequest containing all required candidate and job details
        
    Returns:
        OfferLetterResponse: Generated offer letter or error message
    )candidate_name	job_title
departmentcompany_name
start_datesalaryemployment_typebenefitsrg   offer_letterrf   )rg   r  rf   FzError generating offer letter: r   N)r  r  r  r  r  r	  r
  r  offer_letter_servicegenerate_offer_letterr   rp   r   r$   )r:   candidate_datar   r   s       r.   r  r    s      
 &44 **!,,#00!,,nn&66((	
 &;;NK"9%N3**W%
 	
  
"3CF8<
 	

s5   CBB# "C#	C,C
CC
CCz/test-api-keysc                  P
  K   ddl } ddl}ddlm}  |        ddi d}	 ddlm}  |       rdd	d
|d   d<   nddd
|d   d<   	  | j                  d      } | j                  d      } | j                  d      }t        |||g      sddd
|d   d<   nOddl
m}	  |	|ddg||fd      }
|
j                  j                         }dd|j                  dd       d
|d   d<    | j                  d      } | j                  d      } | j                  d       } | j                  d!      }t        ||||g      s#dd"|rd#nd$|rd#nd$|rd#nd$|rd#nd$d%d&|d   d'<   n|d(d)}i }	 |} |j                  ||d*+      }|j                  d,k(  rd-|d.<   nd/|j                   |d.<   	 | d1| } |j                  ||d*+      }|j                  d,k(  r9|j!                         }d2|v r|d2   j                  d3d      }d4| d5|d6<   nBd7|d6<   n<|j                  d8k(  rd9|d6<   n'|j                  d:k(  rd;|d6<   nd/|j                   |d6<   	 |j#                  d<       d<} |j                  ||d*+      }|j                  d,k(  rE|j!                         }|j                  d2i       j                  d=g       }d4t%        |       d>|d?<   nd/|j                   |d?<   t        d@ |j'                         D              rdA}n%t)        dB |j'                         D              rdC}ndD}||||||r|ddE  dFndGdHdI|d   d'<   |d   j'                         D cg c]  }|d   	 }}t        dJ |D              rdK|dL<   nt)        dM |D              rdN|dL<   ndO|dL<   g }dP|d   j                  d'i       j                  ddQ      v r|j+                  dR       dP|d   j                  di       j                  ddQ      v r|j+                  dS       dP|d   j                  di       j                  ddQ      v r|j+                  dT       |s|j+                  dU       ||dV<   |S # t        $ r}dt        |      d
|d   d<   Y d}~3d}~ww xY w# t        $ r}dt        |      d
|d   d<   Y d}~d}~ww xY w# t        $ r}d0t        |       |d.<   Y d}~&d}~ww xY w# t        $ r}d0t        |       |d6<   Y d}~d}~ww xY w# t        $ r}d0t        |       |d?<   Y d}~Ed}~ww xY wc c}w w)Wz
    Test all API keys and configurations for Qdrant, OpenSearch, and MongoDB
    
    Returns:
        dict: Comprehensive test results for all services
    r   N)load_dotenvz2024-12-02T10:00:00zchecking...)	timestampoverall_statusservices)is_database_connectedu   ✅ ConnectedzDatabase connection successful)rj   detailsr  mongodbu   ❌ Connection FailedzCannot connect to MongoDBu	   ❌ ErrorOPENSEARCH_HOSTOPENSEARCH_USEROPENSEARCH_PASSWORDu   ❌ Configuration Missingz2OpenSearch credentials not complete in environment
opensearch)
OpenSearchi  )hostportT)hosts	http_authuse_sslzCluster status: rj   unknownQDRANT_API_URLQDRANT_SEARCH_URLQDRANT_API_KEYQDRANT_COLLECTION_NAMEz)Qdrant environment variables not completeu   ✅ Setu   ❌ Missing)r#  r$  r%  r&  )rj   r  configqdrantzapplication/json)zapi-keyzContent-Type
   )headerstimeoutr   u   ✅ Healthyhealthu   ❌ Status u   ❌ Error: /r   points_countu   ✅ Accessible (z points)
collectionu"   ✅ Accessible (structure unknown)i  u8   ❌ Collection 'resumes' not found - needs to be createdi  u   ❌ Access forbiddenz/collectionscollectionsz collections)collections_listc              3   $   K   | ]  }d |v  
 ywu   ✅Nr7   .0rj   s     r.   	<genexpr>z$test_all_api_keys.<locals>.<genexpr>  s     C6uC   u   ✅ Fully Functionalc              3   $   K   | ]  }d |v  
 ywr3  r7   r4  s     r.   r6  z$test_all_api_keys.<locals>.<genexpr>  s     EV&Er7  u   ⚠️ Partial Accessu   ❌ No Access   r   zNot set)api_url
search_urlr/  api_key_preview)rj   testsr'  c              3   $   K   | ]  }d |v  
 ywr3  r7   r4  s     r.   r6  z$test_all_api_keys.<locals>.<genexpr>  s     
=v5F?
=r7  u   ✅ All Systems Operationalr  c              3   $   K   | ]  }d |v  
 yw)   ❌Nr7   r4  s     r.   r6  z$test_all_api_keys.<locals>.<genexpr>  s     ?Uf_?r7  u   ❌ Critical Issues Detectedu   ⚠️ Some Issues Detectedr@  rc   u2   🔧 Update Qdrant API key with proper permissionsu2   🔧 Check OpenSearch credentials and connectivityu'   🔧 Verify MongoDB connection settingsu'   ✨ All services are working correctly!recommendations)osrequestsdotenvr  services.databaser  r   r$   getenvallopensearchpyr  clusterr,  rp   ra   jsonrstripru   valuesanyrr   ) rB  rC  r  test_resultsr  r   opensearch_hostopensearch_useropensearch_passwordr  clientr,  r#  r$  r%  r&  r*  qdrant_testsbase_urlhealth_responsecollection_urlcollection_responsecollection_infor.  collections_urlcollections_responsecollections_datar1  qdrant_statusserviceall_services_statusrA  s                                    r.   test_all_api_keysr_  '  s*     "M +'L
; ");3L$Y/ 263L$Y/ 
#"))$56#"))$56'bii(=>O_6IJK5O6L$\2 0 /=>*,?@F ^^**,F *-fjj9.M-NO6L$\2 RYY/0N!		"56RYY/0N&RYY'?@  1>CYZ[1B/=)=2CY/=)=7M)S`		.
Z * &.

 	<%H*hll8WbQO**c1)6X&+67R7R6S)TX&
	@ 12!4J3KLN".(,,~wXZ"["..#5"5":":"<.#28#<#@#@QR#SL3CL>QY1ZL.1UL.$00C7-g\*$00C7-C\*/:;N;Z;Z:[-\\*

	F!2!9!9.!I J,WO#/8<<Z\#] #//36#7#<#<#> #3#7#7"#E#I#I-Y[#\ 5EcJZF[E\\i3j/05@AUAaAa@b3c/0
 C\-@-@-BCC2ME|/B/B/DEE3M+M $!)/4BPnSb&9%:##>V_		.
Z * =I<T<[<[<]^78,^^

=)<
==)F%&	?+>?	?)G%&)F%& OZ(,,Xr:>>xLLSTZ(,,\2>BB8RPPSTZ(,,Y;??"MMHIHI&5L"#}  
!1v/
Z +
F  
-1v2
Z .
R  	<'23q6(%;L"	<(  	@+6s1vh)?L&	@  	F1<SVH/EL+,	F. _s   T&$Q BQ> #BT&%>R) $B S BS9 A8T&T!C;T&	Q;Q60T&6Q;;T&>	R&R!T&!R&&T&)	S2S	T&	ST&	S6S1+T&1S66T&9	TTT&TT&)r)  )N)r)  g      ?)[fastapir   r   r   r   r   fastapi.responsesr   pydanticr	   schemas.request_responser
   r   r   r   r   r   r   r   r   r   services.qdrant_servicer   services.opensearch_servicer   r   r   services.rankingr   services.explainr   services.ai_servicer   services.resume_parserr   services.job_matching_servicer   services.resume_upload_servicer   services.offer_letter_servicer   typingr   r   bsonr    r!   r   rB  rC  logging	getLoggerr2   r   r(   r0   apprY   resume_parserr@   rC   rE   rH   rF   rK   r   rn   r  postrT   r]   r$   r}   r5   r   rp   r   r   putr   deleter   r   r   floatr   r   r   r  r  r_  r7   r8   r.   <module>rw     s   B B *  r  r  r ( 7 9 5 ) + ) 6 < > < !    	   
		8	$	  i[
#%:$&&( "$	

)+ +- )+  )N3,O , 4,, *\2+  3 /*=>7;CyTXY\T] 9D$4 9c 9 ?9x 
$%
# 
 &
B %
 %
 %
P 
()
C 
 *
: 	()
s 
 *
: #$
# 
 %
: %
 %
P 
cT
t*$Zt*bG
G
G
 G
 	G

 G
 G
 G
Z 	&'A
C A
 A
 A
 (A
J 	./N
C N
 N
QV N
 0N
b 
&'"
 "
 ("
J 
:;A
# A
@Y A
 <A
J 
"3FG)
); )
 H)
X 	~ ~r8   