feat: added pretty data generation

This commit is contained in:
Андрей Сумин
2025-03-03 14:03:55 +03:00
parent 04b1db4a1b
commit 774c312979
14 changed files with 410 additions and 15 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 30 KiB

@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-03-03 09:41 # Generated by Django 5.1.6 on 2025-03-03 09:46
import apps.achievement.models import apps.achievement.models
import django.db.models.deletion import django.db.models.deletion
@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-03-03 09:41 # Generated by Django 5.1.6 on 2025-03-03 09:46
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-03-03 09:41 # Generated by Django 5.1.6 on 2025-03-03 09:46
import apps.competition.models import apps.competition.models
import datetime import datetime
@@ -6,6 +6,7 @@ from django.contrib.auth.hashers import make_password
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone from django.utils import timezone
from faker import Faker
from apps.competition.models import Competition, State from apps.competition.models import Competition, State
from apps.review.models import Reviewer from apps.review.models import Reviewer
@@ -16,6 +17,7 @@ from apps.task.models import (
) )
from apps.user.models import User, UserRole from apps.user.models import User, UserRole
faker = Faker("ru_RU")
class Command(BaseCommand): class Command(BaseCommand):
help = "Generate sample data for Users, Competitions, Tasks, Submissions, and States." help = "Generate sample data for Users, Competitions, Tasks, Submissions, and States."
@@ -44,11 +46,10 @@ class Command(BaseCommand):
def create_users(self, count): def create_users(self, count):
users = [] users = []
for i in range(1, count + 1): for i in range(1, count + 1):
email = f"user{i}@example.com" fake_profile = faker.profile()
username = f"user{i}" email = fake_profile["email"]
password = ( username = fake_profile["username"]
"password123" # In production, use proper password handling. password = faker.password()
)
role = random.choice( role = random.choice(
[UserRole.STUDENT.value, UserRole.METODIST.value] [UserRole.STUDENT.value, UserRole.METODIST.value]
) )
@@ -68,7 +69,7 @@ class Command(BaseCommand):
competitions = [] competitions = []
now = timezone.now() now = timezone.now()
for i in range(1, count + 1): for i in range(1, count + 1):
title = f"Competition {i}" title = faker.sentence()
description = f"Description for competition {i}" description = f"Description for competition {i}"
start_date = now - timedelta(days=random.randint(1, 10)) start_date = now - timedelta(days=random.randint(1, 10))
end_date = now + timedelta(days=random.randint(1, 10)) end_date = now + timedelta(days=random.randint(1, 10))
@@ -0,0 +1,380 @@
import random
import uuid
from datetime import timedelta, datetime
from django.contrib.auth.hashers import make_password
from django.core.files.base import ContentFile
from django.core.management.base import BaseCommand
from django.utils import timezone
from apps.competition.models import Competition, State
from apps.review.models import Reviewer
from apps.task.models import (
CompetitionTask,
CompetitionTaskCriteria,
CompetitionTaskSubmission,
)
from apps.user.models import User, UserRole
ans1 = ContentFile(
b"1984",
name=f"submission_{uuid.uuid4().hex}.txt",
)
ans2 = ContentFile(
b"3",
name=f"submission_{uuid.uuid4().hex}.txt",
)
now = timezone.now()
competitions = [
{
"obj": None, # докидывает в процессе
"title": "DANO. Финал",
"description": "Олимпиада по анализу данных от Т-Банка и ВШЭ",
"start_date": now - timedelta(days=2),
"end_date": now + timedelta(days=5),
"type": "competitive",
"participation_type": "solo",
"tasks": [
{
"obj": None,
"title": "Задача 1",
"description": """На маркетплейсе «Е-шопинг» продаются различные товары. Одна из задач аналитика —
прогнозировать, сколько товаров будет продаваться при определенной цене. В ходе
исследований и экспериментов был выявлен вид зависимости:
$Q(P) = Q_0 \times e^{E \times \frac{P_0 - P}{P_0}}$
где Q — это количество проданных единиц товара при цене P,
Q 0 — количество проданных единиц товара при цене P0 ,
E — коэффициент чувствительности количества проданных единиц товара к изменению
цены.
Найдите, сколько заработает продавец при цене по 3 000 ₽ за нож и сковороду
при условии, что себестоимость ножа — 1 000 ₽, а сковородки — 2 000 ₽.Ответ
округлите до целых.""".strip(),
"type": CompetitionTask.CompetitionTaskType.INPUT.value,
"points": 3,
"submission_reviewers_count": 2,
"max_attempts": 20,
"correct_answer_file": ans1
},
{
"obj": None,
"title": "Задача 2",
"description": """Небольшой интернет-магазин собрал данные о действиях пользователей на своем сайте
за последние несколько месяцев.
ecommerce_logs.csv — журнал действий пользователей:
• user_id — идентификатор пользователя.
• action — тип действия пользователя:
— visit — посещение сайта;
— click — клик на карточку товара;
— cart — добавление товара в корзину;
— delete — удаление товара из корзины;
— purchase — покупка товаров.
• date_time — время совершения действия.
• product_id — идентификатор товара.
• quantity — количество добавленного в корзину товара.
• delivery_price — стоимость доставки.
• sex — пол пользователя.
• region — регион пользователя.
• price — цена товара.
Вам нужно изучить воронку конверсии, которая показывает, как пользователи переходят
от одного шага к другому на сайте. В нашем случае воронка состоит из следующих шагов:
1. Посещение сайта.
2. Просмотр карточки товара.
3. Добавление товара в корзину.
4. Покупка.
1. Посещение сайта.
2. Просмотр карточки товара.
3. Добавление товара в корзину.
4. Покупка.
3 / 11
1.) Посчитайте конверсию (округлите ответ до 3 знаков после запятой):
• Из визита на сайт в клик на карточку товара.
• Из клика в добавление в корзину.
• Из добавления в корзину в покупку.
• Из визита на сайт в добавление в корзину.
• Из визита на сайт в покупку.
2. Постройте воронку конверсии с помощью столбчатой диаграммы:
• По оси X — шаги воронки.
• По оси Y — количество уникальных пользователей на каждом шаге.
3. Определите, на каком этапе конверсия из предыдущего шага ниже всего.
Сформулируйте одну гипотезу, связанную с поведением пользователей, которая
может объяснить падение конверсии именно на этом этапе. Обоснуйте механизм
работы приведенной гипотезы.
4. Постройте график динамики (по оси X — дни) для каждой из конверсий:
• Конверсия из визита в клик.
• Конверсия из визита в добавление в корзину.
• Конверсия из визита в покупку.
5. На графике найдите просадку конверсии: укажите, какая конверсия просела
и в какой примерно период это произошло (допустимая погрешность — 1–3
дня).
6. Чем вызвано снижение конверсии в этот период? Какие изменения в бизнесе
или поведении пользователей могли бы объяснить это? Ответьте на оба
вопроса, опираясь на данные.
""".strip(),
"type": CompetitionTask.CompetitionTaskType.REVIEW.value,
"points": 10,
"submission_reviewers_count": 2,
"max_attempts": 1,
"criteries": [
{
"obj": None,
"name": "Обоснованность решения",
"slug": "validity",
"description": "Аргументация",
"max_value": 5
},
{
"obj": None,
"name": "Правильность",
"slug": "correctness",
"description": "Насколько точные и верные ответы были представлены.",
"max_value": 5
}
]
},
{
"obj": None,
"title": "Задача 3",
"description": """
Напишите "hello_dano" на питоне
""".strip(),
"type": CompetitionTask.CompetitionTaskType.CHECKER.value,
"points": 25,
"submission_reviewers_count": 2,
"max_attempts": 50,
}
]
},
{
"obj": None,
"title": "PRODANO. Тур 5",
"description": "Олимпиада по олимпиаде DANO",
"start_date": now - timedelta(days=10),
"end_date": now + timedelta(days=50),
"type": "edu",
"participation_type": "solo",
"tasks": [
{
"obj": None,
"title": "Задача 1",
"description": """Сколько этапов в DANO?""".strip(),
"type": CompetitionTask.CompetitionTaskType.INPUT.value,
"points": 3,
"submission_reviewers_count": 2,
"max_attempts": 20,
"correct_answer_file": ans2
},
{
"obj": None,
"title": "Задача 2",
"description": """
Напишите отзыв про DANO(Хороший)
""".strip(),
"type": CompetitionTask.CompetitionTaskType.REVIEW.value,
"points": 15,
"submission_reviewers_count": 2,
"max_attempts": 1,
"criteries": [
{
"obj": None,
"name": "Хорошесть отзыва",
"slug": "validity",
"description": "Хорошесть",
"max_value": 10
},
{
"obj": None,
"name": "Подробность",
"slug": "detail",
"description": "Насколько подробно расписан ответ.",
"max_value": 5
}
]
},
{
"obj": None,
"title": "Задача 4",
"description": """
Напишите выведите 1+3 на питоне
""".strip(),
"type": CompetitionTask.CompetitionTaskType.CHECKER.value,
"points": 30,
"submission_reviewers_count": 2,
"max_attempts": 100,
}
]
}
]
users = [
{
"email": "germanivanov1984@gmail.com",
"username": "germanivanov",
"password": "password123!",
"role": UserRole.STUDENT.value,
},
{
"email": "dreamonovich@gmail.com",
"username": "dreamonovich",
"password": "password123!",
"role": UserRole.STUDENT.value,
}
]
reviewers = [
{
"name": "Владислав",
"surname": "Пикиневич",
"token": "pikinevich"
},
{
"name": "Александр",
"surname": "Шахов",
"token": "ashakhov"
}
]
class Command(BaseCommand):
help = "Generate sample data for Users, Competitions, Tasks, Submissions, and States."
def handle(self, *args, **options):
self.stdout.write("Starting data generation...")
users = self.create_users(5)
competitions = self.create_competitions(2, users)
self.reviewers = self.create_reviewers(2)
tasks = self.create_tasks()
self.create_incorrect_submissions(tasks, users)
self.create_states(competitions, users)
self.stdout.write("Data generation completed.")
def create_reviewers(self, count):
reviewers_objs = []
for reviewer in reviewers:
name = reviewer['name']
surname = reviewer['surname']
token = reviewer['token']
reviewer_obj = Reviewer(name=name, surname=surname, token=token)
reviewer_obj.save()
reviewers_objs.append(reviewer_obj)
return reviewers_objs
def create_users(self, count):
users_objs = []
for user in users:
user_obj, created = User.objects.get_or_create(
email=user['email'],
defaults={
"username": user['username'],
"password": make_password(user['password']),
"status": user['role'],
},
)
users_objs.append(user_obj)
self.stdout.write(f"Created user: {user['username']}")
return users_objs
def create_competitions(self, count, users):
competitions_objs = []
for i, competition in enumerate(competitions):
competition_obj = Competition.objects.create(
title=competition['title'],
description=competition['description'],
start_date=competition['start_date'],
end_date=competition['end_date'],
type=competition['type'],
participation_type=competition['participation_type'],
)
competitions[i]['obj'] = competition_obj
competition_obj.participants.add(*users)
competitions_objs.append(competition_obj)
self.stdout.write(f"Created competition: {competition['title']}")
return competitions_objs
def create_tasks(self):
tasks_objs = []
task_types = [
CompetitionTask.CompetitionTaskType.INPUT.value,
CompetitionTask.CompetitionTaskType.REVIEW.value,
CompetitionTask.CompetitionTaskType.INPUT.value,
]
for i, competition in enumerate(competitions):
for j, task in enumerate(competition['tasks']):
task_obj = CompetitionTask.objects.create(
in_competition_position=j,
competition=competition['obj'],
title=task['title'],
description=task['description'],
type=task['type'],
points=task['points'],
submission_reviewers_count=task['submission_reviewers_count'],
max_attempts=task['max_attempts'],
)
competitions[i]['tasks'][j]['obj'] = task_obj
if task['type'] == CompetitionTask.CompetitionTaskType.INPUT.value:
task_obj.correct_answer_file = task['correct_answer_file']
if (
task['type']
== CompetitionTask.CompetitionTaskType.REVIEW.value
):
for k, criteria in enumerate(task['criteries']):
criteria_obj = CompetitionTaskCriteria.objects.create(
task=task_obj,
name=criteria['name'],
slug=criteria['slug'],
description=criteria['description'],
max_value=criteria['max_value'],
)
competitions[i]['tasks'][j]['criteries'][k]['obj'] = criteria_obj
self.stdout.write(f"Created criteria: {criteria['slug']}")
tasks_objs.append(task_obj)
self.stdout.write(f"Created task: {task['title']} (type: {task['type']})")
self.add_reviewers_to_task(tasks_objs)
return tasks_objs
def add_reviewers_to_task(self, tasks):
for task in tasks:
task.reviewers.set(self.reviewers)
task.save()
def create_incorrect_submissions(self, tasks, users):
for user in users:
for task in tasks:
if task.type == CompetitionTask.CompetitionTaskType.REVIEW.value:
num_submissions = random.randint(1, 3)
for m in range(num_submissions):
dummy_content = ContentFile(
b"otvet: 112 sto proc" ,
name=f"submission_{uuid.uuid4().hex}.txt",
)
submission = CompetitionTaskSubmission.objects.create(
user=user,
task=task,
content=dummy_content,
)
submission.save()
submission.send_on_review()
self.stdout.write(
f"Created submission for task '{task.title}' by user '{user.username}'"
)
def create_states(self, competitions, users):
for comp in competitions:
for user in comp.participants.all():
state_obj, created = State.objects.get_or_create(
user=user,
competition=comp,
defaults={
"state": "started",
"changed_at": timezone.now() - timedelta(days=random.randint(1, 30)),
},
)
self.stdout.write(
f"Created state '{state_obj.state}' for user '{user.username}' in competition '{comp.title}'"
)
@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-03-03 09:41 # Generated by Django 5.1.6 on 2025-03-03 09:46
import uuid import uuid
from django.db import migrations, models from django.db import migrations, models
@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-03-03 09:41 # Generated by Django 5.1.6 on 2025-03-03 09:46
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-03-03 09:41 # Generated by Django 5.1.6 on 2025-03-03 09:46
import apps.task.models import apps.task.models
import django.db.models.deletion import django.db.models.deletion
@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-03-03 09:41 # Generated by Django 5.1.6 on 2025-03-03 09:46
import django.db.models.deletion import django.db.models.deletion
import uuid import uuid
+1 -1
View File
@@ -8,4 +8,4 @@ class UsersConfig(AppConfig):
verbose_name = "Пользователи (веб)" verbose_name = "Пользователи (веб)"
def ready(self): def ready(self):
pass import apps.user.signals
@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-03-03 09:41 # Generated by Django 5.1.6 on 2025-03-03 09:46
import uuid import uuid
from django.db import migrations, models from django.db import migrations, models
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
python manage.py migrate
if [ $? -ne 0 ]; then
echo "Migration failed"
exit 1
fi
if [ "$DJANGO_CREATE_SUPERUSER" = "True" ]; then
python manage.py createsuperuser --noinput --username "$DJANGO_SUPERUSER_USERNAME" --email "$DJANGO_SUPERUSER_EMAIL" || true
fi
python manage.py init_achievments
python manage.py generate_pretty_data