- 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]>
94 lines
2.4 KiB
TypeScript
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,
|
|
),
|
|
}),
|
|
);
|
|
}
|