Files
ApiCore/ApiCore-Backend/src/suggestions/texts/rule-texts.spec.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

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();
}
});
});