Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CU-8693pytr0_Results #48

Merged
merged 14 commits into from
Sep 26, 2024
Binary file modified requirements.txt
Binary file not shown.
1 change: 1 addition & 0 deletions tgbot/handlers/teacher.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ async def get_subjects(
{"text": "Invite students", "name": "add_subject"},
{"text": "Add task", "name": "add_task"},
{"text": "See tasks", "name": "see_tasks"},
{"text": "Show stats", "name": "subject_stats"},
],
)
await message.answer("Here are your subjects:")
Expand Down
71 changes: 71 additions & 0 deletions tgbot/misc/charts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import io
import logging
import os
from enum import Enum

import pandas as pd
import seaborn as sns
from aiogram.types import FSInputFile, Message
from matplotlib import pyplot as plt


class ChartType(Enum):
BAR = "bar"
HIST = "hist"


async def send_chart(
message: Message,
data: dict,
chart_type: ChartType,
x_legend: str | None = None,
y_legend: str | None = None,
title: str | None = None,
) -> Message:
if chart_type == ChartType.BAR:
ax = prepare_bar_chart(data, x_legend, y_legend, title)
elif chart_type == ChartType.HIST:
ax = prepare_hist_chart(data, x_legend, title)
else:
raise ValueError(f"Unknown chart type: {chart_type}")
try:
buffer = io.BytesIO()
fig = ax.get_figure()
fig.savefig(buffer, format="png")
buffer.seek(0)
mezgoodle marked this conversation as resolved.
Show resolved Hide resolved
temp_file = "temp.png"
with open(temp_file, "wb") as f:
f.write(buffer.read())
file = FSInputFile(temp_file)
mezgoodle marked this conversation as resolved.
Show resolved Hide resolved
except ValueError as ve:
logging.error(ve)
return await message.answer("Error creating chart. Try again later.")
except Exception as e:
logging.error(e)
return await message.answer("Unexpected error. Try again later.")
try:
return await message.answer_photo(file)
finally:
os.remove(temp_file)


def prepare_hist_chart(data: dict, x_legend: str, title: str):
sample_data = pd.DataFrame({x_legend: tuple(data[x_legend])})
plt.clf()
plt.xticks([1, 2, 3, 4, 5])
ax = sns.histplot(x=x_legend, data=sample_data)
ax.title.set_text(title)
return ax
mezgoodle marked this conversation as resolved.
Show resolved Hide resolved


def prepare_bar_chart(data: dict, x_legend: str, y_legend: str, title: str):
sample_data = pd.DataFrame(
{
x_legend: tuple(data[x_legend]),
y_legend: tuple(data[y_legend]),
}
)
plt.clf()
ax = sns.barplot(x=x_legend, y=y_legend, data=sample_data)
ax.title.set_text(title)
return ax
mezgoodle marked this conversation as resolved.
Show resolved Hide resolved
mezgoodle marked this conversation as resolved.
Show resolved Hide resolved
20 changes: 20 additions & 0 deletions tgbot/misc/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,26 @@ async def get_solutions_for_task(
.prefetch_related("student")
)

async def get_count_solutions_by_subject(
self, subject: Subject
) -> dict[str, int]:
students = await self.student.filter(subjects=subject).count()
tasks = await self.subjecttask.filter(subject=subject).all()
stats = {}
if students:
for task in tasks:
solutions = await self.solution.filter(
subject_task=task
).count()
stats[task.name] = solutions / students
return stats

async def get_grades_by_subject(self, subject: Subject) -> list[int]:
solutions = await self.solution.filter(
subject_task__subject=subject
).all()
return [solution.grade for solution in solutions]

mezgoodle marked this conversation as resolved.
Show resolved Hide resolved
async def get_student_solution(
self, student_id: int, subject_task_id: int
) -> Solution | None:
Expand Down
59 changes: 59 additions & 0 deletions tgbot/misc/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from loader import bot
from tgbot.keyboards.inline.support_keyboard import support_keyboard
from tgbot.keyboards.inline.task_keyboard import task_keyboard
from tgbot.misc.charts import ChartType, send_chart
mezgoodle marked this conversation as resolved.
Show resolved Hide resolved
from tgbot.misc.database import Database
from tgbot.models.models import Student, Subject, SubjectTask
from tgbot.states.states import Task
Expand Down Expand Up @@ -156,10 +157,68 @@ async def ask_teacher(
)


async def get_subject_statistics(
db: Database, subject_id: int
) -> tuple[Subject | None, dict | None, dict | None]:
subject = await db.get_subject(subject_id)
if not subject:
logging.error(f"Subject with id {subject_id} not found")
return None, None, None

try:
subject_stats = await db.get_count_solutions_by_subject(subject)
grades = await db.get_grades_by_subject(subject)
return subject, subject_stats, grades
except Exception as e:
logging.error(f"Error fetching subject statistics: {e}")
return subject, None, None

mezgoodle marked this conversation as resolved.
Show resolved Hide resolved

async def prepare_chart_data(subject_stats, grades):
stats = {
"Tasks names": subject_stats.keys(),
"Solutions": subject_stats.values(),
}
grades_data = {"Grades": grades}
return stats, grades_data

mezgoodle marked this conversation as resolved.
Show resolved Hide resolved

async def subject_stats(
message: Message, payload: dict, db: Database, *args, **kwargs
) -> str:
subject, subject_stats, grades = await get_subject_statistics(
db, payload.get("id")
)
if not subject:
return await message.answer("Subject not found.")
if not grades:
return await message.answer(
f"Stats for subject {hbold(subject.name)}: No data"
)
stats, grades_data = await prepare_chart_data(subject_stats, grades)
await message.answer(f"Stats for subject {hbold(subject.name)}:")
await send_chart(
message=message,
data=grades_data,
x_legend="Grades",
title="Grades",
chart_type=ChartType.HIST,
)
return await send_chart(
message=message,
data=stats,
x_legend="Tasks names",
y_legend="Solutions",
title="Number of solutions for tasks",
chart_type=ChartType.BAR,
)
mezgoodle marked this conversation as resolved.
Show resolved Hide resolved


utils = {
"add_subject": add_student_to_subject,
"quit_subject": quit_student_to_subject,
"add_task": add_task,
"see_tasks": see_tasks,
"ask_teacher": ask_teacher,
"subject_stats": subject_stats,
}
2 changes: 1 addition & 1 deletion tgbot/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class Solution(TimedBaseModel):
subject_task: fields.ForeignKeyRelation[SubjectTask] = (
fields.ForeignKeyField(
"models.SubjectTask",
related_name="students",
related_name="solutions",
description="Task subject",
on_delete=fields.OnDelete.CASCADE,
)
Expand Down
Loading