- 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]>
109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
import type { EvidenceLookup } from '../evidence-lookup';
|
|
import { renderSuggestion } from '../suggestion-texts';
|
|
import type { SuggestionEvidence } from '../suggestion.types';
|
|
import { RULE_TEXTS } from './rule-texts';
|
|
|
|
const lookup: EvidenceLookup = {
|
|
colonyCode: (id) => (id === 'gone' ? null : 'LWG'),
|
|
queenCode: () => null,
|
|
treatmentProduct: (id) => (id === 'gone' ? null : 'Ameisensäure'),
|
|
apiaryName: () => null,
|
|
stockItemName: () => 'Gläser 500 g',
|
|
};
|
|
|
|
const TODAY = '2026-10-14';
|
|
|
|
function render(
|
|
reasonCode: string,
|
|
evidence: SuggestionEvidence,
|
|
intent: 'VARROA_TREAT' | 'FOLLOW_UP' | 'STOCK_REORDER' = 'FOLLOW_UP',
|
|
) {
|
|
return renderSuggestion(
|
|
{ reasonCode, evidence: evidence as never, intent },
|
|
lookup,
|
|
TODAY,
|
|
);
|
|
}
|
|
|
|
describe('rule texts', () => {
|
|
it('has a template for every reason code of the rule suggestions', () => {
|
|
expect(Object.keys(RULE_TEXTS).sort()).toEqual(
|
|
[
|
|
'COLONY_HEALTH_CRITICAL',
|
|
'FEEDING_FOLLOW_UP',
|
|
'FEED_STATUS_CRITICAL',
|
|
'QUEENLESS_NO_EGGS',
|
|
'RAW_MATERIAL_LOW',
|
|
'STOCK_ITEM_LOW',
|
|
'SWARM_MOOD_HIGH',
|
|
'TREATMENT_FOLLOW_UP',
|
|
'VARROA_RED',
|
|
].sort(),
|
|
);
|
|
});
|
|
|
|
it('VARROA_RED names rate, day and limit with the colony code', () => {
|
|
expect(render('VARROA_RED', RULE_TEXTS.VARROA_RED.example)).toEqual({
|
|
title: 'Behandlung prüfen',
|
|
reasons: ['12,4 Milben pro Tag am 10.05.', 'Grenze: 10 (LWG).'],
|
|
});
|
|
});
|
|
|
|
it('VARROA_RED words a percentage method in percent', () => {
|
|
const { reasons } = render('VARROA_RED', {
|
|
colonyId: 'c',
|
|
measuredOn: '2026-07-10',
|
|
value: 6,
|
|
redAbove: 5,
|
|
unit: 'PERCENT',
|
|
});
|
|
|
|
expect(reasons).toEqual(['6 % Befall am 10.07.', 'Grenze: 5 % (LWG).']);
|
|
});
|
|
|
|
it('QUEENLESS_NO_EGGS and the inspection rules name the day and the colony', () => {
|
|
expect(
|
|
render('QUEENLESS_NO_EGGS', RULE_TEXTS.QUEENLESS_NO_EGGS.example).reasons,
|
|
).toEqual(['Königin nicht gesehen, keine Eier am 05.05.']);
|
|
expect(
|
|
render('FEED_STATUS_CRITICAL', RULE_TEXTS.FEED_STATUS_CRITICAL.example)
|
|
.reasons,
|
|
).toEqual(['Futterstatus kritisch am 05.05.']);
|
|
});
|
|
|
|
it('STOCK_ITEM_LOW names the item and the minimum', () => {
|
|
expect(
|
|
render('STOCK_ITEM_LOW', RULE_TEXTS.STOCK_ITEM_LOW.example).reasons,
|
|
).toEqual(['Gläser 500 g: noch 2 auf Lager, Mindestbestand 5.']);
|
|
});
|
|
|
|
it('RAW_MATERIAL_LOW names the kind in German', () => {
|
|
expect(
|
|
render('RAW_MATERIAL_LOW', RULE_TEXTS.RAW_MATERIAL_LOW.example).reasons,
|
|
).toEqual(['Bienenwachs: noch 400 g auf Lager, Mindestmenge 1.000 g.']);
|
|
});
|
|
|
|
it('TREATMENT_FOLLOW_UP names the product, or none when the treatment is gone', () => {
|
|
expect(
|
|
render('TREATMENT_FOLLOW_UP', RULE_TEXTS.TREATMENT_FOLLOW_UP.example)
|
|
.reasons,
|
|
).toEqual(['Nach Behandlung mit Ameisensäure am 10.08.']);
|
|
expect(
|
|
render('TREATMENT_FOLLOW_UP', {
|
|
colonyId: 'gone',
|
|
treatmentId: 'gone',
|
|
endedOn: '2026-08-10',
|
|
}).reasons,
|
|
).toEqual(['Nach der Behandlung am 10.08.']);
|
|
});
|
|
|
|
it('survives evidence without its optional parts', () => {
|
|
for (const [reasonCode, template] of Object.entries(RULE_TEXTS)) {
|
|
const { title, reasons } = render(reasonCode, {});
|
|
expect(title).not.toBe('');
|
|
expect(reasons.length).toBeLessThanOrEqual(2);
|
|
expect(template.ruleKey).toBeTruthy();
|
|
}
|
|
});
|
|
});
|