Skip to main content

printobserver_sdk/
contract.rs

1//! Generated by `contract-codegen` from the checked-in contract schemas.
2//!
3//! This file is the Rust client's request and response types.
4//!
5//! Do not edit by hand: change the contracts, regenerate with `just
6//! generate-clients`, and commit what that writes.
7
8use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11
12use crate::{Client, ClientError};
13
14/// The version of the server contract these types were generated from: the
15/// version of the workspace this crate is compiled from. A published crate
16/// reports the release version it was built at, and a crate compiled from a
17/// checkout reports that checkout's workspace version.
18pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
19
20/// Every operation this client exposes a method for, which is every operation
21/// the server declares and no other.
22pub const OPERATION_NAMES: [&str; 28] = [
23    "prints",
24    "status",
25    "context",
26    "image",
27    "history",
28    "events",
29    "manifest_get",
30    "look",
31    "manifest_set",
32    "spools",
33    "spool",
34    "printer_history",
35    "preflight",
36    "pause",
37    "resume",
38    "cancel",
39    "start_print",
40    "set_feedrate_factor",
41    "set_flowrate_factor",
42    "set_tool_target_c",
43    "set_bed_target_c",
44    "set_fan_percent",
45    "acknowledge_failure",
46    "register_spool",
47    "load_spool",
48    "unload_spool",
49    "adjust_spool",
50    "retire_spool",
51];
52
53/// A field that was sent, null included, as what was sent.
54fn present<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
55where
56    D: serde::Deserializer<'de>,
57    T: Deserialize<'de>,
58{
59    T::deserialize(deserializer).map(Some)
60}
61
62/// What an operator's acknowledgement of a failure event asks for next.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum AcknowledgementDisposition {
65    /// Carry on printing.
66    #[serde(rename = "continue")]
67    Continue,
68    /// Carry on printing, watched more closely.
69    #[serde(rename = "watch")]
70    Watch,
71    /// The print should stop. Recorded for whoever may cancel it: the
72    /// acknowledgement itself asks nothing of the machine.
73    #[serde(rename = "stop")]
74    Stop,
75}
76
77/// What one mutating request left behind it.
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct ActionAnswer {
80    /// The bounded intervention it opened, when it opened one.
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub intervention: Option<Intervention>,
83    /// What the printer said, when the request reached it and it refused.
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub printer_refusal: Option<String>,
86    /// The record of the request and the decision taken on it. A rejected
87    /// request's rejection is here, carrying its reason, the value asked for
88    /// and the range allowed, so a caller can ask again inside the range.
89    pub record: ActionRecord,
90}
91
92/// An accepted action reached the printer.
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct ActionExecutedPayload {
95    /// The action's identifier.
96    pub action_id: ActionId,
97    /// The bounded intervention it opened, when it opened one.
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub intervention_id: Option<InterventionId>,
100}
101
102/// A lowercase hyphenated version 7 UUID identifying one requested action.
103pub type ActionId = String;
104
105/// One action of the closed vocabulary, named without its payload.
106///
107/// This is what a safety envelope grants and what a policy rejection names;
108/// the
109/// payload lives on `PrintAction` itself.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111pub enum ActionKind {
112    /// Pause the print.
113    #[serde(rename = "pause")]
114    Pause,
115    /// Resume the print.
116    #[serde(rename = "resume")]
117    Resume,
118    /// Cancel the print.
119    #[serde(rename = "cancel")]
120    Cancel,
121    /// Start a print of a named file.
122    #[serde(rename = "start_print")]
123    StartPrint,
124    /// Set the feedrate factor.
125    #[serde(rename = "set_feedrate_factor")]
126    SetFeedrateFactor,
127    /// Set the flowrate factor.
128    #[serde(rename = "set_flowrate_factor")]
129    SetFlowrateFactor,
130    /// Set a tool's target temperature.
131    #[serde(rename = "set_tool_target_c")]
132    SetToolTargetC,
133    /// Set the bed's target temperature.
134    #[serde(rename = "set_bed_target_c")]
135    SetBedTargetC,
136    /// Set the fan percentage.
137    #[serde(rename = "set_fan_percent")]
138    SetFanPercent,
139    /// Acknowledge a failure event.
140    #[serde(rename = "acknowledge_failure")]
141    AcknowledgeFailure,
142    /// Register a spool.
143    #[serde(rename = "register_spool")]
144    RegisterSpool,
145    /// Load a spool onto a tool.
146    #[serde(rename = "load_spool")]
147    LoadSpool,
148    /// Unload a spool from its tool.
149    #[serde(rename = "unload_spool")]
150    UnloadSpool,
151    /// Correct a spool's remaining figure.
152    #[serde(rename = "adjust_spool")]
153    AdjustSpool,
154    /// Retire a spool.
155    #[serde(rename = "retire_spool")]
156    RetireSpool,
157}
158
159/// One request, the decision taken on it, and what became of it.
160///
161/// `executed_at` and `outcome` are both optional and are absent together: a
162/// record is written when the decision is taken, which is before — and, for a
163/// rejected request, instead of — anything reaching the printer.
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub struct ActionRecord {
166    /// The decision policy took on it.
167    pub decision: PolicyDecision,
168    /// When it reached the printer, if it did.
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub executed_at: Option<Timestamp>,
171    /// This record's identifier, minted by the store.
172    pub id: ActionId,
173    /// What the printer made of it, if it reached the printer.
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub outcome: Option<ExecutionOutcome>,
176    /// The print this request was made against.
177    pub print_id: PrintId,
178    /// The request itself.
179    pub request: ActionRequest,
180}
181
182/// Policy refused an action.
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub struct ActionRejectedPayload {
185    /// The action's identifier.
186    pub action_id: ActionId,
187    /// The whole decision, carrying which rejection it was.
188    pub decision: PolicyDecision,
189}
190
191/// One request an actor made, at the instant it made it.
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct ActionRequest {
194    /// What was asked for.
195    pub action: PrintAction,
196    /// Who asked.
197    pub actor: Actor,
198    /// When they asked.
199    pub requested_at: Timestamp,
200}
201
202/// An actor asked for an action.
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204pub struct ActionRequestedPayload {
205    /// What was asked for.
206    pub action: PrintAction,
207    /// The action's identifier.
208    pub action_id: ActionId,
209    /// Who asked.
210    pub actor: Actor,
211}
212
213/// Who asked for something.
214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215pub enum Actor {
216    /// The supervising agent, naming its session.
217    #[serde(rename = "agent")]
218    Agent {
219        /// The supervision session the agent is acting in.
220        session_name: String,
221    },
222    /// A person.
223    #[serde(rename = "operator")]
224    Operator,
225    /// The supervisor itself.
226    #[serde(rename = "system")]
227    System,
228}
229
230/// An actor class, which is what a safety envelope grants actions to.
231#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232pub enum ActorClass {
233    /// The supervising agent.
234    #[serde(rename = "agent")]
235    Agent,
236    /// A person.
237    #[serde(rename = "operator")]
238    Operator,
239    /// The supervisor itself.
240    #[serde(rename = "system")]
241    System,
242}
243
244/// One thing an adjustment may change.
245pub type Adjustable = String;
246
247/// The agent's written record of one supervision turn.
248///
249/// This is deliberately not how the agent acts: acting is a
250/// `PrintAction` that policy rules on.
251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252pub struct AgentAssessment {
253    /// How sure the agent is.
254    pub confidence: Confidence,
255    /// What the agent did.
256    pub did: String,
257    /// Whether the agent is escalating to a person.
258    pub escalating: bool,
259    /// Whether the print should carry on.
260    pub should_continue: bool,
261    /// One line saying what is happening.
262    pub summary: String,
263    /// Why it did it.
264    pub why: String,
265}
266
267/// The agent wrote down what it made of a turn.
268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269pub struct AgentAssessmentPayload {
270    /// What the agent answered with.
271    pub assessment: AgentAssessment,
272    /// The session the turn ran in.
273    pub session_name: String,
274}
275
276/// Somebody took a fresh look at the print.
277///
278/// Written down before the camera is asked for a frame, so that a frame the
279/// camera would not give is recorded against a look that is already in the
280/// history; the frame itself, when there is one, is this event's image.
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
282pub struct CameraLookPayload {
283    /// The events for the print that arrived while it waited, and that it
284    /// handed to whoever looked, oldest first.
285    #[serde(default, skip_serializing_if = "Option::is_none")]
286    pub delivered: Option<Vec<EventId>>,
287    /// How long the look waited for something to happen before it was taken,
288    /// in whole seconds.
289    pub waited_s: i64,
290}
291
292/// The durable identity of one debit, so that a replay can never debit twice.
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
294#[serde(tag = "key")]
295pub enum ChargeKey {
296    /// One segment of a print this system watched, on one tool.
297    #[serde(rename = "print")]
298    Print {
299        /// The print.
300        print_id: PrintId,
301        /// Which segment: zero from the print's start, and one more each time
302        /// a mid-print swap or a filament-change signal closes the current
303        /// one.
304        segment: i64,
305        /// The tool, in the printer's own numbering.
306        tool: i64,
307    },
308    /// One print this system never saw, on one tool.
309    #[serde(rename = "unobserved")]
310    Unobserved {
311        /// The file that was printed.
312        file_name: String,
313        /// The file's tally generation, which advances each time the
314        /// printer's
315        /// tally for the file is reset.
316        generation: i64,
317        /// Where the file lives, in the printer's own vocabulary.
318        origin: String,
319        /// The print's ordinal in the file's tally — completed and failed
320        /// prints counted up to and including this one — within that
321        /// generation.
322        print_number: i64,
323        /// The tool, in the printer's own numbering.
324        tool: i64,
325    },
326}
327
328/// How the length a charge debits was worked out.
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
330pub enum ChargeMethod {
331    /// A completed print: the length the file's analysis gives.
332    #[serde(rename = "slicer_estimate")]
333    SlicerEstimate,
334    /// The extrusion the file's own moves add up to, up to a position in it.
335    #[serde(rename = "gcode_extrusion")]
336    GcodeExtrusion,
337    /// The analysed length scaled by how far into the file the print got; the
338    /// fallback used only when the file itself could not be read.
339    #[serde(rename = "file_fraction")]
340    FileFraction,
341    /// A completed print this system never saw: the file's analysed length.
342    #[serde(rename = "unobserved_estimate")]
343    UnobservedEstimate,
344}
345
346/// Filament a tool fed after an unconfirmed filament change was held for the
347/// next spool loaded onto that tool, or confirmed on it.
348#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
349pub struct ChargePendedPayload {
350    /// What was held.
351    pub pending: PendingCharge,
352}
353
354/// What a charge took from a spool, and the whole calculation behind it.
355///
356/// Present or absent as one: a debit names its spool exactly when it carries
357/// the grams, the figures they were worked out with, and the spool's figure
358/// either side of them.
359#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
360pub struct ChargedFilament {
361    /// The density the grams were worked out with, in grams per cubic
362    /// centimetre.
363    pub density_g_cm3: f64,
364    /// The diameter the grams were worked out with, in millimetres.
365    pub diameter_mm: f64,
366    /// What the length weighs, in grams; what is subtracted from the spool.
367    pub grams: f64,
368    /// The spool's figure after it, in grams.
369    pub remaining_after_g: f64,
370    /// The spool's figure before the charge, in grams.
371    pub remaining_before_g: f64,
372    /// The spool charged.
373    pub spool_id: SpoolId,
374}
375
376/// One mark a spool command cleared, and the spool it was cleared from.
377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
378pub struct ClearedMark {
379    /// The reason that no longer stands.
380    pub reason: UnverifiedReason,
381    /// The spool the mark was cleared from.
382    pub spool_id: SpoolId,
383}
384
385/// How sure the agent is.
386///
387/// A closed vocabulary rather than a number, because a number invites a
388/// precision the agent does not have.
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
390pub enum Confidence {
391    /// Not sure.
392    #[serde(rename = "low")]
393    Low,
394    /// Fairly sure.
395    #[serde(rename = "medium")]
396    Medium,
397    /// Sure.
398    #[serde(rename = "high")]
399    High,
400}
401
402/// What a context read answers.
403///
404/// The latest image is **materialized** here rather than left as the handle
405/// `PrintContext` carries: the one thing a supervision turn does with an
406/// image is look at it, and a handle is not something anything can open. The
407/// path is absolute on this server's own filesystem, exactly as
408/// `ImageAnswer`'s is, and is absent when there is no image or when the
409/// record is intact and the file is not.
410#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
411pub struct ContextAnswer {
412    /// Everything a supervision turn is given about the print.
413    pub context: PrintContext,
414    /// Where the latest image's bytes are, absolute on this host.
415    #[serde(default, skip_serializing_if = "Option::is_none")]
416    pub image_path: Option<String>,
417}
418
419/// The envelope intersected with the print's manifest.
420///
421/// This is what context reports, so that the agent can see its own limits
422/// before it asks. Computing one is the supervision core's; this crate
423/// declares
424/// the shape and nothing that produces it.
425#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
426pub struct EffectiveBounds {
427    /// The range each adjustable may be set to, inclusive.
428    pub allowed: BTreeMap<String, Range>,
429}
430
431/// What this server answers when it will not do what it was asked.
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
433pub struct ErrorAnswer {
434    /// One line saying why, in the words of whatever refused it.
435    pub error: String,
436}
437
438/// A lowercase hyphenated version 7 UUID identifying one event.
439pub type EventId = String;
440
441/// The name one kind of event is written down under: lowercase `snake_case`,
442/// declared by the domain that owns the event.
443pub type EventKind = String;
444
445/// One event, as the store holds it and the server serves it.
446///
447/// `raw` holds the bytes exactly as received for an externally sourced event
448/// and is absent for an internally raised one — it is what makes the history
449/// auditable when a normalization turns out to be wrong. `print_id` is
450/// optional, because an externally sourced event may name no print this
451/// system
452/// knows.
453#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
454pub struct EventRecord {
455    /// This event's identifier, minted by the store.
456    pub id: EventId,
457    /// The image it arrived with, when it arrived with one.
458    #[serde(default, skip_serializing_if = "Option::is_none")]
459    pub image: Option<ImageRef>,
460    /// Which event this is.
461    pub kind: EventKind,
462    /// What it carries, in the form its kind declares.
463    pub payload: serde_json::Value,
464    /// The print it belongs to, when it belongs to one.
465    #[serde(default, skip_serializing_if = "Option::is_none")]
466    pub print_id: Option<PrintId>,
467    /// The bytes exactly as received, for an externally sourced event.
468    #[serde(default, skip_serializing_if = "Option::is_none")]
469    pub raw: Option<RawBytes>,
470    /// When it was received.
471    pub received_at: Timestamp,
472    /// Where it came from.
473    pub source: EventSource,
474}
475
476/// Where an event came from, as the bare string the domain that raised it
477/// declares for itself.
478pub type EventSource = String;
479
480/// What an events read answers: the events stored after its cursor that pass
481/// its filters, oldest first, and where the next read picks up.
482#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
483pub struct EventsAnswer {
484    /// The last event this read examined, whether or not it passed the
485    /// filters, given as `after` to read on from it; null when the log held
486    /// nothing to start after. Always sent, as null when it is that.
487    pub cursor: Option<EventId>,
488    /// The events, oldest first in the order they were stored, each exactly
489    /// as
490    /// a history read serves it.
491    pub events: Vec<EventRecord>,
492    /// The watched print's end as stored, whatever the filters said, and null
493    /// while it is open. Absent from a read that names no print.
494    #[serde(
495        default,
496        skip_serializing_if = "Option::is_none",
497        deserialize_with = "present"
498    )]
499    pub print_ended: Option<Option<PrintEnded>>,
500}
501
502/// What happened when an accepted action reached the printer.
503#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
504pub enum ExecutionOutcome {
505    /// The printer took it.
506    #[serde(rename = "succeeded")]
507    Succeeded,
508    /// The printer did not, for this reason.
509    #[serde(rename = "failed")]
510    Failed {
511        /// Why it failed.
512        reason: String,
513    },
514}
515
516/// A filament change was signalled during a print.
517#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
518pub struct FilamentChangeSignalledPayload {
519    /// The position the spool was charged up to, and how fresh it is, when
520    /// one
521    /// was known; absent, there is no position and so no claim about one.
522    #[serde(default, skip_serializing_if = "Option::is_none")]
523    pub position: Option<SignalPosition>,
524    /// The kind of the alert that carried the signal.
525    pub source_kind: EventKind,
526    /// The spool that was on it, when one was tracked.
527    #[serde(default, skip_serializing_if = "Option::is_none")]
528    pub spool_id: Option<SpoolId>,
529    /// The tool the change was signalled for, when it is known.
530    #[serde(default, skip_serializing_if = "Option::is_none")]
531    pub tool: Option<i64>,
532}
533
534/// Filament was charged against a spool, or recorded uncharged.
535#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
536pub struct FilamentConsumedPayload {
537    /// The file's analysed length for the tool, in millimetres, when it had
538    /// one.
539    #[serde(default, skip_serializing_if = "Option::is_none")]
540    pub analysed_length_mm: Option<f64>,
541    /// The durable identity of this debit.
542    pub charge_key: ChargeKey,
543    /// The spool charged and the whole calculation of what it was charged;
544    /// absent when nothing was tracked on the tool, and the filament was
545    /// recorded uncharged.
546    #[serde(default, skip_serializing_if = "Option::is_none")]
547    pub charged: Option<ChargedFilament>,
548    /// How much was fed, in millimetres.
549    pub length_mm: f64,
550    /// How the length was worked out.
551    pub method: ChargeMethod,
552    /// Anything a reader needs to make sense of the figure.
553    #[serde(default, skip_serializing_if = "Option::is_none")]
554    pub note: Option<String>,
555    /// How far into the file the charge runs, when that was read.
556    #[serde(default, skip_serializing_if = "Option::is_none")]
557    pub position: Option<FilePosition>,
558    /// The tool that fed it, as its key names it.
559    pub tool: i64,
560}
561
562/// A file name a printer's own file API can be asked for: no path separator,
563/// no NUL byte, no `.` or `..` segment, no drive prefix, and not empty.
564pub type FileName = String;
565
566/// How far into its file a print had got, and when that was read.
567#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
568pub struct FilePosition {
569    /// The byte the printer had read the file up to.
570    pub bytes: i64,
571    /// When the position was read.
572    pub observed_at: Timestamp,
573    /// The file's size in bytes, when it was known.
574    #[serde(default, skip_serializing_if = "Option::is_none")]
575    pub size_bytes: Option<i64>,
576}
577
578/// The printer's tally for a file went down — the file was deleted and
579/// uploaded again — so the file's tally generation advanced, and the new
580/// count
581/// is that generation's growth from zero.
582///
583/// The rebaseline itself marks no spool; only an `unverified` disposition
584/// does.
585#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
586pub struct FileTallyRebaselinedPayload {
587    /// What became of the prints the new generation counts.
588    pub disposition: RebaselineDisposition,
589    /// The file whose tally went down.
590    pub file_name: String,
591    /// The failed prints the new tally counts.
592    pub new_failures: i64,
593    /// The generation it is in now.
594    pub new_generation: i64,
595    /// The completed prints the new tally counts.
596    pub new_successes: i64,
597    /// The failed prints the old tally counted.
598    pub old_failures: i64,
599    /// The generation the tally was in.
600    pub old_generation: i64,
601    /// The completed prints the old tally counted.
602    pub old_successes: i64,
603    /// Where it lives, in the printer's own vocabulary.
604    pub origin: String,
605}
606
607/// One heater, as a source reported it.
608///
609/// Every field is optional, because a source that reports no heater at all
610/// reports none of these; each is a plausibility-ranged reported value in
611/// degrees Celsius.
612#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
613pub struct HeaterSnapshot {
614    /// The temperature the heater is at.
615    #[serde(default, skip_serializing_if = "Option::is_none")]
616    pub actual_c: Option<Reported>,
617    /// The offset applied to this heater's target.
618    #[serde(default, skip_serializing_if = "Option::is_none")]
619    pub offset_c: Option<Reported>,
620    /// The temperature the heater is driving towards.
621    #[serde(default, skip_serializing_if = "Option::is_none")]
622    pub target_c: Option<Reported>,
623}
624
625/// What a history read answers.
626#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
627pub struct HistoryAnswer {
628    /// The print's events, newest first.
629    pub events: Vec<EventRecord>,
630}
631
632/// What an image read answers.
633///
634/// The path is absolute on this server's own filesystem, and is the whole of
635/// the answer about the bytes: nothing here renders them, encodes them or
636/// serves them. It is absent when the record is intact and the file is not,
637/// which is a different answer from there being no such image.
638#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
639pub struct ImageAnswer {
640    /// Where its bytes are, absolute on this host.
641    #[serde(default, skip_serializing_if = "Option::is_none")]
642    pub path: Option<String>,
643    /// The record.
644    pub record: ImageRecord,
645}
646
647/// A lowercase hyphenated version 7 UUID identifying one image.
648pub type ImageId = String;
649
650/// One image, stored beside the event it arrived with.
651///
652/// `relative_path` is relative to the configured state directory, so that the
653/// store stays portable. Materializing an image resolves that to an absolute
654/// path; nothing in this system renders image bytes into JSON.
655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
656pub struct ImageRecord {
657    /// How many bytes it is.
658    pub byte_len: i64,
659    /// The content type it was served as.
660    pub content_type: String,
661    /// The event it arrived with.
662    pub event_id: EventId,
663    /// When it was fetched.
664    pub fetched_at: Timestamp,
665    /// This image's identifier, minted by the store.
666    pub id: ImageId,
667    /// The print it belongs to.
668    pub print_id: PrintId,
669    /// Where it lives, relative to the configured state directory.
670    pub relative_path: String,
671    /// The SHA-256 of its bytes, lowercase hexadecimal.
672    pub sha256: String,
673    /// Where it was fetched from, when it was fetched from somewhere.
674    #[serde(default, skip_serializing_if = "Option::is_none")]
675    pub source_url: Option<String>,
676}
677
678/// The handle an image travels in context under.
679#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
680pub struct ImageRef {
681    /// The image's identifier.
682    pub id: ImageId,
683    /// The SHA-256 of its bytes, lowercase hexadecimal.
684    pub sha256: String,
685}
686
687/// One adjustment made for a bounded time.
688///
689/// `prior_value` is read from the printer snapshot taken before the change,
690/// and
691/// is absent when the printer reported none — in which case expiry restores
692/// nothing and the outcome says so rather than guessing a default.
693#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
694pub struct Intervention {
695    /// The action that asked for it.
696    pub action_id: ActionId,
697    /// What it changed.
698    pub adjustable: Adjustable,
699    /// When it was applied.
700    pub applied_at: Timestamp,
701    /// What it was changed to.
702    pub applied_value: f64,
703    /// When it stops standing.
704    pub expires_at: Timestamp,
705    /// This intervention's identifier, minted by the store.
706    pub id: InterventionId,
707    /// What became of it.
708    pub outcome: InterventionOutcome,
709    /// The print it was made against.
710    pub print_id: PrintId,
711    /// What the printer reported before the change, if it reported anything.
712    #[serde(default, skip_serializing_if = "Option::is_none")]
713    pub prior_value: Option<f64>,
714    /// When the prior value was put back, if it was.
715    #[serde(default, skip_serializing_if = "Option::is_none")]
716    pub restored_at: Option<Timestamp>,
717}
718
719/// A bounded intervention expired.
720#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
721pub struct InterventionExpiredPayload {
722    /// What it had changed.
723    pub adjustable: Adjustable,
724    /// The intervention's identifier.
725    pub intervention_id: InterventionId,
726    /// What became of it.
727    pub outcome: InterventionOutcome,
728}
729
730/// A lowercase hyphenated version 7 UUID identifying one bounded
731/// intervention.
732pub type InterventionId = String;
733
734/// What became of a bounded change.
735#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
736pub enum InterventionOutcome {
737    /// It is still in force.
738    #[serde(rename = "still_active")]
739    StillActive,
740    /// The prior value was put back.
741    #[serde(rename = "restored")]
742    Restored,
743    /// There was no prior value to put back.
744    #[serde(rename = "restore_unavailable")]
745    RestoreUnavailable,
746    /// Putting the prior value back failed.
747    #[serde(rename = "restore_failed")]
748    RestoreFailed {
749        /// Why it failed.
750        reason: String,
751    },
752    /// Another intervention replaced it before it expired.
753    #[serde(rename = "superseded")]
754    Superseded {
755        /// The intervention that replaced it.
756        by: InterventionId,
757    },
758}
759
760/// What a sliced job declares about itself and about what may be adjusted.
761///
762/// An adjustable the manifest does not name takes the envelope's own range; a
763/// manifest range wider than the envelope's is narrowed to the envelope's and
764/// the narrowing is recorded on the print. A manifest may only narrow.
765#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
766pub struct JobManifest {
767    /// The range each named adjustable may take, inclusive.
768    pub allowed: BTreeMap<String, Range>,
769    /// The file this manifest is about, as the slicer named it.
770    pub file_name: String,
771    /// The material the job is sliced for.
772    pub material: String,
773    /// Whatever else the slicer recorded.
774    pub metadata: BTreeMap<String, String>,
775    /// The nozzle the job is sliced for, in millimetres.
776    pub nozzle_diameter_mm: f64,
777    /// The slicer profile the job was sliced with.
778    pub slicer_profile: String,
779}
780
781/// The job a printer reports it is running.
782///
783/// Every field but `state` is optional: an absent one means the source did
784/// not
785/// report it.
786#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
787pub struct JobSnapshot {
788    /// How far through the print is, as a fraction from zero to one.
789    ///
790    /// Normalized to a fraction here regardless of how the source expresses
791    /// it.
792    #[serde(default, skip_serializing_if = "Option::is_none")]
793    pub completion: Option<Reported>,
794    /// The error the source reports, when it reports one.
795    #[serde(default, skip_serializing_if = "Option::is_none")]
796    pub error: Option<String>,
797    /// The whole print's estimated duration, in whole seconds.
798    #[serde(default, skip_serializing_if = "Option::is_none")]
799    pub estimated_print_time_s: Option<i64>,
800    /// The filament the source's analysis of the file says each tool uses;
801    /// empty when the source reported none.
802    #[serde(default, skip_serializing_if = "Option::is_none")]
803    pub filament: Option<Vec<ToolFilament>>,
804    /// The name of the file being printed, as the source reported it.
805    #[serde(default, skip_serializing_if = "Option::is_none")]
806    pub file_name: Option<String>,
807    /// Where the file lives, in the source's own vocabulary.
808    #[serde(default, skip_serializing_if = "Option::is_none")]
809    pub file_origin: Option<String>,
810    /// How far into the file the printer has read, in bytes, when the source
811    /// reported it.
812    #[serde(default, skip_serializing_if = "Option::is_none")]
813    pub file_position_bytes: Option<i64>,
814    /// How long the print has left, in whole seconds.
815    #[serde(default, skip_serializing_if = "Option::is_none")]
816    pub print_time_left_s: Option<i64>,
817    /// How long the print has been running, in whole seconds.
818    #[serde(default, skip_serializing_if = "Option::is_none")]
819    pub print_time_s: Option<i64>,
820    /// The file's size in bytes.
821    #[serde(default, skip_serializing_if = "Option::is_none")]
822    pub size_bytes: Option<i64>,
823    /// The state the source reports the job to be in.
824    pub state: PrinterState,
825}
826
827/// One spool loaded on one tool, as a print started.
828#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
829pub struct LoadedSpool {
830    /// The spool loaded on it.
831    pub spool_id: SpoolId,
832    /// The tool, in the printer's own numbering.
833    pub tool: i64,
834}
835
836/// One fresh look at a print.
837///
838/// An answer rather than a record, so it admits fields beside its own the way
839/// every answer of the server does: a client that could not open the frame's
840/// path says so in a field of its own beside the rest of the answer.
841#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
842pub struct Look {
843    /// The events for this print that arrived while the look waited, oldest
844    /// first; the look returned early when there were any.
845    #[serde(default, skip_serializing_if = "Option::is_none")]
846    pub arrived: Option<Vec<EventRecord>>,
847    /// Whether the print is paused and the pause is the detector's, which is
848    /// what an adjustment asked for now would be applied under.
849    pub detector_paused: bool,
850    /// The look itself, as it was written into the print's history, carrying
851    /// the frame as its image when there is one.
852    pub event: EventRecord,
853    /// The frame the camera gave, absent when no camera is configured or it
854    /// gave none — which the history records as a port failure.
855    #[serde(default, skip_serializing_if = "Option::is_none")]
856    pub frame: Option<ImageRef>,
857    /// The absolute path the frame's bytes are at, on the supervisor's host.
858    #[serde(default, skip_serializing_if = "Option::is_none")]
859    pub image_path: Option<String>,
860    /// The job the printer reports now, absent when it could not be read.
861    #[serde(default, skip_serializing_if = "Option::is_none")]
862    pub job: Option<JobSnapshot>,
863    /// The printer's state now, absent when it could not be read.
864    #[serde(default, skip_serializing_if = "Option::is_none")]
865    pub printer: Option<PrinterSnapshot>,
866}
867
868/// An external body arrived that could not be read.
869///
870/// This kind always carries its `raw` bytes, and it exists so that an alert
871/// this system cannot read is written down rather than dropped.
872#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
873pub struct MalformedExternalEventPayload {
874    /// One line saying why the body could not be read.
875    pub detail: String,
876}
877
878/// What a manifest read or write answers.
879#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
880pub struct ManifestAnswer {
881    /// The manifest, when the print has one.
882    #[serde(default, skip_serializing_if = "Option::is_none")]
883    pub manifest: Option<JobManifest>,
884    /// Every range this manifest asked wider than the envelope allows,
885    /// narrowed
886    /// to the envelope's — recorded on the print, so nobody has to wonder
887    /// later
888    /// which bound applied.
889    pub narrowings: Vec<ManifestNarrowing>,
890}
891
892/// One adjustable whose manifest range was wider than the envelope's.
893#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
894pub struct ManifestNarrowing {
895    /// The adjustable that was narrowed.
896    pub adjustable: Adjustable,
897    /// The range that stands.
898    pub applied: Range,
899    /// The range the manifest asked for.
900    pub requested: Range,
901}
902
903/// Obico reported a print failure.
904///
905/// The two instants are optional because Obico's own field for each is a Unix
906/// timestamp number, an empty string, or absent, and the last two both mean
907/// the
908/// producer reported no instant. An absent field here is that, never an epoch
909/// date standing in for it.
910#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
911pub struct ObicoFailureAlertPayload {
912    /// When the print ended, when Obico reported an instant for it.
913    #[serde(default, skip_serializing_if = "Option::is_none")]
914    pub ended_at: Option<Timestamp>,
915    /// The file being printed, when Obico named one.
916    #[serde(default, skip_serializing_if = "Option::is_none")]
917    pub file_name: Option<String>,
918    /// Whether Obico called it a warning rather than a failure.
919    pub is_warning: bool,
920    /// Obico's own identifier for the print, when it named one.
921    #[serde(default, skip_serializing_if = "Option::is_none")]
922    pub obico_print_id: Option<i64>,
923    /// Whether Obico paused the print itself.
924    pub print_paused: bool,
925    /// When the print started, when Obico reported an instant for it.
926    #[serde(default, skip_serializing_if = "Option::is_none")]
927    pub started_at: Option<Timestamp>,
928}
929
930/// The kind of printer notification Obico sent, normalized.
931#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
932pub enum ObicoNotificationType {
933    /// A print started.
934    #[serde(rename = "started")]
935    Started,
936    /// A print finished.
937    #[serde(rename = "done")]
938    Done,
939    /// A print was cancelled.
940    #[serde(rename = "cancelled")]
941    Cancelled,
942    /// A print was paused.
943    #[serde(rename = "paused")]
944    Paused,
945    /// A print was resumed.
946    #[serde(rename = "resumed")]
947    Resumed,
948    /// The printer is waiting for a filament change.
949    #[serde(rename = "filament_change")]
950    FilamentChange,
951    /// A heater cooled down.
952    #[serde(rename = "heater_cooled")]
953    HeaterCooled,
954    /// A heater reached its target.
955    #[serde(rename = "heater_target")]
956    HeaterTarget,
957}
958
959/// Obico sent a printer notification.
960///
961/// The two instants are optional for the same reason
962/// `ObicoFailureAlertPayload`'s are, and are absent along with the rest of
963/// the print's fields when the notification is about no print at all.
964#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
965pub struct ObicoPrinterNotificationPayload {
966    /// When the print ended, when Obico reported an instant for it.
967    #[serde(default, skip_serializing_if = "Option::is_none")]
968    pub ended_at: Option<Timestamp>,
969    /// The file being printed, when the notification is about one.
970    #[serde(default, skip_serializing_if = "Option::is_none")]
971    pub file_name: Option<String>,
972    /// Which notification it is.
973    pub notification_type: ObicoNotificationType,
974    /// Obico's own identifier for the print, when the notification is about
975    /// one.
976    #[serde(default, skip_serializing_if = "Option::is_none")]
977    pub obico_print_id: Option<i64>,
978    /// When the print started, when Obico reported an instant for it.
979    #[serde(default, skip_serializing_if = "Option::is_none")]
980    pub started_at: Option<Timestamp>,
981}
982
983/// An operator acknowledged an event.
984#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
985pub struct OperatorAcknowledgementPayload {
986    /// The event being acknowledged.
987    pub acknowledged_event_id: EventId,
988    /// What the operator asked for next.
989    pub disposition: AcknowledgementDisposition,
990}
991
992/// Filament a tool fed after a filament change nobody has confirmed, held
993/// until
994/// somebody says which spool fed it.
995///
996/// A length rather than grams, because the grams follow the spool that takes
997/// it.
998#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
999pub struct PendingCharge {
1000    /// The file's analysed length for the tool, in millimetres, when it had
1001    /// one.
1002    #[serde(default, skip_serializing_if = "Option::is_none")]
1003    pub analysed_length_mm: Option<f64>,
1004    /// The print segment the filament was fed in, which names the print and
1005    /// the tool that fed it.
1006    pub charge_key: PrintSegmentKey,
1007    /// How much was fed, in millimetres.
1008    pub length_mm: f64,
1009    /// How that length was worked out.
1010    pub method: ChargeMethod,
1011    /// Anything a reader needs to make sense of the figure.
1012    #[serde(default, skip_serializing_if = "Option::is_none")]
1013    pub note: Option<String>,
1014    /// When it was held.
1015    pub pended_at: Timestamp,
1016    /// How far into the file the segment ran, when that was read.
1017    #[serde(default, skip_serializing_if = "Option::is_none")]
1018    pub position: Option<FilePosition>,
1019}
1020
1021/// One pending charge a spool command took.
1022#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1023pub struct PendingTaken {
1024    /// The key it is charged under.
1025    pub charge_key: ChargeKey,
1026    /// The print it was fed in.
1027    pub print_id: PrintId,
1028}
1029
1030/// The supervisor looked in on an active print on its own schedule, with no
1031/// detector having said anything about it.
1032///
1033/// What a failure detector never alerts on — a progress that stopped moving,
1034/// a
1035/// heater whose target fell to nothing mid-print, a fan cut at a bridge, a
1036/// wall
1037/// going thin — is still on the machine and in the camera's frame, so every
1038/// active print is given one of these each interval and a turn on it. It
1039/// carries what the printer reported as it was taken; the frame the camera
1040/// gave
1041/// at the same moment, untagged by any detector, is this event's image.
1042#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1043pub struct PeriodicObservationPayload {
1044    /// How often these are taken, in whole seconds.
1045    pub interval_s: i64,
1046    /// The job the printer reported as the observation was taken, absent when
1047    /// it could not be read.
1048    #[serde(default, skip_serializing_if = "Option::is_none")]
1049    pub job: Option<JobSnapshot>,
1050    /// The printer's state as the observation was taken, absent when it could
1051    /// not be read — which the history records as a port failure.
1052    #[serde(default, skip_serializing_if = "Option::is_none")]
1053    pub printer: Option<PrinterSnapshot>,
1054}
1055
1056/// The decision policy took on one request.
1057#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1058pub enum PolicyDecision {
1059    /// The request may proceed.
1060    #[serde(rename = "accepted")]
1061    Accepted,
1062    /// The request may not, for this reason.
1063    #[serde(rename = "rejected")]
1064    Rejected(RejectionReason),
1065}
1066
1067/// A port failed while one event was being handled.
1068///
1069/// The event is named rather than implied, so that a reader holding an
1070/// event's
1071/// identifier reaches every failure recorded while that event was being
1072/// handled. A failure recorded here is one the handling survived: the event
1073/// is
1074/// already in the history by the time any of these sites is reached, and the
1075/// loop goes on to handle the next event.
1076#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1077pub struct PortFailurePayload {
1078    /// What the port said about it, in the port's own words.
1079    pub detail: String,
1080    /// The event whose handling reached the failing call.
1081    pub event_id: EventId,
1082    /// Where it failed.
1083    pub site: PortFailureSite,
1084}
1085
1086/// Where a port failed while an event was being handled.
1087///
1088/// A closed set of exactly the sites at which a failure has nowhere else to
1089/// be
1090/// recorded. The printer's action methods record theirs on the
1091/// `ActionRecord` the request minted, and a restoring
1092/// call records its own on the `Intervention` it was
1093/// expiring; those are not sites here, because a second record of them would
1094/// be
1095/// a second version of one fact.
1096#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1097pub enum PortFailureSite {
1098    /// Reading the printer's own state.
1099    #[serde(rename = "printer_snapshot")]
1100    PrinterSnapshot,
1101    /// Reading the job the printer reports it is running.
1102    #[serde(rename = "printer_job")]
1103    PrinterJob,
1104    /// Writing the image the event arrived with.
1105    #[serde(rename = "image_write")]
1106    ImageWrite,
1107    /// Running the supervision turn the event prompted.
1108    #[serde(rename = "supervision_turn")]
1109    SupervisionTurn,
1110    /// Taking a fresh frame from the camera, for a look at the print or for a
1111    /// periodic observation of it.
1112    #[serde(rename = "camera_look")]
1113    CameraLook,
1114    /// Telling the detector that paused the print its detection was handled.
1115    #[serde(rename = "detector_acknowledgement")]
1116    DetectorAcknowledgement,
1117}
1118
1119/// How fresh the position a filament change charged up to is.
1120#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1121pub enum PositionFreshness {
1122    /// Read from the job as the signal was handled: where the spool stopped.
1123    #[serde(rename = "read_at_signal")]
1124    ReadAtSignal,
1125    /// The last position recorded before the signal, the job reporting none
1126    /// as it was handled: the spool may have fed past it.
1127    #[serde(rename = "last_recorded")]
1128    LastRecorded,
1129}
1130
1131/// What a filament check found for one print.
1132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1133pub struct Preflight {
1134    /// When the check was made.
1135    pub checked_at: Timestamp,
1136    /// The file the print is of.
1137    pub file_name: String,
1138    /// What it found for each tool.
1139    pub tools: Vec<PreflightTool>,
1140    /// The worst of the tools' verdicts, in the order insufficient,
1141    /// unverified spool, no analysis, warned, overridden, unchecked, passed.
1142    pub verdict: PreflightVerdict,
1143}
1144
1145/// What a filament check answers.
1146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1147pub struct PreflightAnswer {
1148    /// What the check found, read now.
1149    pub preflight: Preflight,
1150}
1151
1152/// The filament check before a start was made.
1153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1154pub struct PreflightCheckedPayload {
1155    /// The start it was made for, when it was made for one.
1156    #[serde(default, skip_serializing_if = "Option::is_none")]
1157    pub action_id: Option<ActionId>,
1158    /// What the check found.
1159    pub preflight: Preflight,
1160}
1161
1162/// What a print needs from one spool, in grams: both figures a comparison
1163/// with what the spool holds uses, or neither.
1164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1165pub struct PreflightNeed {
1166    /// The margin asked for beyond that.
1167    pub buffer_g: f64,
1168    /// What the print needs from the spool, by the file's analysis and the
1169    /// spool's own diameter and density.
1170    pub required_g: f64,
1171}
1172
1173/// What a check that refuses a start found for one tool.
1174#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1175#[serde(tag = "verdict")]
1176pub enum PreflightObjection {
1177    /// The spool holds less than the print needs and its buffer.
1178    #[serde(rename = "insufficient")]
1179    Insufficient {
1180        /// The spool's marks at the check, oldest first, so a spool both
1181        /// short
1182        /// and unverified says both.
1183        #[serde(default, skip_serializing_if = "Option::is_none")]
1184        marks: Option<Vec<UnverifiedMark>>,
1185        /// What the print needs from it.
1186        need: PreflightNeed,
1187        /// What the spool holds, in grams.
1188        remaining_g: f64,
1189        /// The spool.
1190        spool: PreflightSpool,
1191    },
1192    /// The spool's figure is in doubt.
1193    #[serde(rename = "unverified_spool")]
1194    UnverifiedSpool {
1195        /// Every reason the spool's figure is in doubt, oldest first; never
1196        /// empty.
1197        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
1198        marks: Vec<UnverifiedMark>,
1199        /// What the print needs from it, when the file's analysis says.
1200        #[serde(default, skip_serializing_if = "Option::is_none")]
1201        need: Option<PreflightNeed>,
1202        /// What the spool holds, in grams.
1203        remaining_g: f64,
1204        /// The spool.
1205        spool: PreflightSpool,
1206    },
1207    /// The file has no analysis, so what the spool must hold is unknown.
1208    #[serde(rename = "no_analysis")]
1209    NoAnalysis {
1210        /// What the spool holds, in grams.
1211        remaining_g: f64,
1212        /// The spool.
1213        spool: PreflightSpool,
1214    },
1215}
1216
1217/// The spool a check judged, named beside its identifier so a reader need
1218/// not look it up.
1219#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1220pub struct PreflightSpool {
1221    /// The spool's identifier.
1222    pub id: SpoolId,
1223    /// The spool's name.
1224    pub name: String,
1225}
1226
1227/// What a filament check found for one tool.
1228#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1229pub struct PreflightTool {
1230    /// What the check found for it, with the evidence that finding rests on.
1231    pub result: PreflightToolResult,
1232    /// The tool, in the printer's own numbering.
1233    pub tool: i64,
1234}
1235
1236/// What a filament check found for one tool, tagged by its verdict, each
1237/// verdict carrying exactly the evidence it rests on.
1238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1239#[serde(tag = "verdict")]
1240pub enum PreflightToolResult {
1241    /// The spool holds what the print needs and its buffer, and its figure is
1242    /// not in doubt.
1243    #[serde(rename = "passed")]
1244    Passed {
1245        /// What the print needs from it.
1246        need: PreflightNeed,
1247        /// What the spool holds, in grams.
1248        remaining_g: f64,
1249        /// The spool.
1250        spool: PreflightSpool,
1251    },
1252    /// No tracked spool is loaded on the tool, so nothing was checked.
1253    #[serde(rename = "unchecked")]
1254    Unchecked,
1255    /// The spool holds less than the print needs and its buffer.
1256    #[serde(rename = "insufficient")]
1257    Insufficient {
1258        /// The spool's marks at the check, oldest first, so a spool both
1259        /// short
1260        /// and unverified says both.
1261        #[serde(default, skip_serializing_if = "Option::is_none")]
1262        marks: Option<Vec<UnverifiedMark>>,
1263        /// What the print needs from it.
1264        need: PreflightNeed,
1265        /// What the spool holds, in grams.
1266        remaining_g: f64,
1267        /// The spool.
1268        spool: PreflightSpool,
1269    },
1270    /// The spool's figure is in doubt.
1271    #[serde(rename = "unverified_spool")]
1272    UnverifiedSpool {
1273        /// Every reason the spool's figure is in doubt, oldest first; never
1274        /// empty.
1275        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
1276        marks: Vec<UnverifiedMark>,
1277        /// What the print needs from it, when the file's analysis says.
1278        #[serde(default, skip_serializing_if = "Option::is_none")]
1279        need: Option<PreflightNeed>,
1280        /// What the spool holds, in grams.
1281        remaining_g: f64,
1282        /// The spool.
1283        spool: PreflightSpool,
1284    },
1285    /// `OctoPrint`'s analysis of the file had not appeared, so what the spool
1286    /// must hold is unknown.
1287    #[serde(rename = "no_analysis")]
1288    NoAnalysis {
1289        /// What the spool holds, in grams.
1290        remaining_g: f64,
1291        /// The spool.
1292        spool: PreflightSpool,
1293    },
1294    /// The start went ahead despite this objection, because an operator said
1295    /// so.
1296    #[serde(rename = "overridden")]
1297    Overridden {
1298        /// What the check objected to.
1299        objection: PreflightObjection,
1300    },
1301    /// The check is set to warn, and let the start go ahead despite this
1302    /// objection.
1303    #[serde(rename = "warned")]
1304    Warned {
1305        /// What the check objected to.
1306        objection: PreflightObjection,
1307    },
1308}
1309
1310/// What a filament check found, for one tool or for a whole print.
1311#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1312pub enum PreflightVerdict {
1313    /// The spool holds what the print needs and its buffer.
1314    #[serde(rename = "passed")]
1315    Passed,
1316    /// No tracked spool is loaded on the tool, so nothing was checked.
1317    #[serde(rename = "unchecked")]
1318    Unchecked,
1319    /// The spool holds less than the print needs and its buffer.
1320    #[serde(rename = "insufficient")]
1321    Insufficient,
1322    /// The spool's figure is in doubt.
1323    #[serde(rename = "unverified_spool")]
1324    UnverifiedSpool,
1325    /// `OctoPrint`'s analysis of the file had still not appeared when the
1326    /// start's wait for it elapsed, or has not appeared yet when the check is
1327    /// only read.
1328    #[serde(rename = "no_analysis")]
1329    NoAnalysis,
1330    /// The start went ahead despite the check, because the actor said so.
1331    #[serde(rename = "overridden")]
1332    Overridden,
1333    /// The check is set to warn, and let an insufficient, unverified or
1334    /// unanalysed start go ahead.
1335    #[serde(rename = "warned")]
1336    Warned,
1337}
1338
1339/// The whole vocabulary an actor may ask for, and there is no other.
1340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1341#[serde(tag = "action")]
1342pub enum PrintAction {
1343    /// Pause the print.
1344    #[serde(rename = "pause")]
1345    Pause {
1346        /// Who is asking.
1347        actor: Actor,
1348        /// Why the actor is asking for this.
1349        reason: String,
1350    },
1351    /// Resume the print.
1352    #[serde(rename = "resume")]
1353    Resume {
1354        /// Who is asking.
1355        actor: Actor,
1356        /// Why the actor is asking for this.
1357        reason: String,
1358    },
1359    /// Cancel the print.
1360    #[serde(rename = "cancel")]
1361    Cancel {
1362        /// Who is asking.
1363        actor: Actor,
1364        /// Why the actor is asking for this.
1365        reason: String,
1366    },
1367    /// Start a print of a named file, with a manifest.
1368    #[serde(rename = "start_print")]
1369    StartPrint {
1370        /// Who is asking.
1371        actor: Actor,
1372        /// The file to print, validated as a name a file API can be asked
1373        /// for.
1374        file_name: FileName,
1375        /// The manifest this print is bounded by.
1376        manifest: JobManifest,
1377        /// Start despite an insufficient or unverified spool, or a file
1378        /// `OctoPrint` has not analysed; the start's own reason is the
1379        /// override's reason.
1380        #[serde(default, skip_serializing_if = "Option::is_none")]
1381        override_filament_check: Option<bool>,
1382        /// Why the actor is asking for this.
1383        reason: String,
1384    },
1385    /// Set the feedrate factor, where one means one hundred percent.
1386    #[serde(rename = "set_feedrate_factor")]
1387    SetFeedrateFactor {
1388        /// Who is asking.
1389        actor: Actor,
1390        /// How long the change stands for, in whole seconds.
1391        #[serde(default, skip_serializing_if = "Option::is_none")]
1392        duration_s: Option<i64>,
1393        /// The multiplier asked for.
1394        factor: f64,
1395        /// Why the actor is asking for this.
1396        reason: String,
1397    },
1398    /// Set the flowrate factor, where one means one hundred percent.
1399    #[serde(rename = "set_flowrate_factor")]
1400    SetFlowrateFactor {
1401        /// Who is asking.
1402        actor: Actor,
1403        /// How long the change stands for, in whole seconds.
1404        #[serde(default, skip_serializing_if = "Option::is_none")]
1405        duration_s: Option<i64>,
1406        /// The multiplier asked for.
1407        factor: f64,
1408        /// Why the actor is asking for this.
1409        reason: String,
1410    },
1411    /// Set one tool's target temperature.
1412    #[serde(rename = "set_tool_target_c")]
1413    SetToolTargetC {
1414        /// Who is asking.
1415        actor: Actor,
1416        /// How long the change stands for, in whole seconds.
1417        #[serde(default, skip_serializing_if = "Option::is_none")]
1418        duration_s: Option<i64>,
1419        /// Why the actor is asking for this.
1420        reason: String,
1421        /// The target temperature asked for, in degrees Celsius.
1422        target_c: f64,
1423        /// The tool, in the printer's own numbering.
1424        tool: i64,
1425    },
1426    /// Set the bed's target temperature.
1427    #[serde(rename = "set_bed_target_c")]
1428    SetBedTargetC {
1429        /// Who is asking.
1430        actor: Actor,
1431        /// How long the change stands for, in whole seconds.
1432        #[serde(default, skip_serializing_if = "Option::is_none")]
1433        duration_s: Option<i64>,
1434        /// Why the actor is asking for this.
1435        reason: String,
1436        /// The target temperature asked for, in degrees Celsius.
1437        target_c: f64,
1438    },
1439    /// Set the part-cooling fan percentage.
1440    #[serde(rename = "set_fan_percent")]
1441    SetFanPercent {
1442        /// Who is asking.
1443        actor: Actor,
1444        /// How long the change stands for, in whole seconds.
1445        #[serde(default, skip_serializing_if = "Option::is_none")]
1446        duration_s: Option<i64>,
1447        /// The percentage asked for.
1448        percent: f64,
1449        /// Why the actor is asking for this.
1450        reason: String,
1451    },
1452    /// Acknowledge a failure event, with a disposition.
1453    #[serde(rename = "acknowledge_failure")]
1454    AcknowledgeFailure {
1455        /// Who is asking.
1456        actor: Actor,
1457        /// What to do next.
1458        disposition: AcknowledgementDisposition,
1459        /// The event being acknowledged.
1460        event_id: EventId,
1461        /// Why the actor is asking for this.
1462        reason: String,
1463    },
1464}
1465
1466/// Everything a supervision turn is given about one print.
1467#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1468pub struct PrintContext {
1469    /// The limits in force, so that the agent can see them before it asks.
1470    pub bounds: EffectiveBounds,
1471    /// The interventions still active.
1472    pub interventions: Vec<Intervention>,
1473    /// The job, when a snapshot could be taken.
1474    #[serde(default, skip_serializing_if = "Option::is_none")]
1475    pub job: Option<JobSnapshot>,
1476    /// The most recent image, when there is one.
1477    #[serde(default, skip_serializing_if = "Option::is_none")]
1478    pub latest_image: Option<ImageRef>,
1479    /// The manifest, when the print has one.
1480    #[serde(default, skip_serializing_if = "Option::is_none")]
1481    pub manifest: Option<JobManifest>,
1482    /// The print itself.
1483    pub print: PrintRecord,
1484    /// The printer, when a snapshot could be taken.
1485    #[serde(default, skip_serializing_if = "Option::is_none")]
1486    pub printer: Option<PrinterSnapshot>,
1487    /// A bounded list of this print's events, newest first.
1488    pub recent_events: Vec<EventRecord>,
1489}
1490
1491/// One print's end, as its `print_ended` event stored it.
1492#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1493pub struct PrintEnded {
1494    /// The `print_ended` event.
1495    pub event_id: EventId,
1496    /// How the print ended.
1497    pub outcome: PrintOutcome,
1498    /// When that event was received.
1499    pub received_at: Timestamp,
1500}
1501
1502/// A print ended.
1503#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1504pub struct PrintEndedPayload {
1505    /// How far into its file it last read, when that was read.
1506    #[serde(default, skip_serializing_if = "Option::is_none")]
1507    pub last_position: Option<FilePosition>,
1508    /// How it ended.
1509    pub outcome: PrintOutcome,
1510    /// Why it ended, in the words the end was recorded with.
1511    pub reason: String,
1512    /// The state the printer was in as it ended.
1513    pub state: PrinterState,
1514}
1515
1516/// A lowercase hyphenated version 7 UUID identifying one print.
1517pub type PrintId = String;
1518
1519/// How a print ended.
1520#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1521pub enum PrintOutcome {
1522    /// It ran to its end.
1523    #[serde(rename = "completed")]
1524    Completed,
1525    /// Somebody cancelled it.
1526    #[serde(rename = "cancelled")]
1527    Cancelled,
1528    /// It stopped on an error.
1529    #[serde(rename = "failed")]
1530    Failed,
1531}
1532
1533/// One print, and the record supervision keys from.
1534///
1535/// The provider's own print id is carried beside this record's identifier
1536/// rather than as it, because a print may be observed before the provider
1537/// that reports it has one.
1538#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1539pub struct PrintRecord {
1540    /// Why it ended, if it has.
1541    #[serde(default, skip_serializing_if = "Option::is_none")]
1542    pub end_reason: Option<String>,
1543    /// When it ended, if it has.
1544    #[serde(default, skip_serializing_if = "Option::is_none")]
1545    pub ended_at: Option<Timestamp>,
1546    /// The file being printed, as the source reported it.
1547    #[serde(default, skip_serializing_if = "Option::is_none")]
1548    pub file_name: Option<String>,
1549    /// This print's identifier, minted by the store.
1550    pub id: PrintId,
1551    /// The longest running time, in whole seconds, the printer reported for
1552    /// this print's job at a read that found it printing rather than paused.
1553    ///
1554    /// The time a job has spent printing only grows, pause or no pause, so a
1555    /// job of this print's file reporting less — by more than
1556    /// `listing::JOB_IDENTITY_TOLERANCE_S` — is a later job. Absent until a
1557    /// read finds the job printing.
1558    #[serde(default, skip_serializing_if = "Option::is_none")]
1559    pub job_print_time_s: Option<i64>,
1560    /// When the printer's job this print records began, as the printer's own
1561    /// running time put it at the latest read that found the job running or
1562    /// paused: the instant of that read, less the running time it reported.
1563    ///
1564    /// `OctoPrint` counts a pause into the running time until the job resumes
1565    /// and then takes the pause back out, so a pause moves this later: a read
1566    /// putting it further than `listing::JOB_IDENTITY_TOLERANCE_S` from the
1567    /// one
1568    /// recorded resets it, rather than it being fixed once. Absent when no
1569    /// read has
1570    /// found this print's job reporting a running time — a print opened by an
1571    /// alert or a start before any read, and every print recorded before this
1572    /// was.
1573    #[serde(default, skip_serializing_if = "Option::is_none")]
1574    pub job_started_at: Option<Timestamp>,
1575    /// Every manifest range this print narrowed to the envelope's.
1576    pub narrowings: Vec<ManifestNarrowing>,
1577    /// When the print was opened.
1578    pub opened_at: Timestamp,
1579    /// The provider's own identifier for the print, whichever provider
1580    /// reported it, when one has.
1581    #[serde(default, skip_serializing_if = "Option::is_none")]
1582    pub provider_print_id: Option<i64>,
1583    /// The state the print is in.
1584    pub state: PrinterState,
1585}
1586
1587/// One segment of a print this system watched, on one tool: the key of a
1588/// `ChargeKey::Print` debit, and the only key filament held pending can be
1589/// under.
1590#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1591pub struct PrintSegmentKey {
1592    /// The print.
1593    pub print_id: PrintId,
1594    /// Which segment, numbered as `ChargeKey::Print` numbers it.
1595    pub segment: i64,
1596    /// The tool, in the printer's own numbering.
1597    pub tool: i64,
1598}
1599
1600/// A print started.
1601#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1602pub struct PrintStartedPayload {
1603    /// The file being printed, when it is known.
1604    #[serde(default, skip_serializing_if = "Option::is_none")]
1605    pub file_name: Option<String>,
1606    /// The spool loaded on each tool as it started.
1607    pub loaded: Vec<LoadedSpool>,
1608    /// The filament check made before it started, when one was.
1609    #[serde(default, skip_serializing_if = "Option::is_none")]
1610    pub preflight: Option<Preflight>,
1611    /// The action that started it; absent when the print was observed rather
1612    /// than started through this system.
1613    #[serde(default, skip_serializing_if = "Option::is_none")]
1614    pub started_by: Option<ActionId>,
1615}
1616
1617/// A print that was already open when spool tracking was installed: it is
1618/// never charged, and this says why it carries no charge.
1619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1620pub struct PrintUntrackedPayload {
1621    /// Why the print carries no charge.
1622    pub reason: String,
1623}
1624
1625/// A printer, as a source reported it at one instant.
1626#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1627pub struct PrinterSnapshot {
1628    /// The bed heater, when the printer reports one.
1629    #[serde(default, skip_serializing_if = "Option::is_none")]
1630    pub bed: Option<HeaterSnapshot>,
1631    /// The chamber heater, when the printer reports one.
1632    #[serde(default, skip_serializing_if = "Option::is_none")]
1633    pub chamber: Option<HeaterSnapshot>,
1634    /// The state the printer is in.
1635    pub connection: PrinterState,
1636    /// The part-cooling fan, in percent.
1637    #[serde(default, skip_serializing_if = "Option::is_none")]
1638    pub fan_percent: Option<Reported>,
1639    /// The feedrate multiplier, where one means one hundred percent.
1640    #[serde(default, skip_serializing_if = "Option::is_none")]
1641    pub feedrate_factor: Option<Reported>,
1642    /// The flowrate multiplier, where one means one hundred percent.
1643    #[serde(default, skip_serializing_if = "Option::is_none")]
1644    pub flowrate_factor: Option<Reported>,
1645    /// The instant this observation was taken.
1646    pub observed_at: Timestamp,
1647    /// The tool heaters, indexed by tool number.
1648    pub tools: Vec<HeaterSnapshot>,
1649}
1650
1651/// The state a source reports a printer or a print to be in.
1652///
1653/// The `unknown` arm exists so that a state nobody anticipated is recorded
1654/// carrying the source's own word for it rather than lost.
1655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1656pub enum PrinterState {
1657    /// Connected and idle.
1658    #[serde(rename = "operational")]
1659    Operational,
1660    /// Printing, but paused.
1661    #[serde(rename = "paused")]
1662    Paused,
1663    /// Printing.
1664    #[serde(rename = "printing")]
1665    Printing,
1666    /// Cancelling a print.
1667    #[serde(rename = "cancelling")]
1668    Cancelling,
1669    /// In an error state.
1670    #[serde(rename = "error")]
1671    Error,
1672    /// Not reachable.
1673    #[serde(rename = "offline")]
1674    Offline,
1675    /// A state this vocabulary does not name, in the source's own word for
1676    /// it.
1677    #[serde(rename = "unknown")]
1678    Unknown(String),
1679}
1680
1681/// What the prints read answers: every print, and the one the printer is
1682/// running.
1683///
1684/// An open print's recorded `state` is the state it was opened in, which is
1685/// `printing` whether or not the machine is paused now; what the machine is
1686/// doing is the status read's answer.
1687#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1688pub struct PrintsAnswer {
1689    /// The print the printer's current job belongs to, present exactly when
1690    /// the
1691    /// printer reports a job it is printing or has paused. Absent when the
1692    /// printer could not be read.
1693    #[serde(default, skip_serializing_if = "Option::is_none")]
1694    pub active: Option<PrintId>,
1695    /// Every print this server holds, ended or not, most recently opened
1696    /// first.
1697    pub prints: Vec<PrintRecord>,
1698}
1699
1700/// An inclusive pair of 64-bit floats.
1701#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1702pub struct Range {
1703    /// The highest value the range admits, inclusive.
1704    pub max: f64,
1705    /// The lowest value the range admits, inclusive.
1706    pub min: f64,
1707}
1708
1709/// Bytes exactly as received, base64-encoded.
1710pub type RawBytes = String;
1711
1712/// What became of the prints a file's new tally generation counts.
1713#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1714pub enum RebaselineDisposition {
1715    /// They were accounted as prints this system never saw.
1716    #[serde(rename = "accounted")]
1717    Accounted,
1718    /// It could not be told which of them this system already settled, so the
1719    /// loaded spool was marked unverified rather than charged on a guess.
1720    #[serde(rename = "unverified")]
1721    Unverified,
1722}
1723
1724/// Why a request was refused.
1725///
1726/// Each rejection is a distinct variant, so a consumer distinguishes them by
1727/// matching rather than by reading a message.
1728#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1729pub enum RejectionReason {
1730    /// The value asked for is outside the range allowed for that adjustable.
1731    #[serde(rename = "out_of_bounds")]
1732    OutOfBounds {
1733        /// The adjustable that was asked for.
1734        adjustable: Adjustable,
1735        /// The range that was allowed.
1736        allowed: Range,
1737        /// The value that was asked for.
1738        requested: f64,
1739    },
1740    /// This actor class may not request this action at all.
1741    #[serde(rename = "actor_may_not_request")]
1742    ActorMayNotRequest {
1743        /// The action they asked for.
1744        action: ActionKind,
1745        /// The class of the actor that asked.
1746        actor_class: ActorClass,
1747    },
1748    /// There is no active print to act on.
1749    #[serde(rename = "no_active_print")]
1750    NoActivePrint,
1751    /// The printer is not in a state this action is valid from.
1752    #[serde(rename = "invalid_from_state")]
1753    InvalidFromState {
1754        /// The state the printer is in.
1755        state: PrinterState,
1756    },
1757    /// The agent's minimum interval has not elapsed.
1758    #[serde(rename = "min_interval_not_elapsed")]
1759    MinIntervalNotElapsed {
1760        /// The minimum interval, in whole seconds.
1761        interval_s: i64,
1762        /// How long it has been since the last agent action, in whole
1763        /// seconds.
1764        since_last_s: i64,
1765    },
1766    /// The adjustable is not one this printer has.
1767    #[serde(rename = "unsupported_adjustable")]
1768    UnsupportedAdjustable {
1769        /// The adjustable this printer cannot express.
1770        adjustable: Adjustable,
1771    },
1772    /// A start was refused because the spool on one tool holds less than the
1773    /// print needs and its buffer.
1774    #[serde(rename = "insufficient_filament")]
1775    InsufficientFilament {
1776        /// The margin asked for beyond that, in grams.
1777        buffer_g: f64,
1778        /// What the spool holds, in grams.
1779        remaining_g: f64,
1780        /// What the print needs from it, in grams.
1781        required_g: f64,
1782        /// The spool loaded on it.
1783        spool_id: SpoolId,
1784        /// That spool's name.
1785        spool_name: String,
1786        /// The tool whose spool is short.
1787        tool: i64,
1788        /// The spool's oldest mark, when the short spool is also unverified.
1789        #[serde(default, skip_serializing_if = "Option::is_none")]
1790        unverified: Option<UnverifiedReason>,
1791    },
1792    /// A start was refused because the spool on one tool is unverified.
1793    #[serde(rename = "unverified_spool")]
1794    UnverifiedSpool {
1795        /// The spool's oldest mark.
1796        reason: UnverifiedReason,
1797        /// The spool loaded on it.
1798        spool_id: SpoolId,
1799        /// That spool's name.
1800        spool_name: String,
1801        /// The tool whose spool is unverified.
1802        tool: i64,
1803    },
1804    /// A start was refused because `OctoPrint`'s analysis of the file had not
1805    /// appeared when the start's wait for it elapsed.
1806    #[serde(rename = "analysis_unavailable")]
1807    AnalysisUnavailable {
1808        /// The file that has no analysis.
1809        file_name: String,
1810        /// How long the start waited for one, in whole seconds.
1811        waited_s: i64,
1812    },
1813    /// A spool command could not be carried out.
1814    #[serde(rename = "spool_refused")]
1815    SpoolRefused {
1816        /// Why, in a form a client acts on without reading `message`.
1817        cause: SpoolRefusalCause,
1818        /// Why, in a sentence naming what to do instead, for a person.
1819        message: String,
1820    },
1821}
1822
1823/// A value as a source reported it, flagged when it is outside the
1824/// plausibility range this crate declares for its field.
1825#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1826pub struct Reported {
1827    /// One shape of the contracts, which say nothing more about it.
1828    pub out_of_range: bool,
1829    /// One shape of the contracts, which say nothing more about it.
1830    pub value: f64,
1831}
1832
1833/// The position a filament change charged up to, and how fresh it is.
1834#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1835pub struct SignalPosition {
1836    /// Whether it was read when the signal was handled, or is the last one
1837    /// recorded before it.
1838    pub freshness: PositionFreshness,
1839    /// The position.
1840    pub position: FilePosition,
1841}
1842
1843/// A spool's remaining figure was corrected.
1844#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1845pub struct SpoolAdjustedPayload {
1846    /// Who corrected it.
1847    pub actor: Actor,
1848    /// Every mark the correction cleared.
1849    #[serde(default, skip_serializing_if = "Option::is_none")]
1850    pub cleared_marks: Option<Vec<ClearedMark>>,
1851    /// The scale reading the figure was worked out from, when it was given as
1852    /// one.
1853    #[serde(default, skip_serializing_if = "Option::is_none")]
1854    pub gross_g: Option<f64>,
1855    /// Why.
1856    pub reason: String,
1857    /// Its figure after, in grams.
1858    pub remaining_after_g: f64,
1859    /// Its figure before, in grams, after any pending charge it took.
1860    pub remaining_before_g: f64,
1861    /// The spool corrected.
1862    pub spool_id: SpoolId,
1863    /// The pending charges the correction took first, each also its own
1864    /// `filament_consumed` event under its key.
1865    #[serde(default, skip_serializing_if = "Option::is_none")]
1866    pub took_pending: Option<Vec<PendingTaken>>,
1867}
1868
1869/// What one spool's read answers: the spool, and the newest of the printer's
1870/// own events that name it.
1871#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1872pub struct SpoolAnswer {
1873    /// The newest timeline events naming it, newest first.
1874    pub recent: Vec<EventRecord>,
1875    /// The spool, retired or not.
1876    pub spool: SpoolRecord,
1877}
1878
1879/// The whole vocabulary of the spool ledger, and there is no other.
1880#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1881#[serde(tag = "command")]
1882pub enum SpoolCommand {
1883    /// Register a spool.
1884    #[serde(rename = "register_spool")]
1885    RegisterSpool {
1886        /// Who is asking.
1887        actor: Actor,
1888        /// The filament's colour.
1889        #[serde(default, skip_serializing_if = "Option::is_none")]
1890        color: Option<String>,
1891        /// The material's density, in grams per cubic centimetre; the known
1892        /// density of the material when absent, and a material with none
1893        /// known
1894        /// is refused.
1895        #[serde(default, skip_serializing_if = "Option::is_none")]
1896        density_g_cm3: Option<f64>,
1897        /// The filament's diameter, in millimetres; 1.75 when absent.
1898        #[serde(default, skip_serializing_if = "Option::is_none")]
1899        diameter_mm: Option<f64>,
1900        /// The net filament on the spool when full, in grams.
1901        initial_g: f64,
1902        /// The material, such as `PLA`.
1903        material: String,
1904        /// The operator's own label for it.
1905        name: String,
1906        /// Why the actor is asking for this.
1907        reason: String,
1908        /// The filament on it now, in grams; the full figure when absent.
1909        #[serde(default, skip_serializing_if = "Option::is_none")]
1910        remaining_g: Option<f64>,
1911        /// The empty spool's own weight, in grams.
1912        #[serde(default, skip_serializing_if = "Option::is_none")]
1913        tare_g: Option<f64>,
1914    },
1915    /// Load a spool onto a tool.
1916    ///
1917    /// Onto an occupied tool it is a swap, and the spool there is unloaded in
1918    /// the same write. Naming the spool already on the tool confirms it was
1919    /// never swapped, which is accepted only while that tool holds a pending
1920    /// charge or that spool holds a filament-change mark.
1921    #[serde(rename = "load_spool")]
1922    LoadSpool {
1923        /// Who is asking.
1924        actor: Actor,
1925        /// Why the actor is asking for this.
1926        reason: String,
1927        /// The spool to load.
1928        spool_id: SpoolId,
1929        /// The tool, in the printer's own numbering.
1930        tool: i64,
1931    },
1932    /// Unload a spool from its tool.
1933    #[serde(rename = "unload_spool")]
1934    UnloadSpool {
1935        /// Who is asking.
1936        actor: Actor,
1937        /// Why the actor is asking for this.
1938        reason: String,
1939        /// The spool to unload.
1940        spool_id: SpoolId,
1941    },
1942    /// Correct a spool's remaining figure, from exactly one of the two; a
1943    /// command naming both or neither is refused as `spool_refused`.
1944    #[serde(rename = "adjust_spool")]
1945    AdjustSpool {
1946        /// Who is asking.
1947        actor: Actor,
1948        /// A scale reading of the spool, in grams, which needs its tare.
1949        #[serde(default, skip_serializing_if = "Option::is_none")]
1950        gross_g: Option<f64>,
1951        /// Why the actor is asking for this.
1952        reason: String,
1953        /// The filament left, in grams.
1954        #[serde(default, skip_serializing_if = "Option::is_none")]
1955        remaining_g: Option<f64>,
1956        /// The spool to correct.
1957        spool_id: SpoolId,
1958    },
1959    /// Retire a spool, after which it refuses every command.
1960    #[serde(rename = "retire_spool")]
1961    RetireSpool {
1962        /// Who is asking.
1963        actor: Actor,
1964        /// Why the actor is asking for this.
1965        reason: String,
1966        /// The spool to retire.
1967        spool_id: SpoolId,
1968    },
1969}
1970
1971/// What a spool command answers, accepted or refused.
1972///
1973/// A refusal is the same shape and deliberately so: the decision carries the
1974/// grant or the ledger's own reason, and the events are the record the
1975/// refusal
1976/// wrote.
1977#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1978pub struct SpoolCommandAnswer {
1979    /// The decision taken on the command.
1980    pub decision: PolicyDecision,
1981    /// Every event it wrote, in the order they were written.
1982    pub events: Vec<EventRecord>,
1983    /// The spool it was about, as the command left it, when there is one.
1984    #[serde(default, skip_serializing_if = "Option::is_none")]
1985    pub spool: Option<SpoolRecord>,
1986}
1987
1988/// A spool command was refused.
1989#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1990pub struct SpoolCommandRejectedPayload {
1991    /// The command, as it was asked for.
1992    pub command: SpoolCommand,
1993    /// The whole decision, carrying which refusal it was.
1994    pub decision: PolicyDecision,
1995}
1996
1997/// One field of a spool command, as the command names it.
1998#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1999pub enum SpoolField {
2000    /// The operator's own label for the spool.
2001    #[serde(rename = "name")]
2002    Name,
2003    /// The material, such as `PLA`.
2004    #[serde(rename = "material")]
2005    Material,
2006    /// The filament's diameter, in millimetres.
2007    #[serde(rename = "diameter_mm")]
2008    DiameterMm,
2009    /// The material's density, in grams per cubic centimetre.
2010    #[serde(rename = "density_g_cm3")]
2011    DensityGCm3,
2012    /// The net filament on the spool when full, in grams.
2013    #[serde(rename = "initial_g")]
2014    InitialG,
2015    /// The filament on the spool now, in grams.
2016    #[serde(rename = "remaining_g")]
2017    RemainingG,
2018    /// The empty spool's own weight, in grams.
2019    #[serde(rename = "tare_g")]
2020    TareG,
2021    /// A scale reading of the spool, in grams.
2022    #[serde(rename = "gross_g")]
2023    GrossG,
2024}
2025
2026/// A lowercase hyphenated version 7 UUID identifying one registered spool.
2027pub type SpoolId = String;
2028
2029/// Where a spool is in its life.
2030///
2031/// One state rather than a loaded tool beside a retirement instant, because a
2032/// retired spool is on no tool: retiring a loaded spool takes it off.
2033#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2034#[serde(tag = "state")]
2035pub enum SpoolLifecycle {
2036    /// Registered, and loaded on no tool.
2037    #[serde(rename = "shelved")]
2038    Shelved,
2039    /// Loaded on one tool.
2040    #[serde(rename = "loaded")]
2041    Loaded {
2042        /// The tool, in the printer's own numbering.
2043        tool: i64,
2044    },
2045    /// Retired: it takes no further command, and is on no tool.
2046    #[serde(rename = "retired")]
2047    Retired {
2048        /// When it was retired.
2049        at: Timestamp,
2050    },
2051}
2052
2053/// A spool was loaded onto a tool, or confirmed as never having left it.
2054#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2055pub struct SpoolLoadedPayload {
2056    /// Who loaded it.
2057    pub actor: Actor,
2058    /// Every mark the load cleared, on either spool.
2059    #[serde(default, skip_serializing_if = "Option::is_none")]
2060    pub cleared_marks: Option<Vec<ClearedMark>>,
2061    /// Why.
2062    pub reason: String,
2063    /// The spool the load swapped off the tool, when it was occupied.
2064    #[serde(default, skip_serializing_if = "Option::is_none")]
2065    pub replaced: Option<SpoolId>,
2066    /// The spool loaded.
2067    pub spool_id: SpoolId,
2068    /// The pending charges the load took, each also its own
2069    /// `filament_consumed` event under its key.
2070    #[serde(default, skip_serializing_if = "Option::is_none")]
2071    pub took_pending: Option<Vec<PendingTaken>>,
2072    /// The tool, in the printer's own numbering.
2073    pub tool: i64,
2074}
2075
2076/// One spool the operator registered, and what is known of it now.
2077#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2078pub struct SpoolRecord {
2079    /// The filament's colour, when the operator gave one.
2080    #[serde(default, skip_serializing_if = "Option::is_none")]
2081    pub color: Option<String>,
2082    /// The material's density, in grams per cubic centimetre; above zero.
2083    pub density_g_cm3: f64,
2084    /// The filament's diameter, in millimetres; above zero.
2085    pub diameter_mm: f64,
2086    /// This spool's identifier, minted when it was registered.
2087    pub id: SpoolId,
2088    /// The net filament on the spool when it was full, in grams; above zero.
2089    pub initial_g: f64,
2090    /// Where the spool is in its life: on the shelf, loaded on a tool, or
2091    /// retired — and never both of the last two.
2092    pub lifecycle: SpoolLifecycle,
2093    /// The material, as given and trimmed, such as `PLA`; never empty.
2094    pub material: String,
2095    /// The operator's own label for the spool; never empty.
2096    pub name: String,
2097    /// When the spool was registered.
2098    pub registered_at: Timestamp,
2099    /// The filament left, in grams.
2100    ///
2101    /// Always the initial figure less every charge plus every correction. A
2102    /// charge is never refused or clamped, so this may go below zero.
2103    pub remaining_g: f64,
2104    /// The empty spool's own weight, in grams, when the operator gave it.
2105    #[serde(default, skip_serializing_if = "Option::is_none")]
2106    pub tare_g: Option<f64>,
2107    /// How far the remaining figure can be trusted.
2108    pub trust: SpoolTrust,
2109    /// When anything about the spool last changed.
2110    pub updated_at: Timestamp,
2111}
2112
2113/// Why the spool ledger refused a command, each cause carrying what a client
2114/// needs to choose the next valid request.
2115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2116#[serde(tag = "cause")]
2117pub enum SpoolRefusalCause {
2118    /// No spool was ever registered under this identifier.
2119    #[serde(rename = "unknown_spool")]
2120    UnknownSpool {
2121        /// The identifier the command named.
2122        spool_id: SpoolId,
2123    },
2124    /// The spool is retired, and takes no further command.
2125    #[serde(rename = "retired_spool")]
2126    RetiredSpool {
2127        /// The retired spool.
2128        spool_id: SpoolId,
2129    },
2130    /// The spool named is already on the tool, and nothing is pending there
2131    /// or
2132    /// marked on it for a load to confirm.
2133    #[serde(rename = "nothing_to_confirm")]
2134    NothingToConfirm {
2135        /// The tool the spool is already on.
2136        tool: i64,
2137    },
2138    /// A figure the command gave is not one the field can hold: not a number,
2139    /// or out of the field's range.
2140    #[serde(rename = "invalid_adjustment_figure")]
2141    InvalidAdjustmentFigure {
2142        /// The field the figure was given for.
2143        field: SpoolField,
2144        /// The figure given; always a number, because a figure JSON cannot
2145        /// carry is refused as it would be written rather than written as
2146        /// `null`.
2147        value: f64,
2148    },
2149    /// A scale reading was given for a spool with no tare, so it cannot be
2150    /// turned into a figure.
2151    #[serde(rename = "tare_required")]
2152    TareRequired {
2153        /// The spool with no tare.
2154        spool_id: SpoolId,
2155    },
2156    /// The material has no known density, so the command needs one.
2157    #[serde(rename = "density_required")]
2158    DensityRequired {
2159        /// The material, as given and trimmed.
2160        material: String,
2161        /// The spool, when the command was about one that exists; absent for
2162        /// a registration.
2163        #[serde(default, skip_serializing_if = "Option::is_none")]
2164        spool_id: Option<SpoolId>,
2165    },
2166    /// A field the command needs was empty or absent.
2167    #[serde(rename = "missing_field")]
2168    MissingField {
2169        /// The field; for a correction naming neither figure, `remaining_g`,
2170        /// which `gross_g` may stand in for.
2171        field: SpoolField,
2172    },
2173    /// The command gave fields only one of which it may carry.
2174    #[serde(rename = "conflicting_fields")]
2175    ConflictingFields {
2176        /// Every field given that conflicts, in the command's own order.
2177        fields: Vec<SpoolField>,
2178    },
2179    /// The command named a tool the printer cannot have.
2180    #[serde(rename = "unknown_tool")]
2181    UnknownTool {
2182        /// The tool named.
2183        tool: i64,
2184    },
2185    /// The spool is loaded on no tool, so there is nothing to unload.
2186    #[serde(rename = "not_loaded")]
2187    NotLoaded {
2188        /// The spool.
2189        spool_id: SpoolId,
2190    },
2191    /// The ledger refused the write the command made, because the spools
2192    /// changed under it; reading them again shows how.
2193    #[serde(rename = "ledger_refused")]
2194    LedgerRefused,
2195}
2196
2197/// A spool was registered.
2198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2199pub struct SpoolRegisteredPayload {
2200    /// Who registered it.
2201    pub actor: Actor,
2202    /// Why.
2203    pub reason: String,
2204    /// The spool, as it was registered.
2205    pub spool: SpoolRecord,
2206}
2207
2208/// A spool was retired.
2209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2210pub struct SpoolRetiredPayload {
2211    /// Who retired it.
2212    pub actor: Actor,
2213    /// Why.
2214    pub reason: String,
2215    /// Its figure as it was retired, in grams.
2216    pub remaining_g: f64,
2217    /// The spool retired.
2218    pub spool_id: SpoolId,
2219}
2220
2221/// How far a spool's remaining figure can be trusted.
2222#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2223#[serde(tag = "state")]
2224pub enum SpoolTrust {
2225    /// Every charge against the spool was taken exactly.
2226    #[serde(rename = "verified")]
2227    Verified,
2228    /// At least one thing happened that could not be charged exactly.
2229    #[serde(rename = "unverified")]
2230    Unverified {
2231        /// Every reason the figure is in doubt, oldest first, at most one per
2232        /// distinct reason; never empty.
2233        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
2234        marks: Vec<UnverifiedMark>,
2235    },
2236}
2237
2238/// A spool was unloaded from its tool.
2239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2240pub struct SpoolUnloadedPayload {
2241    /// Who unloaded it.
2242    pub actor: Actor,
2243    /// Why.
2244    pub reason: String,
2245    /// The spool unloaded.
2246    pub spool_id: SpoolId,
2247    /// The tool it left.
2248    pub tool: i64,
2249}
2250
2251/// A spool was marked as one whose figure is in doubt.
2252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2253pub struct SpoolUnverifiedPayload {
2254    /// Why its figure is in doubt.
2255    pub reason: UnverifiedReason,
2256    /// The spool marked.
2257    pub spool_id: SpoolId,
2258}
2259
2260/// What the spools read answers.
2261///
2262/// `at_a_glance` is one line per spool, in the order `spools` lists them,
2263/// composed here so that what is left on each spool is the first thing a
2264/// reader of either rendering meets rather than a figure inside a whole
2265/// record. Its name is what puts it first: both renderings walk an answer's
2266/// fields in name order, ahead of `pending` and `spools`.
2267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2268pub struct SpoolsAnswer {
2269    /// One line per spool in `spools`, in the same order.
2270    pub at_a_glance: Vec<String>,
2271    /// Every charge held after a filament change nobody has confirmed, oldest
2272    /// first: what the next spool loaded on that tool is charged.
2273    pub pending: Vec<PendingCharge>,
2274    /// Every spool, retired ones only when asked for, most recently
2275    /// registered
2276    /// first.
2277    pub spools: Vec<SpoolRecord>,
2278}
2279
2280/// What one restart put back the way it found it.
2281///
2282/// A supervisor that has been restarted adopts whatever the store holds
2283/// rather
2284/// than starting empty, and each of these is one of those adoptions. They are
2285/// recorded rather than merely done, because a print that carried on across a
2286/// restart and one that was started again look identical afterwards unless
2287/// the
2288/// history says which happened.
2289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2290pub enum StartupOutcome {
2291    /// A print left open was adopted as the print this supervisor is
2292    /// watching.
2293    #[serde(rename = "print_adopted")]
2294    PrintAdopted,
2295    /// A session left open was resumed rather than replaced.
2296    #[serde(rename = "session_resumed")]
2297    SessionResumed {
2298        /// The session's own name in the harness.
2299        session_name: String,
2300    },
2301    /// An intervention already past its expiry was expired on start.
2302    #[serde(rename = "intervention_expired")]
2303    InterventionExpired {
2304        /// What it had changed.
2305        adjustable: Adjustable,
2306        /// The intervention that had outlived its bound.
2307        intervention_id: InterventionId,
2308        /// What became of putting the prior value back.
2309        outcome: InterventionOutcome,
2310    },
2311}
2312
2313/// A supervisor reconciled one thing the store held when it started.
2314#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2315pub struct StartupReconciliationPayload {
2316    /// What was reconciled.
2317    pub outcome: StartupOutcome,
2318    /// The print it is about.
2319    pub print_id: PrintId,
2320}
2321
2322/// What a status read answers.
2323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2324pub struct StatusAnswer {
2325    /// The bounded interventions still in force.
2326    pub interventions: Vec<Intervention>,
2327    /// The job, when a snapshot could be taken.
2328    #[serde(default, skip_serializing_if = "Option::is_none")]
2329    pub job: Option<JobSnapshot>,
2330    /// The print.
2331    pub print: PrintRecord,
2332    /// The printer, when a snapshot could be taken.
2333    #[serde(default, skip_serializing_if = "Option::is_none")]
2334    pub printer: Option<PrinterSnapshot>,
2335    /// The supervision session watching it, when one is.
2336    #[serde(default, skip_serializing_if = "Option::is_none")]
2337    pub session: Option<SupervisionSession>,
2338}
2339
2340/// The supervision session keyed to one print.
2341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2342pub struct SupervisionSession {
2343    /// Why it was closed, if it was.
2344    #[serde(default, skip_serializing_if = "Option::is_none")]
2345    pub close_reason: Option<String>,
2346    /// When the session was closed, if it was.
2347    #[serde(default, skip_serializing_if = "Option::is_none")]
2348    pub closed_at: Option<Timestamp>,
2349    /// When the session was opened.
2350    pub created_at: Timestamp,
2351    /// The identity the harness ran the session under.
2352    pub harness_identity: String,
2353    /// When the last turn ran.
2354    pub last_turn_at: Timestamp,
2355    /// The print this session watches.
2356    pub print_id: PrintId,
2357    /// The session's own name in the harness.
2358    pub session_name: String,
2359}
2360
2361/// A supervision session was closed.
2362#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2363pub struct SupervisionSessionClosedPayload {
2364    /// Why it was closed.
2365    pub close_reason: String,
2366    /// The session's own name in the harness.
2367    pub session_name: String,
2368}
2369
2370/// A supervision session was opened.
2371#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2372pub struct SupervisionSessionOpenedPayload {
2373    /// The identity the harness ran it under.
2374    pub harness_identity: String,
2375    /// The session's own name in the harness.
2376    pub session_name: String,
2377}
2378
2379/// What a printer history read answers: one page of the printer's own events.
2380#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2381pub struct TimelineAnswer {
2382    /// The events, newest first.
2383    pub events: Vec<EventRecord>,
2384    /// The cursor for the following, older page, given as `before`; absent
2385    /// when there is none.
2386    #[serde(default, skip_serializing_if = "Option::is_none")]
2387    pub next: Option<EventId>,
2388}
2389
2390/// An instant in UTC, as an RFC 3339 string with a zero offset.
2391pub type Timestamp = String;
2392
2393/// The filament one tool feeds, as a length and, when it is known, a volume.
2394#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2395pub struct ToolFilament {
2396    /// The length of filament fed, in millimetres.
2397    pub length_mm: f64,
2398    /// The tool, in the printer's own numbering.
2399    pub tool: i64,
2400    /// The volume of filament fed, in cubic centimetres, when the source
2401    /// reported one.
2402    #[serde(default, skip_serializing_if = "Option::is_none")]
2403    pub volume_cm3: Option<f64>,
2404}
2405
2406/// A print this system never saw was found in the printer's own tally.
2407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2408pub struct UnaccountedPrintPayload {
2409    /// Whether it was charged against a spool.
2410    pub charged: bool,
2411    /// The file that was printed.
2412    pub file_name: String,
2413    /// When it was printed, when the printer says.
2414    #[serde(default, skip_serializing_if = "Option::is_none")]
2415    pub printed_at: Option<Timestamp>,
2416    /// Whether it completed.
2417    pub success: bool,
2418}
2419
2420/// One reason a spool's figure is in doubt, and since when.
2421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2422pub struct UnverifiedMark {
2423    /// Why the figure is in doubt.
2424    pub reason: UnverifiedReason,
2425    /// When the spool was marked for it.
2426    pub since: Timestamp,
2427}
2428
2429/// Why a spool's figure is in doubt.
2430#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2431#[serde(tag = "reason")]
2432pub enum UnverifiedReason {
2433    /// A print ran on the spool's tool and nothing could be charged, because
2434    /// the file had no analysis — an SD-card file, for one.
2435    #[serde(rename = "uncharged_print")]
2436    UnchargedPrint {
2437        /// The print nothing could be charged for.
2438        print_id: PrintId,
2439    },
2440    /// A print this system never saw ran and did not complete.
2441    #[serde(rename = "unaccounted_print")]
2442    UnaccountedPrint {
2443        /// The file that was printed.
2444        file_name: String,
2445        /// When it was printed.
2446        printed_at: Timestamp,
2447    },
2448    /// A filament change was signalled and nobody has said which spool is now
2449    /// loaded. The spool was charged up to a position read when the signal
2450    /// was
2451    /// handled.
2452    #[serde(rename = "filament_change_unconfirmed")]
2453    FilamentChangeUnconfirmed {
2454        /// The print the change was signalled during.
2455        print_id: PrintId,
2456    },
2457    /// A filament change was signalled, nobody has said which spool is now
2458    /// loaded, and no fresh position could be read at the signal: the spool
2459    /// was
2460    /// charged only up to the last recorded position, so its figure may be
2461    /// overstated.
2462    #[serde(rename = "filament_change_unconfirmed_position")]
2463    FilamentChangeUnconfirmedPosition {
2464        /// The print the change was signalled during.
2465        print_id: PrintId,
2466    },
2467    /// A file's print tally was reset, and it cannot be told which of the
2468    /// prints since the re-upload were already settled.
2469    #[serde(rename = "tally_reset")]
2470    TallyReset {
2471        /// The file whose tally was reset.
2472        file_name: String,
2473        /// Where the file lives, in the printer's own vocabulary.
2474        origin: String,
2475    },
2476}
2477
2478/// One payload type declared under a kind name, by the domain that owns it.
2479///
2480/// The table from kind name to payload type, as the server declares it: every
2481/// kind below has exactly one implementation here, and a kind this client
2482/// does not know flows through `EventRecord` untouched.
2483pub trait EventPayloadKind: serde::de::DeserializeOwned {
2484    /// The name this payload's events are written down under.
2485    const KIND: &'static str;
2486}
2487
2488impl EventPayloadKind for ActionExecutedPayload {
2489    const KIND: &'static str = "action_executed";
2490}
2491
2492impl EventPayloadKind for ActionRejectedPayload {
2493    const KIND: &'static str = "action_rejected";
2494}
2495
2496impl EventPayloadKind for ActionRequestedPayload {
2497    const KIND: &'static str = "action_requested";
2498}
2499
2500impl EventPayloadKind for AgentAssessmentPayload {
2501    const KIND: &'static str = "agent_assessment";
2502}
2503
2504impl EventPayloadKind for CameraLookPayload {
2505    const KIND: &'static str = "camera_look";
2506}
2507
2508impl EventPayloadKind for ChargePendedPayload {
2509    const KIND: &'static str = "charge_pended";
2510}
2511
2512impl EventPayloadKind for FilamentChangeSignalledPayload {
2513    const KIND: &'static str = "filament_change_signalled";
2514}
2515
2516impl EventPayloadKind for FilamentConsumedPayload {
2517    const KIND: &'static str = "filament_consumed";
2518}
2519
2520impl EventPayloadKind for FileTallyRebaselinedPayload {
2521    const KIND: &'static str = "file_tally_rebaselined";
2522}
2523
2524impl EventPayloadKind for InterventionExpiredPayload {
2525    const KIND: &'static str = "intervention_expired";
2526}
2527
2528impl EventPayloadKind for MalformedExternalEventPayload {
2529    const KIND: &'static str = "malformed_external_event";
2530}
2531
2532impl EventPayloadKind for ObicoFailureAlertPayload {
2533    const KIND: &'static str = "obico_failure_alert";
2534}
2535
2536impl EventPayloadKind for ObicoPrinterNotificationPayload {
2537    const KIND: &'static str = "obico_printer_notification";
2538}
2539
2540impl EventPayloadKind for OperatorAcknowledgementPayload {
2541    const KIND: &'static str = "operator_acknowledgement";
2542}
2543
2544impl EventPayloadKind for PeriodicObservationPayload {
2545    const KIND: &'static str = "periodic_observation";
2546}
2547
2548impl EventPayloadKind for PortFailurePayload {
2549    const KIND: &'static str = "port_failure";
2550}
2551
2552impl EventPayloadKind for PreflightCheckedPayload {
2553    const KIND: &'static str = "preflight_checked";
2554}
2555
2556impl EventPayloadKind for PrintEndedPayload {
2557    const KIND: &'static str = "print_ended";
2558}
2559
2560impl EventPayloadKind for PrintStartedPayload {
2561    const KIND: &'static str = "print_started";
2562}
2563
2564impl EventPayloadKind for PrintUntrackedPayload {
2565    const KIND: &'static str = "print_untracked";
2566}
2567
2568impl EventPayloadKind for SpoolAdjustedPayload {
2569    const KIND: &'static str = "spool_adjusted";
2570}
2571
2572impl EventPayloadKind for SpoolCommandRejectedPayload {
2573    const KIND: &'static str = "spool_command_rejected";
2574}
2575
2576impl EventPayloadKind for SpoolLoadedPayload {
2577    const KIND: &'static str = "spool_loaded";
2578}
2579
2580impl EventPayloadKind for SpoolRegisteredPayload {
2581    const KIND: &'static str = "spool_registered";
2582}
2583
2584impl EventPayloadKind for SpoolRetiredPayload {
2585    const KIND: &'static str = "spool_retired";
2586}
2587
2588impl EventPayloadKind for SpoolUnloadedPayload {
2589    const KIND: &'static str = "spool_unloaded";
2590}
2591
2592impl EventPayloadKind for SpoolUnverifiedPayload {
2593    const KIND: &'static str = "spool_unverified";
2594}
2595
2596impl EventPayloadKind for StartupReconciliationPayload {
2597    const KIND: &'static str = "startup_reconciliation";
2598}
2599
2600impl EventPayloadKind for SupervisionSessionClosedPayload {
2601    const KIND: &'static str = "supervision_session_closed";
2602}
2603
2604impl EventPayloadKind for SupervisionSessionOpenedPayload {
2605    const KIND: &'static str = "supervision_session_opened";
2606}
2607
2608impl EventPayloadKind for UnaccountedPrintPayload {
2609    const KIND: &'static str = "unaccounted_print";
2610}
2611
2612/// Every kind the server declares a payload type for, in name order.
2613pub const EVENT_KINDS: [&str; 31] = [
2614    "action_executed",
2615    "action_rejected",
2616    "action_requested",
2617    "agent_assessment",
2618    "camera_look",
2619    "charge_pended",
2620    "filament_change_signalled",
2621    "filament_consumed",
2622    "file_tally_rebaselined",
2623    "intervention_expired",
2624    "malformed_external_event",
2625    "obico_failure_alert",
2626    "obico_printer_notification",
2627    "operator_acknowledgement",
2628    "periodic_observation",
2629    "port_failure",
2630    "preflight_checked",
2631    "print_ended",
2632    "print_started",
2633    "print_untracked",
2634    "spool_adjusted",
2635    "spool_command_rejected",
2636    "spool_loaded",
2637    "spool_registered",
2638    "spool_retired",
2639    "spool_unloaded",
2640    "spool_unverified",
2641    "startup_reconciliation",
2642    "supervision_session_closed",
2643    "supervision_session_opened",
2644    "unaccounted_print",
2645];
2646
2647impl EventRecord {
2648    /// The payload as `P`, when this event is under the kind `P` declares.
2649    ///
2650    /// Answers `None` for an event of any other kind — one this client knows
2651    /// or one it does not — and the parse failure for an event under the
2652    /// right kind whose payload is not of the type.
2653    #[must_use]
2654    pub fn payload_as<P: EventPayloadKind>(&self) -> Option<Result<P, serde_json::Error>> {
2655        (self.kind == P::KIND).then(|| serde_json::from_value(self.payload.clone()))
2656    }
2657}
2658
2659impl Client {
2660    /// Call `prints` on the configured supervisor.
2661    ///
2662    /// # Errors
2663    ///
2664    /// Returns `ClientError` when the supervisor could not be reached, when
2665    /// it answered something this client cannot read.
2666    // llmlint: ignore[names_match_behavior] See suppressions.toml.
2667    pub fn prints(&self) -> Result<PrintsAnswer, ClientError> {
2668        let target = String::from("/v1/prints");
2669        let asked: Vec<(String, String)> = Vec::new();
2670        let sending: Option<serde_json::Value> = None;
2671        self.call("GET", &target, &asked, sending.as_ref())
2672    }
2673
2674    /// Call `status` on the configured supervisor.
2675    ///
2676    /// # Errors
2677    ///
2678    /// Returns `ClientError` when the supervisor could not be reached, when
2679    /// it answered something this client cannot read.
2680    pub fn status(&self, print_id: &str) -> Result<StatusAnswer, ClientError> {
2681        let target = format!("/v1/prints/{print_id}/status");
2682        let asked: Vec<(String, String)> = Vec::new();
2683        let sending: Option<serde_json::Value> = None;
2684        self.call("GET", &target, &asked, sending.as_ref())
2685    }
2686
2687    /// Call `context` on the configured supervisor.
2688    ///
2689    /// # Errors
2690    ///
2691    /// Returns `ClientError` when the supervisor could not be reached, when
2692    /// it answered something this client cannot read.
2693    pub fn context(&self, print_id: &str) -> Result<ContextAnswer, ClientError> {
2694        let target = format!("/v1/prints/{print_id}/context");
2695        let asked: Vec<(String, String)> = Vec::new();
2696        let sending: Option<serde_json::Value> = None;
2697        self.call("GET", &target, &asked, sending.as_ref())
2698    }
2699
2700    /// Call `image` on the configured supervisor.
2701    ///
2702    /// # Errors
2703    ///
2704    /// Returns `ClientError` when the supervisor could not be reached, when
2705    /// it answered something this client cannot read.
2706    pub fn image(&self, image_id: &str) -> Result<ImageAnswer, ClientError> {
2707        let target = format!("/v1/images/{image_id}");
2708        let asked: Vec<(String, String)> = Vec::new();
2709        let sending: Option<serde_json::Value> = None;
2710        self.call("GET", &target, &asked, sending.as_ref())
2711    }
2712
2713    /// Call `history` on the configured supervisor.
2714    ///
2715    /// # Errors
2716    ///
2717    /// Returns `ClientError` when the supervisor could not be reached, when
2718    /// it answered something this client cannot read.
2719    pub fn history(
2720        &self,
2721        print_id: &str,
2722        limit: Option<i64>,
2723    ) -> Result<HistoryAnswer, ClientError> {
2724        let target = format!("/v1/prints/{print_id}/history");
2725        let mut asked: Vec<(String, String)> = Vec::new();
2726        if let Some(value) = limit {
2727            asked.push(("limit".to_owned(), value.to_string()));
2728        }
2729        let sending: Option<serde_json::Value> = None;
2730        self.call("GET", &target, &asked, sending.as_ref())
2731    }
2732
2733    /// Call `events` on the configured supervisor.
2734    ///
2735    /// # Errors
2736    ///
2737    /// Returns `ClientError` when the supervisor could not be reached, when
2738    /// it answered something this client cannot read.
2739    pub fn events(
2740        &self,
2741        after: Option<String>,
2742        print_id: Option<String>,
2743        kinds: Option<Vec<String>>,
2744        exclude_kinds: Option<Vec<String>>,
2745        wait_s: Option<i64>,
2746        limit: Option<i64>,
2747    ) -> Result<EventsAnswer, ClientError> {
2748        let target = String::from("/v1/events");
2749        let mut asked: Vec<(String, String)> = Vec::new();
2750        if let Some(value) = after {
2751            asked.push(("after".to_owned(), value));
2752        }
2753        if let Some(value) = print_id {
2754            asked.push(("print_id".to_owned(), value));
2755        }
2756        for value in kinds.into_iter().flatten() {
2757            asked.push(("kinds".to_owned(), value));
2758        }
2759        for value in exclude_kinds.into_iter().flatten() {
2760            asked.push(("exclude_kinds".to_owned(), value));
2761        }
2762        if let Some(value) = wait_s {
2763            asked.push(("wait_s".to_owned(), value.to_string()));
2764        }
2765        if let Some(value) = limit {
2766            asked.push(("limit".to_owned(), value.to_string()));
2767        }
2768        let sending: Option<serde_json::Value> = None;
2769        self.call("GET", &target, &asked, sending.as_ref())
2770    }
2771
2772    /// Call `manifest_get` on the configured supervisor.
2773    ///
2774    /// # Errors
2775    ///
2776    /// Returns `ClientError` when the supervisor could not be reached, when
2777    /// it answered something this client cannot read.
2778    pub fn manifest_get(&self, print_id: &str) -> Result<ManifestAnswer, ClientError> {
2779        let target = format!("/v1/prints/{print_id}/manifest");
2780        let asked: Vec<(String, String)> = Vec::new();
2781        let sending: Option<serde_json::Value> = None;
2782        self.call("GET", &target, &asked, sending.as_ref())
2783    }
2784
2785    /// Call `look` on the configured supervisor.
2786    ///
2787    /// # Errors
2788    ///
2789    /// Returns `ClientError` when the supervisor could not be reached, when
2790    /// it answered something this client cannot read.
2791    pub fn look(&self, print_id: &str, wait_s: Option<i64>) -> Result<Look, ClientError> {
2792        let target = format!("/v1/prints/{print_id}/look");
2793        let mut asked: Vec<(String, String)> = Vec::new();
2794        if let Some(value) = wait_s {
2795            asked.push(("wait_s".to_owned(), value.to_string()));
2796        }
2797        let sending: Option<serde_json::Value> = None;
2798        self.call("GET", &target, &asked, sending.as_ref())
2799    }
2800
2801    /// Call `manifest_set` on the configured supervisor.
2802    ///
2803    /// # Errors
2804    ///
2805    /// Returns `ClientError` when the supervisor could not be reached, when
2806    /// it answered something this client cannot read, when this client
2807    /// refuses the call before making it because the reason is empty, and
2808    /// when the policy refused the action — which arrives as
2809    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2810    /// the range allowed.
2811    pub fn manifest_set(
2812        &self,
2813        print_id: &str,
2814        reason: &str,
2815        manifest: &JobManifest,
2816    ) -> Result<ManifestAnswer, ClientError> {
2817        crate::reason_given(reason)?;
2818        let target = format!("/v1/prints/{print_id}/manifest");
2819        let asked: Vec<(String, String)> = Vec::new();
2820        let mut sending = serde_json::Map::new();
2821        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2822        sending.insert("manifest".to_owned(), crate::as_value(&manifest)?);
2823        let sending = Some(serde_json::Value::Object(sending));
2824        self.call("PUT", &target, &asked, sending.as_ref())
2825    }
2826
2827    /// Call `spools` on the configured supervisor.
2828    ///
2829    /// # Errors
2830    ///
2831    /// Returns `ClientError` when the supervisor could not be reached, when
2832    /// it answered something this client cannot read.
2833    pub fn spools(&self, include_retired: Option<bool>) -> Result<SpoolsAnswer, ClientError> {
2834        let target = String::from("/v1/spools");
2835        let mut asked: Vec<(String, String)> = Vec::new();
2836        if let Some(value) = include_retired {
2837            asked.push(("include_retired".to_owned(), value.to_string()));
2838        }
2839        let sending: Option<serde_json::Value> = None;
2840        self.call("GET", &target, &asked, sending.as_ref())
2841    }
2842
2843    /// Call `spool` on the configured supervisor.
2844    ///
2845    /// # Errors
2846    ///
2847    /// Returns `ClientError` when the supervisor could not be reached, when
2848    /// it answered something this client cannot read.
2849    pub fn spool(&self, spool_id: &str) -> Result<SpoolAnswer, ClientError> {
2850        let target = format!("/v1/spools/{spool_id}");
2851        let asked: Vec<(String, String)> = Vec::new();
2852        let sending: Option<serde_json::Value> = None;
2853        self.call("GET", &target, &asked, sending.as_ref())
2854    }
2855
2856    /// Call `printer_history` on the configured supervisor.
2857    ///
2858    /// # Errors
2859    ///
2860    /// Returns `ClientError` when the supervisor could not be reached, when
2861    /// it answered something this client cannot read.
2862    pub fn printer_history(
2863        &self,
2864        kinds: Option<Vec<String>>,
2865        since: Option<String>,
2866        until: Option<String>,
2867        spool_id: Option<String>,
2868        before: Option<String>,
2869        limit: Option<i64>,
2870    ) -> Result<TimelineAnswer, ClientError> {
2871        let target = String::from("/v1/history");
2872        let mut asked: Vec<(String, String)> = Vec::new();
2873        for value in kinds.into_iter().flatten() {
2874            asked.push(("kinds".to_owned(), value));
2875        }
2876        if let Some(value) = since {
2877            asked.push(("since".to_owned(), value));
2878        }
2879        if let Some(value) = until {
2880            asked.push(("until".to_owned(), value));
2881        }
2882        if let Some(value) = spool_id {
2883            asked.push(("spool_id".to_owned(), value));
2884        }
2885        if let Some(value) = before {
2886            asked.push(("before".to_owned(), value));
2887        }
2888        if let Some(value) = limit {
2889            asked.push(("limit".to_owned(), value.to_string()));
2890        }
2891        let sending: Option<serde_json::Value> = None;
2892        self.call("GET", &target, &asked, sending.as_ref())
2893    }
2894
2895    /// Call `preflight` on the configured supervisor.
2896    ///
2897    /// # Errors
2898    ///
2899    /// Returns `ClientError` when the supervisor could not be reached, when
2900    /// it answered something this client cannot read.
2901    pub fn preflight(&self, file_name: &str) -> Result<PreflightAnswer, ClientError> {
2902        let target = String::from("/v1/preflight");
2903        let asked: Vec<(String, String)> = vec![("file_name".to_owned(), file_name.to_owned())];
2904        let sending: Option<serde_json::Value> = None;
2905        self.call("GET", &target, &asked, sending.as_ref())
2906    }
2907
2908    /// Call `pause` on the configured supervisor.
2909    ///
2910    /// # Errors
2911    ///
2912    /// Returns `ClientError` when the supervisor could not be reached, when
2913    /// it answered something this client cannot read, when this client
2914    /// refuses the call before making it because the reason is empty, and
2915    /// when the policy refused the action — which arrives as
2916    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2917    /// the range allowed.
2918    pub fn pause(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
2919        crate::reason_given(reason)?;
2920        let target = format!("/v1/prints/{print_id}/actions/pause");
2921        let asked: Vec<(String, String)> = Vec::new();
2922        let mut sending = serde_json::Map::new();
2923        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2924        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2925        let sending = Some(serde_json::Value::Object(sending));
2926        self.call("POST", &target, &asked, sending.as_ref())
2927    }
2928
2929    /// Call `resume` on the configured supervisor.
2930    ///
2931    /// # Errors
2932    ///
2933    /// Returns `ClientError` when the supervisor could not be reached, when
2934    /// it answered something this client cannot read, when this client
2935    /// refuses the call before making it because the reason is empty, and
2936    /// when the policy refused the action — which arrives as
2937    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2938    /// the range allowed.
2939    pub fn resume(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
2940        crate::reason_given(reason)?;
2941        let target = format!("/v1/prints/{print_id}/actions/resume");
2942        let asked: Vec<(String, String)> = Vec::new();
2943        let mut sending = serde_json::Map::new();
2944        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2945        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2946        let sending = Some(serde_json::Value::Object(sending));
2947        self.call("POST", &target, &asked, sending.as_ref())
2948    }
2949
2950    /// Call `cancel` on the configured supervisor.
2951    ///
2952    /// # Errors
2953    ///
2954    /// Returns `ClientError` when the supervisor could not be reached, when
2955    /// it answered something this client cannot read, when this client
2956    /// refuses the call before making it because the reason is empty, and
2957    /// when the policy refused the action — which arrives as
2958    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2959    /// the range allowed.
2960    pub fn cancel(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
2961        crate::reason_given(reason)?;
2962        let target = format!("/v1/prints/{print_id}/actions/cancel");
2963        let asked: Vec<(String, String)> = Vec::new();
2964        let mut sending = serde_json::Map::new();
2965        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2966        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2967        let sending = Some(serde_json::Value::Object(sending));
2968        self.call("POST", &target, &asked, sending.as_ref())
2969    }
2970
2971    /// Call `start_print` on the configured supervisor.
2972    ///
2973    /// # Errors
2974    ///
2975    /// Returns `ClientError` when the supervisor could not be reached, when
2976    /// it answered something this client cannot read, when this client
2977    /// refuses the call before making it because the reason is empty, and
2978    /// when the policy refused the action — which arrives as
2979    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2980    /// the range allowed.
2981    pub fn start_print(
2982        &self,
2983        print_id: &str,
2984        file_name: &FileName,
2985        manifest: &JobManifest,
2986        reason: &str,
2987        override_filament_check: Option<bool>,
2988    ) -> Result<ActionAnswer, ClientError> {
2989        crate::reason_given(reason)?;
2990        let target = format!("/v1/prints/{print_id}/actions/start_print");
2991        let asked: Vec<(String, String)> = Vec::new();
2992        let mut sending = serde_json::Map::new();
2993        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2994        sending.insert("file_name".to_owned(), crate::as_value(&file_name)?);
2995        sending.insert("manifest".to_owned(), crate::as_value(&manifest)?);
2996        if let Some(value) = override_filament_check {
2997            sending.insert(
2998                "override_filament_check".to_owned(),
2999                crate::as_value(&value)?,
3000            );
3001        }
3002        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3003        let sending = Some(serde_json::Value::Object(sending));
3004        self.call("POST", &target, &asked, sending.as_ref())
3005    }
3006
3007    /// Call `set_feedrate_factor` on the configured supervisor.
3008    ///
3009    /// # Errors
3010    ///
3011    /// Returns `ClientError` when the supervisor could not be reached, when
3012    /// it answered something this client cannot read, when this client
3013    /// refuses the call before making it because the reason is empty, and
3014    /// when the policy refused the action — which arrives as
3015    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3016    /// the range allowed.
3017    pub fn set_feedrate_factor(
3018        &self,
3019        print_id: &str,
3020        factor: f64,
3021        reason: &str,
3022        duration_s: Option<i64>,
3023    ) -> Result<ActionAnswer, ClientError> {
3024        crate::reason_given(reason)?;
3025        let target = format!("/v1/prints/{print_id}/actions/set_feedrate_factor");
3026        let asked: Vec<(String, String)> = Vec::new();
3027        let mut sending = serde_json::Map::new();
3028        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3029        if let Some(value) = duration_s {
3030            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3031        }
3032        sending.insert("factor".to_owned(), crate::as_value(&factor)?);
3033        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3034        let sending = Some(serde_json::Value::Object(sending));
3035        self.call("POST", &target, &asked, sending.as_ref())
3036    }
3037
3038    /// Call `set_flowrate_factor` on the configured supervisor.
3039    ///
3040    /// # Errors
3041    ///
3042    /// Returns `ClientError` when the supervisor could not be reached, when
3043    /// it answered something this client cannot read, when this client
3044    /// refuses the call before making it because the reason is empty, and
3045    /// when the policy refused the action — which arrives as
3046    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3047    /// the range allowed.
3048    pub fn set_flowrate_factor(
3049        &self,
3050        print_id: &str,
3051        factor: f64,
3052        reason: &str,
3053        duration_s: Option<i64>,
3054    ) -> Result<ActionAnswer, ClientError> {
3055        crate::reason_given(reason)?;
3056        let target = format!("/v1/prints/{print_id}/actions/set_flowrate_factor");
3057        let asked: Vec<(String, String)> = Vec::new();
3058        let mut sending = serde_json::Map::new();
3059        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3060        if let Some(value) = duration_s {
3061            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3062        }
3063        sending.insert("factor".to_owned(), crate::as_value(&factor)?);
3064        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3065        let sending = Some(serde_json::Value::Object(sending));
3066        self.call("POST", &target, &asked, sending.as_ref())
3067    }
3068
3069    /// Call `set_tool_target_c` on the configured supervisor.
3070    ///
3071    /// # Errors
3072    ///
3073    /// Returns `ClientError` when the supervisor could not be reached, when
3074    /// it answered something this client cannot read, when this client
3075    /// refuses the call before making it because the reason is empty, and
3076    /// when the policy refused the action — which arrives as
3077    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3078    /// the range allowed.
3079    pub fn set_tool_target_c(
3080        &self,
3081        print_id: &str,
3082        reason: &str,
3083        target_c: f64,
3084        tool: i64,
3085        duration_s: Option<i64>,
3086    ) -> Result<ActionAnswer, ClientError> {
3087        crate::reason_given(reason)?;
3088        let target = format!("/v1/prints/{print_id}/actions/set_tool_target_c");
3089        let asked: Vec<(String, String)> = Vec::new();
3090        let mut sending = serde_json::Map::new();
3091        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3092        if let Some(value) = duration_s {
3093            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3094        }
3095        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3096        sending.insert("target_c".to_owned(), crate::as_value(&target_c)?);
3097        sending.insert("tool".to_owned(), crate::as_value(&tool)?);
3098        let sending = Some(serde_json::Value::Object(sending));
3099        self.call("POST", &target, &asked, sending.as_ref())
3100    }
3101
3102    /// Call `set_bed_target_c` on the configured supervisor.
3103    ///
3104    /// # Errors
3105    ///
3106    /// Returns `ClientError` when the supervisor could not be reached, when
3107    /// it answered something this client cannot read, when this client
3108    /// refuses the call before making it because the reason is empty, and
3109    /// when the policy refused the action — which arrives as
3110    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3111    /// the range allowed.
3112    pub fn set_bed_target_c(
3113        &self,
3114        print_id: &str,
3115        reason: &str,
3116        target_c: f64,
3117        duration_s: Option<i64>,
3118    ) -> Result<ActionAnswer, ClientError> {
3119        crate::reason_given(reason)?;
3120        let target = format!("/v1/prints/{print_id}/actions/set_bed_target_c");
3121        let asked: Vec<(String, String)> = Vec::new();
3122        let mut sending = serde_json::Map::new();
3123        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3124        if let Some(value) = duration_s {
3125            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3126        }
3127        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3128        sending.insert("target_c".to_owned(), crate::as_value(&target_c)?);
3129        let sending = Some(serde_json::Value::Object(sending));
3130        self.call("POST", &target, &asked, sending.as_ref())
3131    }
3132
3133    /// Call `set_fan_percent` on the configured supervisor.
3134    ///
3135    /// # Errors
3136    ///
3137    /// Returns `ClientError` when the supervisor could not be reached, when
3138    /// it answered something this client cannot read, when this client
3139    /// refuses the call before making it because the reason is empty, and
3140    /// when the policy refused the action — which arrives as
3141    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3142    /// the range allowed.
3143    pub fn set_fan_percent(
3144        &self,
3145        print_id: &str,
3146        percent: f64,
3147        reason: &str,
3148        duration_s: Option<i64>,
3149    ) -> Result<ActionAnswer, ClientError> {
3150        crate::reason_given(reason)?;
3151        let target = format!("/v1/prints/{print_id}/actions/set_fan_percent");
3152        let asked: Vec<(String, String)> = Vec::new();
3153        let mut sending = serde_json::Map::new();
3154        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3155        if let Some(value) = duration_s {
3156            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3157        }
3158        sending.insert("percent".to_owned(), crate::as_value(&percent)?);
3159        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3160        let sending = Some(serde_json::Value::Object(sending));
3161        self.call("POST", &target, &asked, sending.as_ref())
3162    }
3163
3164    /// Call `acknowledge_failure` on the configured supervisor.
3165    ///
3166    /// # Errors
3167    ///
3168    /// Returns `ClientError` when the supervisor could not be reached, when
3169    /// it answered something this client cannot read, when this client
3170    /// refuses the call before making it because the reason is empty, and
3171    /// when the policy refused the action — which arrives as
3172    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3173    /// the range allowed.
3174    pub fn acknowledge_failure(
3175        &self,
3176        print_id: &str,
3177        disposition: &AcknowledgementDisposition,
3178        event_id: &EventId,
3179        reason: &str,
3180    ) -> Result<ActionAnswer, ClientError> {
3181        crate::reason_given(reason)?;
3182        let target = format!("/v1/prints/{print_id}/actions/acknowledge_failure");
3183        let asked: Vec<(String, String)> = Vec::new();
3184        let mut sending = serde_json::Map::new();
3185        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3186        sending.insert("disposition".to_owned(), crate::as_value(&disposition)?);
3187        sending.insert("event_id".to_owned(), crate::as_value(&event_id)?);
3188        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3189        let sending = Some(serde_json::Value::Object(sending));
3190        self.call("POST", &target, &asked, sending.as_ref())
3191    }
3192
3193    /// Call `register_spool` on the configured supervisor.
3194    ///
3195    /// # Errors
3196    ///
3197    /// Returns `ClientError` when the supervisor could not be reached, when
3198    /// it answered something this client cannot read, when this client
3199    /// refuses the call before making it because the reason is empty, and
3200    /// when the policy refused the action — which arrives as
3201    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3202    /// the range allowed.
3203    // One argument per value the operation declares, as every client takes them; suppressions.toml has the reason.
3204    #[expect(clippy::too_many_arguments)]
3205    pub fn register_spool(
3206        &self,
3207        initial_g: f64,
3208        material: &str,
3209        name: &str,
3210        reason: &str,
3211        color: Option<String>,
3212        density_g_cm3: Option<f64>,
3213        diameter_mm: Option<f64>,
3214        remaining_g: Option<f64>,
3215        tare_g: Option<f64>,
3216    ) -> Result<SpoolCommandAnswer, ClientError> {
3217        crate::reason_given(reason)?;
3218        let target = String::from("/v1/spools/actions/register_spool");
3219        let asked: Vec<(String, String)> = Vec::new();
3220        let mut sending = serde_json::Map::new();
3221        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3222        if let Some(value) = color {
3223            sending.insert("color".to_owned(), crate::as_value(&value)?);
3224        }
3225        if let Some(value) = density_g_cm3 {
3226            sending.insert("density_g_cm3".to_owned(), crate::as_value(&value)?);
3227        }
3228        if let Some(value) = diameter_mm {
3229            sending.insert("diameter_mm".to_owned(), crate::as_value(&value)?);
3230        }
3231        sending.insert("initial_g".to_owned(), crate::as_value(&initial_g)?);
3232        sending.insert("material".to_owned(), crate::as_value(&material)?);
3233        sending.insert("name".to_owned(), crate::as_value(&name)?);
3234        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3235        if let Some(value) = remaining_g {
3236            sending.insert("remaining_g".to_owned(), crate::as_value(&value)?);
3237        }
3238        if let Some(value) = tare_g {
3239            sending.insert("tare_g".to_owned(), crate::as_value(&value)?);
3240        }
3241        let sending = Some(serde_json::Value::Object(sending));
3242        self.call("POST", &target, &asked, sending.as_ref())
3243    }
3244
3245    /// Call `load_spool` on the configured supervisor.
3246    ///
3247    /// # Errors
3248    ///
3249    /// Returns `ClientError` when the supervisor could not be reached, when
3250    /// it answered something this client cannot read, when this client
3251    /// refuses the call before making it because the reason is empty, and
3252    /// when the policy refused the action — which arrives as
3253    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3254    /// the range allowed.
3255    pub fn load_spool(
3256        &self,
3257        spool_id: &str,
3258        reason: &str,
3259        tool: i64,
3260    ) -> Result<SpoolCommandAnswer, ClientError> {
3261        crate::reason_given(reason)?;
3262        let target = format!("/v1/spools/{spool_id}/actions/load_spool");
3263        let asked: Vec<(String, String)> = Vec::new();
3264        let mut sending = serde_json::Map::new();
3265        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3266        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3267        sending.insert("tool".to_owned(), crate::as_value(&tool)?);
3268        let sending = Some(serde_json::Value::Object(sending));
3269        self.call("POST", &target, &asked, sending.as_ref())
3270    }
3271
3272    /// Call `unload_spool` on the configured supervisor.
3273    ///
3274    /// # Errors
3275    ///
3276    /// Returns `ClientError` when the supervisor could not be reached, when
3277    /// it answered something this client cannot read, when this client
3278    /// refuses the call before making it because the reason is empty, and
3279    /// when the policy refused the action — which arrives as
3280    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3281    /// the range allowed.
3282    pub fn unload_spool(
3283        &self,
3284        spool_id: &str,
3285        reason: &str,
3286    ) -> Result<SpoolCommandAnswer, ClientError> {
3287        crate::reason_given(reason)?;
3288        let target = format!("/v1/spools/{spool_id}/actions/unload_spool");
3289        let asked: Vec<(String, String)> = Vec::new();
3290        let mut sending = serde_json::Map::new();
3291        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3292        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3293        let sending = Some(serde_json::Value::Object(sending));
3294        self.call("POST", &target, &asked, sending.as_ref())
3295    }
3296
3297    /// Call `adjust_spool` on the configured supervisor.
3298    ///
3299    /// # Errors
3300    ///
3301    /// Returns `ClientError` when the supervisor could not be reached, when
3302    /// it answered something this client cannot read, when this client
3303    /// refuses the call before making it because the reason is empty, and
3304    /// when the policy refused the action — which arrives as
3305    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3306    /// the range allowed.
3307    pub fn adjust_spool(
3308        &self,
3309        spool_id: &str,
3310        reason: &str,
3311        gross_g: Option<f64>,
3312        remaining_g: Option<f64>,
3313    ) -> Result<SpoolCommandAnswer, ClientError> {
3314        crate::reason_given(reason)?;
3315        let target = format!("/v1/spools/{spool_id}/actions/adjust_spool");
3316        let asked: Vec<(String, String)> = Vec::new();
3317        let mut sending = serde_json::Map::new();
3318        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3319        if let Some(value) = gross_g {
3320            sending.insert("gross_g".to_owned(), crate::as_value(&value)?);
3321        }
3322        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3323        if let Some(value) = remaining_g {
3324            sending.insert("remaining_g".to_owned(), crate::as_value(&value)?);
3325        }
3326        let sending = Some(serde_json::Value::Object(sending));
3327        self.call("POST", &target, &asked, sending.as_ref())
3328    }
3329
3330    /// Call `retire_spool` on the configured supervisor.
3331    ///
3332    /// # Errors
3333    ///
3334    /// Returns `ClientError` when the supervisor could not be reached, when
3335    /// it answered something this client cannot read, when this client
3336    /// refuses the call before making it because the reason is empty, and
3337    /// when the policy refused the action — which arrives as
3338    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3339    /// the range allowed.
3340    pub fn retire_spool(
3341        &self,
3342        spool_id: &str,
3343        reason: &str,
3344    ) -> Result<SpoolCommandAnswer, ClientError> {
3345        crate::reason_given(reason)?;
3346        let target = format!("/v1/spools/{spool_id}/actions/retire_spool");
3347        let asked: Vec<(String, String)> = Vec::new();
3348        let mut sending = serde_json::Map::new();
3349        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3350        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3351        let sending = Some(serde_json::Value::Object(sending));
3352        self.call("POST", &target, &asked, sending.as_ref())
3353    }
3354}