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:
1 parent
7097fcd97c
commit
d99a697739
116 files changed
+11239
-801
No files matched your search
@@ -0,0 +1,19 @@
|
||||
-- T4 migration 1/2: new enum values only. PostgreSQL cannot use a value in
|
||||
-- the transaction that adds it, so everything that uses them (the Suggestion
|
||||
-- table, rule rows) comes in later migrations.
|
||||
-- AlterEnum
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'INSPECTION_INTERVAL';
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'QUEEN_CELLS_SWARM_CHECK';
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'QUEEN_ACCEPTANCE_CHECK';
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'SPLIT_SCHEDULE';
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'COLONY_ENDED_CLEANUP';
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'WITHDRAWAL_ENDED';
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'HONEY_WATER_CONTENT';
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'SEASON_AUTOPILOT';
|
||||
ALTER TYPE "AutomationTriggerType" ADD VALUE 'PHENOLOGY';
|
||||
|
||||
-- AlterEnum
|
||||
ALTER TYPE "BatchMovementType" ADD VALUE 'BOTTLING';
|
||||
|
||||
-- AlterEnum
|
||||
ALTER TYPE "CalendarSyncSourceType" ADD VALUE 'WITHDRAWAL';
|
||||
@@ -0,0 +1,71 @@
|
||||
-- T4 migration 2/2: suggestions (rule, chain and autopilot proposals the
|
||||
-- beekeeper decides on). dedupeKey is nullable and unique: Postgres allows many
|
||||
-- NULLs, so EXPIRED/RESOLVED rows release their key.
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SuggestionStatus" AS ENUM ('OPEN', 'ACCEPTED', 'DISMISSED', 'RESOLVED', 'EXPIRED', 'OBSOLETE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SuggestionMode" AS ENUM ('PLAN', 'DO');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SuggestionSource" AS ENUM ('RULE', 'CHAIN', 'AUTOPILOT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SuggestionIntent" AS ENUM ('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');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Task" ADD COLUMN "sourceSuggestionId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Suggestion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"ruleKey" "AutomationTriggerType" NOT NULL,
|
||||
"source" "SuggestionSource" NOT NULL,
|
||||
"mode" "SuggestionMode" NOT NULL,
|
||||
"intent" "SuggestionIntent" NOT NULL,
|
||||
"status" "SuggestionStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"priority" "TaskPriority" NOT NULL DEFAULT 'NORMAL',
|
||||
"relatedType" "TaskRelatedType" NOT NULL,
|
||||
"relatedId" TEXT,
|
||||
"colonyId" TEXT,
|
||||
"reasonCode" TEXT NOT NULL,
|
||||
"evidence" JSONB NOT NULL,
|
||||
"requiredPermission" TEXT,
|
||||
"dueDate" TIMESTAMP(3),
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"plan" JSONB,
|
||||
"dedupeKey" TEXT,
|
||||
"sourceEventId" TEXT,
|
||||
"decidedAt" TIMESTAMP(3),
|
||||
"decidedByUserId" TEXT,
|
||||
"decisionNote" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Suggestion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Suggestion_dedupeKey_key" ON "Suggestion"("dedupeKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Suggestion_status_colonyId_idx" ON "Suggestion"("status", "colonyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Suggestion_status_dueDate_idx" ON "Suggestion"("status", "dueDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Suggestion_ruleKey_relatedType_relatedId_status_idx" ON "Suggestion"("ruleKey", "relatedType", "relatedId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Task_sourceSuggestionId_idx" ON "Task"("sourceSuggestionId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Task" ADD CONSTRAINT "Task_sourceSuggestionId_fkey" FOREIGN KEY ("sourceSuggestionId") REFERENCES "Suggestion"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Suggestion" ADD CONSTRAINT "Suggestion_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Suggestion" ADD CONSTRAINT "Suggestion_decidedByUserId_fkey" FOREIGN KEY ("decidedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
-- T4 migration 3/n: one vocabulary for the feed condition. Inspection.feedStatus
|
||||
-- was free text; it becomes ColonyCondition, like the colony card. Known words
|
||||
-- (ausreichend, niedrig, kritisch; trimmed of spaces, tabs and line breaks like
|
||||
-- JS trim, any case) map to the enum, any other text moves to the end of
|
||||
-- "notes" as 'Futter (alt): <text>'. Nothing is lost.
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Inspection" ADD COLUMN "feedCondition" "ColonyCondition";
|
||||
|
||||
-- Known words map to the enum.
|
||||
UPDATE "Inspection"
|
||||
SET "feedCondition" = CASE lower(btrim("feedStatus", E' \t\r\n'))
|
||||
WHEN 'ausreichend' THEN 'GOOD'::"ColonyCondition"
|
||||
WHEN 'niedrig' THEN 'WEAK'::"ColonyCondition"
|
||||
WHEN 'kritisch' THEN 'CRITICAL'::"ColonyCondition"
|
||||
END
|
||||
WHERE "feedStatus" IS NOT NULL;
|
||||
|
||||
-- Every other non-empty text goes to the notes.
|
||||
DO $$
|
||||
DECLARE
|
||||
moved integer;
|
||||
BEGIN
|
||||
UPDATE "Inspection"
|
||||
SET "notes" = concat_ws(E'\n', NULLIF("notes", ''), 'Futter (alt): ' || btrim("feedStatus", E' \t\r\n'))
|
||||
WHERE "feedCondition" IS NULL
|
||||
AND btrim("feedStatus", E' \t\r\n') <> '';
|
||||
GET DIAGNOSTICS moved = ROW_COUNT;
|
||||
RAISE NOTICE 't4_feed_vocabulary: % free-text feed values moved into notes', moved;
|
||||
END $$;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Inspection" DROP COLUMN "feedStatus";
|
||||
ALTER TABLE "Inspection" RENAME COLUMN "feedCondition" TO "feedStatus";
|
||||
@@ -0,0 +1,8 @@
|
||||
-- T4 migration 5/n: where the queen of a split colony comes from. Optional:
|
||||
-- an observation is never filled in for old rows.
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SplitQueenSource" AS ENUM ('RAISE_OWN', 'QUEEN_CELL', 'QUEEN');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "ColonyLineage" ADD COLUMN "queenSource" "SplitQueenSource";
|
||||
@@ -0,0 +1,5 @@
|
||||
-- T4 migration 7/n (part sampleBees): number of bees in a powdered-sugar or
|
||||
-- washing sample, so that the infestation percentage can be computed.
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "VarroaMeasurement" ADD COLUMN "sampleBees" INTEGER;
|
||||
Reference in new issue
Block a user