@jjlmoya/utils-aquarium 1.57.0 → 1.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/category/index.ts +3 -1
- package/src/entries.ts +7 -1
- package/src/index.ts +2 -0
- package/src/layouts/PreviewLayout.astro +14 -1
- package/src/tests/i18n_coverage.test.ts +3 -1
- package/src/tests/tool_validation.test.ts +8 -4
- package/src/tool/aquariumFilterTurnoverStockingPlanner/aquarium-filter-turnover-stocking-planner.css +404 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/bibliography.astro +7 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/bibliography.ts +16 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/component.astro +140 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/content-builder.ts +43 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/controller.ts +94 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/dom-views.ts +63 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/entry.ts +27 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/evaluator.ts +17 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/de.ts +48 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/en.ts +124 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/es.ts +41 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/fr.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/id.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/it.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/ja.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/ko.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/nl.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/pl.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/pt.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/ru.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/sv.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/tr.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/i18n/zh.ts +36 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/index.ts +11 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/logic.test.ts +78 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/logic.ts +99 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/seo.astro +14 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/storage.ts +21 -0
- package/src/tool/aquariumFilterTurnoverStockingPlanner/ui.ts +40 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/aquarium-lighting-photoperiod-planner.css +667 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/bibliography.astro +6 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/bibliography.ts +12 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/component.astro +92 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/controller.ts +179 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/dom-views.test.ts +26 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/dom-views.ts +187 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/entry.ts +29 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/evaluator.ts +47 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/de.ts +125 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/en.ts +125 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/es.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/fr.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/id.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/it.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/ja.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/ko.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/nl.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/pl.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/pt.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/ru.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/sv.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/tr.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/i18n/zh.ts +110 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/index.ts +11 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/logic.test.ts +115 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/logic.ts +248 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/seo.astro +13 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/storage.ts +39 -0
- package/src/tool/aquariumLightingPhotoperiodPlanner/ui.ts +44 -0
- package/src/tools.ts +12 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export type AquariumSetup = 'community' | 'planted' | 'highWaste';
|
|
2
|
+
export type StockingLoad = 'light' | 'moderate' | 'heavy';
|
|
3
|
+
export type TurnoverStatus = 'under' | 'target' | 'high';
|
|
4
|
+
|
|
5
|
+
export interface AquariumPlannerInput {
|
|
6
|
+
tankVolumeLiters: number;
|
|
7
|
+
ratedFlowLitersPerHour: number;
|
|
8
|
+
flowLossPercent: number;
|
|
9
|
+
fishCount: number;
|
|
10
|
+
averageAdultLengthCm: number;
|
|
11
|
+
setup: AquariumSetup;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AquariumPlannerResult {
|
|
15
|
+
effectiveFlowLitersPerHour: number;
|
|
16
|
+
ratedTurnover: number;
|
|
17
|
+
effectiveTurnover: number;
|
|
18
|
+
targetMinTurnover: number;
|
|
19
|
+
targetMaxTurnover: number;
|
|
20
|
+
targetMinFlowLitersPerHour: number;
|
|
21
|
+
targetMaxFlowLitersPerHour: number;
|
|
22
|
+
stockingProxyCmPerLiter: number;
|
|
23
|
+
stockingLoad: StockingLoad;
|
|
24
|
+
turnoverStatus: TurnoverStatus;
|
|
25
|
+
flowGapLitersPerHour: number;
|
|
26
|
+
flowAboveTargetLitersPerHour: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SetupProfile {
|
|
30
|
+
min: number;
|
|
31
|
+
max: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SETUP_PROFILES: Record<AquariumSetup, SetupProfile> = {
|
|
35
|
+
community: { min: 4, max: 6 },
|
|
36
|
+
planted: { min: 3, max: 5 },
|
|
37
|
+
highWaste: { min: 6, max: 10 },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function positive(value: number, fallback: number): number {
|
|
41
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function bounded(value: number, min: number, max: number): number {
|
|
45
|
+
return Math.min(max, Math.max(min, Number.isFinite(value) ? value : min));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function normalizeInput(input: AquariumPlannerInput): AquariumPlannerInput {
|
|
49
|
+
return {
|
|
50
|
+
tankVolumeLiters: positive(input.tankVolumeLiters, 1),
|
|
51
|
+
ratedFlowLitersPerHour: positive(input.ratedFlowLitersPerHour, 1),
|
|
52
|
+
flowLossPercent: bounded(input.flowLossPercent, 0, 90),
|
|
53
|
+
fishCount: bounded(input.fishCount, 0, 200),
|
|
54
|
+
averageAdultLengthCm: bounded(input.averageAdultLengthCm, 0, 80),
|
|
55
|
+
setup: input.setup in SETUP_PROFILES ? input.setup : 'community',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getStockingLoad(proxy: number): StockingLoad {
|
|
60
|
+
if (proxy <= 0.6) return 'light';
|
|
61
|
+
if (proxy <= 1.2) return 'moderate';
|
|
62
|
+
return 'heavy';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getTurnoverStatus(turnover: number, profile: SetupProfile): TurnoverStatus {
|
|
66
|
+
if (turnover < profile.min) return 'under';
|
|
67
|
+
if (turnover > profile.max) return 'high';
|
|
68
|
+
return 'target';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function calculatePlanner(input: AquariumPlannerInput): AquariumPlannerResult {
|
|
72
|
+
const values = normalizeInput(input);
|
|
73
|
+
const profile = SETUP_PROFILES[values.setup];
|
|
74
|
+
const effectiveFlow = values.ratedFlowLitersPerHour * (1 - values.flowLossPercent / 100);
|
|
75
|
+
const ratedTurnover = values.ratedFlowLitersPerHour / values.tankVolumeLiters;
|
|
76
|
+
const effectiveTurnover = effectiveFlow / values.tankVolumeLiters;
|
|
77
|
+
const stockingProxy = (values.fishCount * values.averageAdultLengthCm) / values.tankVolumeLiters;
|
|
78
|
+
const flowGap = Math.max(0, profile.min * values.tankVolumeLiters - effectiveFlow);
|
|
79
|
+
const flowAbove = Math.max(0, effectiveFlow - profile.max * values.tankVolumeLiters);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
effectiveFlowLitersPerHour: effectiveFlow,
|
|
83
|
+
ratedTurnover,
|
|
84
|
+
effectiveTurnover,
|
|
85
|
+
targetMinTurnover: profile.min,
|
|
86
|
+
targetMaxTurnover: profile.max,
|
|
87
|
+
targetMinFlowLitersPerHour: profile.min * values.tankVolumeLiters,
|
|
88
|
+
targetMaxFlowLitersPerHour: profile.max * values.tankVolumeLiters,
|
|
89
|
+
stockingProxyCmPerLiter: stockingProxy,
|
|
90
|
+
stockingLoad: getStockingLoad(stockingProxy),
|
|
91
|
+
turnoverStatus: getTurnoverStatus(effectiveTurnover, profile),
|
|
92
|
+
flowGapLitersPerHour: flowGap,
|
|
93
|
+
flowAboveTargetLitersPerHour: flowAbove,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getSetupProfile(setup: AquariumSetup): SetupProfile {
|
|
98
|
+
return SETUP_PROFILES[setup];
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { aquariumFilterTurnoverStockingPlanner } from './entry';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props { locale?: KnownLocale; }
|
|
7
|
+
|
|
8
|
+
const { locale = 'en' } = Astro.props;
|
|
9
|
+
const loader = aquariumFilterTurnoverStockingPlanner.i18n[locale] ?? aquariumFilterTurnoverStockingPlanner.i18n.en;
|
|
10
|
+
const content = loader === undefined ? undefined : await loader();
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AquariumPlannerInput } from './logic';
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = 'jjlmoya-aquarium-filter-turnover-planner';
|
|
4
|
+
|
|
5
|
+
export function loadPlannerState(): AquariumPlannerInput | undefined {
|
|
6
|
+
try {
|
|
7
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
8
|
+
if (!raw) return undefined;
|
|
9
|
+
return JSON.parse(raw) as AquariumPlannerInput;
|
|
10
|
+
} catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function savePlannerState(input: AquariumPlannerInput): void {
|
|
16
|
+
try {
|
|
17
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(input));
|
|
18
|
+
} catch {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface AquariumFilterTurnoverStockingPlannerUI extends Record<string, string> {
|
|
2
|
+
presetsLabel: string;
|
|
3
|
+
presetCommunity: string;
|
|
4
|
+
presetPlanted: string;
|
|
5
|
+
presetHighWaste: string;
|
|
6
|
+
tankVolumeLabel: string;
|
|
7
|
+
tankVolumeHint: string;
|
|
8
|
+
ratedFlowLabel: string;
|
|
9
|
+
ratedFlowHint: string;
|
|
10
|
+
flowLossLabel: string;
|
|
11
|
+
flowLossHint: string;
|
|
12
|
+
fishCountLabel: string;
|
|
13
|
+
averageLengthLabel: string;
|
|
14
|
+
setupLabel: string;
|
|
15
|
+
setupCommunity: string;
|
|
16
|
+
setupPlanted: string;
|
|
17
|
+
setupHighWaste: string;
|
|
18
|
+
resultEyebrow: string;
|
|
19
|
+
effectiveTurnoverLabel: string;
|
|
20
|
+
targetLabel: string;
|
|
21
|
+
effectiveFlowLabel: string;
|
|
22
|
+
stockingProxyLabel: string;
|
|
23
|
+
currentTitle: string;
|
|
24
|
+
currentDescription: string;
|
|
25
|
+
statusUnder: string;
|
|
26
|
+
statusTarget: string;
|
|
27
|
+
statusHigh: string;
|
|
28
|
+
loadLight: string;
|
|
29
|
+
loadModerate: string;
|
|
30
|
+
loadHeavy: string;
|
|
31
|
+
cautionTitle: string;
|
|
32
|
+
bandSignalInside: string;
|
|
33
|
+
bandSignalBelow: string;
|
|
34
|
+
bandSignalAbove: string;
|
|
35
|
+
maintenanceText: string;
|
|
36
|
+
useTestKits: string;
|
|
37
|
+
metricLiters: string;
|
|
38
|
+
metricPerHour: string;
|
|
39
|
+
metricCentimetersPerLiter: string;
|
|
40
|
+
}
|