gapguide-api / apps /skills /models.py
arifRB's picture
Deploy GapGuide backend (Docker)
ffd36e0 verified
Raw
History Blame Contribute Delete
3.27 kB
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})'