""" Эндпоинты для работы с тестом ДДО Е.А. Климова. 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///.svg. Путь к файлу хранится в таблице radar_results.image_path. """ from fastapi import APIRouter, HTTPException from typing import Optional import uuid from bd import get_session 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": ( "Творческие профессии. Требуются воображение, образное мышление, талант, трудолюбие. " "Примеры: художник, музыкант, актёр, архитектор, фотограф, дизайнер, писатель." ), }, ] @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///.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/." ), }