Files
ApiCore/ApiCore-Backend/src/automation/rule-config/simple.config.ts
T
Nick SzaunigandClaude Opus 5.5 02eb16924e feat: T4 wave 4 - rules suggest, takeover, chains, autopilot A1-A3, suggestions page, varroa bands UI
- Automation rules propose suggestions instead of creating tasks;
  notify per rule config; corrected varroa measurements refresh or
  resolve their suggestion
- Inspection values can be taken over into the colony card
  (occupiedFrames 0-40, strength deprecated); warning counts use the
  same visibility rule as the notification list
- Biotech chains: swarm check after queen cells, queen acceptance
  check, split schedules (raise own, queen cell, queen)
- Season autopilot core with A1 inspection due, A2 varroa measurement,
  A3 recheck after treatment; colony sync and daily step
- Frontend: suggestions page with groups, bulk accept, undo, paging,
  focus management and permission gating
- Frontend: varroa traffic light from seasonal bands, sample size,
  one range request for the colony chart
- Notifications: mark-read answers 404 for rows the user may no
  longer see

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-14 15:22:36 +02:00

94 lines
2.4 KiB
TypeScript

import { RawMaterialKind } from '../../../generated/prisma';
import {
bool,
childScope,
has,
isCleared,
makeParser,
numberIn,
optionalIntIn,
stringIn,
} from './parse-helpers';
import type {
NumberFormatConfig,
RuleConfigParser,
SimpleRuleConfig,
} from './rule-config.types';
const MAX_DUE_IN_DAYS = 365;
/** Far above any minimum stock of wax or propolis. */
const MAX_MIN_GRAMS = 1_000_000_000;
const MAX_NUMBER_FORMAT_LENGTH = 100;
const RAW_MATERIAL_KINDS = Object.values(RawMaterialKind);
export interface SimpleRuleOptions {
/** Whether the rule notifies by default. */
notify: boolean;
/** RAW_MATERIAL_LOW reads a minimum per kind. */
withMinGrams?: boolean;
}
/**
* Rules that propose one suggestion: notify, an optional follow-up interval
* (dueInDays) and, for the raw material rule, minGrams per kind.
*/
export function simpleParser(
trigger: string,
options: SimpleRuleOptions,
): RuleConfigParser<SimpleRuleConfig> {
return makeParser<SimpleRuleConfig>(
trigger,
{ notify: options.notify },
(scope) => {
const config: SimpleRuleConfig = {
notify: bool(scope, 'notify', options.notify),
};
const dueInDays = optionalIntIn(scope, 'dueInDays', 0, MAX_DUE_IN_DAYS);
if (dueInDays !== undefined) {
config.dueInDays = dueInDays;
}
if (
options.withMinGrams &&
has(scope, 'minGrams') &&
!isCleared(scope, 'minGrams')
) {
const minScope = childScope(scope, 'minGrams');
const minGrams: Partial<Record<RawMaterialKind, number>> = {};
for (const kind of RAW_MATERIAL_KINDS) {
if (isCleared(minScope, kind)) {
continue;
}
const grams = numberIn(minScope, kind, Number.NaN, 0, MAX_MIN_GRAMS);
if (!Number.isNaN(grams)) {
minGrams[kind] = grams;
}
}
if (Object.keys(minGrams).length > 0) {
config.minGrams = minGrams;
}
}
return config;
},
);
}
/** The batch/lot number rules: one format string with placeholders. */
export function numberFormatParser(
trigger: string,
defaultFormat: string,
): RuleConfigParser<NumberFormatConfig> {
return makeParser<NumberFormatConfig>(
trigger,
{ format: defaultFormat },
(scope) => ({
format: stringIn(
scope,
'format',
defaultFormat,
1,
MAX_NUMBER_FORMAT_LENGTH,
),
}),
);
}