Skip to content

AdametherzLab/rain-harvest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rain-harvest

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.

Installation

bun add @adametherzlab/rain-harvest
# or: npm install @adametherzlab/rain-harvest

Quick Start

import { 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`);

Usage Examples

Collection & Yield Calculations

Multiple Collection Surfaces

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

Single Rainfall Event

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`);

Tank Sizing & Storage

Optimal Tank Size Recommendation

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`);

Cost Analysis

Calculate Savings and ROI

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

Resilience Scoring

Drought Resilience Assessment

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

Input Validation

Validating Configuration

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

Complete Workflow Example

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

API Reference

Core Functions

  • calculateAnnualYield(input) - Calculate total annual collection potential
  • calculateEffectiveCollection(surface, rainfall, firstFlush) - Single event collection
  • calculateTotalArea(surfaces) - Sum of all collection areas
  • calculateAverageRunoffCoefficient(surfaces) - Area-weighted coefficient
  • millimetersToLitersPerSqM(mm) - Unit conversion (1mm = 1L/m²)

Storage & Sizing

  • recommendTankSize(input, targetReliability, maxDryPeriodDays) - Optimal tank capacity
  • calculateOverflowProbability(input, tankCapacity) - Overflow risk analysis

Cost Analysis

  • calculateCostSavings(annualYield, waterRate, systemCost) - Financial analysis
  • estimateSystemCost(tankCapacity, surfaceCount) - Rough installation estimate

Resilience

  • calculateResilienceScore(input) - Drought resilience grading (0-100)

Validation

  • validateCollectionInput(input) - Validate collection configuration
  • validateCalculationInput(input) - Validate full simulation input
  • validateResilienceInput(input) - Validate resilience parameters
  • validateWaterRate(rate) - Validate pricing
  • validateLocation(location) - Validate location data
  • validateStorageTank(tank) - Validate tank configuration
  • validateDemandPattern(demand) - Validate demand pattern

Types

Key TypeScript interfaces exported from the package:

  • CollectionInput - Surfaces, rainfall, and first-flush settings
  • CalculationInput - Full simulation configuration
  • CollectionSurface - Individual roof/surface definition
  • Location - Geographic and climate data
  • StorageTank - Tank specifications
  • DemandPattern - Water usage patterns
  • ResilienceInput - Resilience calculation parameters
  • WaterRate - Pricing for cost calculations

License

MIT

About

Rainwater harvesting calculator — collection, tank sizing, cost savings, drought resilience. Zero deps.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors