From 3bf871a423a226b5c0350b32ffcba8c6c06b2af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BC=D0=B8=D1=80=20=D0=9C=D0=B0=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D1=88=D0=B0=D0=BE=D0=B2?= Date: Mon, 27 Oct 2025 19:07:59 +0300 Subject: [PATCH 1/2] [UPDATE] Initial commit for weekly-score branch --- .gitignore | 4 +++- app/api/v1/facts.py | 8 ++++++++ app/api/v1/group.py | 8 ++++++++ app/api/v1/listeners.py | 8 ++++++++ app/api/v1/students.py | 2 +- 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 app/api/v1/facts.py create mode 100644 app/api/v1/group.py create mode 100644 app/api/v1/listeners.py diff --git a/.gitignore b/.gitignore index c6aa9b8..5061802 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,6 @@ Thumbs.db venv/ env/ .venv/ - +TutorAI/lib/ +TutorAI/pyvenv.cfg +TutorAI/bin/ \ No newline at end of file diff --git a/app/api/v1/facts.py b/app/api/v1/facts.py new file mode 100644 index 0000000..4b56bbf --- /dev/null +++ b/app/api/v1/facts.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +# Получение фактов недели на студента +router = APIRouter(prefix="/students", tags=["students"]) + +@router.get("/facts") +async def get_weekly_facts(week: int = None, student_id: int = None): + return {"message": "Facts API - coming soon"} \ No newline at end of file diff --git a/app/api/v1/group.py b/app/api/v1/group.py new file mode 100644 index 0000000..c9b7e0f --- /dev/null +++ b/app/api/v1/group.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +# Запрос конфигурации группы/периода/программы +router = APIRouter(prefix="/groups", tags=["groups"]) + +@router.get("/config") +async def get_group_config(stream_id: str = None, week: int = None, module_id: str = None): + return {"message": "Group API - coming soon"} \ No newline at end of file diff --git a/app/api/v1/listeners.py b/app/api/v1/listeners.py new file mode 100644 index 0000000..a2722aa --- /dev/null +++ b/app/api/v1/listeners.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +# Выбор слушателей недели +router = APIRouter(prefix="/students", tags=["students"]) + +@router.get("/") +async def get_students(week: int = None): # какой параметр передавать? + return {"message": "Listeners API - coming soon"} \ No newline at end of file diff --git a/app/api/v1/students.py b/app/api/v1/students.py index c1c91d5..f557e1c 100644 --- a/app/api/v1/students.py +++ b/app/api/v1/students.py @@ -4,4 +4,4 @@ @router.get("/") async def get_students(): - return {"message": "Students API - coming soon"} + return {"message": "Students API - coming soon"} \ No newline at end of file From 7e5fd976922702f52752175d64a95ce9bd854395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BC=D0=B8=D1=80=20=D0=9C=D0=B0=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D1=88=D0=B0=D0=BE=D0=B2?= Date: Sat, 29 Nov 2025 18:20:26 +0300 Subject: [PATCH 2/2] Minimal changes --- app/api/v1/config.json | 30 +++++++ app/api/v1/facts.py | 52 +++++++++++- app/api/v1/get_config.py | 38 +++++++++ app/api/v1/group.py | 8 -- app/api/v1/listeners.py | 63 +++++++++++++- app/api/v1/mock.json | 174 +++++++++++++++++++++++++++++++++++++++ app/main.py | 8 +- 7 files changed, 357 insertions(+), 16 deletions(-) create mode 100644 app/api/v1/config.json create mode 100644 app/api/v1/get_config.py delete mode 100644 app/api/v1/group.py create mode 100644 app/api/v1/mock.json diff --git a/app/api/v1/config.json b/app/api/v1/config.json new file mode 100644 index 0000000..9d2825d --- /dev/null +++ b/app/api/v1/config.json @@ -0,0 +1,30 @@ +{ + "programs": { + "AI-BOOTCAMP": { + "default_frequency": "weekly", + "groups": { + "AI-101": { + "frequency": "biweekly", + "start_date": "2025-10-28" + }, + "AI-102": { + "frequency": "weekly", + "start_date": "2025-10-21" + } + } + }, + "ML-BASICS": { + "default_frequency": "weekly", + "groups": { + "ML-101": { + "frequency": "biweekly", + "start_date": "2025-10-17" + }, + "ML-102": { + "frequency": "weekly", + "start_date": "2025-11-01" + } + } + } + } +} \ No newline at end of file diff --git a/app/api/v1/facts.py b/app/api/v1/facts.py index 4b56bbf..0695226 100644 --- a/app/api/v1/facts.py +++ b/app/api/v1/facts.py @@ -1,8 +1,54 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Depends +from app.core.database import get_db +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from app.models.education import Assignment +from datetime import datetime, timedelta, timezone + +import json +from pathlib import Path + +MOSCOW_TZ = timezone(timedelta(hours=3)) + +def get_days_ago_start(days_ago: int): + now_msk = datetime.now(MOSCOW_TZ) + target_date = now_msk - timedelta(days=days_ago) + target_date = target_date.replace(hour=0, minute=0, second=0, microsecond=0) + return target_date # Получение фактов недели на студента router = APIRouter(prefix="/students", tags=["students"]) @router.get("/facts") -async def get_weekly_facts(week: int = None, student_id: int = None): - return {"message": "Facts API - coming soon"} \ No newline at end of file +async def get_weekly_facts(student_id: str = None, period: int = 7, db: AsyncSession = Depends(get_db)): + # period_start = get_days_ago_start(period) + # query = select(Assignment).where(Assignment.submitted_at >= period_start) + + # if student_id: + # query = query.where(Assignment.student_id == student_id) + + # result = await db.execute(query) + # facts = result.scalars.all() + + # return [{ + # "id": a.student_id, + # "grade": a.grade, + # "submitted_at": a.submitted_at + # } + # for a in facts] + + mock_path = Path("app/api/v1/mock.json") + mock_data = json.loads(mock_path.read_text()) + + student = next((s for s in mock_data["students"] if s["student_id"] == student_id), None) + if not student: + return {"error": f"Student with id {student_id} not found"} + + return [ + { + "id": student["student_id"], + "grade": a["grade"], + "submitted_at": a["submitted_at"] + } + for a in student["assignments"] + ] \ No newline at end of file diff --git a/app/api/v1/get_config.py b/app/api/v1/get_config.py new file mode 100644 index 0000000..007f5cf --- /dev/null +++ b/app/api/v1/get_config.py @@ -0,0 +1,38 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session +from app.core.database import get_db +from app.models.education import Stream +import json +from pathlib import Path + +router = APIRouter(prefix="/config", tags=["config"]) + +@router.get("/") +async def get_group_config(program_id: str = None, group_id: str = None, db: Session = Depends(get_db)): + config_path = Path("app/api/v1/config.json") + config = json.loads(config_path.read_text()) + + # Загружаем базовую структуру + if not program_id: + return config + + program = config["programs"].get(program_id) + if not program: + return {"error": "Program not found"} + + if not group_id: + return program + + group = program.get("groups", {}).get(group_id) + if not group: + return {"error": "Group not found"} + + # Получаем дату начала из базы + stream = db.query(Stream).filter(Stream.name == group_id).first() + start_date = stream.start_date if stream else None + + final = program.copy() + if group: + final.update(group) + final["start_date"] = str(start_date) if start_date else None + return final diff --git a/app/api/v1/group.py b/app/api/v1/group.py deleted file mode 100644 index c9b7e0f..0000000 --- a/app/api/v1/group.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import APIRouter - -# Запрос конфигурации группы/периода/программы -router = APIRouter(prefix="/groups", tags=["groups"]) - -@router.get("/config") -async def get_group_config(stream_id: str = None, week: int = None, module_id: str = None): - return {"message": "Group API - coming soon"} \ No newline at end of file diff --git a/app/api/v1/listeners.py b/app/api/v1/listeners.py index a2722aa..6e48651 100644 --- a/app/api/v1/listeners.py +++ b/app/api/v1/listeners.py @@ -1,8 +1,63 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session +from app.core.database import get_db +from app.models.education import Stream -# Выбор слушателей недели router = APIRouter(prefix="/students", tags=["students"]) @router.get("/") -async def get_students(week: int = None): # какой параметр передавать? - return {"message": "Listeners API - coming soon"} \ No newline at end of file +async def get_students(group_id: str, db: Session = Depends(get_db)): + """ + Возвращает список студентов по названию группы (stream.stream_id = group_id) + """ + + if group_id == "AI-102": + return [ + { + "student_id": "st_001", + "group_id": group_id + }, + { + "student_id": "st_002", + "group_id": group_id + }, + { + "student_id": "st_003", + "group_id": group_id + }, + { + "student_id": "st_004", + "group_id": group_id + }, + { + "student_id": "st_005", + "group_id": group_id + } +] + if group_id == "ML-102": + return [ + { + "student_id": "st_101", + "group_id": group_id + }, + { + "student_id": "st_102", + "group_id": group_id + }, + { + "student_id": "st_103", + "group_id": group_id + }, + { + "student_id": "st_104", + "group_id": group_id + }, + { + "student_id": "st_105", + "group_id": group_id + } +] + # Ищем поток по имени (названию группы) + # stream = db.query(Stream).filter(Stream.stream_id == group_id).first() + + # return [{"student_id": s.student_id} for s in stream.students] diff --git a/app/api/v1/mock.json b/app/api/v1/mock.json new file mode 100644 index 0000000..ccb6255 --- /dev/null +++ b/app/api/v1/mock.json @@ -0,0 +1,174 @@ +{ + "students": [ + { + "student_id": "st_101", + "name": "Alice Johnson", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_001", + "grade": 88, + "submitted_at": "2025-11-03T14:32:00Z" + }, + { + "id": "as_002", + "grade": 95, + "submitted_at": "2025-11-07T10:18:00Z" + } + ] + }, + { + "student_id": "st_102", + "name": "Michael Smith", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_010", + "grade": 76, + "submitted_at": "2025-11-04T09:40:00Z" + }, + { + "id": "as_011", + "grade": 82, + "submitted_at": "2025-11-06T15:10:00Z" + }, + { + "id": "as_012", + "grade": 79, + "submitted_at": "2025-11-08T11:55:00Z" + } + ] + }, + { + "student_id": "st_103", + "name": "Sofia Martinez", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_020", + "grade": 90, + "submitted_at": "2025-11-02T13:22:00Z" + }, + { + "id": "as_021", + "grade": 93, + "submitted_at": "2025-11-05T08:40:00Z" + } + ] + }, + { + "student_id": "st_104", + "name": "David Lee", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_030", + "grade": 100, + "submitted_at": "2025-11-01T16:05:00Z" + }, + { + "id": "as_031", + "grade": 97, + "submitted_at": "2025-11-06T19:30:00Z" + } + ] + }, + { + "student_id": "st_105", + "name": "Emma Brown", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_040", + "grade": 65, + "submitted_at": "2025-11-03T12:00:00Z" + } + ] + }, + { + "student_id": "st_001", + "name": "Alice Johnson", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_001", + "grade": 88, + "submitted_at": "2025-11-03T14:32:00Z" + }, + { + "id": "as_002", + "grade": 95, + "submitted_at": "2025-11-07T10:18:00Z" + } + ] + }, + { + "student_id": "st_002", + "name": "Michael Smith", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_010", + "grade": 76, + "submitted_at": "2025-11-04T09:40:00Z" + }, + { + "id": "as_011", + "grade": 82, + "submitted_at": "2025-11-06T15:10:00Z" + }, + { + "id": "as_012", + "grade": 79, + "submitted_at": "2025-11-08T11:55:00Z" + } + ] + }, + { + "student_id": "st_003", + "name": "Sofia Martinez", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_020", + "grade": 90, + "submitted_at": "2025-11-02T13:22:00Z" + }, + { + "id": "as_021", + "grade": 93, + "submitted_at": "2025-11-05T08:40:00Z" + } + ] + }, + { + "student_id": "st_004", + "name": "David Lee", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_030", + "grade": 100, + "submitted_at": "2025-11-01T16:05:00Z" + }, + { + "id": "as_031", + "grade": 97, + "submitted_at": "2025-11-06T19:30:00Z" + } + ] + }, + { + "student_id": "st_005", + "name": "Emma Brown", + "group_id": "group_2025_B", + "assignments": [ + { + "id": "as_040", + "grade": 65, + "submitted_at": "2025-11-03T12:00:00Z" + } + ] + } + ] +} diff --git a/app/main.py b/app/main.py index a504962..05214e0 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.responses import RedirectResponse from app.admin.views import setup_admin +from app.api.v1 import get_config, facts, listeners app = FastAPI( title="AI Tutor API", @@ -18,4 +19,9 @@ def root(): @app.get("/health") def health_check(): - return {"status": "ok"} \ No newline at end of file + return {"status": "ok"} + +# Подключение API маршрутов +app.include_router(facts.router) +app.include_router(get_config.router) +app.include_router(listeners.router) \ No newline at end of file