Rainwater harvesting calculator for developers, engineers, and sustainability enthusiasts. Calculate collection area, size your storage tank, estimate cost savings, score drought resilience, and match demand patterns -- all in TypeScript with zero dependencies.
bun add @adametherzlab/rain-harvest
# or: npm install @adametherzlab/rain-harvestimport { calculateAnnualYield, RoofMaterial } from "@adametherzlab/rain-harvest";
// Calculate annual yield for a 150m² metal roof
const result = calculateAnnualYield({
surfaces: [{
id: "roof1",
name: "Main Roof",
area: 150,
runoffCoefficient: 0.9,
material: RoofMaterial.Metal
}],
monthlyRainfall: [80, 70, 65, 50, 40, 30, 25, 30, 40, 60, 75, 85],
firstFlush: { volumePerSqM: 0.5, enabled: true }
});
console.log(`Annual yield: ${result.totalYield.toLocaleString()} liters`);
console.log(`Effective yield: ${result.effectiveYield.toLocaleString()} liters`);Calculate yield from multiple roof surfaces with different materials:
import {
calculateAnnualYield,
calculateTotalArea,
calculateAverageRunoffCoefficient,
RoofMaterial
} from "@adametherzlab/rain-harvest";
const surfaces = [
{ id: "metal", name: "Metal Roof", area: 120, runoffCoefficient: 0.9, material: RoofMaterial.Metal },
{ id: "tiled", name: "Tiled Roof", area: 80, runoffCoefficient: 0.7, material: RoofMaterial.Tiled },
{ id: "concrete", name: "Concrete Patio", area: 50, runoffCoefficient: 0.6, material: RoofMaterial.Concrete }
];
const totalArea = calculateTotalArea(surfaces);
const avgCoefficient = calculateAverageRunoffCoefficient(surfaces);
console.log(`Total collection area: ${totalArea}m²`);
console.log(`Weighted runoff coefficient: ${avgCoefficient.toFixed(2)}`);
const result = calculateAnnualYield({
surfaces,
monthlyRainfall: [120, 100, 90, 70, 50, 30, 20, 25, 40, 80, 110, 130],
firstFlush: { volumePerSqM: 1.0, enabled: true }
});Calculate collection for a specific rainfall event:
import { calculateEffectiveCollection, RoofMaterial } from "@adametherzlab/rain-harvest";
const surface = {
id: "emergency",
name: "Emergency Collection",
area: 200,
runoffCoefficient: 0.85,
material: RoofMaterial.Metal
};
// Calculate yield from a 25mm rainfall event
const liters = calculateEffectiveCollection(
surface,
25, // mm of rain
{ volumePerSqM: 0.5, enabled: true }
);
console.log(`Collected ${liters} liters from 25mm rainfall`);import {
recommendTankSize,
calculateOverflowProbability,
RoofMaterial
} from "@adametherzlab/rain-harvest";
const location = {
id: "austin",
name: "Austin, TX",
countryCode: "US",
latitude: 30.2672,
longitude: -97.7431,
annualRainfall: 900,
monthlyRainfall: [60, 55, 70, 65, 110, 100, 60, 70, 75, 85, 75, 75]
};
const surfaces = [{
id: "roof",
name: "House Roof",
area: 200,
runoffCoefficient: 0.85,
material: RoofMaterial.Metal
}];
const tank = {
id: "tank1",
name: "Primary Tank",
capacity: 10000,
initialVolume: 0,
overflowPipeHeight: 9500
};
const demand = {
id: "household",
name: "Household Demand",
dailyDemand: 300,
monthlyMultiplier: [1.0, 1.0, 1.0, 0.9, 0.9, 1.1, 1.2, 1.2, 1.0, 1.0, 1.0, 1.0]
};
const calculationInput = {
location,
surfaces,
tank,
demand,
firstFlush: { volumePerSqM: 1, enabled: true },
simulationDays: 365,
startMonth: 0
};
// Recommend tank size for 95% reliability and 30-day dry period coverage
const sizing = recommendTankSize(calculationInput, 0.95, 30);
console.log(`Recommended tank size: ${sizing.recommendedCapacity.toLocaleString()} liters`);
console.log(`Can bridge ${sizing.dryPeriodDays} dry days`);
console.log(`Overflow probability: ${(sizing.overflowProbability * 100).toFixed(1)}%`);
console.log(`Explanation: ${sizing.explanation}`);
// Analyze overflow risk for a specific tank size
const overflow = calculateOverflowProbability(calculationInput, 15000);
console.log(`Critical months for overflow: ${overflow.criticalMonths.join(", ")}`);
console.log(`Estimated annual overflow: ${overflow.estimatedAnnualOverflow.toLocaleString()} liters`);import {
calculateCostSavings,
estimateSystemCost,
calculateAnnualYield,
RoofMaterial
} from "@adametherzlab/rain-harvest";
// First, calculate your annual yield
const yieldResult = calculateAnnualYield({
surfaces: [{
id: "roof",
name: "Main Roof",
area: 250,
runoffCoefficient: 0.9,
material: RoofMaterial.Metal
}],
monthlyRainfall: [100, 90, 80, 70, 60, 50, 40, 50, 60, 70, 80, 90],
firstFlush: { volumePerSqM: 1, enabled: true }
});
// Estimate system installation cost
const systemCost = estimateSystemCost(20000, 1); // 20,000L tank, 1 surface
console.log(`Estimated system cost: $${systemCost.totalCost.toLocaleString()}`);
console.log(` - Tank: $${systemCost.tankCost.toLocaleString()}`);
console.log(` - Plumbing: $${systemCost.plumbingCost.toLocaleString()}`);
// Calculate cost savings over system lifetime
const waterRate = { pricePerLiter: 0.005, currency: "USD" }; // $0.005/L = $5/m³
const savings = calculateCostSavings(
yieldResult.effectiveYield,
waterRate,
systemCost.totalCost
);
console.log(`Annual savings: $${savings.annualSavings.toLocaleString()}`);
console.log(`Monthly savings: $${savings.monthlySavings.toLocaleString()}`);
console.log(`Payback period: ${savings.paybackYears.toFixed(1)} years`);
console.log(`25-year ROI: ${savings.roi.toFixed(0)}%`);
console.log(`Lifetime savings: $${savings.lifetimeSavings.toLocaleString()}`);import { calculateResilienceScore } from "@adametherzlab/rain-harvest";
// Assess system resilience for a 10,000L tank with 250L/day demand
const resilience = calculateResilienceScore({
tankCapacity: 10000,
dailyDemand: 250,
monthlyReliability: [0.95, 0.92, 0.88, 0.85, 0.80, 0.75, 0.70, 0.72, 0.80, 0.88, 0.92, 0.95],
longestDrySpan: 45 // days
});
console.log(`Resilience Score: ${resilience.score}/100 (Grade ${resilience.grade})`);
console.log(`Dry days covered: ${resilience.dryDaysCovered} days`);
console.log(`Critical months: ${resilience.criticalMonths.map(m => ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][m]).join(", ")}`);
console.log("\nRecommendations:");
resilience.recommendations.forEach(rec => console.log(` • ${rec}`));import {
validateCollectionInput,
validateCalculationInput,
validateResilienceInput,
validateWaterRate,
RoofMaterial
} from "@adametherzlab/rain-harvest";
// Validate collection configuration before calculation
const collectionValidation = validateCollectionInput({
surfaces: [
{ id: "roof1", name: "Main Roof", area: 200, runoffCoefficient: 0.9, material: RoofMaterial.Metal },
{ id: "roof2", name: "Garage", area: 50, runoffCoefficient: 0.8, material: RoofMaterial.Tiled }
],
monthlyRainfall: [80, 70, 65, 50, 40, 30, 25, 30, 40, 60, 75, 85],
firstFlush: { volumePerSqM: 1, enabled: true }
});
if (!collectionValidation.isValid) {
console.error("Validation errors:");
collectionValidation.errors.forEach(err => {
console.error(` ${err.field}: ${err.message} (got ${err.value})`);
});
}
// Validate water rates
const rateValidation = validateWaterRate({ pricePerLiter: 0.005, currency: "USD" });
console.log(`Water rate valid: ${rateValidation.isValid}`);
// Validate resilience input
const resilienceValidation = validateResilienceInput({
tankCapacity: 10000,
dailyDemand: 300,
monthlyReliability: [0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9],
longestDrySpan: 30
});import {
calculateAnnualYield,
recommendTankSize,
calculateCostSavings,
estimateSystemCost,
calculateResilienceScore,
calculateOverflowProbability,
validateCollectionInput,
RoofMaterial
} from "@adametherzlab/rain-harvest";
// Step 1: Define your location and collection surfaces
const location = {
id: "portland",
name: "Portland, OR",
countryCode: "US",
latitude: 45.5152,
longitude: -122.6784,
annualRainfall: 1000,
monthlyRainfall: [120, 100, 90, 70, 60, 40, 20, 25, 45, 80, 110, 140]
};
const surfaces = [
{ id: "main", name: "House Roof", area: 180, runoffCoefficient: 0.9, material: RoofMaterial.Metal },
{ id: "garage", name: "Garage Roof", area: 40, runoffCoefficient: 0.8, material: RoofMaterial.Tiled }
];
// Step 2: Validate and calculate annual yield
const collectionInput = {
surfaces,
monthlyRainfall: location.monthlyRainfall,
firstFlush: { volumePerSqM: 0.5, enabled: true }
};
const validation = validateCollectionInput(collectionInput);
if (!validation.isValid) {
console.error("Configuration errors:", validation.errors);
process.exit(1);
}
const yieldResult = calculateAnnualYield(collectionInput);
console.log(`Potential collection: ${yieldResult.totalYield.toLocaleString()} L/year`);
// Step 3: Size the tank
const tank = {
id: "main",
name: "Primary Storage",
capacity: 10000, // Will be optimized
initialVolume: 0,
overflowPipeHeight: 9500
};
const demand = {
id: "household",
name: "Household Use",
dailyDemand: 250,
monthlyMultiplier: [1.0, 1.0, 1.0, 0.9, 0.9, 1.1, 1.2, 1.2, 1.0, 1.0, 1.0, 1.0]
};
const calculationInput = {
location,
surfaces,
tank,
demand,
firstFlush: collectionInput.firstFlush,
simulationDays: 365,
startMonth: 0
};
const sizing = recommendTankSize(calculationInput, 0.95, 30);
console.log(`\nRecommended tank: ${sizing.recommendedCapacity.toLocaleString()} liters`);
console.log(`Confidence: ${(sizing.confidenceLevel * 100).toFixed(0)}%`);
console.log(`Explanation: ${sizing.explanation}`);
// Step 4: Check overflow risk
const overflow = calculateOverflowProbability(calculationInput, sizing.recommendedCapacity);
console.log(`\nOverflow probability: ${(overflow.overflowProbability * 100).toFixed(1)}%`);
console.log(`Peak overflow risk: ${(overflow.peakOverflowRisk * 100).toFixed(1)}%`);
// Step 5: Calculate costs and ROI
const systemCost = estimateSystemCost(sizing.recommendedCapacity, surfaces.length);
const savings = calculateCostSavings(
yieldResult.effectiveYield,
{ pricePerLiter: 0.006, currency: "USD" },
systemCost.totalCost
);
console.log(`\nSystem Cost: $${systemCost.totalCost.toLocaleString()}`);
console.log(` - Tank: $${systemCost.tankCost.toLocaleString()}`);
console.log(` - Plumbing: $${systemCost.plumbingCost.toLocaleString()}`);
console.log(`\nAnnual savings: $${savings.annualSavings.toLocaleString()}`);
console.log(`Payback period: ${savings.paybackYears.toFixed(1)} years`);
console.log(`25-year ROI: ${savings.roi.toFixed(0)}%`);
// Step 6: Resilience scoring
const resilience = calculateResilienceScore({
tankCapacity: sizing.recommendedCapacity,
dailyDemand: demand.dailyDemand,
monthlyReliability: [0.95, 0.92, 0.88, 0.85, 0.80, 0.75, 0.70, 0.72, 0.80, 0.88, 0.92, 0.95],
longestDrySpan: 45
});
console.log(`\nResilience Score: ${resilience.score}/100 (Grade ${resilience.grade})`);
console.log(`Can survive ${resilience.dryDaysCovered} days without rain`);
console.log("Recommendations:");
resilience.recommendations.forEach(rec => console.log(` • ${rec}`));calculateAnnualYield(input)- Calculate total annual collection potentialcalculateEffectiveCollection(surface, rainfall, firstFlush)- Single event collectioncalculateTotalArea(surfaces)- Sum of all collection areascalculateAverageRunoffCoefficient(surfaces)- Area-weighted coefficientmillimetersToLitersPerSqM(mm)- Unit conversion (1mm = 1L/m²)
recommendTankSize(input, targetReliability, maxDryPeriodDays)- Optimal tank capacitycalculateOverflowProbability(input, tankCapacity)- Overflow risk analysis
calculateCostSavings(annualYield, waterRate, systemCost)- Financial analysisestimateSystemCost(tankCapacity, surfaceCount)- Rough installation estimate
calculateResilienceScore(input)- Drought resilience grading (0-100)
validateCollectionInput(input)- Validate collection configurationvalidateCalculationInput(input)- Validate full simulation inputvalidateResilienceInput(input)- Validate resilience parametersvalidateWaterRate(rate)- Validate pricingvalidateLocation(location)- Validate location datavalidateStorageTank(tank)- Validate tank configurationvalidateDemandPattern(demand)- Validate demand pattern
Key TypeScript interfaces exported from the package:
CollectionInput- Surfaces, rainfall, and first-flush settingsCalculationInput- Full simulation configurationCollectionSurface- Individual roof/surface definitionLocation- Geographic and climate dataStorageTank- Tank specificationsDemandPattern- Water usage patternsResilienceInput- Resilience calculation parametersWaterRate- Pricing for cost calculations
MIT