- 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]>
113 lines
3.0 KiB
TypeScript
113 lines
3.0 KiB
TypeScript
import {
|
|
childScope,
|
|
has,
|
|
makeParser,
|
|
numberIn,
|
|
optionalNumberIn,
|
|
report,
|
|
type Scope,
|
|
} from './parse-helpers';
|
|
import type { WaterContentConfig } from './rule-config.types';
|
|
|
|
const MIN_WATER_PERCENT = 10;
|
|
const MAX_WATER_PERCENT = 30;
|
|
const MAX_HONEY_TYPES = 50;
|
|
const MAX_HONEY_TYPE_LENGTH = 100;
|
|
|
|
export const DEFAULT_WATER_CONTENT_CONFIG: WaterContentConfig = {
|
|
warnAbovePercent: 18,
|
|
confirmAbovePercent: 20,
|
|
byHoneyType: {},
|
|
};
|
|
|
|
function percent(scope: Scope, key: string, fallback: number): number {
|
|
return numberIn(scope, key, fallback, MIN_WATER_PERCENT, MAX_WATER_PERCENT);
|
|
}
|
|
|
|
const ORDER_MESSAGE =
|
|
'Die Warnschwelle darf nicht über der Bestätigungsschwelle liegen.';
|
|
|
|
function readHoneyTypes(scope: Scope): WaterContentConfig['byHoneyType'] {
|
|
const types = childScope(scope, 'byHoneyType');
|
|
const names = Object.keys(types.raw);
|
|
if (names.length > MAX_HONEY_TYPES) {
|
|
report(scope, 'byHoneyType', `Höchstens ${MAX_HONEY_TYPES} Sorten.`);
|
|
return {};
|
|
}
|
|
const entries: [
|
|
string,
|
|
{ warnAbovePercent?: number; confirmAbovePercent?: number },
|
|
][] = [];
|
|
for (const name of names) {
|
|
if (name.length === 0 || name.length > MAX_HONEY_TYPE_LENGTH) {
|
|
report(types, name.slice(0, 30), 'Der Sortenname ist ungültig.');
|
|
continue;
|
|
}
|
|
const entry = childScope(types, name);
|
|
const before = entry.issues.length;
|
|
const value: { warnAbovePercent?: number; confirmAbovePercent?: number } =
|
|
{};
|
|
const warn = optionalNumberIn(
|
|
entry,
|
|
'warnAbovePercent',
|
|
MIN_WATER_PERCENT,
|
|
MAX_WATER_PERCENT,
|
|
);
|
|
const confirm = optionalNumberIn(
|
|
entry,
|
|
'confirmAbovePercent',
|
|
MIN_WATER_PERCENT,
|
|
MAX_WATER_PERCENT,
|
|
);
|
|
if (warn !== undefined && confirm !== undefined && warn > confirm) {
|
|
report(entry, 'warnAbovePercent', ORDER_MESSAGE);
|
|
}
|
|
if (entry.issues.length > before) {
|
|
continue;
|
|
}
|
|
if (warn !== undefined) {
|
|
value.warnAbovePercent = warn;
|
|
}
|
|
if (confirm !== undefined) {
|
|
value.confirmAbovePercent = confirm;
|
|
}
|
|
entries.push([name, value]);
|
|
}
|
|
// fromEntries defines own properties, so a name like "__proto__" is harmless.
|
|
return Object.fromEntries(entries);
|
|
}
|
|
|
|
export const waterContentParser = makeParser<WaterContentConfig>(
|
|
'HONEY_WATER_CONTENT',
|
|
DEFAULT_WATER_CONTENT_CONFIG,
|
|
(scope) => {
|
|
const d = DEFAULT_WATER_CONTENT_CONFIG;
|
|
let warnAbovePercent = percent(
|
|
scope,
|
|
'warnAbovePercent',
|
|
d.warnAbovePercent,
|
|
);
|
|
let confirmAbovePercent = percent(
|
|
scope,
|
|
'confirmAbovePercent',
|
|
d.confirmAbovePercent,
|
|
);
|
|
if (warnAbovePercent > confirmAbovePercent) {
|
|
report(
|
|
scope,
|
|
has(scope, 'warnAbovePercent')
|
|
? 'warnAbovePercent'
|
|
: 'confirmAbovePercent',
|
|
ORDER_MESSAGE,
|
|
);
|
|
warnAbovePercent = d.warnAbovePercent;
|
|
confirmAbovePercent = d.confirmAbovePercent;
|
|
}
|
|
return {
|
|
warnAbovePercent,
|
|
confirmAbovePercent,
|
|
byHoneyType: readHoneyTypes(scope),
|
|
};
|
|
},
|
|
);
|