Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion migrations/versions/0fbda260a023_add_user_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from alembic import op


# revision identifiers, used by Alembic.
revision = '0fbda260a023'
down_revision = '5659e13277b6'
branch_labels = None
Expand Down
42 changes: 42 additions & 0 deletions migrations/versions/16fe9919c686_lecturer_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""lecturer-rating

Revision ID: 16fe9919c686
Revises: fc7cb93684e0
Create Date: 2025-08-24 00:25:32.995215

"""

import sqlalchemy as sa
from alembic import op


revision = '16fe9919c686'
down_revision = 'fc7cb93684e0'
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
'lecturer_rating',
sa.Column('id', sa.Integer(), nullable=False, comment='Идентификатор препода'),
sa.Column(
'mark_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка преподавателя, посчитана в dwh'
),
sa.Column(
'mark_kindness_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка доброты, посчитана в dwh'
),
sa.Column(
'mark_clarity_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка понятности, посчитана в dwh'
),
sa.Column(
'mark_freebie_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка халявности, посчитана в dwh'
),
sa.Column('rank', sa.Integer(), nullable=True, comment='Место в рейтинге, посчитана в dwh'),
sa.Column('update_ts', sa.DateTime(), nullable=True, comment='Время обновления записи'),
sa.PrimaryKeyConstraint('id'),
)


def downgrade():
op.drop_table('lecturer_rating')
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from alembic import op


# revision identifiers, used by Alembic.
revision = '20181e0d6aab'
down_revision = 'edcc1a448ffb'
branch_labels = None
Expand Down
1 change: 0 additions & 1 deletion migrations/versions/5cf69f1026d9_fixing_comments_pk.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from alembic import op


# revision identifiers, used by Alembic.
revision = '5cf69f1026d9'
down_revision = '933db669e7ef'
branch_labels = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from alembic import op


# revision identifiers, used by Alembic.
revision = '933db669e7ef'
down_revision = '20181e0d6aab'
branch_labels = None
Expand Down
5 changes: 0 additions & 5 deletions migrations/versions/fc7cb93684e0_likes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@
from alembic import op


# revision identifiers, used by Alembic.
revision = 'fc7cb93684e0'
down_revision = '1c001709fc55'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'comment_reaction',
sa.Column('uuid', sa.UUID(), nullable=False),
Expand All @@ -33,10 +31,7 @@ def upgrade():
),
sa.PrimaryKeyConstraint('uuid'),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('comment_reaction')
# ### end Alembic commands ###
8 changes: 8 additions & 0 deletions rating_api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,11 @@ def __init__(self, msg: str):
f"{msg} Conflict with update a resource that already exists or has conflicting information.",
f"{msg} Конфликт с обновлением ресурса, который уже существует или имеет противоречивую информацию.",
)


class ValidObjectNotFound(RatingAPIError):
def __init__(self, obj: type, obj_id_or_name: int | str):
super().__init__(
f"Object {obj.__name__} {obj_id_or_name=} not found valid",
f"Объект {obj.__name__} с идентификатором {obj_id_or_name} не найден валидным",
)
19 changes: 19 additions & 0 deletions rating_api/models/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from sqlalchemy import Enum as DbEnum
from sqlalchemy import (
Float,
ForeignKey,
Integer,
String,
Expand Down Expand Up @@ -211,3 +212,21 @@ class CommentReaction(BaseDbModel):
edited_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
user_id: Mapped[int] = mapped_column(Integer, nullable=False)
comment = relationship("Comment", back_populates="reactions")


class LecturerRating(BaseDbModel):
id: Mapped[int] = mapped_column(Integer, primary_key=True, comment="Идентификатор препода")
mark_weighted: Mapped[float] = mapped_column(
Float, nullable=True, comment="Взвешенная оценка преподавателя, посчитана в dwh"
)
mark_kindness_weighted: Mapped[float] = mapped_column(
Float, nullable=True, comment="Взвешенная оценка доброты, посчитана в dwh"
)
mark_clarity_weighted: Mapped[float] = mapped_column(
Float, nullable=True, comment="Взвешенная оценка понятности, посчитана в dwh"
)
mark_freebie_weighted: Mapped[float] = mapped_column(
Float, nullable=True, comment="Взвешенная оценка халявности, посчитана в dwh"
)
rank: Mapped[int] = mapped_column(Integer, nullable=True, comment="Место в рейтинге, посчитана в dwh")
update_ts: Mapped[datetime.datetime] = mapped_column(DateTime, nullable=True, comment="Время обновления записи")
34 changes: 34 additions & 0 deletions rating_api/models/dwh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from datetime import date, datetime
from uuid import UUID

from models import DWHBaseDbModel
from sqlalchemy.orm import Mapped, mapped_column


class DWHLecturer(DWHBaseDbModel):
__tablename__ = 'lecturer'
__tableargs__ = {'schema': "DWH_RATING"}

uuid: Mapped[UUID] = mapped_column(primary_key=True, comment="Техническое поле в dwh")
api_id: Mapped[int] = mapped_column(comment="Идентифиактор в rating-api")
first_name: Mapped[str] = mapped_column(comment="Имя преподавателя")
last_name: Mapped[str] = mapped_column(comment="Фамилия преподавателя")
middle_name: Mapped[str] = mapped_column(comment="отчество преподавателя")
subject: Mapped[str | None] = mapped_column(comment="Список предметов преподавателя")
avatar_link: Mapped[str | None] = mapped_column(comment="Ссылка на аватар преподавателя")
timetable_id: Mapped[int] = mapped_column(comment="Идертификатор в timetable-api")
rank: Mapped[int] = mapped_column(comment="Место в рейтинге", default=0, server_default="0")
mark_weighted: Mapped[float] = mapped_column(
nullable=False, comment="Взвешенная оценка преподавателя", default=0, server_default="0"
)
mark_kindness_weighted: Mapped[float] = mapped_column(
nullable=False, comment="Взвешенная доброта преподавателя", default=0, server_default="0"
)
mark_clarity_weighted: Mapped[float] = mapped_column(
nullable=False, comment="Взверешенная понятность преподавателя", default=0, server_default="0"
)
mark_freebie_weighted: Mapped[float] = mapped_column(
nullable=False, comment="Взвешенная халявность преподавателя", default=0, server_default="0"
)
valid_from_dt: Mapped[date | None] = mapped_column(comment="Дата начала действия записи")
valid_to_dt: Mapped[date | None] = mapped_column(comment="Дата конца действия записи")
56 changes: 56 additions & 0 deletions rating_api/models/dwh_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import re

from sqlalchemy.exc import NoResultFound
from sqlalchemy.orm import Query, Session, as_declarative, declared_attr

from rating_api.exceptions import ObjectNotFound, ValidObjectNotFound


as_declarative()


class DWHBase:
"""Base class for all dwh entities"""

@declared_attr
def __tablename__(cls) -> str: # pylint: disable=no-self-argument
"""Generate database table name automatically.
Convert CamelCase class name to snake_case db table name.
"""
return re.sub(r"(?<!^)(?=[A-Z])", "_", cls.__name__).lower()

def __repr__(self):
attrs = []
for c in self.__table__.columns:
attrs.append(f"{c.name}={getattr(self, c.name)}")
return "{}({})".format(c.__class__.__name__, ', '.join(attrs))


class DWHBaseDbModel(DWHBase):
__abstract__ = True

@classmethod
def get(cls, id: int | str, *, session: Session) -> DWHBaseDbModel:
"""Get valid object"""

objs = session.query(cls)
try:
if hasattr(cls, "valid_to_dt"):
objs = objs.filter(cls.valid_to_dt.is_(None))
except NoResultFound:
raise ValidObjectNotFound(cls, id)
try:
if hasattr(cls, "api_id"):
return objs.filter(cls.api_id == id).one()
except NoResultFound:
raise ObjectNotFound(cls, id)

@classmethod
def get_all(cls, *, session: Session) -> [DWHBaseDbModel]:
"Get all valid objects"
objs = session.query(cls)
try:
if hasattr(cls, "valid_to_dt"):
objs = objs.filter(cls.valid_to_dt.is_(None))
except NoResultFound:
raise ValidObjectNotFound(cls, id)
8 changes: 7 additions & 1 deletion rating_api/routes/lecturer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
from rating_api.exceptions import AlreadyExists, ObjectNotFound
from rating_api.models import Comment, Lecturer, LecturerUserComment, ReviewStatus
from rating_api.schemas.base import StatusResponseModel
from rating_api.schemas.models import CommentGet, LecturerGet, LecturerGetAll, LecturerPatch, LecturerPost
from rating_api.schemas.models import (
CommentGet,
LecturerGet,
LecturerGetAll,
LecturerPatch,
LecturerPost,
)
from rating_api.utils.mark import calc_weighted_mark


Expand Down
27 changes: 27 additions & 0 deletions rating_api/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,30 @@ class LecturerPatch(Base):
middle_name: str | None = None
avatar_link: str | None = None
timetable_id: int | None = None


class LecturerRankRatingApi(Base):
id: int | None = None
mark_weighted: float | None = None
mark_kindness_weighted: float | None = None
mark_clarity_weighted: float | None = None
mark_freebie_weighted: float | None = None
rank: float | None = None


class LecturerRankDWH(Base):
uuid: UUID | None = None
id: int
first_name: str | None = None
last_name: str | None = None
middle_name: str | None = None
subject: str | None = None
avatar_link: str | None = None
timetable_id: int | None = None
valid_from_dt: datetime.datetime
valid_to_dt: datetime.datetime
rank: int
mark_weighted: float
mark_kindness_weighted: float
mark_clarity_weighted: float
mark_freebie_weighted: float
32 changes: 32 additions & 0 deletions rating_api/services/dwh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import datetime

from models.db import LecturerRating
from models.dwh import DWHLecturer
from settings import get_settings
from sqlalchemy import create_engine
from sqlalchemy.orm import Session

from rating_api.exceptions import ObjectNotFound


settings = get_settings()

engine = create_engine(settings.DWH_DB_DSN)


def copy_from_dwh(api_session):
with Session(engine) as dwh_session:
lecturers = DWHLecturer.get_all(session=dwh_session)
for lecturer in lecturers:
fields = {
"id": lecturer.api_id,
"mark_weighted": lecturer.mark_weighted,
"mark_kindness_weighted": lecturer.mark_kindness_weighted,
"mark_clarity_weighted": lecturer.mark_clarity_weighted,
"mark_freebie_weighted": lecturer.mark_freebie_weighted,
"rank": lecturer.rank,
"update_ts": datetime.datetime.now(datetime.timezone.utc),
}

if LecturerRating.get(fields["id"], session=api_session):
LecturerRating.update(fields["id"], session=api_session, **fields)
1 change: 1 addition & 0 deletions rating_api/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Settings(BaseSettings):
"""Application settings"""

DB_DSN: PostgresDsn = 'postgresql://postgres@localhost:5432/postgres'
DWH_DB_DSN: PostgresDsn = 'postgresql://postgres@localhost:5432/postgres'
ROOT_PATH: str = '/' + os.getenv("APP_NAME", "")
SERVICE_ID: int = os.getenv("SERVICE_ID", -3) # Указать какой id сервиса
COMMENT_FREQUENCY_IN_MONTH: int = 10
Expand Down