Spaces:
Sleeping
Sleeping
| from django.conf import settings | |
| from django.core.validators import MinValueValidator, MaxValueValidator | |
| from django.db import models | |
| from pgvector.django import HnswIndex, VectorField | |
| class Skill(models.Model): | |
| DIFFICULTY_CHOICES = [ | |
| ('BEGINNER', 'Beginner'), | |
| ('INTERMEDIATE', 'Intermediate'), | |
| ('ADVANCED', 'Advanced'), | |
| ] | |
| skill_name = models.CharField(max_length=255, unique=True) | |
| category = models.CharField(max_length=100) | |
| description = models.TextField(blank=True) | |
| difficulty_level = models.CharField( | |
| max_length=20, | |
| choices=DIFFICULTY_CHOICES, | |
| default='BEGINNER' | |
| ) | |
| def __str__(self): | |
| return self.skill_name | |
| class UserSkill(models.Model): | |
| DIFFICULTY_CHOICES = [ | |
| ('BEGINNER', 'Beginner'), | |
| ('INTERMEDIATE', 'Intermediate'), | |
| ('ADVANCED', 'Advanced'), | |
| ] | |
| user = models.ForeignKey( | |
| settings.AUTH_USER_MODEL, | |
| on_delete=models.CASCADE, | |
| related_name='user_skills' | |
| ) | |
| skill = models.ForeignKey( | |
| Skill, | |
| on_delete=models.CASCADE, | |
| related_name='user_skills' | |
| ) | |
| proficiency = models.IntegerField( | |
| validators=[MinValueValidator(0), MaxValueValidator(100)] | |
| ) | |
| user_level = models.CharField(max_length=20, choices=DIFFICULTY_CHOICES) | |
| updated_at = models.DateTimeField(auto_now=True) | |
| # Cursor used by the upgrade-suggestion flow. Advanced on BOTH dismiss AND | |
| # apply (see apps.progress.services). A suggestion is eligible when the | |
| # latest related checkpoint completion is newer than this timestamp. | |
| # Name is historical — kept to avoid a field-rename migration. | |
| dismissed_upgrade_at = models.DateTimeField( | |
| null=True, | |
| blank=True, | |
| help_text=( | |
| "Last-action cursor for the upgrade-suggestion flow. Set on both " | |
| "dismiss and apply (see apps.progress.services.apply_upgrade and " | |
| "dismiss_upgrade). A new upgrade is eligible when any related " | |
| "resource completion is newer than this timestamp." | |
| ), | |
| ) | |
| class Meta: | |
| unique_together = [['user', 'skill']] | |
| def __str__(self): | |
| return f'{self.user.email} - {self.skill.skill_name}' | |
| class SkillEmbedding(models.Model): | |
| """SBERT embedding of a Skill, queried by Module 8 Layer 4 (SBERT canonical | |
| mapping) when higher-layer NER models fail to resolve a candidate span. | |
| 384 dims matches all-MiniLM-L6-v2; swapping models means re-running | |
| scripts/build_skill_embeddings.py against a fresh catalog. | |
| """ | |
| skill = models.OneToOneField( | |
| Skill, | |
| on_delete=models.CASCADE, | |
| related_name='embedding', | |
| ) | |
| embedding = VectorField(dimensions=384) | |
| source_text = models.TextField() | |
| model_name = models.CharField(max_length=100, default='all-MiniLM-L6-v2') | |
| updated_at = models.DateTimeField(auto_now=True) | |
| class Meta: | |
| indexes = [ | |
| HnswIndex( | |
| name='skillemb_hnsw_idx', | |
| fields=['embedding'], | |
| m=16, | |
| ef_construction=64, | |
| opclasses=['vector_cosine_ops'], | |
| ), | |
| ] | |
| def __str__(self): | |
| return f'Embedding({self.skill.skill_name})' | |