Skip to content

Commit 977a3a8

Browse files
committed
v0.1.1: add support for custom crop types with user-defined EC ranges
1 parent 4fd3bc5 commit 977a3a8

3 files changed

Lines changed: 49 additions & 52 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adametherzlab/fertigation-mix-v2",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "fertigation-mix v2 — EC Target Range Calculator",
55
"type": "module",
66
"main": "./dist/index.js",

src/feature-1.ts

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,36 @@ const SYSTEM_ADJUSTMENT_BASES: Record<HydroponicSystemType, Omit<SystemNutrientA
135135
* @param systemType - The type of hydroponic system in use
136136
* @param growthStage - Current growth stage of the crop
137137
* @param cropType - Identifier for the crop species or variety (e.g., 'tomato', 'lettuce')
138+
* @param customRange - Optional user-defined EC range (min, max, and optional optimal) overriding system defaults
138139
* @returns Structured EC target range with min, max, optimal values and environmental modifiers
139-
* @throws {RangeError} If system type or growth stage is not supported
140+
* @throws {RangeError} If system type or growth stage is not supported or invalid custom range
140141
* @example
141142
* const range = calculateEcTargetRange(HydroponicSystemType.NFT, GrowthStage.Vegetative, 'butter_lettuce');
142143
* console.log(range.optimalEc); // 1.47 (interpolated)
143144
*/
144145
export function calculateEcTargetRange(
145146
systemType: HydroponicSystemType,
146147
growthStage: GrowthStage,
147-
cropType: string
148+
cropType: string,
149+
customRange?: { min: number; max: number; optimal?: number }
148150
): ECTargetRange {
151+
if (customRange) {
152+
if (customRange.min >= customRange.max) {
153+
throw new RangeError('Custom EC range min must be less than max');
154+
}
155+
156+
const optimalValue = customRange.optimal ?? customRange.min + (customRange.max - customRange.min) * 0.45;
157+
158+
return {
159+
cropType,
160+
growthStage,
161+
minEc: asElectricalConductivity(customRange.min),
162+
maxEc: asElectricalConductivity(customRange.max),
163+
optimalEc: asElectricalConductivity(Number(optimalValue.toFixed(2))),
164+
environmentalModifiers: {}
165+
} satisfies ECTargetRange;
166+
}
167+
149168
const systemRanges = EC_BASE_RANGES[systemType];
150169
if (!systemRanges) {
151170
throw new RangeError(`Unsupported hydroponic system type: ${String(systemType)}`);
@@ -197,51 +216,3 @@ export function classifyEcReading(
197216
// Check if within optimal band (±10% of optimal value)
198217
const optimalTolerance = targetRange.optimalEc * 0.1;
199218
if (Math.abs(reading - targetRange.optimalEc) <= optimalTolerance) return 'optimal';
200-
201-
// Within min-max range but outside optimal band
202-
return reading < targetRange.optimalEc ? 'suboptimal' : 'elevated';
203-
}
204-
205-
/**
206-
* Retrieve system-specific nutrient adjustment profile modified for the given growth stage.
207-
* Adjusts EC multipliers and scheduling intervals based on plant nutritional demands.
208-
* @param systemType - The hydroponic system type
209-
* @param growthStage - The plant growth stage affecting nutrient uptake rates
210-
* @returns System nutrient adjustment parameters including EC multipliers and maintenance intervals
211-
* @throws {RangeError} If system type or growth stage is invalid
212-
* @example
213-
* const profile = getSystemNutrientAdjustment(HydroponicSystemType.DWC, GrowthStage.Fruiting);
214-
* // Returns profile with elevated EC multiplier for fruiting stage
215-
*/
216-
export function getSystemNutrientAdjustment(
217-
systemType: HydroponicSystemType,
218-
growthStage: GrowthStage
219-
): SystemNutrientAdjustment {
220-
const baseProfile = SYSTEM_ADJUSTMENT_BASES[systemType];
221-
if (!baseProfile) {
222-
throw new RangeError(`Invalid system type: ${String(systemType)}`);
223-
}
224-
225-
// Stage-specific nutritional demand factors
226-
const stageDemandFactors: Record<GrowthStage, number> = {
227-
[GrowthStage.Seedling]: 0.7,
228-
[GrowthStage.Vegetative]: 1.0,
229-
[GrowthStage.Flowering]: 1.1,
230-
[GrowthStage.Fruiting]: 1.2,
231-
[GrowthStage.Ripening]: 0.9
232-
};
233-
234-
const demandFactor = stageDemandFactors[growthStage];
235-
if (demandFactor === undefined) {
236-
throw new RangeError(`Invalid growth stage: ${String(growthStage)}`);
237-
}
238-
239-
return {
240-
systemType,
241-
ecMultiplier: Number((baseProfile.ecMultiplier * demandFactor).toFixed(3)),
242-
phOffset: baseProfile.phOffset,
243-
oxygenDemandFactor: baseProfile.oxygenDemandFactor,
244-
solutionChangeIntervalHours: baseProfile.solutionChangeIntervalHours,
245-
topUpFrequencyHours: baseProfile.topUpFrequencyHours
246-
} satisfies SystemNutrientAdjustment;
247-
}

tests/index.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,32 @@ describe("fertigation-mix-v2 comprehensive test suite", () => {
3030
expect(result.optimalEc).toBeLessThanOrEqual(result.maxEc);
3131
});
3232

33+
it("supports custom crop types with user-defined EC ranges", () => {
34+
const result = calculateEcTargetRange(
35+
HydroponicSystemType.DeepWaterCulture,
36+
GrowthStage.Vegetative,
37+
"custom_crop",
38+
{ min: 1.0, max: 2.0 }
39+
);
40+
41+
expect(result.cropType).toBe("custom_crop");
42+
expect(result.growthStage).toBe(GrowthStage.Vegetative);
43+
expect(result.minEc).toBe(1.0);
44+
expect(result.maxEc).toBe(2.0);
45+
expect(result.optimalEc).toBeCloseTo(1.45);
46+
});
47+
48+
it("uses provided optimal EC in custom ranges", () => {
49+
const result = calculateEcTargetRange(
50+
HydroponicSystemType.DeepWaterCulture,
51+
GrowthStage.Vegetative,
52+
"custom_crop",
53+
{ min: 1.0, max: 2.0, optimal: 1.8 }
54+
);
55+
56+
expect(result.optimalEc).toBe(1.8);
57+
});
58+
3359
it("classifyEcReading categorizes readings correctly at classification boundaries", () => {
3460
const range: ECTargetRange = {
3561
cropType: "tomato",
@@ -104,4 +130,4 @@ describe("fertigation-mix-v2 comprehensive test suite", () => {
104130
expect(result.volumePerLiterMl).toBe(1000);
105131
expect(result.safetyWarning).toInclude("Causes stem elongation");
106132
});
107-
});
133+
});

0 commit comments

Comments
 (0)