feat: add versioned backend scoring workflow
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from pgvector.sqlalchemy import Vector
|
||||
from sqlalchemy import JSON, Boolean, Column, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
|
||||
from sqlalchemy.types import TypeDecorator
|
||||
|
||||
from .db import Base
|
||||
|
||||
|
||||
def now():
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class Embedding(TypeDecorator):
|
||||
impl = JSON
|
||||
cache_ok = True
|
||||
|
||||
def load_dialect_impl(self, dialect):
|
||||
return dialect.type_descriptor(Vector(1024) if dialect.name == "postgresql" else JSON())
|
||||
|
||||
|
||||
class Course(Base):
|
||||
__tablename__ = "courses"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(200), unique=True, nullable=False)
|
||||
code = Column(String(100), unique=True, nullable=False)
|
||||
created_at = Column(DateTime(timezone=True), default=now)
|
||||
|
||||
|
||||
class CourseVersion(Base):
|
||||
__tablename__ = "course_versions"
|
||||
id = Column(Integer, primary_key=True)
|
||||
course_id = Column(Integer, ForeignKey("courses.id"), nullable=False)
|
||||
version = Column(Integer, nullable=False)
|
||||
title = Column(String(200), nullable=False)
|
||||
objectives = Column(JSON, default=list, nullable=False)
|
||||
indicators = Column(JSON, default=list, nullable=False)
|
||||
directions = Column(JSON, default=list, nullable=False)
|
||||
prompts = Column(JSON, default=dict, nullable=False)
|
||||
templates = Column(JSON, default=list, nullable=False)
|
||||
published = Column(Boolean, default=False, nullable=False)
|
||||
created_at = Column(DateTime(timezone=True), default=now)
|
||||
__table_args__ = (UniqueConstraint("course_id", "version"),)
|
||||
|
||||
|
||||
class CourseObjective(Base):
|
||||
__tablename__ = "course_objectives"
|
||||
id = Column(Integer, primary_key=True)
|
||||
course_version_id = Column(Integer, ForeignKey("course_versions.id"), nullable=False)
|
||||
code = Column(String(50), nullable=False)
|
||||
name = Column(String(300), nullable=False)
|
||||
description = Column(Text, default="")
|
||||
indicator_codes = Column(JSON, default=list)
|
||||
__table_args__ = (UniqueConstraint("course_version_id", "code"),)
|
||||
|
||||
|
||||
class GraduationIndicator(Base):
|
||||
__tablename__ = "graduation_indicators"
|
||||
id = Column(Integer, primary_key=True)
|
||||
course_version_id = Column(Integer, ForeignKey("course_versions.id"), nullable=False)
|
||||
code = Column(String(50), nullable=False)
|
||||
description = Column(Text, nullable=False)
|
||||
__table_args__ = (UniqueConstraint("course_version_id", "code"),)
|
||||
|
||||
|
||||
class TopicDirection(Base):
|
||||
__tablename__ = "topic_directions"
|
||||
id = Column(Integer, primary_key=True)
|
||||
course_version_id = Column(Integer, ForeignKey("course_versions.id"), nullable=False)
|
||||
name = Column(String(200), nullable=False)
|
||||
examples = Column(JSON, default=list)
|
||||
__table_args__ = (UniqueConstraint("course_version_id", "name"),)
|
||||
|
||||
|
||||
class RubricVersion(Base):
|
||||
__tablename__ = "rubric_versions"
|
||||
id = Column(Integer, primary_key=True)
|
||||
course_version_id = Column(Integer, ForeignKey("course_versions.id"), nullable=False)
|
||||
version = Column(Integer, nullable=False)
|
||||
criteria = Column(JSON, default=list, nullable=False)
|
||||
weights = Column(JSON, default=dict, nullable=False)
|
||||
published = Column(Boolean, default=False, nullable=False)
|
||||
created_at = Column(DateTime(timezone=True), default=now)
|
||||
__table_args__ = (UniqueConstraint("course_version_id", "version"),)
|
||||
|
||||
|
||||
class RubricCriterion(Base):
|
||||
__tablename__ = "rubric_criteria"
|
||||
id = Column(Integer, primary_key=True)
|
||||
rubric_version_id = Column(Integer, ForeignKey("rubric_versions.id"), nullable=False)
|
||||
criterion_id = Column(String(100), nullable=False)
|
||||
category = Column(String(50), nullable=False)
|
||||
name = Column(String(300), nullable=False)
|
||||
description = Column(Text, nullable=False)
|
||||
max_score = Column(Float, nullable=False)
|
||||
linked_objectives = Column(JSON, default=list)
|
||||
linked_outcomes = Column(JSON, default=list)
|
||||
required_evidence = Column(JSON, default=list)
|
||||
__table_args__ = (UniqueConstraint("rubric_version_id", "criterion_id"),)
|
||||
|
||||
|
||||
class RubricScoreLevel(Base):
|
||||
__tablename__ = "rubric_score_levels"
|
||||
id = Column(Integer, primary_key=True)
|
||||
rubric_criterion_id = Column(Integer, ForeignKey("rubric_criteria.id"), nullable=False)
|
||||
min_score = Column(Float, nullable=False)
|
||||
max_score = Column(Float, nullable=False)
|
||||
description = Column(Text, nullable=False)
|
||||
|
||||
|
||||
class Student(Base):
|
||||
__tablename__ = "students"
|
||||
id = Column(Integer, primary_key=True)
|
||||
student_no = Column(String(80), unique=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
|
||||
|
||||
class Project(Base):
|
||||
__tablename__ = "projects"
|
||||
id = Column(Integer, primary_key=True)
|
||||
course_version_id = Column(Integer, ForeignKey("course_versions.id"), nullable=False)
|
||||
student_id = Column(Integer, ForeignKey("students.id"), nullable=False)
|
||||
topic = Column(String(500), nullable=False)
|
||||
direction = Column(String(200), default="")
|
||||
source_dir = Column(String(1000), nullable=False)
|
||||
status = Column(String(40), default="imported")
|
||||
facts = Column(JSON, default=dict, nullable=False)
|
||||
created_at = Column(DateTime(timezone=True), default=now)
|
||||
__table_args__ = (UniqueConstraint("course_version_id", "source_dir"),)
|
||||
|
||||
|
||||
class Submission(Base):
|
||||
__tablename__ = "submissions"
|
||||
id = Column(Integer, primary_key=True)
|
||||
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
|
||||
category = Column(String(50), nullable=False)
|
||||
status = Column(String(40), nullable=False)
|
||||
__table_args__ = (UniqueConstraint("project_id", "category"),)
|
||||
|
||||
|
||||
class SubmissionFile(Base):
|
||||
__tablename__ = "submission_files"
|
||||
id = Column(Integer, primary_key=True)
|
||||
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
|
||||
submission_id = Column(Integer, ForeignKey("submissions.id"), nullable=True)
|
||||
category = Column(String(40), nullable=False)
|
||||
relative_path = Column(String(1200), nullable=False)
|
||||
mime_type = Column(String(100), nullable=False)
|
||||
size = Column(Integer, nullable=False)
|
||||
mtime = Column(Float, nullable=False)
|
||||
sha256 = Column(String(64), nullable=False)
|
||||
recognition_status = Column(String(40), nullable=False)
|
||||
parse_status = Column(String(40), default="pending")
|
||||
parse_error = Column(Text, default="")
|
||||
parsed = Column(JSON, default=dict, nullable=False)
|
||||
__table_args__ = (UniqueConstraint("project_id", "relative_path"),)
|
||||
|
||||
|
||||
class KnowledgeDocument(Base):
|
||||
__tablename__ = "knowledge_documents"
|
||||
id = Column(Integer, primary_key=True)
|
||||
course_version_id = Column(Integer, ForeignKey("course_versions.id"), nullable=False)
|
||||
project_id = Column(Integer, ForeignKey("projects.id"), nullable=True)
|
||||
submission_file_id = Column(Integer, ForeignKey("submission_files.id"), nullable=True)
|
||||
title = Column(String(500), nullable=False)
|
||||
document_type = Column(String(80), nullable=False)
|
||||
source_hash = Column(String(64), nullable=False)
|
||||
status = Column(String(40), default="ready")
|
||||
__table_args__ = (UniqueConstraint("course_version_id", "project_id", "submission_file_id", "source_hash"),)
|
||||
|
||||
|
||||
class KnowledgeChunk(Base):
|
||||
__tablename__ = "knowledge_chunks"
|
||||
id = Column(Integer, primary_key=True)
|
||||
document_id = Column(Integer, ForeignKey("knowledge_documents.id"), nullable=False)
|
||||
course_version_id = Column(Integer, nullable=False)
|
||||
project_id = Column(Integer, nullable=True)
|
||||
document_type = Column(String(80), nullable=False)
|
||||
page = Column(Integer, nullable=False)
|
||||
section = Column(String(500), default="")
|
||||
text = Column(Text, nullable=False)
|
||||
embedding = Column(Embedding, nullable=True)
|
||||
|
||||
|
||||
class ScoringBatch(Base):
|
||||
__tablename__ = "scoring_batches"
|
||||
id = Column(Integer, primary_key=True)
|
||||
course_version_id = Column(Integer, ForeignKey("course_versions.id"), nullable=False)
|
||||
rubric_version_id = Column(Integer, ForeignKey("rubric_versions.id"), nullable=False)
|
||||
course_snapshot = Column(JSON, nullable=False)
|
||||
rubric_snapshot = Column(JSON, nullable=False)
|
||||
model_config_snapshot = Column(JSON, nullable=False)
|
||||
embedding_model_name = Column(String(200), nullable=False)
|
||||
embedding_model_version = Column(String(100), nullable=False)
|
||||
status = Column(String(40), default="queued")
|
||||
progress_done = Column(Integer, default=0)
|
||||
progress_total = Column(Integer, default=0)
|
||||
error = Column(Text, default="")
|
||||
created_at = Column(DateTime(timezone=True), default=now)
|
||||
finished_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
|
||||
class CriterionScore(Base):
|
||||
__tablename__ = "criterion_scores"
|
||||
id = Column(Integer, primary_key=True)
|
||||
batch_id = Column(Integer, ForeignKey("scoring_batches.id"), nullable=False)
|
||||
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
|
||||
criterion_id = Column(String(100), nullable=False)
|
||||
model_score = Column(Float, nullable=True)
|
||||
teacher_score = Column(Float, nullable=True)
|
||||
model_comment = Column(Text, default="")
|
||||
teacher_comment = Column(Text, nullable=True)
|
||||
confidence = Column(Float, nullable=True)
|
||||
missing_evidence = Column(JSON, default=list)
|
||||
suggestion = Column(Text, default="")
|
||||
status = Column(String(40), default="pending")
|
||||
error = Column(Text, default="")
|
||||
__table_args__ = (UniqueConstraint("batch_id", "project_id", "criterion_id"),)
|
||||
|
||||
@property
|
||||
def final_score(self):
|
||||
return self.teacher_score if self.teacher_score is not None else self.model_score
|
||||
|
||||
|
||||
class ScoreEvidence(Base):
|
||||
__tablename__ = "score_evidences"
|
||||
id = Column(Integer, primary_key=True)
|
||||
score_id = Column(Integer, ForeignKey("criterion_scores.id"), nullable=False)
|
||||
document_id = Column(Integer, ForeignKey("knowledge_documents.id"), nullable=True)
|
||||
submission_file_id = Column(Integer, ForeignKey("submission_files.id"), nullable=True)
|
||||
page = Column(Integer, nullable=False)
|
||||
quote = Column(Text, nullable=False)
|
||||
image_path = Column(String(1200), nullable=True)
|
||||
|
||||
|
||||
class TeacherRevision(Base):
|
||||
__tablename__ = "teacher_revisions"
|
||||
id = Column(Integer, primary_key=True)
|
||||
score_id = Column(Integer, ForeignKey("criterion_scores.id"), nullable=False)
|
||||
old_score = Column(Float, nullable=True)
|
||||
new_score = Column(Float, nullable=True)
|
||||
old_comment = Column(Text, nullable=True)
|
||||
new_comment = Column(Text, nullable=True)
|
||||
reason = Column(Text, default="")
|
||||
created_at = Column(DateTime(timezone=True), default=now)
|
||||
|
||||
|
||||
class ExportTask(Base):
|
||||
__tablename__ = "export_tasks"
|
||||
id = Column(Integer, primary_key=True)
|
||||
batch_id = Column(Integer, ForeignKey("scoring_batches.id"), nullable=False)
|
||||
project_id = Column(Integer, ForeignKey("projects.id"), nullable=True)
|
||||
kind = Column(String(20), nullable=False)
|
||||
path = Column(String(1200), nullable=True)
|
||||
status = Column(String(40), default="queued")
|
||||
error = Column(Text, default="")
|
||||
created_at = Column(DateTime(timezone=True), default=now)
|
||||
|
||||
|
||||
class AppSetting(Base):
|
||||
__tablename__ = "app_settings"
|
||||
key = Column(String(100), primary_key=True)
|
||||
encrypted_value = Column(LargeBinary, nullable=False)
|
||||
updated_at = Column(DateTime(timezone=True), default=now)
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
__tablename__ = "audit_logs"
|
||||
id = Column(Integer, primary_key=True)
|
||||
action = Column(String(100), nullable=False)
|
||||
entity_type = Column(String(100), nullable=False)
|
||||
entity_id = Column(Integer, nullable=True)
|
||||
details = Column(JSON, default=dict)
|
||||
created_at = Column(DateTime(timezone=True), default=now)
|
||||
Reference in New Issue
Block a user