FCT / services /score_persistence.py
Parthnuwal7
Auto create student records for APAAR ID
ed6391b
"""
Score Persistence Service - Save and retrieve analytics scores from Supabase
"""
import logging
from typing import Dict, Any, Optional
from datetime import datetime
logger = logging.getLogger(__name__)
class ScorePersistenceService:
"""Service to persist computed analytics scores to Supabase"""
def __init__(self, supabase_client):
self.supabase = supabase_client
self.table_name = 'analytics_scores'
def save_score(self, student_id: str, score_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Save or update a student's analytics score
Args:
student_id: Student identifier
score_data: Dictionary containing score data with keys:
- final_score
- component_scores (universal, personality, text)
- domain, domain_confidence
- fidelity_score
- strengths, improvement_areas, career_suggestions, etc.
Returns:
Saved record or error
"""
try:
# Determine tier based on final score
final_score = score_data.get('final_score', 0)
tier = self._get_tier(final_score)
# Extract component scores
component_scores = score_data.get('component_scores', {})
# Prepare record
record = {
'student_id': student_id,
'final_score': final_score,
'tier': tier,
'universal_score': component_scores.get('universal', 0),
'personality_score': component_scores.get('personality', 0),
'text_score': component_scores.get('text', 0),
'fidelity_score': score_data.get('fidelity_score'),
'detected_domain': score_data.get('domain'),
'domain_confidence': score_data.get('domain_confidence'),
'full_report': score_data, # Store complete report as JSON
'updated_at': datetime.utcnow().isoformat()
}
# Upsert (insert or update)
result = self.supabase.table(self.table_name).upsert(
record,
on_conflict='student_id'
).execute()
logger.info(f"Saved score for student {student_id}: {final_score}")
return {'success': True, 'data': result.data}
except Exception as e:
logger.error(f"Failed to save score for {student_id}: {e}")
return {'success': False, 'error': str(e)}
def get_score(self, student_id: str) -> Optional[Dict[str, Any]]:
"""
Get a student's saved analytics score
Args:
student_id: Student identifier
Returns:
Score record or None
"""
try:
result = self.supabase.table(self.table_name).select('*').eq(
'student_id', student_id
).single().execute()
if result.data:
return result.data
return None
except Exception as e:
logger.error(f"Failed to get score for {student_id}: {e}")
return None
def get_scores_by_tier(self, tier: str, limit: int = 100) -> list:
"""Get all scores in a specific tier"""
try:
result = self.supabase.table(self.table_name).select('*').eq(
'tier', tier
).limit(limit).execute()
return result.data or []
except Exception as e:
logger.error(f"Failed to get scores by tier {tier}: {e}")
return []
def get_scores_by_domain(self, domain: str, limit: int = 100) -> list:
"""Get all scores in a specific domain"""
try:
result = self.supabase.table(self.table_name).select('*').eq(
'detected_domain', domain
).limit(limit).execute()
return result.data or []
except Exception as e:
logger.error(f"Failed to get scores by domain {domain}: {e}")
return []
def get_batch_stats(self) -> Dict[str, Any]:
"""Get aggregate statistics for all scores"""
try:
# Get all scores
result = self.supabase.table(self.table_name).select('*').execute()
scores = result.data or []
if not scores:
return {'total': 0}
# Calculate stats
final_scores = [s['final_score'] for s in scores if s['final_score']]
tier_counts = {}
domain_counts = {}
for s in scores:
tier = s.get('tier', 'Unknown')
domain = s.get('detected_domain', 'Unknown')
tier_counts[tier] = tier_counts.get(tier, 0) + 1
domain_counts[domain] = domain_counts.get(domain, 0) + 1
return {
'total': len(scores),
'average_score': sum(final_scores) / len(final_scores) if final_scores else 0,
'min_score': min(final_scores) if final_scores else 0,
'max_score': max(final_scores) if final_scores else 0,
'tier_distribution': tier_counts,
'domain_distribution': domain_counts
}
except Exception as e:
logger.error(f"Failed to get batch stats: {e}")
return {'error': str(e)}
def _get_tier(self, score: float) -> str:
"""Determine tier based on score"""
if score >= 80:
return 'Excellent'
elif score >= 60:
return 'Good'
elif score >= 40:
return 'Average'
return 'Needs Work'
# Singleton instance
_persistence_service = None
def get_score_persistence_service(supabase_client=None):
"""Get or create singleton ScorePersistenceService"""
global _persistence_service
if _persistence_service is None:
if supabase_client is None:
from database.db import get_supabase
supabase_client = get_supabase()
_persistence_service = ScorePersistenceService(supabase_client)
return _persistence_service