librus-python-api 1.0.3__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- librus_python_api/__init__.py +31 -1
- librus_python_api/config.py +159 -5
- librus_python_api/models.py +154 -0
- librus_python_api/modern_mailbox.py +137 -9
- librus_python_api/modern_messages.py +23 -0
- librus_python_api/school_year_archive.py +432 -0
- librus_python_api/service.py +244 -25
- librus_python_api/transport.py +3 -6
- {librus_python_api-1.0.3.dist-info → librus_python_api-1.3.0.dist-info}/METADATA +15 -7
- {librus_python_api-1.0.3.dist-info → librus_python_api-1.3.0.dist-info}/RECORD +12 -11
- {librus_python_api-1.0.3.dist-info → librus_python_api-1.3.0.dist-info}/WHEEL +0 -0
- {librus_python_api-1.0.3.dist-info → librus_python_api-1.3.0.dist-info}/licenses/LICENSE +0 -0
librus_python_api/__init__.py
CHANGED
|
@@ -16,6 +16,13 @@ from librus_python_api.models import (
|
|
|
16
16
|
AgendaEvent,
|
|
17
17
|
Announcement,
|
|
18
18
|
Announcements,
|
|
19
|
+
ArchiveAbsences,
|
|
20
|
+
ArchiveAchievement,
|
|
21
|
+
ArchiveCounts,
|
|
22
|
+
ArchiveDescriptive,
|
|
23
|
+
ArchiveMarks,
|
|
24
|
+
ArchiveSubject,
|
|
25
|
+
ArchiveYear,
|
|
19
26
|
AttachmentHeaders,
|
|
20
27
|
AttachmentMetadata,
|
|
21
28
|
Attendance,
|
|
@@ -65,6 +72,9 @@ from librus_python_api.models import (
|
|
|
65
72
|
MessageTimestamp,
|
|
66
73
|
MessagingBackend,
|
|
67
74
|
ModernAccountData,
|
|
75
|
+
ModernCorrespondent,
|
|
76
|
+
ModernCorrespondentReference,
|
|
77
|
+
ModernCorrespondents,
|
|
68
78
|
ModernIdentity,
|
|
69
79
|
ModernMessageAttachment,
|
|
70
80
|
ModernMessageAttachmentReference,
|
|
@@ -82,6 +92,10 @@ from librus_python_api.models import (
|
|
|
82
92
|
ModernRecipientTypeReference,
|
|
83
93
|
ModernRecipientTypes,
|
|
84
94
|
ModernSendSubmission,
|
|
95
|
+
ModernTeacherSubject,
|
|
96
|
+
ModernTeacherSubjects,
|
|
97
|
+
ModernUnreadCounts,
|
|
98
|
+
ModernUnreadFolders,
|
|
85
99
|
NotificationCategory,
|
|
86
100
|
NotificationCount,
|
|
87
101
|
NotificationCounts,
|
|
@@ -103,6 +117,7 @@ from librus_python_api.models import (
|
|
|
103
117
|
SchoolAverage,
|
|
104
118
|
SchoolDetail,
|
|
105
119
|
SchoolReference,
|
|
120
|
+
SchoolYearArchive,
|
|
106
121
|
SendResult,
|
|
107
122
|
SendStatus,
|
|
108
123
|
SendSubmission,
|
|
@@ -120,9 +135,17 @@ from librus_python_api.models import (
|
|
|
120
135
|
from librus_python_api.sending import SendAttempt
|
|
121
136
|
from librus_python_api.service import AccountClient, LibrusService
|
|
122
137
|
|
|
123
|
-
__version__ = "1.0
|
|
138
|
+
__version__ = "1.3.0"
|
|
124
139
|
|
|
125
140
|
__all__ = [
|
|
141
|
+
"ArchiveAbsences",
|
|
142
|
+
"ArchiveAchievement",
|
|
143
|
+
"ArchiveCounts",
|
|
144
|
+
"ArchiveDescriptive",
|
|
145
|
+
"ArchiveMarks",
|
|
146
|
+
"ArchiveSubject",
|
|
147
|
+
"ArchiveYear",
|
|
148
|
+
"SchoolYearArchive",
|
|
126
149
|
"AccountContext",
|
|
127
150
|
"MessagingBackend",
|
|
128
151
|
"ModernAccountData",
|
|
@@ -135,6 +158,13 @@ __all__ = [
|
|
|
135
158
|
"ModernMessageRecipientReceipt",
|
|
136
159
|
"ModernMessages",
|
|
137
160
|
"ModernMessagesCursor",
|
|
161
|
+
"ModernUnreadCounts",
|
|
162
|
+
"ModernCorrespondent",
|
|
163
|
+
"ModernCorrespondentReference",
|
|
164
|
+
"ModernCorrespondents",
|
|
165
|
+
"ModernTeacherSubject",
|
|
166
|
+
"ModernTeacherSubjects",
|
|
167
|
+
"ModernUnreadFolders",
|
|
138
168
|
"ModernMessagesPage",
|
|
139
169
|
"ModernMessageSummary",
|
|
140
170
|
"ModernRecipient",
|
librus_python_api/config.py
CHANGED
|
@@ -207,6 +207,60 @@ ENDPOINTS: Mapping[str, Endpoint] = MappingProxyType(
|
|
|
207
207
|
Evidence.INDEPENDENTLY_OBSERVED,
|
|
208
208
|
"messages",
|
|
209
209
|
),
|
|
210
|
+
Endpoint(
|
|
211
|
+
"modern_archive_messages_received",
|
|
212
|
+
"GET",
|
|
213
|
+
"/api/archive/inbox/messages",
|
|
214
|
+
SideEffect.NONE,
|
|
215
|
+
False,
|
|
216
|
+
Evidence.INDEPENDENTLY_OBSERVED,
|
|
217
|
+
"messages",
|
|
218
|
+
),
|
|
219
|
+
Endpoint(
|
|
220
|
+
"modern_archive_messages_sent",
|
|
221
|
+
"GET",
|
|
222
|
+
"/api/archive/outbox/messages",
|
|
223
|
+
SideEffect.NONE,
|
|
224
|
+
False,
|
|
225
|
+
Evidence.INDEPENDENTLY_OBSERVED,
|
|
226
|
+
"messages",
|
|
227
|
+
),
|
|
228
|
+
Endpoint(
|
|
229
|
+
"modern_unread_counts",
|
|
230
|
+
"GET",
|
|
231
|
+
"/api/inbox/unreadMessagesCount",
|
|
232
|
+
SideEffect.NONE,
|
|
233
|
+
False,
|
|
234
|
+
Evidence.INDEPENDENTLY_OBSERVED,
|
|
235
|
+
"messages",
|
|
236
|
+
),
|
|
237
|
+
Endpoint(
|
|
238
|
+
"modern_senders",
|
|
239
|
+
"GET",
|
|
240
|
+
"/api/inbox/messages/senders",
|
|
241
|
+
SideEffect.NONE,
|
|
242
|
+
False,
|
|
243
|
+
Evidence.INDEPENDENTLY_OBSERVED,
|
|
244
|
+
"messages",
|
|
245
|
+
),
|
|
246
|
+
Endpoint(
|
|
247
|
+
"modern_receivers",
|
|
248
|
+
"GET",
|
|
249
|
+
"/api/outbox/messages/receivers",
|
|
250
|
+
SideEffect.NONE,
|
|
251
|
+
False,
|
|
252
|
+
Evidence.INDEPENDENTLY_OBSERVED,
|
|
253
|
+
"messages",
|
|
254
|
+
),
|
|
255
|
+
Endpoint(
|
|
256
|
+
"modern_teacher_subjects",
|
|
257
|
+
"GET",
|
|
258
|
+
"/api/receivers/student-subjects",
|
|
259
|
+
SideEffect.NONE,
|
|
260
|
+
False,
|
|
261
|
+
Evidence.INDEPENDENTLY_OBSERVED,
|
|
262
|
+
"messages",
|
|
263
|
+
),
|
|
210
264
|
Endpoint(
|
|
211
265
|
"modern_content_received",
|
|
212
266
|
"GET",
|
|
@@ -451,6 +505,14 @@ ENDPOINTS: Mapping[str, Endpoint] = MappingProxyType(
|
|
|
451
505
|
True,
|
|
452
506
|
Evidence.INDEPENDENTLY_OBSERVED,
|
|
453
507
|
),
|
|
508
|
+
Endpoint(
|
|
509
|
+
"school_year_archive",
|
|
510
|
+
"GET",
|
|
511
|
+
"/archiwum",
|
|
512
|
+
SideEffect.NONE,
|
|
513
|
+
True,
|
|
514
|
+
Evidence.INDEPENDENTLY_OBSERVED,
|
|
515
|
+
),
|
|
454
516
|
Endpoint(
|
|
455
517
|
"agenda",
|
|
456
518
|
"POST",
|
|
@@ -668,6 +730,35 @@ ANNOUNCEMENT_MAX_ITEMS = 256
|
|
|
668
730
|
ANNOUNCEMENT_MAX_FIELD_LENGTH = 1024
|
|
669
731
|
ANNOUNCEMENT_MAX_CONTENT_LENGTH = 65536
|
|
670
732
|
ANNOUNCEMENT_MAX_TOTAL_TEXT_LENGTH = 262144
|
|
733
|
+
|
|
734
|
+
# School-year archive (/archiwum): labels as rendered upstream, including the
|
|
735
|
+
# absence spellings. Matching is case-insensitive and exact otherwise.
|
|
736
|
+
ARCHIVE_TABLE_CLASSES = frozenset({"decorated"})
|
|
737
|
+
ARCHIVE_ACHIEVEMENT_TABLE_CLASSES = frozenset({"decorated", "big", "center"})
|
|
738
|
+
ARCHIVE_YEAR_PATTERN = (
|
|
739
|
+
r"Klasa: (?P<class_name>\S(?:.*\S)?) Rok: (?P<first>[0-9]{4})/(?P<second>[0-9]{4})"
|
|
740
|
+
)
|
|
741
|
+
ARCHIVE_PERIOD_LABELS = ("okres 1", "okres 2", "koniec roku")
|
|
742
|
+
ARCHIVE_BEHAVIOUR_HEADING = "zachowanie"
|
|
743
|
+
ARCHIVE_ABSENCES_HEADING = "nieobecności"
|
|
744
|
+
ARCHIVE_ABSENCE_LABELS = MappingProxyType(
|
|
745
|
+
{
|
|
746
|
+
"nieusprawiedlione": "unexcused",
|
|
747
|
+
"usprawiedlione": "excused",
|
|
748
|
+
"spóźnienia": "late",
|
|
749
|
+
}
|
|
750
|
+
)
|
|
751
|
+
ARCHIVE_ACHIEVEMENT_HEADERS = ("data", "klasa", "kategoria", "osiągnięcie")
|
|
752
|
+
ARCHIVE_YEAR_HEADER_TAGS = frozenset({"span", "b"})
|
|
753
|
+
ARCHIVE_MAX_YEARS = 16
|
|
754
|
+
ARCHIVE_MAX_SUBJECTS = 128
|
|
755
|
+
ARCHIVE_MAX_DESCRIPTIVE = 32
|
|
756
|
+
ARCHIVE_MAX_ACHIEVEMENTS = 256
|
|
757
|
+
ARCHIVE_MAX_TEXT_LENGTH = 65536
|
|
758
|
+
ARCHIVE_MAX_TOTAL_TEXT_LENGTH = 262144
|
|
759
|
+
ARCHIVE_MAX_YEAR_HEADER_LENGTH = 1024 + len("Klasa: Rok: 0000/0000")
|
|
760
|
+
ARCHIVE_EMPTY_TITLE = "brak danych"
|
|
761
|
+
ARCHIVE_EMPTY_BOX_CLASSES = frozenset({"warning-box", "information", "medium"})
|
|
671
762
|
ANNOUNCEMENT_TABLE_CLASSES = frozenset(
|
|
672
763
|
{"decorated", "big", "center", "printable", "margin-top"}
|
|
673
764
|
)
|
|
@@ -878,17 +969,67 @@ def modern_directory_query(
|
|
|
878
969
|
|
|
879
970
|
|
|
880
971
|
def modern_mailbox_query(
|
|
881
|
-
folder: MessageFolder,
|
|
972
|
+
folder: MessageFolder,
|
|
973
|
+
page: int,
|
|
974
|
+
page_size: int,
|
|
975
|
+
*,
|
|
976
|
+
correspondent: str | None = None,
|
|
977
|
+
unread_only: bool = False,
|
|
882
978
|
) -> dict[str, str]:
|
|
979
|
+
"""Page selection plus the web app's own sender/receiver/unread filters."""
|
|
883
980
|
if (
|
|
884
981
|
not isinstance(folder, MessageFolder)
|
|
885
982
|
or type(page) is not int
|
|
886
983
|
or not 1 <= page <= 1000
|
|
887
984
|
or type(page_size) is not int
|
|
888
985
|
or not 1 <= page_size <= 50
|
|
986
|
+
or type(unread_only) is not bool
|
|
987
|
+
or (unread_only and folder is not MessageFolder.RECEIVED)
|
|
988
|
+
or (
|
|
989
|
+
correspondent is not None
|
|
990
|
+
and (
|
|
991
|
+
type(correspondent) is not str
|
|
992
|
+
or re.fullmatch(r"[0-9]{1,64}", correspondent) is None
|
|
993
|
+
)
|
|
994
|
+
)
|
|
889
995
|
):
|
|
890
996
|
raise LibrusError(ErrorKind.INVALID_INPUT)
|
|
891
|
-
|
|
997
|
+
query = {}
|
|
998
|
+
if correspondent is not None:
|
|
999
|
+
query[MODERN_CORRESPONDENT_FILTERS[folder]] = correspondent
|
|
1000
|
+
if unread_only:
|
|
1001
|
+
query["unreadOnly"] = "1"
|
|
1002
|
+
return query | {"page": str(page), "limit": str(page_size)}
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
MODERN_MAILBOX_OPERATIONS = frozenset(
|
|
1006
|
+
{
|
|
1007
|
+
"modern_messages_received",
|
|
1008
|
+
"modern_messages_sent",
|
|
1009
|
+
"modern_archive_messages_received",
|
|
1010
|
+
"modern_archive_messages_sent",
|
|
1011
|
+
}
|
|
1012
|
+
)
|
|
1013
|
+
MODERN_CORRESPONDENT_FILTERS = MappingProxyType(
|
|
1014
|
+
{MessageFolder.RECEIVED: "senderId", MessageFolder.SENT: "receiverId"}
|
|
1015
|
+
)
|
|
1016
|
+
# Filters observed in the web app for the current mailbox only.
|
|
1017
|
+
MODERN_MAILBOX_FILTERS = MappingProxyType(
|
|
1018
|
+
{
|
|
1019
|
+
"modern_messages_received": frozenset({"senderId", "unreadOnly"}),
|
|
1020
|
+
"modern_messages_sent": frozenset({"receiverId"}),
|
|
1021
|
+
}
|
|
1022
|
+
)
|
|
1023
|
+
MODERN_UNREAD_COUNT_FIELDS = (
|
|
1024
|
+
"inbox",
|
|
1025
|
+
"notes",
|
|
1026
|
+
"alerts",
|
|
1027
|
+
"substitutions",
|
|
1028
|
+
"absences",
|
|
1029
|
+
"justifications",
|
|
1030
|
+
"trash",
|
|
1031
|
+
)
|
|
1032
|
+
MODERN_MAX_UNREAD_COUNT = 1000000
|
|
892
1033
|
|
|
893
1034
|
|
|
894
1035
|
def validate_modern_query(operation: str, query: Mapping[str, str]) -> None:
|
|
@@ -897,14 +1038,27 @@ def validate_modern_query(operation: str, query: Mapping[str, str]) -> None:
|
|
|
897
1038
|
type(v) is not str for v in query.values()
|
|
898
1039
|
):
|
|
899
1040
|
raise LibrusError(ErrorKind.INVALID_INPUT)
|
|
900
|
-
if operation in
|
|
901
|
-
|
|
1041
|
+
if operation in MODERN_MAILBOX_OPERATIONS:
|
|
1042
|
+
filters = MODERN_MAILBOX_FILTERS.get(operation, frozenset())
|
|
1043
|
+
if not {"page", "limit"} <= set(query) <= {"page", "limit"} | filters or any(
|
|
902
1044
|
re.fullmatch(r"[1-9][0-9]{0,3}", query[name]) is None
|
|
903
1045
|
for name in ("page", "limit")
|
|
904
1046
|
):
|
|
905
1047
|
raise LibrusError(ErrorKind.INVALID_INPUT)
|
|
1048
|
+
folder = (
|
|
1049
|
+
MessageFolder.SENT
|
|
1050
|
+
if operation == "modern_messages_sent"
|
|
1051
|
+
else MessageFolder.RECEIVED
|
|
1052
|
+
)
|
|
1053
|
+
unread = query.get("unreadOnly")
|
|
1054
|
+
if unread not in (None, "1"):
|
|
1055
|
+
raise LibrusError(ErrorKind.INVALID_INPUT)
|
|
906
1056
|
modern_mailbox_query(
|
|
907
|
-
|
|
1057
|
+
folder,
|
|
1058
|
+
int(query["page"]),
|
|
1059
|
+
int(query["limit"]),
|
|
1060
|
+
correspondent=query.get(MODERN_CORRESPONDENT_FILTERS[folder]),
|
|
1061
|
+
unread_only=unread == "1",
|
|
908
1062
|
)
|
|
909
1063
|
return
|
|
910
1064
|
allowed = [
|
librus_python_api/models.py
CHANGED
|
@@ -38,6 +38,7 @@ type OperationName = Literal[
|
|
|
38
38
|
"attachment_download",
|
|
39
39
|
"recipient_groups",
|
|
40
40
|
"recipients",
|
|
41
|
+
"school_year_archive",
|
|
41
42
|
"notification_counts",
|
|
42
43
|
"consume_schedule_events",
|
|
43
44
|
"decode_schedule_events",
|
|
@@ -48,6 +49,12 @@ type OperationName = Literal[
|
|
|
48
49
|
"modern_send_message",
|
|
49
50
|
"modern_school_recipients",
|
|
50
51
|
"modern_class_parents",
|
|
52
|
+
"modern_archive_messages_received",
|
|
53
|
+
"modern_archive_messages_sent",
|
|
54
|
+
"modern_unread_counts",
|
|
55
|
+
"modern_senders",
|
|
56
|
+
"modern_receivers",
|
|
57
|
+
"modern_teacher_subjects",
|
|
51
58
|
"modern_messages_received",
|
|
52
59
|
"modern_messages_sent",
|
|
53
60
|
"modern_content_received",
|
|
@@ -393,6 +400,46 @@ class ModernMessageReference:
|
|
|
393
400
|
folder: MessageFolder
|
|
394
401
|
identifier: str = field(repr=False)
|
|
395
402
|
account: str = field(repr=False)
|
|
403
|
+
# Listed from the archive mailbox. Archived content opens are not supported.
|
|
404
|
+
archived: bool = False
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@dataclass(frozen=True, slots=True)
|
|
408
|
+
class ModernCorrespondentReference:
|
|
409
|
+
"""A sender (received folder) or receiver (sent folder) to filter a list by."""
|
|
410
|
+
|
|
411
|
+
folder: MessageFolder
|
|
412
|
+
identifier: str = field(repr=False)
|
|
413
|
+
account: str = field(repr=False)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@dataclass(frozen=True, slots=True)
|
|
417
|
+
class ModernCorrespondent:
|
|
418
|
+
reference: ModernCorrespondentReference = field(repr=False)
|
|
419
|
+
first_name: str = field(repr=False)
|
|
420
|
+
last_name: str = field(repr=False)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@dataclass(frozen=True, slots=True)
|
|
424
|
+
class ModernCorrespondents:
|
|
425
|
+
identity: "Identity" = field(repr=False)
|
|
426
|
+
folder: MessageFolder
|
|
427
|
+
items: tuple[ModernCorrespondent, ...] = field(repr=False)
|
|
428
|
+
observation: "Observation"
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@dataclass(frozen=True, slots=True)
|
|
432
|
+
class ModernTeacherSubject:
|
|
433
|
+
# The teacher's modern account ID, as a decimal string.
|
|
434
|
+
teacher_identifier: str = field(repr=False)
|
|
435
|
+
subject: str = field(repr=False)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@dataclass(frozen=True, slots=True)
|
|
439
|
+
class ModernTeacherSubjects:
|
|
440
|
+
identity: "Identity" = field(repr=False)
|
|
441
|
+
items: tuple[ModernTeacherSubject, ...] = field(repr=False)
|
|
442
|
+
observation: "Observation"
|
|
396
443
|
|
|
397
444
|
|
|
398
445
|
@dataclass(frozen=True, slots=True)
|
|
@@ -417,6 +464,11 @@ class ModernMessagesPage:
|
|
|
417
464
|
items: tuple[ModernMessageSummary, ...] = field(repr=False)
|
|
418
465
|
fingerprint: str = field(repr=False)
|
|
419
466
|
observation: "Observation"
|
|
467
|
+
archived: bool = False
|
|
468
|
+
correspondent: ModernCorrespondentReference | None = field(default=None, repr=False)
|
|
469
|
+
unread_only: bool = False
|
|
470
|
+
# Archive listings only: the upstream archivingInProgress status flag.
|
|
471
|
+
archiving_in_progress: bool | None = None
|
|
420
472
|
|
|
421
473
|
|
|
422
474
|
@dataclass(frozen=True, slots=True)
|
|
@@ -429,6 +481,9 @@ class ModernMessagesCursor:
|
|
|
429
481
|
total_count: int
|
|
430
482
|
fingerprint: str = field(repr=False)
|
|
431
483
|
seen_ids: tuple[str, ...] = field(repr=False)
|
|
484
|
+
archived: bool = False
|
|
485
|
+
correspondent: str | None = field(default=None, repr=False)
|
|
486
|
+
unread_only: bool = False
|
|
432
487
|
|
|
433
488
|
|
|
434
489
|
@dataclass(frozen=True, slots=True)
|
|
@@ -441,6 +496,32 @@ class ModernMessages:
|
|
|
441
496
|
next_cursor: ModernMessagesCursor | None = field(repr=False)
|
|
442
497
|
truncation_reason: Literal["item_limit", "page_limit"] | None
|
|
443
498
|
observation: "Observation"
|
|
499
|
+
archived: bool = False
|
|
500
|
+
correspondent: ModernCorrespondentReference | None = field(default=None, repr=False)
|
|
501
|
+
unread_only: bool = False
|
|
502
|
+
# Archive listings only: the upstream archivingInProgress status flag.
|
|
503
|
+
archiving_in_progress: bool | None = None
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
@dataclass(frozen=True, slots=True)
|
|
507
|
+
class ModernUnreadFolders:
|
|
508
|
+
"""Unread counters the modern mailbox shows for one mailbox section."""
|
|
509
|
+
|
|
510
|
+
inbox: int
|
|
511
|
+
notes: int
|
|
512
|
+
alerts: int
|
|
513
|
+
substitutions: int
|
|
514
|
+
absences: int
|
|
515
|
+
justifications: int
|
|
516
|
+
trash: int
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@dataclass(frozen=True, slots=True)
|
|
520
|
+
class ModernUnreadCounts:
|
|
521
|
+
identity: "Identity" = field(repr=False)
|
|
522
|
+
current: ModernUnreadFolders
|
|
523
|
+
archive: ModernUnreadFolders
|
|
524
|
+
observation: "Observation"
|
|
444
525
|
|
|
445
526
|
|
|
446
527
|
@dataclass(frozen=True, slots=True)
|
|
@@ -698,6 +779,79 @@ class Announcements:
|
|
|
698
779
|
observation: Observation
|
|
699
780
|
|
|
700
781
|
|
|
782
|
+
@dataclass(frozen=True, slots=True)
|
|
783
|
+
class ArchiveCounts:
|
|
784
|
+
"""Counts per period as rendered; the year-end value is not checked as a sum."""
|
|
785
|
+
|
|
786
|
+
first_semester: int
|
|
787
|
+
second_semester: int
|
|
788
|
+
year_end: int
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
@dataclass(frozen=True, slots=True)
|
|
792
|
+
class ArchiveAbsences:
|
|
793
|
+
unexcused: ArchiveCounts
|
|
794
|
+
excused: ArchiveCounts
|
|
795
|
+
late: ArchiveCounts
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
@dataclass(frozen=True, slots=True)
|
|
799
|
+
class ArchiveMarks:
|
|
800
|
+
"""Rendered marks; "-" and empty text are preserved, never invented.
|
|
801
|
+
|
|
802
|
+
`year_end` is the archive's year-end column, not asserted to equal the
|
|
803
|
+
published annual grade of `SubjectGradeSummary`.
|
|
804
|
+
"""
|
|
805
|
+
|
|
806
|
+
first_semester: str = field(repr=False)
|
|
807
|
+
second_semester: str = field(repr=False)
|
|
808
|
+
year_end: str = field(repr=False)
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
@dataclass(frozen=True, slots=True)
|
|
812
|
+
class ArchiveSubject:
|
|
813
|
+
subject: str = field(repr=False)
|
|
814
|
+
marks: ArchiveMarks = field(repr=False)
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
@dataclass(frozen=True, slots=True)
|
|
818
|
+
class ArchiveDescriptive:
|
|
819
|
+
label: str = field(repr=False)
|
|
820
|
+
text: str = field(repr=False)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
@dataclass(frozen=True, slots=True)
|
|
824
|
+
class ArchiveYear:
|
|
825
|
+
"""One earlier school year. Behaviour cells are raw positional text: the
|
|
826
|
+
first-semester column and the cell spanning second semester and year end;
|
|
827
|
+
their populated meaning is not established."""
|
|
828
|
+
|
|
829
|
+
class_name: str = field(repr=False)
|
|
830
|
+
school_year: str = field(repr=False)
|
|
831
|
+
first_year: int = field(repr=False)
|
|
832
|
+
subjects: tuple[ArchiveSubject, ...] = field(repr=False)
|
|
833
|
+
descriptive: tuple[ArchiveDescriptive, ...] = field(repr=False)
|
|
834
|
+
behaviour_first_semester: str = field(repr=False)
|
|
835
|
+
behaviour_second_semester_and_year_end: str = field(repr=False)
|
|
836
|
+
absences: ArchiveAbsences = field(repr=False)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
@dataclass(frozen=True, slots=True)
|
|
840
|
+
class ArchiveAchievement:
|
|
841
|
+
day: date = field(repr=False)
|
|
842
|
+
class_name: str = field(repr=False)
|
|
843
|
+
category: str = field(repr=False)
|
|
844
|
+
text: str = field(repr=False)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
@dataclass(frozen=True, slots=True)
|
|
848
|
+
class SchoolYearArchive:
|
|
849
|
+
identity: Identity
|
|
850
|
+
years: tuple[ArchiveYear, ...] = field(repr=False)
|
|
851
|
+
achievements: tuple[ArchiveAchievement, ...] = field(repr=False)
|
|
852
|
+
observation: Observation
|
|
853
|
+
|
|
854
|
+
|
|
701
855
|
@dataclass(frozen=True, slots=True)
|
|
702
856
|
class AgendaEvent:
|
|
703
857
|
day: date = field(repr=False)
|