o
    iuP                     @   sv   d dl mZmZmZmZ d dlZd dlmZ d dlm	Z	m
Z
mZ d dlmZ d dlmZ d dlmZ G dd	 d	ZdS )
    )DictListAnyOptionalN)RoleplayScenario)SkillAssessment
SkillScoreConversationTurn)GroqService)OllamaService)JSONHandlerc                   @   s  e Zd Zd0ddZdefddZdedefd	d
Zde	e
 dedeeef fddZdedefddZdededefddZdeeef defddZde	e dede	e fddZ	d1dedede	e
 dededee fdd Zdedede	e
 dedee f
d!d"Zd#edefd$d%Zdededee fd&d'Zdede	eeef  fd(d)Zd*e	e deeef fd+d,Zd-e	e defd.d/ZdS )2SkillAnalyzerNc                 C   s$   |pt  | _|p
t | _t | _d S N)r
   groq_servicer   ollama_servicer   json_handler)selfr   r    r   F/var/www/eduai.edurigo.com/roleplay/staging/services/skill_analyzer.py__init__
   s   zSkillAnalyzer.__init__use_groqc                 C   s   |r| j S | jS )z9Get the appropriate AI service based on groqRoleplay flag)r   r   )r   r   r   r   r   _get_ai_service   s   zSkillAnalyzer._get_ai_service	score_valreturnc              	   C   s  |du rdS t |tr|S t |trt|S t |tr|  }|s%dS d|v rCztt|dd  W S  ttfyB   Y nw d|v rcztt| dd  W S  ttfyb   Y nw zt	
d|}|rvtt|dW S W dS  ttfy   Y dS w dS )z.Robustly parse a score value from AI response.Nr   /z out of z(\d+\.?\d*)   )
isinstanceintfloatstrstriplowersplit
ValueError
IndexErrorresearchgroup	TypeError)r   r   smatchr   r   r   _parse_score   sB   


 zSkillAnalyzer._parse_scoreconversation_turnsscenarioc                 C   sR  dd |D }t |}ddd |D }| rt | nd}|dkr)|| nd}|dkr4| |nd}| ||}	|dkrEd}
d}nK|d	krP|rPd
}
d}n@|d	kra|dk ra|	dk rad
}
d}n/|dkrr|dk rr|	dk rrd}
d}n|dkr|	dk rd}
d}n|dkr|	dkrd}
d}nd}
d}||t|d	|t|	d|
|d}td|  |S )z
        Analyze the depth and quality of the learner's participation.
        Returns engagement metrics and a hard score cap.
        c                 S      g | ]	}|j d kr|qS learnerspeaker.0tr   r   r   
<listcomp>E       z=SkillAnalyzer._analyze_conversation_depth.<locals>.<listcomp> c                 s   s    | ]}|j  V  qd S r   )messager    r3   r   r   r   	<genexpr>I   s    z<SkillAnalyzer._analyze_conversation_depth.<locals>.<genexpr>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: )lenjoinr    r"   _is_greeting_only_estimate_objective_coverageroundprint)r   r,   r-   learner_turnsrI   all_learner_textrJ   rK   rL   rM   rN   rO   depth_analysisr   r   r   _analyze_conversation_depth?   sJ   
z)SkillAnalyzer._analyze_conversation_depthtextc                    s   |    tdd  g d}t  }|dkrdS t fdd|D }g d}t fd	d|D }|d
krE|dkrE|dkrEdS |dk rO|d
krOdS dS )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        | ]}t | rd V  qdS r   Nr%   r&   r4   pcleanedr   r   r:          z2SkillAnalyzer._is_greeting_only.<locals>.<genexpr>)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   r]   r^   r_   r`   rb   r   r   r:      rd   r   r      TrH   )r!   r    r%   subrP   r"   sum)r   rZ   greeting_patterns
word_countgreeting_matchesaction_indicatorsaction_matchesr   rb   r   rR   w   s   zSkillAnalyzer._is_greeting_onlylearner_textc           
         s   |  sdS |  |j }td|}dd |D }|s dS d}|D ]-}h dfdd| D }|rQt fdd	|D }|td
t|d krQ|d
7 }q$|rZ|t| nd}	t|	dS )z
        Estimate what fraction of the scenario objectives the learner's text addresses.
        Returns a float between 0.0 and 1.0.
                z[,&\n]+c                 S   s$   g | ]}t | d kr| qS )rA   )rP   r    )r4   r)   r   r   r   r6         $ z>SkillAnalyzer._estimate_objective_coverage.<locals>.<listcomp>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shouldc                    s$   g | ]}| vrt |d kr|qS )r=   )rP   )r4   w)
stop_wordsr   r   r6      ro   c                 3   s    | ]	}| v rd V  qdS r^   r   )r4   kw)learner_lowerr   r   r:      s    z=SkillAnalyzer._estimate_objective_coverage.<locals>.<genexpr>r   g333333?      ?)	r    r!   	objectiver%   r"   rg   maxrP   min)
r   rm   r-   objective_textsub_objectivesaddressed_countsub_objkeywordsmatchescoverager   )r   r   r   rS      s&   

z*SkillAnalyzer._estimate_objective_coveragerX   c                 C   s`   d|d  d|d  d|d  d|d rd	nd
 dt |d d  d|d   d|d  dS )zf
        Build a human-readable engagement metadata string for injection into the LLM prompt.
        z<--- CONVERSATION ENGAGEMENT SUMMARY ---
