@wilm-ai/wilma-client 1.6.1 → 1.7.0

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.
package/dist/types.d.ts CHANGED
@@ -18,8 +18,91 @@ export interface Message {
18
18
  recipients?: string[] | null;
19
19
  /** Message detail: the thread's replies, oldest first. */
20
20
  replies?: MessageReply[];
21
+ /** Message detail: the sender opened the thread for everyone ("avoin keskustelu"), so a reply goes to all recipients. */
22
+ discussion?: boolean;
23
+ /** Message detail: Wilma allows answering it (null when Wilma doesn't say). */
24
+ canReply?: boolean | null;
25
+ /** Message detail: the time slots and this family's choice, when the message is an event invitation. */
26
+ invitation?: Invitation | null;
21
27
  fetchedAt: Date;
22
28
  }
29
+ /** An event invitation (Tapahtumakutsu): a parents' evening, a learning discussion with time slots… */
30
+ export interface Invitation {
31
+ /** Wilma asks for a sign-up, not just attendance. */
32
+ mustApply: boolean;
33
+ slots: InvitationSlot[];
34
+ /** Wilma offers "En osallistu" (won't attend) for this invitation. */
35
+ canDecline: boolean;
36
+ /** The family has said it won't attend ("En osallistu"). */
37
+ declined: boolean;
38
+ }
39
+ export interface InvitationSlot {
40
+ /** Wilma's event id, for answering. */
41
+ id: string;
42
+ start: Date | null;
43
+ end: Date | null;
44
+ /** Sign-up opens / closes. */
45
+ enrollStart: Date | null;
46
+ enrollEnd: Date | null;
47
+ /** The last moment a sign-up can be cancelled. */
48
+ cancelUntil: Date | null;
49
+ info: string;
50
+ subject: string;
51
+ /** 0 when there's no limit. */
52
+ maxPeople: number;
53
+ peopleCount: number;
54
+ /** You signed up for this slot (Wilma: "selected-cancellable" while it can be cancelled, "selected" after). */
55
+ selected: boolean;
56
+ /** Another guardian signed up for this slot, as Wilma names them ("Name (child, class)"). */
57
+ selectedByOther: string | null;
58
+ /** Wilma's own word for the slot: "available", "selected-cancellable", "selected", "selected-other", "full", "pending", "expired", or "". */
59
+ wilmaState: string;
60
+ /** "En osallistu" covers this slot (CanDismissEvent). */
61
+ canDecline: boolean;
62
+ /** "En osallistu" was answered for this slot (IsDismissed). */
63
+ declined: boolean;
64
+ }
65
+ /**
66
+ * chosen: your slot (chosen-locked: too late to cancel); chosen-by-other:
67
+ * another guardian's; taken: the family already has another slot; open: can
68
+ * sign up now.
69
+ */
70
+ export type InvitationSlotState = "chosen" | "chosen-locked" | "chosen-by-other" | "taken" | "open" | "full" | "not-yet" | "closed";
71
+ /** Someone a message can go to, as Wilma offers them. */
72
+ export interface MessageRecipient {
73
+ /** As Wilma shows it, e.g. "Virtanen Sari (VIR)", or "Kaikki" for a group. */
74
+ name: string;
75
+ /** The list Wilma puts it in: "Omat opettajat", "Opettajat", "Henkilökunta", "Opiskelija"… */
76
+ group: string;
77
+ /** The school, for staff and teachers listed per school. */
78
+ school: string | null;
79
+ /** teacher, staff (other personnel), student (the child), guardian (the other guardians), group (several at once). */
80
+ kind: "teacher" | "staff" | "student" | "guardian" | "group" | "other";
81
+ /** Wilma's compose page with this recipient filled in, for prepareMessage(). */
82
+ composePath: string;
83
+ }
84
+ /** The child's class and the people responsible for it, from the front page. */
85
+ export interface StudentDetails {
86
+ name: string | null;
87
+ /** E.g. "9A". */
88
+ className: string | null;
89
+ /** Class teacher and other supervisors, in Wilma's order. */
90
+ supervisors: {
91
+ name: string;
92
+ role: string;
93
+ recipient: MessageRecipient | null;
94
+ }[];
95
+ /** Groups the front page offers to message at once: all supervisors, all the child's teachers. */
96
+ messageGroups: MessageRecipient[];
97
+ }
98
+ /** An entry in the invitations list (Tapahtumakutsut). */
99
+ export interface InvitationListing {
100
+ message: Message;
101
+ /** Wilma's status for this family: "Applied", "ExpiredReg"…; null when it shows none. */
102
+ status: string | null;
103
+ /** The chosen time or the sign-up deadline, in Wilma's words. */
104
+ extra: string | null;
105
+ }
23
106
  export interface MessageReply {
24
107
  id: number;
25
108
  sentAt: Date;
@@ -65,6 +148,11 @@ export interface StudentInfo {
65
148
  studentNumber: string;
66
149
  name: string;
67
150
  href: string;
151
+ /**
152
+ * Whose login this is for the student, as Wilma's account roles say: a guardian's, or the student's own.
153
+ * Missing when Wilma doesn't say (older Wilmas without the roles API).
154
+ */
155
+ role?: "guardian" | "student";
68
156
  }
69
157
  export interface Municipality {
70
158
  nameFi: string;
@@ -126,6 +214,29 @@ export interface ExamGrade {
126
214
  info: string | null;
127
215
  teacher: string;
128
216
  teacherCode: string;
217
+ /** When a guardian saw the grade in Wilma (YYYY-MM-DD), or null. */
218
+ seen?: string | null;
219
+ }
220
+ /** A past exam from Wilma's exam list (Kokeet > Menneet), graded or not yet. */
221
+ export interface PastExam {
222
+ /** YYYY-MM-DD */
223
+ date: string;
224
+ name: string;
225
+ /** The course title, e.g. "Fysiikka". */
226
+ subject: string;
227
+ /** The course code, e.g. "FY_91" ("" when Wilma doesn't give one). */
228
+ subjectCode: string;
229
+ /** The grade as Wilma shows it ("8+", "9½", "S", "21,5/24"), or null before it's given. */
230
+ grade: string | null;
231
+ verbalGrade: string | null;
232
+ /** The exam's notes (topics, pages), lines kept. */
233
+ info: string | null;
234
+ /** The teachers' names, comma-separated. */
235
+ teacher: string;
236
+ /** The teachers' codes, comma-separated. */
237
+ teacherCode: string;
238
+ /** When a guardian marked the grade seen (YYYY-MM-DD), or null. */
239
+ seen: string | null;
129
240
  }
130
241
  export interface HomeworkItem {
131
242
  /** YYYY-MM-DD */
@@ -0,0 +1,143 @@
1
+ import type { WilmaClient } from "./client.js";
2
+ import { type WilmaForm } from "./parsers/forms.js";
3
+ import type { Invitation, MessageRecipient } from "./types.js";
4
+ export type { WilmaForm } from "./parsers/forms.js";
5
+ export { FormOutsideWilmaError } from "./parsers/forms.js";
6
+ /** Wilma doesn't offer this here right now (no answer form, absence reports closed…). Nothing was sent. */
7
+ export declare class NotOfferedError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /** Wilma turned the form down and said so. Nothing changed. */
11
+ export declare class WriteRefusedError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ /** The form reached Wilma, but the result didn't show. It may have gone through: check Wilma before sending again. */
15
+ export declare class WriteUnconfirmedError extends Error {
16
+ constructor(message: string);
17
+ }
18
+ /**
19
+ * Wilma's absence report form for one child. Seen 2026-10-06 (Helsinki):
20
+ * `/attendance/report` posts to `/attendance/savereport` with formkey,
21
+ * ReportDate (a day number Wilma fills in), type (the reason), text, savebtn.
22
+ */
23
+ export interface AbsenceReportForm {
24
+ readonly kind: "absence-report";
25
+ /** The session the form belongs to (WilmaSession.sessionMark); not posted under another. */
26
+ readonly sessionMark?: number;
27
+ /** The day the report is for (YYYY-MM-DD). Wilma decides: today until 12:00, the next day after that. */
28
+ readonly date: string | null;
29
+ /** Wilma's words for that day, e.g. "Huomenna, 7.10.". */
30
+ readonly dateLabel: string;
31
+ /** The reasons this school offers (Helsinki offers one: "Terveydellinen syy"). */
32
+ readonly reasons: {
33
+ value: string;
34
+ label: string;
35
+ }[];
36
+ /** The form has a field for details. */
37
+ readonly acceptsText: boolean;
38
+ /** Wilma's instructions on the form page, if any. */
39
+ readonly guidance: string;
40
+ /** What send posts. */
41
+ readonly form: WilmaForm;
42
+ readonly reasonField: string;
43
+ readonly textField: string | null;
44
+ /** Notices already on the form page, so a new one after sending can be told apart. */
45
+ readonly noticesBefore: string[];
46
+ }
47
+ /** Wilma's day numbers count days from 30 December 1899 (46302 = 7 October 2026). */
48
+ export declare function wilmaDayToIso(value: string): string | null;
49
+ export declare function prepareAbsenceReport(client: WilmaClient): Promise<AbsenceReportForm>;
50
+ /** Report the absence (once). Returns Wilma's confirmation. */
51
+ export declare function sendAbsenceReport(client: WilmaClient, prepared: AbsenceReportForm, input: {
52
+ reason: string;
53
+ text?: string;
54
+ }): Promise<{
55
+ notice: string;
56
+ }>;
57
+ /**
58
+ * Wilma has two kinds of answer, and the message page says which applies
59
+ * (approach from jmccrosky/wilma-mcp, MIT):
60
+ * - discussion: an open discussion ("avoin keskustelu") has a quick-reply
61
+ * form posting to `/messages/collatedreply/<id>`; everyone on the thread
62
+ * sees the answer;
63
+ * - private: an ordinary message links to a compose form with the sender
64
+ * and "VS:" subject filled in; the answer goes to the sender alone.
65
+ */
66
+ export interface ReplyForm {
67
+ readonly kind: "reply";
68
+ /** The session the form belongs to (WilmaSession.sessionMark); not posted under another. */
69
+ readonly sessionMark?: number;
70
+ readonly messageId: number;
71
+ readonly mode: "discussion" | "private";
72
+ /** The subject Wilma filled in for a private answer ("VS: …"); null for a discussion. */
73
+ readonly subject: string | null;
74
+ /** Who a private answer goes to, as Wilma's form shows them; empty for a discussion. */
75
+ readonly recipients: string[];
76
+ /** What send posts. */
77
+ readonly form: WilmaForm;
78
+ readonly bodyField: string;
79
+ }
80
+ export declare function prepareReply(client: WilmaClient, messageId: number): Promise<ReplyForm>;
81
+ /** Send the answer (once). */
82
+ export declare function sendReply(client: WilmaClient, prepared: ReplyForm, text: string): Promise<void>;
83
+ /**
84
+ * A new message. Wilma's compose page with the recipient filled in
85
+ * (`/messages/compose?r_teacher=<id>`, or a recipient's link from
86
+ * messages.recipients() or student.get()) has the fields formkey, wysiwyg
87
+ * ("ckeditor": the body is HTML), the recipients as r_… fields, Subject,
88
+ * BodyText, ShowRecipients and CollatedReplies; it's sent with addsavebtn,
89
+ * never draftbtn, cancelbtn or select_recipients.
90
+ */
91
+ export interface MessageForm {
92
+ readonly kind: "message";
93
+ /** The session the form belongs to (WilmaSession.sessionMark); not posted under another. */
94
+ readonly sessionMark?: number;
95
+ /** Who it goes to, as Wilma's form shows them. */
96
+ readonly recipients: string[];
97
+ /** What send posts. */
98
+ readonly form: WilmaForm;
99
+ readonly subjectField: string;
100
+ readonly bodyField: string;
101
+ }
102
+ export declare function prepareMessage(client: WilmaClient, to: {
103
+ teacherId: string | number;
104
+ } | Pick<MessageRecipient, "composePath">): Promise<MessageForm>;
105
+ /** Send the message (once). */
106
+ export declare function sendMessage(client: WilmaClient, prepared: MessageForm, input: {
107
+ subject: string;
108
+ text: string;
109
+ }): Promise<void>;
110
+ /**
111
+ * Sign up for an invitation's time slot, or cancel the sign-up. This is what
112
+ * Wilma's own page sends (appointments.js): POST `/messages/apply/` with the
113
+ * page's formkey, apptId (the slot), action, appliedpersons and kortti (the
114
+ * message), answered with JSON {resultmessage: {message, problem}}. Checks
115
+ * the slot's state first, like Wilma's page, and reads the invitation back
116
+ * afterwards. Opening the invitation marks it read, as in Wilma.
117
+ */
118
+ export declare function answerInvitation(client: WilmaClient, messageId: number, slotId: string, action: "apply" | "cancel", opts?: {
119
+ persons?: number;
120
+ }): Promise<{
121
+ notice: string;
122
+ invitation: Invitation;
123
+ }>;
124
+ /**
125
+ * Say the family won't attend ("En osallistu"), or take that back (`undo`).
126
+ * This is what Wilma's own page sends (appointments.js, dismissAppt): POST
127
+ * `/messages/apply` with the page's formkey, action "dismiss" (or
128
+ * "canceldismiss") and kortti (the message), no slot. Declining also cancels
129
+ * a sign-up that can still be cancelled, as Wilma's page warns. Reads the
130
+ * invitation back afterwards.
131
+ */
132
+ export declare function declineInvitation(client: WilmaClient, messageId: number, opts?: {
133
+ undo?: boolean;
134
+ }): Promise<{
135
+ notice: string;
136
+ invitation: Invitation;
137
+ }>;
138
+ /**
139
+ * What goes in the body field. Wilma's message forms say `wysiwyg=ckeditor`:
140
+ * its editor sends HTML, so plain text becomes paragraphs (blank lines) and
141
+ * line breaks, with <, > and & escaped. Other forms get the text as is.
142
+ */
143
+ export declare function editorBody(form: WilmaForm, text: string): string;