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

+118 -9
View File
@@ -115,6 +115,7 @@ model User {
stockMovements StockMovement[]
assignedTasks Task[] @relation("TaskAssignee")
createdTasks Task[] @relation("TaskCreator")
decidedSuggestions Suggestion[] @relation("SuggestionDecider")
assignedTaskTemplates TaskTemplate[] @relation("TaskTemplateAssignee")
createdTaskTemplates TaskTemplate[] @relation("TaskTemplateCreator")
colonyMoves ColonyApiaryHistory[]
@@ -312,6 +313,7 @@ model Colony {
feedings Feeding[]
harvests HarvestColony[]
queenEvaluations QueenEvaluation[]
suggestions Suggestion[]
@@index([apiaryId])
@@index([status])
@@ -340,16 +342,23 @@ enum LineageType {
// Ableger bilden (SPLIT: from=Muttervolk, to=Ableger) und Völker vereinigen
// (MERGE: mehrere from-Zeilen auf denselben to=Zielvolk) – Abschnitt 4.4.
enum SplitQueenSource {
RAISE_OWN
QUEEN_CELL
QUEEN
}
model ColonyLineage {
id String @id @default(cuid())
id String @id @default(cuid())
type LineageType
fromColonyId String
fromColony Colony @relation("LineageFrom", fields: [fromColonyId], references: [id])
fromColony Colony @relation("LineageFrom", fields: [fromColonyId], references: [id])
toColonyId String
toColony Colony @relation("LineageTo", fields: [toColonyId], references: [id])
toColony Colony @relation("LineageTo", fields: [toColonyId], references: [id])
date DateTime
queenSource SplitQueenSource?
notes String?
createdAt DateTime @default(now())
createdAt DateTime @default(now())
@@index([fromColonyId])
@@index([toColonyId])
@@ -447,11 +456,11 @@ model QueenColonyHistory {
// ---------------------------------------------------------------------------
model Inspection {
id String @id @default(cuid())
id String @id @default(cuid())
colonyId String
colony Colony @relation(fields: [colonyId], references: [id])
colony Colony @relation(fields: [colonyId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
user User @relation(fields: [userId], references: [id])
date DateTime
weather String?
temperature Float?
@@ -467,13 +476,13 @@ model Inspection {
swarmMood Int?
gentleness Int?
combBehavior Int?
feedStatus String?
feedStatus ColonyCondition?
honeySuperStatus String?
healthIssues String?
varroaIssues String?
measures String?
notes String?
createdAt DateTime @default(now())
createdAt DateTime @default(now())
tasks Task[]
@@ -529,12 +538,99 @@ model Task {
// rule see that it already left an open task for the same object instead
// of piling up a duplicate on every further trigger.
sourceAutomationTrigger AutomationTriggerType?
// Set when the task was created by accepting a suggestion.
sourceSuggestionId String?
sourceSuggestion Suggestion? @relation(fields: [sourceSuggestionId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
completedAt DateTime?
@@index([status, dueDate])
@@index([relatedType, relatedId])
@@index([sourceAutomationTrigger, relatedType, relatedId, status])
@@index([sourceSuggestionId])
}
// ---------------------------------------------------------------------------
// Vorschlaege (T4): Regeln, Ketten und der Saison-Autopilot schlagen vor, der
// Imker entscheidet. Uebernehmen legt eine Aufgabe an (Task.sourceSuggestionId).
// ---------------------------------------------------------------------------
enum SuggestionStatus {
OPEN
ACCEPTED
DISMISSED
RESOLVED
EXPIRED
OBSOLETE
}
enum SuggestionMode {
PLAN
DO
}
enum SuggestionSource {
RULE
CHAIN
AUTOPILOT
}
enum SuggestionIntent {
INSPECT
SWARM_CHECK
QUEEN_CHECK
QUEEN_ACCEPTANCE
QUEEN_REPLACE
SPLIT_PLAN
VARROA_MEASURE
VARROA_RECHECK
VARROA_TREAT
SUMMER_TREATMENT
FEED
HONEY_SUPER
WINTER_CHECK
HEALTH_CHECK
FOLLOW_UP
STOCK_REORDER
CLEANUP
}
model Suggestion {
id String @id @default(cuid())
ruleKey AutomationTriggerType
source SuggestionSource
mode SuggestionMode
intent SuggestionIntent
status SuggestionStatus @default(OPEN)
priority TaskPriority @default(NORMAL)
relatedType TaskRelatedType
relatedId String?
colonyId String?
colony Colony? @relation(fields: [colonyId], references: [id])
reasonCode String
// Only IDs, numbers and YYYY-MM-DD.
evidence Json
// e.g. 'varroa:read'; without that permission the suggestion is invisible.
requiredPermission String?
dueDate DateTime?
expiresAt DateTime?
// PLAN with several dates: [{ step, offsetDays, windowDays? }]
plan Json?
// '<ruleKey>:<relatedId|none>:<anchor>'; EXPIRED/RESOLVED clear it.
dedupeKey String? @unique
sourceEventId String?
decidedAt DateTime?
decidedByUserId String?
decidedByUser User? @relation("SuggestionDecider", fields: [decidedByUserId], references: [id])
decisionNote String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tasks Task[]
@@index([status, colonyId])
@@index([status, dueDate])
@@index([ruleKey, relatedType, relatedId, status])
}
enum TaskTemplateRecurrenceType {
@@ -589,6 +685,7 @@ model VarroaMeasurement {
method VarroaMethod
durationMinutes Int?
miteCount Int?
sampleBees Int?
calculatedValue Float?
assessment String?
notes String?
@@ -1172,6 +1269,8 @@ enum BatchMovementType {
// Rohstoff in einer Herstellungscharge verbraucht (minus) bzw. beim
// Loeschen einer irrtuemlich angelegten Charge zurueckgegeben (plus).
CONSUMPTION
// Abfuellung entnimmt Honig aus der Charge (Task 23: Bewegungsbuch)
BOTTLING
}
enum WithdrawalReason {
@@ -1358,6 +1457,15 @@ enum AutomationTriggerType {
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
}
model AutomationRule {
@@ -1446,6 +1554,7 @@ enum CalendarSyncSourceType {
FEEDING
HARVEST
QUEEN_ASSIGNMENT
WITHDRAWAL
}
enum CalendarSyncStatus {