Learner Turn Count: rI   z
Total Learner Words: rJ   z
Average Words Per Turn: rK   z
Learner Response Type: rL   z"Generic greeting/introduction ONLYzContains substantive contentz
Objective Coverage: rM   d   z%
Engagement Level: rN   z
Maximum Allowed Score: rO   z"/10
--- END ENGAGEMENT SUMMARY ---)r   upper)r   rX   r   r   r   _build_engagement_metadata_text   s    
z-SkillAnalyzer._build_engagement_metadata_textskill_scoresrO   c                 C   sn   g }|D ]0}|j }t||}||kr$td|j d| d| d| d	 |t|j||j|j|jd q|S )z
        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   rU   r   appendr   r   r   r   )r   r   rO   capped_scoresssoriginal_scorecapped_scorer   r   r   _apply_score_cap   s   
$
zSkillAnalyzer._apply_score_capT
session_idis_adminc                    s  dd |D }|s|  ||||S | ||}|d dkr&|  ||||S | |}dd |D }	| |}
|
| |	|I dH }|sGdS zg }|jD ]/}||d v r||d | }t|| |	dd	|	d
g |	dg |	dg d}|
| qM|d }| ||}|rtdd |D t| }nd}t|t|}| |}td| d|d  d| d tj||jt|d||||	di |	di d}td|  | j| |rtd |W S td W dS  ty } ztd|  d	dl}|  W Y d}~dS d}~ww )z;Perform comprehensive skill analysis on a completed sessionc                 S   r.   r/   r1   r4   turnr   r   r   r6      r7   z1SkillAnalyzer.analyze_session.<locals>.<listcomp>rN   r;   c                 S   s   g | ]	}|j |jd qS )r2   r9   r   r   r   r   r   r6   	  s    Nskill_analysisr   r   r   r   r   r   rO   c                 s   s    | ]}|j V  qd S r   )r   )r4   r   r   r   r   r:   /  s    z0SkillAnalyzer.analyze_session.<locals>.<genexpr>rn   zDEBUG: Final overall score: z (engagement: z, cap: r   r   conversation_analysisrecommendationsr   scenario_idoverall_scoreperformance_levelr   r,   r   r   z1DEBUG: Attempting to save assessment for session z$DEBUG: Assessment saved successfullyz DEBUG: Failed to save assessmentz"Error processing analysis result: )_create_zero_assessmentrY   r   r   analyze_skillsto_dictskills_to_assessr   r+   getr   r   rg   rP   r   r   _determine_performance_levelrU   r   
create_newr   rT   r   save_assessment	Exception	traceback	print_exc)r   r   r-   r,   r   r   rV   rX   engagement_metadataconversation_data
ai_serviceanalysis_resultr   r   
skill_dataskill_scoremax_allowedr   r   
assessmenter   r   r   r   analyze_session   sz   









 

zSkillAnalyzer.analyze_sessionc           	      C   s   g }|j D ]}t|ddgg dgd}|| qtj||jdd||g g dgdd	gd
gg dd}td|  | j|	 |rGtd |S td dS )zBCreate assessment with zero scores when learner didn't participater   z1No learner responses detected in the conversationz1Participate actively in the roleplay conversationr   rn   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   rU   r   r   r   )	r   r   r-   r,   r   r   r   r   r   r   r   r   r   U  s@   
z%SkillAnalyzer._create_zero_assessmentr   c                 C   s4   |dkrdS |dkrdS |dkrdS |dkrdS d	S )
z2Determine performance level based on overall scoreg      "@Expertg      @Advancedg      @Intermediateg      @BeginnerzNeeds Improvementr   )r   r   r   r   r   r     s   z*SkillAnalyzer._determine_performance_levelc                 C   s    | j ||}|rt|S dS )zLoad an existing assessmentN)r   load_assessmentr   	from_dict)r   r   r   assessment_datar   r   r   r     s   
zSkillAnalyzer.load_assessmentc                 C   s   | j |S )zList all assessments)r   list_assessments)r   r   r   r   r   r     s   zSkillAnalyzer.list_assessmentsassessmentsc           	      C   s   |si S i }g }|D ]!}| |j |jD ]}|j}||vr"g ||< ||  |j qq
i }| D ](\}}tt|t| dt	||d t|dkrT|d |d  ndd||< q2t|tt|t| dt	||d || 
|d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   itemsrT   rg   rP   r   _calculate_trend)	r   r   r   overall_scoresr   r   r   skill_averagesscoresr   r   r   get_skill_summary  s4   
zSkillAnalyzer.get_skill_summaryr   c                 C   st   t |dk rdS t|dd t |dd  }t|dd t |dd  }|| }|dkr2dS |dk r8d	S d
S )zCalculate performance trendr=   zInsufficient dataNrA   r   	Improvingg      	DecliningStable)rP   rg   )r   r   
recent_avg	early_avgr   r   r   r   r     s     zSkillAnalyzer._calculate_trend)NN)T)__name__
__module____qualname__r   boolr   r   r   r+   r   r	   r   r   r   rY   rR   r   rS   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   	   sF    

+

8,&

b
-%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   r   <module>   s    