Files
librustui/demo.py
T
2026-11-08 19:28:03 +01:00

564 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Offline demo data for librus-tui.
Start the server with --demo to explore the interface without a real
Librus account. All dates are generated relative to today so the dashboard
and timetable always look alive.
"""
import asyncio
import copy
from datetime import date, datetime, timedelta
WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
SUBJECTS = [
"Język polski",
"Matematyka",
"Język angielski",
"Fizyka",
"Chemia",
"Biologia",
"Historia",
"Informatyka",
"Geografia",
"Wychowanie fizyczne",
]
TEACHERS = {
"polski": {"FirstName": "Maria", "LastName": "Wiśniewska"},
"matma": {"FirstName": "Piotr", "LastName": "Zieliński"},
"angielski": {"FirstName": "Sarah", "LastName": "Kowalczyk"},
"fizyka": {"FirstName": "Andrzej", "LastName": "Lewandowski"},
"chemia": {"FirstName": "Ewa", "LastName": "Szymańska"},
"biologia": {"FirstName": "Katarzyna", "LastName": "Wójcik"},
"historia": {"FirstName": "Tomasz", "LastName": "Kamiński"},
"informatyka": {"FirstName": "Michał", "LastName": "Nowicki"},
"geografia": {"FirstName": "Agnieszka", "LastName": "Dąbrowska"},
"wf": {"FirstName": "Marek", "LastName": "Jankowski"},
}
def iso(d):
return d.strftime("%Y-%m-%d")
def grade(value, weight, category, teacher, days_ago=3, comment="No comment", **extra):
base = {
"Grade": value,
"Weight": weight,
"Comment": comment,
"Category": category,
"isFinal": False,
"isSemester": False,
"separator": False,
"underlined": False,
"Semester": 1,
"AddDate": iso(date.today() - timedelta(days=days_ago)),
"AddedBy": copy.deepcopy(teacher),
}
base.update(extra)
return base
def build_grades():
t = TEACHERS
return {
"Język polski": [
grade("5", "2", "Sprawdzian", t["polski"], 12, "Świetna analiza lektury."),
grade("4", "1", "Kartkówka", t["polski"], 9),
grade("5", "1", "Aktywność", t["polski"], 2),
grade("4", "2", "Wypracowanie", t["polski"], 21, "Bardzo dobry styl, drobne błędy."),
],
"Matematyka": [
grade("5", "3", "Sprawdzian", t["matma"], 7, "Bezbłędnie!"),
grade("4", "1", "Kartkówka", t["matma"], 3),
grade("3", "2", "Praca domowa", t["matma"], 14),
grade("5", "2", "Sprawdzian", t["matma"], 28),
grade("4", "1", "Aktywność", t["matma"], 1),
],
"Język angielski": [
grade("5", "2", "Test", t["angielski"], 5),
grade("5", "1", "Speaking", t["angielski"], 10),
grade("4", "2", "Essay", t["angielski"], 19),
],
"Fizyka": [
grade("4", "2", "Sprawdzian", t["fizyka"], 8),
grade("3", "1", "Kartkówka", t["fizyka"], 16),
grade("4", "1", "Doświadczenie", t["fizyka"], 4),
],
"Chemia": [
grade("4", "2", "Sprawdzian", t["chemia"], 11),
grade("5", "1", "Kartkówka", t["chemia"], 6),
],
"Biologia": [
grade("5", "2", "Sprawdzian", t["biologia"], 13),
grade("5", "1", "Projekt", t["biologia"], 20),
],
"Historia": [
grade("4", "2", "Sprawdzian", t["historia"], 9),
grade("4", "1", "Kartkówka", t["historia"], 2),
grade("5", "1", "Wypowiedź", t["historia"], 25),
],
"Informatyka": [
grade("6", "2", "Projekt", t["informatyka"], 6, "Wyjątkowo dobrze zaprojektowana aplikacja."),
grade("5", "1", "Kartkówka", t["informatyka"], 15),
],
"Geografia": [
grade("4", "2", "Sprawdzian", t["geografia"], 10),
grade("4", "1", "Mapa", t["geografia"], 5),
],
"Wychowanie fizyczne": [
grade("5", "1", "Aktywność", t["wf"], 3),
grade("5", "1", "Test sprawności", t["wf"], 17),
],
}
def lesson(no, subject, teacher, room, start, end, **extra):
item = {
"Lesson": no,
"Subject": subject,
"isSubstitution": False,
"isCancelled": False,
"Teacher": copy.deepcopy(teacher),
"HourFrom": start,
"HourTo": end,
"Classroom": room,
}
item.update(extra)
return item
PLAN = {
"Monday": [
(1, "Język polski", "polski", "A12", "08:00", "08:45"),
(2, "Matematyka", "matma", "B04", "08:55", "09:40"),
(3, "Język angielski", "angielski", "A07", "09:50", "10:35"),
(4, "Historia", "historia", "A21", "10:45", "11:30"),
(6, "Wychowanie fizyczne", "wf", "SALA", "12:45", "13:30"),
],
"Tuesday": [
(1, "Fizyka", "fizyka", "C02", "08:00", "08:45"),
(2, "Chemia", "chemia", "C03", "08:55", "09:40"),
(3, "Matematyka", "matma", "B04", "09:50", "10:35"),
(5, "Język polski", "polski", "A12", "11:45", "12:30"),
(7, "Informatyka", "informatyka", "INF", "13:45", "14:30"),
],
"Wednesday": [
(1, "Biologia", "biologia", "B11", "08:00", "08:45"),
(2, "Geografia", "geografia", "A19", "08:55", "09:40"),
(3, "Język angielski", "angielski", "A07", "09:50", "10:35"),
(4, "Matematyka", "matma", "B04", "10:45", "11:30"),
(6, "Historia", "historia", "A21", "12:45", "13:30"),
],
"Thursday": [
(1, "Matematyka", "matma", "B04", "08:00", "08:45"),
(2, "Fizyka", "fizyka", "C02", "08:55", "09:40"),
(3, "Język polski", "polski", "A12", "09:50", "10:35"),
(5, "Wychowanie fizyczne", "wf", "SALA", "11:45", "12:30"),
],
"Friday": [
(1, "Chemia", "chemia", "C03", "08:00", "08:45"),
(2, "Biologia", "biologia", "B11", "08:55", "09:40"),
(3, "Informatyka", "informatyka", "INF", "09:50", "10:35"),
(4, "Język angielski", "angielski", "A07", "10:45", "11:30"),
],
}
SUBSTITUTIONS = {
"Wednesday": {4: {"isSubstitution": True, "Classroom": "A02"}},
"Friday": {3: {"isCancelled": True, "isSubstitution": True}},
}
def build_timetable(week=0):
today = date.today()
week_start = today - timedelta(days=today.weekday()) + timedelta(weeks=week)
week_end = week_start + timedelta(days=6)
data = {}
for index, day in enumerate(WEEKDAYS[:5]):
entries = []
for no, subject, teacher_key, room, start, end in PLAN[day]:
extra = SUBSTITUTIONS.get(day, {}).get(no, {}) if week == 0 else {}
item = lesson(
no,
subject,
TEACHERS[teacher_key],
extra.get("Classroom", room),
start,
end,
**{k: v for k, v in extra.items() if k != "Classroom"},
)
item["Date"] = iso(week_start + timedelta(days=index))
entries.append(item)
if week == 1 and day == "Thursday":
entries.append(
{
"Lesson": "-",
"Subject": "Konsultacje z matematyki",
"isSubstitution": False,
"isCancelled": False,
"Teacher": copy.deepcopy(TEACHERS["matma"]),
"HourFrom": "15:00",
"HourTo": "16:00",
"Classroom": "B04",
"Date": iso(week_start + timedelta(days=3)),
"isAdditional": True,
}
)
if entries:
data[day] = entries
return {
"Timetable": data,
"weekStart": iso(week_start),
"weekEnd": iso(week_end),
"week": week,
}
def build_exams():
today = date.today()
return [
{
"Lesson": "Matematyka",
"AddedBy": copy.deepcopy(TEACHERS["matma"]),
"Type": "Sprawdzian",
"StartTime": "09:50",
"EndTime": "10:35",
"Date": iso(today + timedelta(days=2)),
"AddDate": iso(today - timedelta(days=4)) + " 14:12:00",
"Content": "Funkcja kwadratowa: postać ogólna, kanoniczna i iloczynowa. Zadania 1-8, str. 142.",
},
{
"Lesson": "Język polski",
"AddedBy": copy.deepcopy(TEACHERS["polski"]),
"Type": "Wypracowanie",
"StartTime": "08:00",
"EndTime": "08:45",
"Date": iso(today + timedelta(days=4)),
"AddDate": iso(today - timedelta(days=2)) + " 10:03:00",
"Content": "Rozprawka: „Czy warto znać przeszłość?”. Minimum 300 słów, odwołanie do lektury.",
},
{
"Lesson": "Fizyka",
"AddedBy": copy.deepcopy(TEACHERS["fizyka"]),
"Type": "Kartkówka",
"StartTime": "08:55",
"EndTime": "09:40",
"Date": iso(today + timedelta(days=7)),
"AddDate": iso(today - timedelta(days=1)) + " 16:44:00",
"Content": "Zasady dynamiki Newtona. Wzory i jednostki.",
},
{
"Lesson": "Informatyka",
"AddedBy": copy.deepcopy(TEACHERS["informatyka"]),
"Type": "Projekt",
"StartTime": "",
"EndTime": "",
"Date": iso(today + timedelta(days=10)),
"AddDate": iso(today - timedelta(days=6)) + " 09:15:00",
"Content": "Aplikacja terminalowa w Pythonie: minimum 3 podkomendy, obsługa błędów i README.",
},
]
def build_attendances():
today = date.today()
entries = [
("Matematyka", "Obecność", "ob.", True, 0),
("Język polski", "Obecność", "ob.", True, 0),
("Fizyka", "Spóźnienie", "sp.", False, 1),
("Chemia", "Nieobecność", "nb.", False, 2),
("Historia", "Obecność", "ob.", True, 2),
("Język angielski", "Zwolnienie", "zw.", True, 3),
("Biologia", "Nieobecność", "nb.", False, 4),
("Informatyka", "Obecność", "ob.", True, 5),
("Geografia", "Obecność", "ob.", True, 6),
("Wychowanie fizyczne", "Obecność", "ob.", True, 7),
("Matematyka", "Nieobecność", "nb.", False, 8),
("Język polski", "Obecność", "ob.", True, 9),
]
result = []
for subject, kind, short, presence, days_ago in entries:
teacher_key = {
"Matematyka": "matma",
"Język polski": "polski",
"Fizyka": "fizyka",
"Chemia": "chemia",
"Historia": "historia",
"Język angielski": "angielski",
"Biologia": "biologia",
"Informatyka": "informatyka",
"Geografia": "geografia",
"Wychowanie fizyczne": "wf",
}[subject]
d = today - timedelta(days=days_ago)
result.append(
{
"Lesson": subject,
"Type": kind,
"Short": short,
"isPresence": presence,
"Added": iso(d) + " 18:00:00",
"Date": iso(d),
"AddedBy": copy.deepcopy(TEACHERS[teacher_key]),
}
)
return result
MESSAGES = [
{
"from": "Maria Wiśniewska",
"subject": "Wyniki próbnej matury z języka polskiego",
"date": "07.11.2026 14:22",
"link": "1081-1",
},
{
"from": "Piotr Zieliński",
"subject": "Konsultacje przed sprawdzianem",
"date": "06.11.2026 09:10",
"link": "1074-1",
},
{
"from": "Sekretariat",
"subject": "Zebranie z rodzicami – 12 listopada",
"date": "05.11.2026 16:45",
"link": "1069-1",
},
{
"from": "Michał Nowicki",
"subject": "Dodatkowe zajęcia z algorytmiki",
"date": "04.11.2026 11:03",
"link": "1062-1",
},
]
MESSAGE_BODIES = {
"1081-1": {
"subject": "Wyniki próbnej matury z języka polskiego",
"from": "Maria Wiśniewska (nauczyciel)",
"date": "07.11.2026 14:22",
"read": "Tak",
"content": (
"<p>Dzień dobry,</p>"
"<p>wyniki próbnej matury z języka polskiego są już dostępne w dzienniku. "
"Średnia klasy to <b>78%</b>. Gratulacje dla wszystkich, którzy przekroczyli 80%.</p>"
"<p>Omówienie arkusza odbędzie się w <b>poniedziałek</b> na lekcji 3. Proszę przynieść "
"zeszyty i arkusze.</p>"
"<p>Pozdrawiam,<br>Maria Wiśniewska</p>"
),
"attachments": [
{"name": "klucz_odpowiedzi.pdf", "nice": "1081-1/klucz.pdf"},
],
},
"1074-1": {
"subject": "Konsultacje przed sprawdzianem",
"from": "Piotr Zieliński (nauczyciel)",
"date": "06.11.2026 09:10",
"read": "Tak",
"content": (
"<p>Przypominam, że w czwartek o <b>15:00</b> w sali B04 odbędą się konsultacje "
"przed sprawdzianem z funkcji kwadratowej.</p>"
"<p>Przygotujcie pytania do zadań z rozdziału 5.</p>"
),
"attachments": [],
},
"1069-1": {
"subject": "Zebranie z rodzicami – 12 listopada",
"from": "Sekretariat (administracja)",
"date": "05.11.2026 16:45",
"read": "Nie",
"content": (
"<p>Uprzejmie informujemy, że zebranie z rodzicami odbędzie się "
"<b>12 listopada 2026 r. o godz. 17:00</b> w sali gimnastycznej.</p>"
"<p>Po spotkaniu ogólnym zapraszamy na spotkania z wychowawcami.</p>"
),
"attachments": [
{"name": "harmonogram_zebran.pdf", "nice": "1069-1/harmonogram.pdf"},
],
},
"1062-1": {
"subject": "Dodatkowe zajęcia z algorytmiki",
"from": "Michał Nowicki (nauczyciel)",
"date": "04.11.2026 11:03",
"read": "Tak",
"content": (
"<p>Zapraszam na dodatkowe zajęcia z algorytmiki w każdą środę o 15:30 "
"w sali INF. Pracujemy nad projektami do olimpiady informatycznej.</p>"
),
"attachments": [],
},
}
def build_free_days():
today = date.today()
return [
{
"Name": "Wigilia szkolna",
"DateFrom": iso(date(today.year, 12, 23)),
"DateTo": iso(date(today.year, 12, 31)),
},
{
"Name": "Dzień Edukacji Narodowej",
"DateFrom": iso(date(today.year, 10, 14)),
"DateTo": iso(date(today.year, 10, 14)),
},
{
"Name": "Ferie zimowe",
"DateFrom": iso(date(today.year + 1, 1, 26)),
"DateTo": iso(date(today.year + 1, 2, 8)),
},
{
"Name": "Wielkanoc",
"DateFrom": iso(date(today.year + 1, 3, 28)),
"DateTo": iso(date(today.year + 1, 4, 2)),
},
]
def build_teacher_free_days():
today = date.today()
return [
{
"Teacher": copy.deepcopy(TEACHERS["matma"]),
"DateFrom": iso(today + timedelta(days=5)),
"DateTo": iso(today + timedelta(days=5)),
"TimeFrom": "10:00:00",
"TimeTo": "14:00:00",
},
{
"Teacher": copy.deepcopy(TEACHERS["angielski"]),
"DateFrom": iso(today + timedelta(days=9)),
"DateTo": iso(today + timedelta(days=11)),
},
]
def build_conferences():
today = date.today()
return [
{
"Name": "Zebranie śródroczne",
"Topic": "Wyniki w nauce i frekwencja",
"Room": "A12",
"Date": iso(today + timedelta(days=4)),
"Time": "17:00:00",
},
{
"Name": "Konsultacje indywidualne",
"Topic": "Matematyka – przygotowanie do matury",
"Room": "B04",
"Date": iso(today + timedelta(days=11)),
"Time": "16:30:00",
},
]
class DemoLibrus:
"""Drop-in replacement for librus.Librus backed by sample data."""
demo = True
credentials_rejected = False
headers = {}
def __init__(self):
self._grades = build_grades()
self._attendances = build_attendances()
async def mktoken(self, login, password):
await asyncio.sleep(0.35)
return True
async def activate_api_access(self):
return True
async def get_me(self):
return {
"FirstName": "Alex",
"LastName": "Nowak",
"Tutor": copy.deepcopy(TEACHERS["polski"]),
"SchoolYearStarts": "2026-09-01",
"SchoolYearMiddles": "2027-01-31",
"SchoolYearEnds": "2027-06-25",
"Type": "Uczeń",
"Class": "3 A",
}
async def get_school(self):
return {
"Name": "I Liceum Ogólnokształcące im. Mikołaja Kopernika",
"Street": "ul. Szkolna",
"BuildingNumber": "12",
"PostCode": "00-001",
"Town": "Warszawa",
}
async def get_lucky_number(self):
return 7
async def get_behaviour_grade(self):
return {
"Grade": "bardzo dobre",
"Teacher": copy.deepcopy(TEACHERS["polski"]),
}
async def get_grades(self):
return copy.deepcopy(self._grades)
async def get_timetable(self, week=0):
return build_timetable(week)
async def get_exams(self):
return build_exams()
async def get_attendances(self):
return copy.deepcopy(self._attendances)
async def get_notifications(self, days=14):
return {
"grades": sum(len(v) for v in self._grades.values()),
"exams": len(build_exams()),
"absences": sum(1 for a in self._attendances if not a["isPresence"]),
"conferences": len(build_conferences()),
}
async def get_messages(self):
return copy.deepcopy(MESSAGES)
async def get_message(self, link):
body = copy.deepcopy(
MESSAGE_BODIES.get(link, MESSAGE_BODIES["1081-1"])
)
body["attachments"] = [
dict(
attachment,
source="https://synergia.librus.pl/wiadomosci/pobierz_zalacznik/"
+ attachment["nice"],
direct="/get",
)
for attachment in body["attachments"]
]
return body
async def download_file(self, link):
return {
"headers": {
"Content-Type": "application/pdf",
"Content-Disposition": 'attachment; filename="demo.pdf"',
},
"content": b"%PDF-1.4\n% librus-tui demo attachment\n",
}
async def get_free_days(self):
return build_free_days()
async def get_teacher_free_days(self):
return build_teacher_free_days()
async def get_parent_teacher_conferences(self):
return build_conferences()