
    fiuP                     |    S SK JrJrJrJr  S SKrS SKJr  S SKJ	r	J
r
Jr  S SKJr  S SKJr  S SKJr   " S S	5      rg)
    )DictListAnyOptionalN)RoleplayScenario)SkillAssessment
SkillScoreConversationTurn)GroqService)OllamaService)JSONHandlerc                      \ rS rSrS"S jrS\4S jrS\S\4S jr	S	\
\   S
\S\\\4   4S jrS\S\4S jrS\S
\S\4S jrS\\\4   S\4S jrS\
\   S\S\
\   4S jr S#S\S
\S	\
\   S\S\S\\   4S jjrS\S
\S	\
\   S\S\\   4
S jrS\S\4S jrS\S\S\\   4S jrS\S\
\\\4      4S jrS\
\   S\\\4   4S jrS\
\   S\4S  jrS!rg)$SkillAnalyzer	   Nc                     U=(       d
    [        5       U l        U=(       d
    [        5       U l        [	        5       U l        g N)r   groq_servicer   ollama_servicer   json_handler)selfr   r   s      I/var/www/eduai.edurigo.com/roleplay/production/services/skill_analyzer.py__init__SkillAnalyzer.__init__
   s*    (9KM,?'M    use_groqc                 @    U(       a  U R                   $ U R                  $ )z9Get the appropriate AI service based on groqRoleplay flag)r   r   )r   r   s     r   _get_ai_serviceSkillAnalyzer._get_ai_service   s    $,t  E$2E2EEr   	score_valreturnc                    Uc  g[        U[        5      (       a  U$ [        U[        5      (       a  [        U5      $ [        U[        5      (       a  UR	                  5       R                  5       nU(       d  gSU;   a5   [        [        UR                  S5      S   R	                  5       5      5      $ SU;   aC   [        [        UR                  5       R                  S5      S   R	                  5       5      5      $  [        R                  " SU5      nU(       a#  [        [        UR                  S5      5      5      $  gg! [        [        4 a     Nf = f! [        [        4 a     Nmf = f! [        [        4 a     gf = f)z.Robustly parse a score value from AI response.r   /z out of z(\d+\.?\d*)   )
isinstanceintfloatstrstriplowersplit
ValueError
IndexErrorresearchgroup	TypeError)r   r   smatchs       r   _parse_scoreSkillAnalyzer._parse_score   sQ   i%%i''y>!i%%!'')A axuQWWS\!_%:%:%<=>>
 QuQWWY__Z%@%C%I%I%KLMM
		.!4uU[[^455 
 % #J/  #J/  	* s8   >3E  9AE <A E,  EEE)(E),E?>E?conversation_turnsscenarioc                    U Vs/ s H  o3R                   S:X  d  M  UPM     nn[        U5      nSR                  S U 5       5      nUR                  5       (       a  [        UR	                  5       5      OSnUS:  a  Xu-  OSnUS:  a  U R                  U5      OSn	U R                  Xb5      n
US:X  a  SnSnOfUS:X  a  U	(       a  SnS	nOTUS:X  a  US
:  a  U
S:  a  SnS	nO=US	::  a  US:  a  U
S:  a  SnSnO&US::  a  U
S:  a  SnSnOUS:  a  U
S:  a  SnSnOSnSnUU[        US5      U	[        U
S	5      UUS.n[        SU 35        U$ s  snf )zp
Analyze the depth and quality of the learner's participation.
Returns engagement metrics and a hard score cap.
learner c              3   T   #    U  H  oR                   R                  5       v   M      g 7fr   )messager(   ).0ts     r   	<genexpr><SkillAnalyzer._analyze_conversation_depth.<locals>.<genexpr>I   s     #M}!IIOO$5$5}s   &(r   Tnoner#   minimal      g?   g?low   g?moderate      g      ?	excellent
   good   )learner_turn_counttotal_learner_wordsavg_words_per_turnis_only_greetingobjective_coverageengagement_levelmax_allowed_scorez$DEBUG: Conversation depth analysis: )	speakerlenjoinr(   r*   _is_greeting_only_estimate_objective_coverageroundprint)r   r5   r6   r=   learner_turnsrN   all_learner_textrO   rP   rQ   rR   rS   rT   depth_analysiss                 r   _analyze_conversation_depth)SkillAnalyzer._analyze_conversation_depth?   s    %7Q$6q))y:P$6Q / 88#M}#MM?O?U?U?W?Wc"2"8"8":;]^K]`aKa1Fgh HZ\]G]4112BCcg ">>?OZ "% !1$)9( !1$)<r)AFX[^F^( !1$)<r)AFX[^F^$ !1$);c)A) !1$);s)B* "% ! #5#6"'(:A"> 0"'(:A"> 0!2
 	4^4DEFa Rs
   EEtextc                 V  ^ UR                  5       R                  5       m[        R                  " SST5      m/ SQn[	        TR                  5       5      nUS:  a  g[        U4S jU 5       5      n/ SQn[        U4S jU 5       5      nUS	:  a  US
:X  a  US::  a  gUS:  a  US	:  a  gg)z
Determine whether the learner's combined text is essentially
just a greeting / self-introduction with no substantive content.
z[^\w\s] )z>^(hi|hello|hey|good\s+(morning|afternoon|evening)|greetings)\bz\b(my name is|i am|i\'m)\s+\w+z?\b(here to help|here to assist|how can i help|how may i help)\bz*\b(nice to meet|pleased to meet|welcome)\b   Fc              3   d   >#    U  H%  n[         R                  " UT5      (       d  M!  S v   M'     g7fr#   Nr-   r.   r<   pcleaneds     r   r>   2SkillAnalyzer._is_greeting_only.<locals>.<genexpr>   s"     U*;QryyG?Tqq*;    0	0)z^\b(check|verify|confirm|look into|investigate|resolve|arrange|schedule|offer|provide|ensure)\bzL\b(room|reservation|booking|account|order|issue|problem|request|complaint)\bz9\b(available|status|update|information|details|options)\bc              3   d   >#    U  H%  n[         R                  " UT5      (       d  M!  S v   M'     g7frf   rg   rh   s     r   r>   rk      s"     S(91RYYq'=RQQ(9rl   r#   r      TrM   )r)   r(   r-   subrV   r*   sum)r   ra   greeting_patterns
word_countgreeting_matchesaction_indicatorsaction_matchesrj   s          @r   rX   SkillAnalyzer._is_greeting_onlyw   s    
 **,$$&&&R1
 )
? U*;UU

 S(9SS q ^q%8Z2=M >.!3r   learner_textc                   ^ UR                  5       (       d  gUR                  5       mUR                  R                  5       n[        R                  " SU5      nU Vs/ s H2  n[        UR                  5       5      S:  d  M"  UR                  5       PM4     nnU(       d  gSnU H  n1 SknUR	                  5        V	s/ s H  oU;  d  M
  [        U	5      S:  d  M  U	PM     n
n	U
(       d  MH  [        U4S jU
 5       5      nU[        S[        U
5      S	-  5      :  d  M{  US-  nM     U(       a  U[        U5      -  OSn[        US
5      $ s  snf s  sn	f )zv
Estimate what fraction of the scenario objectives the learner's text addresses.
Returns a float between 0.0 and 1.0.
        z[,&\n]+rF   r   >#   aanatbebydoinisofonortoandarebutcandidforhadhasmaythewasbeendoeshavewerewillwithbeingcouldmightshallwouldshouldrB   c              3   6   >#    U  H  oT;   d  M
  S v   M     g7frf    )r<   kwlearner_lowers     r   r>   =SkillAnalyzer._estimate_objective_coverage.<locals>.<genexpr>   s     J(BM6Iaa(s   		r#   g333333?      ?)	r(   r)   	objectiver-   r*   rV   rp   maxmin)r   rw   r6   objective_textsub_objectivesr1   addressed_countsub_obj
stop_wordswkeywordsmatchescoverager   s                @r   rY   *SkillAnalyzer._estimate_objective_coverage   s.   
 !!##$**, "++113*n=-;R^s1779~PQ?Q)!'')^R %GCJ $+==?Y?az6IcRSfWXj?HYxJ(JJc!S]S%899#q(O & =K?S%88PS8S!!- S Zs   !EE=	E
EEr^   c                     SUS    SUS    SUS    SUS   (       a  S	OS
 S[        US   S-  5       SUS   R                  5        SUS    S3$ )zV
Build a human-readable engagement metadata string for injection into the LLM prompt.
z<--- CONVERSATION ENGAGEMENT SUMMARY ---
Learner Turn Count: rN   z
Total Learner Words: rO   z
Average Words Per Turn: rP   z
Learner Response Type: rQ   z"Generic greeting/introduction ONLYzContains substantive contentz
Objective Coverage: rR   d   z%
Engagement Level: rS   z
Maximum Allowed Score: rT   z"/10
--- END ENGAGEMENT SUMMARY ---)r%   upper)r   r^   s     r   _build_engagement_metadata_text-SkillAnalyzer._build_engagement_metadata_text   s    
##12F#G"H I$$23H$I#J K''56J'K&L M&N\]oNp&J  wU  &V V##&~6J'Kc'Q#R"S T!!/0B!C!I!I!K L M&&45H&I%J K-.
	
r   skill_scoresrT   c                     / nU H  nUR                   n[        XR5      nXe:w  a"  [        SUR                   SU SU SU S3	5        UR	                  [        UR                  UUR                  UR                  UR                  S95        M     U$ )zw
Clamp every skill score to the engagement-based maximum.
Returns a new list of SkillScore objects with capped scores.
zDEBUG: Score capped for 'z': u    → z (max allowed: )
skill_namescoreevidence	strengthsimprovement_areas)	r   r   r[   r   appendr	   r   r   r   )r   r   rT   capped_scoresssoriginal_scorecapped_scores          r   _apply_score_capSkillAnalyzer._apply_score_cap   s    
 BXXN~AL-1"--NCSSXYeXffu  wH  vI  IJ  K  L  ==",,"$"6"6"   r   
session_idis_adminc                   #    U Vs/ s H  ofR                   S:X  d  M  UPM     nnU(       d  U R                  XX45      $ U R                  X25      nUS   S:X  a  U R                  XX45      $ U R                  U5      n	U Vs/ s H  nUR                   UR                  S.PM     n
nU R                  U5      nUR                  UR                  5       X5      I Sh  vN nU(       d  g / nUR                   H  nXS   ;   d  M  US   U   n[        UU R                  UR                  SS5      5      UR                  S	/ 5      UR                  S
/ 5      UR                  S/ 5      S9nUR                  U5        M     US   nU R                  UU5      nU(       a  [        S U 5       5      [        U5      -  nOSn[!        U[#        U5      5      nU R%                  U5      n['        SU SUS    SU S35        [(        R*                  " UUR,                  [/        US5      UUUUR                  S0 5      UR                  S0 5      S9n['        SU 35        U R0                  R3                  UR                  5       U5      (       a  ['        S5        U$ ['        S5        gs  snf s  snf  GN! [4         a,  n['        SU 35        SSKnUR9                  5          SnAgSnAff = f7f)z;Perform comprehensive skill analysis on a completed sessionr8   rS   r@   )rU   r;   Nskill_analysisr   r   r   r   r   r   rT   c              3   8   #    U  H  oR                   v   M     g 7fr   )r   )r<   r   s     r   r>   0SkillAnalyzer.analyze_session.<locals>.<genexpr>/  s     #J\EKK\s   ry   zDEBUG: Final overall score: z (engagement: z, cap: r   r#   conversation_analysisrecommendationsr   scenario_idoverall_scoreperformance_levelr   r5   r   r   z1DEBUG: Attempting to save assessment for session z$DEBUG: Assessment saved successfullyz DEBUG: Failed to save assessmentz"Error processing analysis result: )rU   _create_zero_assessmentr_   r   r;   r   analyze_skillsto_dictskills_to_assessr	   r3   getr   r   rp   rV   r   r&   _determine_performance_levelr[   r   
create_newr   rZ   r   save_assessment	Exception	traceback	print_exc)r   r   r6   r5   r   r   turnr\   r^   engagement_metadataconversation_data
ai_serviceanalysis_resultr   r   
skill_dataskill_scoremax_allowedr   r   
assessmenter   s                          r   analyze_sessionSkillAnalyzer.analyze_session   s    
 +=Z*<$PY@Y*<Z//
FXcc 99:LW ,-7//
FXcc #BB>R +

 +  <<<< + 	 
 ))(3
 * 9 9 1!
 
 :	L&77
1A!BB!01A!B:!NJ",#-"//
w0JK!+
B!?",..b"A*4..9Lb*Q#K !''4 8 ))<=K00{KL  ##J\#J JSQ]M^ ^ #  u[/ABM !% A A- P0~n]oNpMqqx  zE  yF  FG  H  I )33%$//#M15"3)#5&5&9&9:QSU&V / 3 34Er J	J Ej\RS  001C1C1ExPP<>!!89m ["

|  	6qc:;i113	sj   K#JJAK# #J"#6K#J'K#'J* FJ* K#J* K#*
K 4"KK#K  K#c                 x   / nUR                    H#  n[        USS// S/S9nUR                  U5        M%     [        R                  " UUR
                  SSUU/ / S/S.S	/S
// S.S9n[        SU 35        U R                  R                  UR                  5       U5      (       a  [        S5        U$ [        S5        g)zBCreate assessment with zero scores when learner didn't participater   z1No learner responses detected in the conversationz1Participate actively in the roleplay conversationr   ry   zNo Participationz7Complete lack of participation in the roleplay scenario)r   critical_momentsmissed_opportunitiesz-Start participating in roleplay conversationsz/Begin with simple greetings and basic responses)immediate_focuspractice_suggestionsadvanced_skillsr   zIDEBUG: Creating zero assessment for non-participating learner in session z)DEBUG: Zero assessment saved successfullyz%DEBUG: Failed to save zero assessmentN)
r   r	   r   r   r   r   r[   r   r   r   )	r   r   r6   r5   r   r   r   r   r   s	            r   r   %SkillAnalyzer._create_zero_assessmentU  s    
 "33J$%MN#V"WK , 4 %//! ++0%1$&)b(c# %T#T)Z([#%

( 	YZdYefg,,Z-?-?-A8LL=?9:r   r   c                 <    US:  a  gUS:  a  gUS:  a  gUS:  a  gg	)
z2Determine performance level based on overall scoreg      "@Expertg      @Advancedg      @Intermediateg      @BeginnerzNeeds Improvementr   )r   r   s     r   r   *SkillAnalyzer._determine_performance_level  s1    Cc!c!!c!&r   c                 t    U R                   R                  X5      nU(       a  [        R                  " U5      $ g)zLoad an existing assessmentN)r   load_assessmentr   	from_dict)r   r   r   assessment_datas       r   r   SkillAnalyzer.load_assessment  s/    ++;;JQ",,_==r   c                 8    U R                   R                  U5      $ )zList all assessments)r   list_assessments)r   r   s     r   r   SkillAnalyzer.list_assessments  s      11(;;r   assessmentsc                 X   U(       d  0 $ 0 n/ nU Hc  nUR                  UR                  5        UR                   H5  nUR                  nXb;  a  / X&'   X&   R                  UR                  5        M7     Me     0 nUR                  5        HS  u  ph[        [        U5      [        U5      -  S5      [        U5      US   [        U5      S:  a  US   US   -
  OSS.Xv'   MU     [        U5      [        [        U5      [        U5      -  S5      [        U5      US   UU R                  U5      S.$ )z7Generate summary statistics across multiple assessmentsr#   r   )average_score
best_scorelatest_scoreimprovement)total_sessionsoverall_averagebest_overalllatest_overallskill_breakdownperformance_trend)r   r   r   r   r   itemsrZ   rp   rV   r   _calculate_trend)	r   r   r   overall_scoresr   r   r   skill_averagesscoress	            r   get_skill_summarySkillAnalyzer.get_skill_summary  s+   I 
%J!!*":":;)66(33
/-/J*&--k.?.?@	  7 & ","2"2"4J!&s6{S['@!!D!&k &r
9<VqvbzF1I5a	*N& #5 "+.$S%83~;N%NPQR/,R0-!%!6!6~!F
 	
r   r  c                     [        U5      S:  a  g[        USS 5      [        USS 5      -  n[        USS 5      [        USS 5      -  nX#-
  nUS:  a  gUS:  a  g	g
)zCalculate performance trendrB   zInsufficient dataNrF   r   	Improvingg      	DecliningStable)rV   rp   )r   r  
recent_avg	early_avgr  s        r   r  SkillAnalyzer._calculate_trend  so    v;?&%F23K(88
r
Oc&!*o5	 ,4r   )r   r   r   )NN)T) __name__
__module____qualname____firstlineno__r   boolr   r   r%   r3   r   r
   r   r   r'   r_   rX   r&   rY   r   r	   r   r   r   r   r   r   r   r   r  r  __static_attributes__r   r   r   r   r   	   s   *F F%c %c %V6dCS>T 6/?6DHcN6p*c *d *X$" $"HX $"]b $"L
d38n 
QT 
 T*-= RU Z^_iZj 6 dh` `?O `+/0@+A`MP`\``lt  vE  mF`D+# +AQ +378H3I+UX+]efu]v+Z'% 'C '#  /AZ < <d38n1E <#
T/-B #
tCQTH~ #
JtE{ s r   r   )typingr   r   r   r   r-   models.scenarior   models.assessmentr   r	   r
   services.groq_servicer   services.ollama_servicer   utils.json_handlerr   r   r   r   r   <module>r%     s,    , , 	 , K K - 1 *E Er   