fix install diogram
This commit is contained in:
293
route/klimov_crud.py
Normal file
293
route/klimov_crud.py
Normal file
@@ -0,0 +1,293 @@
|
||||
"""
|
||||
Эндпоинты для работы с тестом ДДО Е.А. Климова.
|
||||
|
||||
POST /polls/klimov — создать опрос в БД (42 пары → нет, 20 пар)
|
||||
POST /polls/klimov/{id}/seed-scores — загрузить ключ расшифровки (5 типов)
|
||||
GET /polls/klimov — список тестов Климова
|
||||
|
||||
После прохождения (POST /polls/{id}/responses) диаграмма строится автоматически
|
||||
через response_crud → compute_radar и сохраняется в data/radar/<poll_title>/<user_id>/<result_id>.svg.
|
||||
Путь к файлу хранится в таблице radar_results.image_path.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import Optional
|
||||
import uuid
|
||||
|
||||
from bd import make_engine
|
||||
from bd.tables.poll import Poll
|
||||
from bd.tables.question import Question
|
||||
from bd.tables.choice import Choice
|
||||
from bd.tables.scale import ScaleDimension, ChoiceScore
|
||||
|
||||
router = APIRouter(tags=["polls"], prefix="/polls")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 20 пар вопросов ДДО Е.А. Климова
|
||||
# Каждая пара: (вариант А, вариант Б)
|
||||
# ---------------------------------------------------------------------------
|
||||
PAIRS: list[tuple[str, str]] = [
|
||||
("Ухаживать за животными.", # 1а
|
||||
"Обслуживать машины, приборы (следить, регулировать)."), # 1б
|
||||
("Помогать больным людям, лечить их.", # 2а
|
||||
"Составлять таблицы, схемы, программы для вычислительных машин."), # 2б
|
||||
("Следить за качеством книжных иллюстраций, плакатов, художественных открыток, грампластинок.", # 3а
|
||||
"Следить за состоянием, развитием растений."), # 3б
|
||||
("Обрабатывать материалы (дерево, ткань, пластмассу, металл и т.п.).", # 4а
|
||||
"Доводить товары до потребителя (рекламировать, продавать)."), # 4б
|
||||
("Обсуждать научно-популярные книги, статьи.", # 5а
|
||||
"Обсуждать художественные книги (или пьесы, концерты)."), # 5б
|
||||
("Выращивать молодняк (животных какой-либо породы).", # 6а
|
||||
"Тренировать товарищей (или младших) в выполнении каких-либо действий (трудовых, учебных, спортивных)."), # 6б
|
||||
("Копировать рисунки, изображения (или настраивать музыкальные инструменты).", # 7а
|
||||
"Управлять каким-либо грузовым (подъёмным или транспортным) средством."), # 7б
|
||||
("Сообщать, разъяснять людям нужные им сведения (в справочном бюро, на экскурсии и т.д.).", # 8а
|
||||
"Художественно оформлять выставки, витрины (или участвовать в подготовке пьес, концертов)."), # 8б
|
||||
("Ремонтировать вещи, изделия (одежду, технику), жилище.", # 9а
|
||||
"Искать и исправлять ошибки в текстах, таблицах, рисунках."), # 9б
|
||||
("Лечить животных.", # 10а
|
||||
"Выполнять вычисления, расчёты."), # 10б
|
||||
("Выводить новые сорта растений.", # 11а
|
||||
"Конструировать, проектировать новые виды промышленных изделий (машины или одежду, дома, продукты питания и т.п.)."), # 11б
|
||||
("Разбирать споры, ссоры между людьми, убеждать, разъяснять, поощрять, наказывать.", # 12а
|
||||
"Разбираться в чертежах, схемах, таблицах (проверять, уточнять, приводить в порядок)."), # 12б
|
||||
("Наблюдать, изучать работу кружков художественной самодеятельности.", # 13а
|
||||
"Наблюдать, изучать жизнь микробов."), # 13б
|
||||
("Обсуждать, налаживать медицинские приборы, аппараты.", # 14а
|
||||
"Оказывать людям медицинскую помощь при ранениях, ушибах, ожогах и т.п."), # 14б
|
||||
("Составлять точные описания, отчёты о наблюдаемых явлениях, событиях, измеряемых объектах.", # 15а
|
||||
"Художественно описывать, изображать события (наблюдаемые или представляемые)."), # 15б
|
||||
("Делать лабораторные анализы в больнице.", # 16а
|
||||
"Принимать, осматривать больных, беседовать с ними, назначать лечение."), # 16б
|
||||
("Красить или расписывать стены помещений, поверхность изделий.", # 17а
|
||||
"Осуществлять монтаж зданий или сборку машин, приборов."), # 17б
|
||||
("Организовывать культпоходы сверстников или младших в театры, музеи, экскурсии, туристические походы и т.п.", # 18а
|
||||
"Играть на сцене, принимать участие в концертах."), # 18б
|
||||
("Изготовлять по чертежам детали, изделия (машины, одежду), строить здания.", # 19а
|
||||
"Заниматься черчением, копированием чертежей, карт."), # 19б
|
||||
("Вести борьбу с болезнями растений, с вредителями леса, сада.", # 20а
|
||||
"Работать на клавишных машинах."), # 20б
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ключ расшифровки: номер пары → (тип варианта А, тип варианта Б)
|
||||
# Типы: П — природа, Т — техника, Ч — человек, Х — худ. образ, З — знаки
|
||||
# Источник: лист ответов ДДО Е.А. Климова
|
||||
# ---------------------------------------------------------------------------
|
||||
CHOICE_KEY: dict[int, tuple[str, str]] = {
|
||||
1: ("П", "Т"),
|
||||
2: ("Ч", "З"),
|
||||
3: ("З", "П"), # 3а — знаковая, 3б — природа
|
||||
4: ("Т", "Ч"),
|
||||
5: ("З", "Х"),
|
||||
6: ("П", "Ч"),
|
||||
7: ("Х", "Т"),
|
||||
8: ("Ч", "Х"),
|
||||
9: ("Т", "З"),
|
||||
10: ("П", "З"),
|
||||
11: ("П", "Т"),
|
||||
12: ("Ч", "З"),
|
||||
13: ("Х", "П"),
|
||||
14: ("Т", "Ч"),
|
||||
15: ("З", "Х"),
|
||||
16: ("П", "Ч"),
|
||||
17: ("Х", "Т"),
|
||||
18: ("Ч", "Х"),
|
||||
19: ("Т", "З"),
|
||||
20: ("П", "З"),
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5 типов профессий по Климову
|
||||
# ---------------------------------------------------------------------------
|
||||
DIMENSIONS = [
|
||||
{
|
||||
"code": "П",
|
||||
"name": "Человек — природа",
|
||||
"position": 1,
|
||||
"color": "#6B8E5E",
|
||||
"description": (
|
||||
"Представители этих профессий связаны с живой природой: сельское хозяйство, "
|
||||
"биология, охрана окружающей среды. Требуются наблюдательность, заботливость, "
|
||||
"терпение, самостоятельность. "
|
||||
"Примеры: агроном, зоотехник, ветеринар, биолог, геолог, лесник, пчеловод."
|
||||
),
|
||||
},
|
||||
{
|
||||
"code": "Т",
|
||||
"name": "Человек — техника",
|
||||
"position": 2,
|
||||
"color": "#4A7FA5",
|
||||
"description": (
|
||||
"Инженерно-технические профессии. Требуются точность, практическое мышление, "
|
||||
"техническая фантазия. "
|
||||
"Примеры: слесарь, токарь, водитель, электрик, инженер, монтажник, программист."
|
||||
),
|
||||
},
|
||||
{
|
||||
"code": "Ч",
|
||||
"name": "Человек — человек",
|
||||
"position": 3,
|
||||
"color": "#D4A84B",
|
||||
"description": (
|
||||
"Профессии, основанные на взаимодействии с людьми. Требуются общительность, "
|
||||
"эмпатия, терпение, умение слушать. "
|
||||
"Примеры: учитель, врач, психолог, продавец, администратор."
|
||||
),
|
||||
},
|
||||
{
|
||||
"code": "З",
|
||||
"name": "Человек — знаковая система",
|
||||
"position": 4,
|
||||
"color": "#8B7355",
|
||||
"description": (
|
||||
"Работа с цифрами, кодами, текстами, чертежами, формулами. Требуются "
|
||||
"внимательность, аккуратность, умение абстрагироваться. "
|
||||
"Примеры: программист, экономист, бухгалтер, корректор, чертёжник, переводчик."
|
||||
),
|
||||
},
|
||||
{
|
||||
"code": "Х",
|
||||
"name": "Человек — художественный образ",
|
||||
"position": 5,
|
||||
"color": "#8E44AD",
|
||||
"description": (
|
||||
"Творческие профессии. Требуются воображение, образное мышление, талант, трудолюбие. "
|
||||
"Примеры: художник, музыкант, актёр, архитектор, фотограф, дизайнер, писатель."
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_session():
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
return sessionmaker(bind=make_engine(), future=True)
|
||||
|
||||
|
||||
@router.post("/klimov", status_code=201)
|
||||
def create_klimov_poll():
|
||||
"""
|
||||
Создаёт тест ДДО Е.А. Климова в базе данных (20 пар вопросов, 2 варианта каждый).
|
||||
После создания вызовите POST /polls/klimov/{id}/seed-scores для загрузки ключа.
|
||||
"""
|
||||
Session = get_session()
|
||||
with Session() as session:
|
||||
poll = Poll(
|
||||
title="ДДО Е.А. Климова — определение типа профессии",
|
||||
description=(
|
||||
"Дифференциально-диагностический опросник Е.А. Климова: выберите из каждой пары "
|
||||
"предпочтительный вид занятий. 20 пар — выявляет ориентацию на 5 типов профессий: "
|
||||
"«Человек — природа», «Человек — техника», «Человек — человек», "
|
||||
"«Человек — знаковая система», «Человек — художественный образ»."
|
||||
),
|
||||
)
|
||||
session.add(poll)
|
||||
for idx, (a, b) in enumerate(PAIRS, start=1):
|
||||
q = Question(text=f"Пара {idx}", position=idx, type="single", poll=poll)
|
||||
session.add(q)
|
||||
ch1 = Choice(question=q, text=f"А) {a}", position=1)
|
||||
ch2 = Choice(question=q, text=f"Б) {b}", position=2)
|
||||
session.add(ch1)
|
||||
session.add(ch2)
|
||||
session.commit()
|
||||
session.refresh(poll)
|
||||
return {"id": str(poll.id), "questions": len(poll.questions)}
|
||||
|
||||
|
||||
@router.get("/klimov")
|
||||
def list_klimov_polls():
|
||||
"""Возвращает список тестов ДДО Климова."""
|
||||
Session = get_session()
|
||||
with Session() as session:
|
||||
rows = session.query(Poll).filter(Poll.title.like("%Климова%")).all()
|
||||
return [{"id": str(p.id), "title": p.title, "description": p.description} for p in rows]
|
||||
|
||||
|
||||
@router.post("/klimov/{poll_id}/seed-scores", status_code=201)
|
||||
def seed_klimov_scores(poll_id: str):
|
||||
"""
|
||||
Загружает расшифровку ДДО Климова в базу данных:
|
||||
- создаёт 5 осей диаграммы (ScaleDimension) для указанного теста
|
||||
- для каждого из 20 вопросов привязывает оба варианта ответа к своей оси (ChoiceScore)
|
||||
|
||||
При повторном вызове оси и расшифровки пересоздаются.
|
||||
Диаграмма строится автоматически после прохождения теста (POST /polls/{id}/responses)
|
||||
и сохраняется в data/radar/<poll_title>/<user_id>/<result_id>.svg.
|
||||
"""
|
||||
Session = get_session()
|
||||
try:
|
||||
pid = uuid.UUID(poll_id)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=400, detail="Invalid poll UUID")
|
||||
|
||||
with Session() as session:
|
||||
poll = session.get(Poll, pid)
|
||||
if not poll:
|
||||
raise HTTPException(status_code=404, detail="Poll not found")
|
||||
|
||||
questions = (
|
||||
session.query(Question)
|
||||
.filter(Question.poll_id == pid)
|
||||
.order_by(Question.position.asc())
|
||||
.all()
|
||||
)
|
||||
if len(questions) != 20:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=f"Ожидается 20 вопросов, найдено {len(questions)}. Убедитесь, что тест создан через POST /polls/klimov.",
|
||||
)
|
||||
|
||||
# Удаляем старые оси (и ChoiceScore через cascade)
|
||||
old_dims = session.query(ScaleDimension).filter(ScaleDimension.poll_id == pid).all()
|
||||
for d in old_dims:
|
||||
session.delete(d)
|
||||
session.flush()
|
||||
|
||||
# Создаём 5 осей: код → объект
|
||||
dim_map: dict[str, ScaleDimension] = {}
|
||||
for d_data in DIMENSIONS:
|
||||
dim = ScaleDimension(
|
||||
poll_id=pid,
|
||||
name=d_data["name"],
|
||||
description=d_data["description"],
|
||||
color=d_data["color"],
|
||||
position=d_data["position"],
|
||||
)
|
||||
session.add(dim)
|
||||
session.flush()
|
||||
dim_map[d_data["code"]] = dim
|
||||
|
||||
# Для каждого вопроса привязываем варианты к осям
|
||||
scores_created = 0
|
||||
for q in questions:
|
||||
pair_num = q.position # 1..20
|
||||
if pair_num not in CHOICE_KEY:
|
||||
continue
|
||||
type_a, type_b = CHOICE_KEY[pair_num]
|
||||
|
||||
choices = (
|
||||
session.query(Choice)
|
||||
.filter(Choice.question_id == q.id)
|
||||
.order_by(Choice.position.asc())
|
||||
.all()
|
||||
)
|
||||
if len(choices) < 2:
|
||||
continue
|
||||
|
||||
choice_a, choice_b = choices[0], choices[1]
|
||||
session.add(ChoiceScore(choice_id=choice_a.id, dimension_id=dim_map[type_a].id, score=1.0))
|
||||
session.add(ChoiceScore(choice_id=choice_b.id, dimension_id=dim_map[type_b].id, score=1.0))
|
||||
scores_created += 2
|
||||
|
||||
session.commit()
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"dimensions": len(DIMENSIONS),
|
||||
"choice_scores": scores_created,
|
||||
"message": (
|
||||
"Расшифровка ДДО Климова успешно загружена. "
|
||||
"После прохождения теста диаграмма строится автоматически и "
|
||||
"сохраняется в data/radar/."
|
||||
),
|
||||
}
|
||||
Reference in New Issue
Block a user