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,67 @@
|
||||
import { addDays } from '../common/dates';
|
||||
import type { SeasonAutopilotConfig } from '../automation/rule-config/rule-config.types';
|
||||
import type { SeasonState } from './season-types';
|
||||
|
||||
export interface VarroaDue {
|
||||
dueOn: string;
|
||||
kind: 'INTERVAL' | 'WINTER_WINDOW';
|
||||
/** Changes with every new measurement (or winter), so one due date stays one reminder. */
|
||||
anchor: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* Invariant: the winter window (SEASON_AUTOPILOT.winterVarroaWindow, default
|
||||
* 11-15) lies inside WINTERRUHE, i.e. it does not begin before the WINTER
|
||||
* date of PHENOLOGY.calendarFallback (default 11-15). Before WINTER the
|
||||
* interval rule applies instead.
|
||||
*/
|
||||
|
||||
/** A measurement from this day on counts for the winter window of the year. */
|
||||
const WINTER_MEASUREMENT_FROM = '11-01';
|
||||
|
||||
/**
|
||||
* The day a varroa measurement is due. Outside WINTERRUHE: the last
|
||||
* measurement (else the creation of the colony) plus the interval. In
|
||||
* WINTERRUHE only inside the winter window, once, if there was no
|
||||
* measurement since 11-01.
|
||||
*/
|
||||
export function varroaMeasurementDue(
|
||||
input: {
|
||||
createdOn: string;
|
||||
lastMeasuredOn: string | null;
|
||||
lastMeasurementId: string | null;
|
||||
},
|
||||
season: Pick<SeasonState, 'segment' | 'springSince'>,
|
||||
config: SeasonAutopilotConfig,
|
||||
today: string,
|
||||
): VarroaDue | null {
|
||||
if (season.segment !== 'WINTERRUHE') {
|
||||
const base = input.lastMeasuredOn ?? input.createdOn;
|
||||
const intervalDue = addDays(base, config.varroaMeasureIntervalDays);
|
||||
// A due day that fell into the winter is taken up at the start of
|
||||
// FRUEHJAHR, like the first inspection of the year.
|
||||
const dueOn =
|
||||
season.springSince !== null && intervalDue < season.springSince
|
||||
? season.springSince
|
||||
: intervalDue;
|
||||
return {
|
||||
dueOn,
|
||||
kind: 'INTERVAL',
|
||||
anchor: input.lastMeasurementId ?? 'none',
|
||||
};
|
||||
}
|
||||
const year = today.slice(0, 4);
|
||||
const { from, to } = config.winterVarroaWindow;
|
||||
const inWindow = today >= `${year}-${from}` && today <= `${year}-${to}`;
|
||||
const measuredThisWinter =
|
||||
input.lastMeasuredOn !== null &&
|
||||
input.lastMeasuredOn >= `${year}-${WINTER_MEASUREMENT_FROM}`;
|
||||
if (!inWindow || measuredThisWinter) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
dueOn: `${year}-${from}`,
|
||||
kind: 'WINTER_WINDOW',
|
||||
anchor: `winter-${year}`,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user