feat: T4 waves 1-2 - rule configs, schema, suggestions core, season, rule forms

- Rule configs with typed parsers and registry; Berlin day helpers
- Schema: T4 enums, Suggestion table, feed vocabulary (feedStatus as
  ColonyCondition with legacy text mapping), split queen source,
  varroa sample size; migration harness for data migrations
- SuggestionsService: propose/refresh/resolve/obsolete/expire,
  accept/dismiss/reopen incl. bulk, lock order, concurrency e2e
- SeasonService: phenology-based segments, inspection/varroa due dates,
  queen age; TimeEventsStep with JobPartialFailure
- Frontend: per-rule settings forms with visible value sources

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-14 13:42:32 +02:00
1 parent 7097fcd97c
commit d99a697739
116 files changed
+11239 -801

No files matched your search

@@ -0,0 +1,164 @@
import { describe, expect, it } from "vitest";
import { ApiError } from "@/lib/auth/api-client";
import { AUTOMATION_TRIGGER_TYPES } from "./types";
import { RULE_FORM_DEFAULTS, RULE_LABELS, RULE_VALUE_SOURCES, issuesByField } from "./rule-configs";
describe("RULE_LABELS", () => {
it("every trigger has a label", () => {
for (const trigger of AUTOMATION_TRIGGER_TYPES) {
expect(RULE_LABELS[trigger], trigger).toBeTruthy();
}
expect(RULE_LABELS.SEASON_AUTOPILOT).toBe("Saison-Autopilot");
expect(RULE_LABELS.PHENOLOGY).toBe("Pflanzenentwicklung");
});
});
describe("RULE_VALUE_SOURCES", () => {
const sources = RULE_VALUE_SOURCES;
it("follows the spec tables for the varroa and water values", () => {
expect(sources.VARROA_THRESHOLD?.["naturalFall.redAbove"]).toMatchObject({ kind: "SOURCE" });
expect(sources.VARROA_THRESHOLD?.["naturalFall.redAbove"].label).toContain("LWG Veitshöchheim");
expect(sources.VARROA_THRESHOLD?.youngColonyFactor).toEqual({
label: "Erfahrungswert",
kind: "SOURCE",
});
expect(sources.HONEY_WATER_CONTENT?.warnAbovePercent.label).toContain("D.I.B.");
expect(sources.HONEY_WATER_CONTENT?.confirmAbovePercent.label).toContain("HonigV Anlage 2");
});
it("names Erfahrungswert for the inspection intervals and the acceptance check", () => {
const intervals = sources.INSPECTION_INTERVAL!;
expect(intervals["intervalDays.FRUEHJAHR"].label).toBe("Erfahrungswert");
expect(intervals["intervalDays.HERBST"].label).toBe("Erfahrungswert");
expect(intervals["intervalDays.SCHWARMZEIT"].label).toBe(
"Bieneninstitute (LWG, LLH Kirchhain)",
);
expect(sources.QUEEN_ACCEPTANCE_CHECK?.dueInDays.label).toBe("Erfahrungswert");
});
it("gives every split step a source from its variant", () => {
const steps = sources.SPLIT_SCHEDULE!;
expect(steps["RAISE_OWN[0].offsetDays"].label).toBe("Erfahrungswert");
expect(steps["RAISE_OWN[1].windowDays"].label).toBe("Erfahrungswert");
expect(steps["QUEEN_CELL[0].offsetDays"].kind).toBe("ASSUMPTION");
expect(steps["QUEEN[0].offsetDays"].label).toBe("wie Königin zugesetzt");
});
it("marks the autopilot window and phenology guesses as assumptions", () => {
const autopilot = sources.SEASON_AUTOPILOT!;
expect(autopilot["winterVarroaWindow.from"]).toEqual({
label: "Annahme (LWG-Konzept)",
kind: "ASSUMPTION",
});
expect(autopilot.varroaMeasureIntervalDays.label).toBe("Erfahrungswert (28 Tage)");
expect(autopilot["rules.A9"].label).toBe("Annahme");
expect(autopilot["rules.A10"].label).toBe("Annahme");
for (let n = 1; n <= 10; n += 1) expect(autopilot[`rules.A${n}`], `A${n}`).toBeDefined();
const phenology = sources.PHENOLOGY!;
expect(phenology["swarmSeason.from"].label).toBe("Annahme nach Bieneninstitut-Praxis");
expect(phenology["gtsThresholds.SALWEIDE"].label).toBe(
"DWD; Gleichsetzung mit Salweidenblüte = Annahme",
);
});
});
// Pinned to the table "Regeln" of the global constraints, so a changed default is a decision.
describe("RULE_FORM_DEFAULTS", () => {
it("matches the built-in settings of the backend", () => {
expect(RULE_FORM_DEFAULTS).toEqual({
VARROA_THRESHOLD: {
naturalFall: {
seasonMonths: [3, 4, 5, 6, 7, 8, 9, 10],
yellowFrom: 5,
redAbove: 10,
winterRedAbove: 0.5,
},
infestationPercent: {
summerMonths: [5, 6, 7],
summer: { yellowFrom: 1, redAbove: 5 },
autumnMonths: [8, 9, 10],
autumn: { yellowFrom: 2, redAbove: 5 },
},
youngColonyFactor: 0.5,
youngColonyTypes: ["YOUNG_COLONY", "NUCLEUS_COLONY", "MATING_NUCLEUS"],
recheckAfterDays: 14,
notify: true,
},
INSPECTION_INTERVAL: {
intervalDays: { FRUEHJAHR: 14, SCHWARMZEIT: 7, SOMMER: 14, SPAETSOMMER: 21, HERBST: 21 },
},
QUEEN_CELLS_SWARM_CHECK: { dueInDays: 7, notify: false },
QUEEN_ACCEPTANCE_CHECK: { dueInDays: 7, notify: false },
SPLIT_SCHEDULE: {
RAISE_OWN: [
{ step: "RAISE_CELLS_CHECK", offsetDays: 9 },
{ step: "MATING_CHECK", offsetDays: 21, windowDays: 3 },
{ step: "EGGS_CHECK", offsetDays: 28 },
],
QUEEN_CELL: [{ step: "EGGS_CHECK", offsetDays: 21 }],
QUEEN: [{ step: "ACCEPTANCE_CHECK", offsetDays: 7 }],
notify: false,
},
WITHDRAWAL_ENDED: { notify: true },
HONEY_WATER_CONTENT: { warnAbovePercent: 18, confirmAbovePercent: 20, byHoneyType: {} },
SEASON_AUTOPILOT: {
honeySuperMinFrames: 8,
varroaMeasureIntervalDays: 28,
winterVarroaWindow: { from: "11-15", to: "12-20" },
queenMaxAgeYears: 2,
suggestionTtlDays: 30,
maxStepsPerColony: 3,
rules: {
A1: true, A2: true, A3: true, A4: true, A5: true,
A6: true, A7: true, A8: true, A9: true, A10: true,
},
},
PHENOLOGY: {
useGts: true,
gtsThresholds: { SALWEIDE: 200 },
calibrateFromReports: true,
calibratePhases: ["KIRSCHE", "APFEL"],
calendarFallback: {
SALWEIDE: "03-20", KIRSCHE: "04-15", APFEL: "04-28", HOLUNDER: "05-30",
LINDE: "06-20", TRACHTENDE: "08-01", HERBST: "09-15", WINTER: "11-15",
},
swarmSeason: { from: "KIRSCHE", until: "LINDE" },
maxGapShare: 0.1,
},
});
});
});
describe("issuesByField", () => {
it("maps the issues of a 400 to their fields", () => {
const error = new ApiError(400, "Die Einstellung ist ungültig.", {
message: "Die Einstellung ist ungültig.",
issues: [
{ field: "naturalFall.redAbove", message: "Zu hoch." },
{ field: "rules.A1", message: "Ungültig." },
],
});
expect(issuesByField(error)).toEqual({
"naturalFall.redAbove": "Zu hoch.",
"rules.A1": "Ungültig.",
});
});
it("keeps the first message of a field", () => {
const error = new ApiError(400, "x", {
issues: [
{ field: "a", message: "erste" },
{ field: "a", message: "zweite" },
],
});
expect(issuesByField(error)).toEqual({ a: "erste" });
});
it("returns nothing for other errors and malformed bodies", () => {
expect(issuesByField(new Error("x"))).toEqual({});
expect(issuesByField(new ApiError(500, "x", { issues: "nope" }))).toEqual({});
expect(issuesByField(new ApiError(400, "x", { issues: [{ field: 3 }, null] }))).toEqual({});
expect(issuesByField(undefined)).toEqual({});
});
});
@@ -0,0 +1,309 @@
import { AutomationTriggerType } from "./types";
export const RULE_LABELS: Record<AutomationTriggerType, string> = {
QUEENLESS_NO_EGGS: "Keine Königin gesehen und keine Stifte",
VARROA_THRESHOLD: "Varroa-Schwellenwert überschritten",
FEED_STATUS_CRITICAL: "Futterstatus kritisch",
SWARM_MOOD_HIGH: "Hohe Schwarmstimmung",
COLONY_HEALTH_CRITICAL: "Gesundheitsstatus kritisch",
STOCK_ITEM_LOW: "Lagerartikel unter Mindestbestand",
HONEY_BATCH_NUMBER_FORMAT: "Chargennummer-Format (Honigchargen)",
BOTTLING_LOT_NUMBER_FORMAT: "Los-/Chargennummer-Format (Abfüllungen)",
TREATMENT_FOLLOW_UP: "Kontrolle nach Behandlung",
FEEDING_FOLLOW_UP: "Kontrolle nach Fütterung",
RAW_MATERIAL_LOT_NUMBER_FORMAT: "Chargennummer-Format (Rohstoffe)",
PRODUCTION_BATCH_NUMBER_FORMAT: "Chargennummer-Format (Herstellung)",
RAW_MATERIAL_LOW: "Rohstoff unter Mindestmenge",
INSPECTION_INTERVAL: "Durchsicht-Rhythmus",
QUEEN_CELLS_SWARM_CHECK: "Weiselzellen: Schwarmkontrolle",
QUEEN_ACCEPTANCE_CHECK: "Königin zugesetzt: Annahme prüfen",
SPLIT_SCHEDULE: "Ableger-Fahrplan",
COLONY_ENDED_CLEANUP: "Volk beendet: Aufräumen",
WITHDRAWAL_ENDED: "Wartezeit zu Ende",
HONEY_WATER_CONTENT: "Wassergehalt Honig",
SEASON_AUTOPILOT: "Saison-Autopilot",
PHENOLOGY: "Pflanzenentwicklung",
};
export const RULE_DESCRIPTIONS: Record<AutomationTriggerType, string> = {
QUEENLESS_NO_EGGS:
"Bei einer Durchsicht: Königin nicht gesehen und keine Stifte vorhanden, dann Kontrollaufgabe.",
VARROA_THRESHOLD:
"Ampel und Vorschlag „Behandlung prüfen“ nach Messmethode, Jahreszeit und Volkstyp.",
FEED_STATUS_CRITICAL:
"Futterstatus bei einer Durchsicht auf kritisch gesetzt, dann Fütterungsaufgabe.",
SWARM_MOOD_HIGH:
"Bei einer Schwarmkontrolle Schwarmstimmung hoch erfasst, dann Kontrollaufgabe.",
COLONY_HEALTH_CRITICAL:
"Gesundheitsstatus eines Volkes wechselt auf kritisch, dann Kontrollaufgabe.",
STOCK_ITEM_LOW:
"Bestand eines Lagerartikels fällt nach einer Buchung unter den Mindestbestand, dann Nachbestellaufgabe.",
HONEY_BATCH_NUMBER_FORMAT:
"Wenn aktiv, wird die Chargennummer neuer Honigchargen automatisch nach diesem Format erzeugt, statt sie manuell einzugeben.",
BOTTLING_LOT_NUMBER_FORMAT:
"Wenn aktiv, wird die Los-/Chargennummer neuer Abfüllungen automatisch nach diesem Format erzeugt, statt sie manuell einzugeben.",
TREATMENT_FOLLOW_UP:
"Nach jeder erfassten Behandlung eine Kontrollaufgabe vorschlagen, fällig die eingestellten Tage nach Behandlungsende. Die passende Frist hängt vom Mittel ab (Packungsbeilage) – deshalb ab Werk aus und ohne voreingestellte Frist.",
FEEDING_FOLLOW_UP:
"Nach jeder erfassten Fütterung eine Kontrollaufgabe vorschlagen, fällig die eingestellten Tage nach der Fütterung. Ab Werk aus und ohne voreingestellte Frist.",
RAW_MATERIAL_LOT_NUMBER_FORMAT:
"Wenn aktiv, wird die Chargennummer neuer Rohstoff-Chargen (Wachs, Propolis) automatisch nach diesem Format erzeugt, statt sie manuell einzugeben.",
PRODUCTION_BATCH_NUMBER_FORMAT:
"Wenn aktiv, wird die Chargennummer neuer Herstellungschargen (Kerzen, Tinkturen, …) automatisch nach diesem Format erzeugt, statt sie manuell einzugeben.",
RAW_MATERIAL_LOW:
"Fällt der Gesamtbestand einer Rohstoff-Art nach Verbrauch, Entnahme oder Inventur unter die Mindestmenge, dann Aufgabe zum Nachschub. Leere Mindestmenge = diese Art wird nicht überwacht.",
INSPECTION_INTERVAL:
"Nach wie vielen Tagen eine Durchsicht fällig ist. Der Wert hängt vom Abschnitt der Saison ab.",
QUEEN_CELLS_SWARM_CHECK:
"Weiselzellen gesehen: Schwarmkontrolle vorschlagen, fällig nach den eingestellten Tagen.",
QUEEN_ACCEPTANCE_CHECK:
"Königin zugesetzt: Prüfung der Annahme vorschlagen, fällig nach den eingestellten Tagen.",
SPLIT_SCHEDULE:
"Nach dem Ableger die Kontrollen vorschlagen. Die Tage zählen ab dem Tag der Bildung.",
COLONY_ENDED_CLEANUP:
"Wird ein Volk beendet, schlägt die App vor, offene Aufgaben und Vorschläge dieses Volkes aufzuräumen. Es gibt nichts einzustellen.",
WITHDRAWAL_ENDED:
"Die Wartezeit einer Behandlung ist vorbei: Ernte ist wieder erlaubt.",
HONEY_WATER_CONTENT:
"Ab welchem Wassergehalt die App beim Honig warnt und ab wann beim Abfüllen eine Bestätigung nötig ist.",
SEASON_AUTOPILOT:
"Die App schlägt je Volk die nächsten Schritte der Saison vor. Jede Regel A1 bis A10 lässt sich einzeln ausschalten.",
PHENOLOGY:
"Wie die App die Saison erkennt: aus deiner Meldung, aus der Wärmesumme (GTS) oder nach dem Kalender.",
};
export interface ValueSource {
label: string;
kind: "SOURCE" | "ASSUMPTION";
}
/** Built-in settings the forms start from when the stored config is partial. */
export const RULE_FORM_DEFAULTS = {
VARROA_THRESHOLD: {
naturalFall: {
seasonMonths: [3, 4, 5, 6, 7, 8, 9, 10],
yellowFrom: 5,
redAbove: 10,
winterRedAbove: 0.5,
},
infestationPercent: {
summerMonths: [5, 6, 7],
summer: { yellowFrom: 1, redAbove: 5 },
autumnMonths: [8, 9, 10],
autumn: { yellowFrom: 2, redAbove: 5 },
},
youngColonyFactor: 0.5,
youngColonyTypes: ["YOUNG_COLONY", "NUCLEUS_COLONY", "MATING_NUCLEUS"],
recheckAfterDays: 14,
notify: true,
},
INSPECTION_INTERVAL: {
intervalDays: {
FRUEHJAHR: 14,
SCHWARMZEIT: 7,
SOMMER: 14,
SPAETSOMMER: 21,
HERBST: 21,
},
},
QUEEN_CELLS_SWARM_CHECK: { dueInDays: 7, notify: false },
QUEEN_ACCEPTANCE_CHECK: { dueInDays: 7, notify: false },
SPLIT_SCHEDULE: {
RAISE_OWN: [
{ step: "RAISE_CELLS_CHECK", offsetDays: 9 },
{ step: "MATING_CHECK", offsetDays: 21, windowDays: 3 },
{ step: "EGGS_CHECK", offsetDays: 28 },
],
QUEEN_CELL: [{ step: "EGGS_CHECK", offsetDays: 21 }],
QUEEN: [{ step: "ACCEPTANCE_CHECK", offsetDays: 7 }],
notify: false,
},
WITHDRAWAL_ENDED: { notify: true },
HONEY_WATER_CONTENT: {
warnAbovePercent: 18,
confirmAbovePercent: 20,
byHoneyType: {},
},
SEASON_AUTOPILOT: {
honeySuperMinFrames: 8,
varroaMeasureIntervalDays: 28,
winterVarroaWindow: { from: "11-15", to: "12-20" },
queenMaxAgeYears: 2,
suggestionTtlDays: 30,
maxStepsPerColony: 3,
rules: {
A1: true,
A2: true,
A3: true,
A4: true,
A5: true,
A6: true,
A7: true,
A8: true,
A9: true,
A10: true,
},
},
PHENOLOGY: {
useGts: true,
gtsThresholds: { SALWEIDE: 200 },
calibrateFromReports: true,
calibratePhases: ["KIRSCHE", "APFEL"],
calendarFallback: {
SALWEIDE: "03-20",
KIRSCHE: "04-15",
APFEL: "04-28",
HOLUNDER: "05-30",
LINDE: "06-20",
TRACHTENDE: "08-01",
HERBST: "09-15",
WINTER: "11-15",
},
swarmSeason: { from: "KIRSCHE", until: "LINDE" },
maxGapShare: 0.1,
},
} as const satisfies Partial<Record<AutomationTriggerType, object>>;
// Spec rows citing "Fahrplan §10" without an institute show "Erfahrungswert".
const FAHRPLAN: ValueSource = { label: "Erfahrungswert", kind: "SOURCE" };
const ASSUMPTION: ValueSource = { label: "Annahme", kind: "ASSUMPTION" };
function source(label: string): ValueSource {
return { label, kind: "SOURCE" };
}
function assumption(label: string): ValueSource {
return { label, kind: "ASSUMPTION" };
}
const LWG_NATURAL_FALL = source(
"LWG Veitshöchheim; Gültigkeit ab März = Annahme", // table from Fahrplan §10
);
const LWG_WINTER = source(
"LWG Veitshöchheim („ab November Behandlung schon über 0,5“); Ende Februar = Annahme",
);
const LWG_SUMMER = source("LWG Veitshöchheim („Juli“); Mai und Juni = Annahme");
const LWG_AUTUMN = source("LWG Veitshöchheim („September“); August und Oktober = Annahme");
const SWARM_CHECK = source(
"Bieneninstitute (LWG, LLH Kirchhain): Verdeckelung nach 8–9 Tagen",
);
/** Plan steps per split variant: offsets and windows with their source (spec 7.4). */
function splitScheduleSources(): Record<string, ValueSource> {
const variantSource: Record<string, ValueSource> = {
RAISE_OWN: FAHRPLAN,
QUEEN_CELL: ASSUMPTION,
QUEEN: source("wie Königin zugesetzt"),
};
const result: Record<string, ValueSource> = {};
const defaults = RULE_FORM_DEFAULTS.SPLIT_SCHEDULE as unknown as Record<
string,
{ offsetDays: number; windowDays?: number }[]
>;
for (const [variant, steps] of Object.entries(defaults)) {
if (!Array.isArray(steps)) continue;
steps.forEach((step, index) => {
result[`${variant}[${index}].offsetDays`] = variantSource[variant];
if (step.windowDays !== undefined) {
result[`${variant}[${index}].windowDays`] = variantSource[variant];
}
});
}
return result;
}
/** Where the built-in values come from; paths follow the config keys. */
export const RULE_VALUE_SOURCES: Partial<
Record<AutomationTriggerType, Record<string, ValueSource>>
> = {
VARROA_THRESHOLD: {
"naturalFall.yellowFrom": LWG_NATURAL_FALL,
"naturalFall.redAbove": LWG_NATURAL_FALL,
"naturalFall.winterRedAbove": LWG_WINTER,
"infestationPercent.summer.yellowFrom": LWG_SUMMER,
"infestationPercent.summer.redAbove": LWG_SUMMER,
"infestationPercent.autumn.yellowFrom": LWG_AUTUMN,
"infestationPercent.autumn.redAbove": LWG_AUTUMN,
youngColonyFactor: FAHRPLAN,
recheckAfterDays: FAHRPLAN,
},
INSPECTION_INTERVAL: {
"intervalDays.FRUEHJAHR": FAHRPLAN,
"intervalDays.SCHWARMZEIT": source(
"Bieneninstitute (LWG, LLH Kirchhain)",
),
"intervalDays.SOMMER": FAHRPLAN,
"intervalDays.SPAETSOMMER": FAHRPLAN,
"intervalDays.HERBST": FAHRPLAN,
},
QUEEN_CELLS_SWARM_CHECK: { dueInDays: SWARM_CHECK },
QUEEN_ACCEPTANCE_CHECK: { dueInDays: FAHRPLAN },
SPLIT_SCHEDULE: splitScheduleSources(),
HONEY_WATER_CONTENT: {
warnAbovePercent: source("D.I.B., Warenzeichen „Echter Deutscher Honig“"),
confirmAbovePercent: source("HonigV Anlage 2 (RL 2001/110/EG Anhang II)"),
},
SEASON_AUTOPILOT: {
honeySuperMinFrames: assumption("Annahme (Imkerpraxis, 8 Waben)"),
varroaMeasureIntervalDays: source("Erfahrungswert (28 Tage)"),
"winterVarroaWindow.from": assumption("Annahme (LWG-Konzept)"),
"winterVarroaWindow.to": assumption("Annahme (LWG-Konzept)"),
queenMaxAgeYears: assumption("Annahme (Imkerpraxis, 2 Jahre)"),
suggestionTtlDays: ASSUMPTION,
maxStepsPerColony: ASSUMPTION,
"rules.A1": source("Fahrplan, Durchsicht-Rhythmus"), // spec 7.2
"rules.A2": source("Erfahrungswert (28 Tage); Winterfenster = Annahme (LWG-Konzept)"),
"rules.A3": source("LWG Veitshöchheim"),
"rules.A4": source("LWG-Varroakonzept: Behandlung nach der letzten Ernte"),
"rules.A5": source("Imkerpraxis; bewusst ohne Zielmenge"),
"rules.A6": source("Erfahrungswert"), // Lastenheft §23
"rules.A7": source("Imkerpraxis; 8 Waben = Annahme"),
"rules.A8": source("Imkerpraxis; 2 Jahre = Annahme"),
"rules.A9": assumption("Annahme"), // Annahme 16
"rules.A10": assumption("Annahme"), // Annahme 16
},
PHENOLOGY: {
"gtsThresholds.SALWEIDE": source(
"DWD; Gleichsetzung mit Salweidenblüte = Annahme",
),
"calendarFallback.SALWEIDE": ASSUMPTION,
"calendarFallback.KIRSCHE": ASSUMPTION,
"calendarFallback.APFEL": ASSUMPTION,
"calendarFallback.HOLUNDER": ASSUMPTION,
"calendarFallback.LINDE": ASSUMPTION,
"calendarFallback.TRACHTENDE": ASSUMPTION,
"calendarFallback.HERBST": ASSUMPTION,
"calendarFallback.WINTER": ASSUMPTION,
"swarmSeason.from": assumption("Annahme nach Bieneninstitut-Praxis"),
"swarmSeason.until": assumption("Annahme nach Bieneninstitut-Praxis"),
maxGapShare: ASSUMPTION,
},
};
/** The server's field messages of a rejected config (400 with `issues`). */
export function issuesByField(error: unknown): Record<string, string> {
const result: Record<string, string> = {};
const body = (error as { body?: unknown } | null | undefined)?.body;
const issues = (body as { issues?: unknown } | null | undefined)?.issues;
if (!Array.isArray(issues)) return result;
for (const issue of issues) {
if (typeof issue !== "object" || issue === null) continue;
const { field, message } = issue as { field?: unknown; message?: unknown };
if (typeof field !== "string" || typeof message !== "string") continue;
if (!(field in result)) result[field] = message;
}
return result;
}
/** Default of the notify switch for the older rules (`DEFAULT_RULES` of the backend). */
export const SIMPLE_RULE_NOTIFY_DEFAULTS: Partial<Record<AutomationTriggerType, boolean>> = {
QUEENLESS_NO_EGGS: true,
COLONY_HEALTH_CRITICAL: true,
FEED_STATUS_CRITICAL: false,
SWARM_MOOD_HIGH: false,
STOCK_ITEM_LOW: false,
RAW_MATERIAL_LOW: false,
TREATMENT_FOLLOW_UP: false,
FEEDING_FOLLOW_UP: false,
};
+26 -14
View File
@@ -1,17 +1,29 @@
export type AutomationTriggerType =
| "QUEENLESS_NO_EGGS"
| "VARROA_THRESHOLD"
| "FEED_STATUS_CRITICAL"
| "SWARM_MOOD_HIGH"
| "COLONY_HEALTH_CRITICAL"
| "STOCK_ITEM_LOW"
| "HONEY_BATCH_NUMBER_FORMAT"
| "BOTTLING_LOT_NUMBER_FORMAT"
| "TREATMENT_FOLLOW_UP"
| "FEEDING_FOLLOW_UP"
| "RAW_MATERIAL_LOT_NUMBER_FORMAT"
| "PRODUCTION_BATCH_NUMBER_FORMAT"
| "RAW_MATERIAL_LOW";
export const AUTOMATION_TRIGGER_TYPES = [
"QUEENLESS_NO_EGGS",
"VARROA_THRESHOLD",
"FEED_STATUS_CRITICAL",
"SWARM_MOOD_HIGH",
"COLONY_HEALTH_CRITICAL",
"STOCK_ITEM_LOW",
"HONEY_BATCH_NUMBER_FORMAT",
"BOTTLING_LOT_NUMBER_FORMAT",
"TREATMENT_FOLLOW_UP",
"FEEDING_FOLLOW_UP",
"RAW_MATERIAL_LOT_NUMBER_FORMAT",
"PRODUCTION_BATCH_NUMBER_FORMAT",
"RAW_MATERIAL_LOW",
"INSPECTION_INTERVAL",
"QUEEN_CELLS_SWARM_CHECK",
"QUEEN_ACCEPTANCE_CHECK",
"SPLIT_SCHEDULE",
"COLONY_ENDED_CLEANUP",
"WITHDRAWAL_ENDED",
"HONEY_WATER_CONTENT",
"SEASON_AUTOPILOT",
"PHENOLOGY",
] as const;
export type AutomationTriggerType = (typeof AUTOMATION_TRIGGER_TYPES)[number];
export interface AutomationRule {
id: string;