Skip to content

AdametherzLab/maple-tap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CI TypeScript License: MIT

🍁 maple-tap

Features

  • Type-safe tracking of sap collections and boil sessions with branded identifiers
  • Rule of 86 calculations for theoretical syrup yield prediction
  • Efficiency metrics including boil ratios, fuel consumption, and season summaries
  • Zero dependencies — runs on Bun and Node.js 20+ with only built-in modules
  • Full TypeScript support with strict mode and comprehensive type definitions

Installation

npm install @adametherzlab/maple-tap
# or
bun add @adametherzlab/maple-tap

Quick Start

// REMOVED external import: import { createStore, addSapCollection, ruleOf86YieldLiters } from "@adametherzlab/maple-tap";
// REMOVED external import: import type { TapId, TreeId, Volume, BrixReading } from "@adametherzlab/maple-tap";

const store = createStore();
const updatedStore = addSapCollection(store, {
  tapId: "tap-001" as TapId,
  treeId: "sugar-maple-01" as TreeId,
  volume: 20 as Volume,
  brix: 2.1 as BrixReading,
  collectedAt: new Date()
});

const theoreticalYield = ruleOf86YieldLiters(20 as Volume, 2.1 as BrixReading);
console.log(`Expected syrup output: ${theoreticalYield.toFixed(2)} liters`);

The Rule of 86

The Rule of 86 is the time-honored approximation used by sugar makers to estimate syrup yield from raw sap. The formula derives from the fact that finished maple syrup contains approximately 66% sugar (66°Bx).

Formula: (sapVolume × sapBrix) / 66 = syrupVolume

Alternative form: 86 ÷ sapBrix = gallons of sap needed per gallon of syrup

For example, with sap measuring 2°Bx (2% sugar), you need approximately 43 gallons of sap to produce 1 gallon of syrup (86 ÷ 2 = 43). This rule helps producers estimate fuel needs, collection schedules, and expected output before firing up the evaporator.

API Reference

Core Types

Type Description
TapId Branded string identifier for a tap spout
TreeId Branded string identifier for a maple tree
SessionId Branded string identifier for a boil session
SeasonId Branded string identifier for a production season (e.g., "2024")
BrixReading Sugar content in degrees Brix (0-100), where 2.0 represents 2% sugar
Volume Liquid volume as a branded number (unit determined by preferences)
DurationMinutes Time duration in minutes
VolumeUnit 'liters' | 'gallons'
TemperatureUnit 'celsius' | 'fahrenheit'
FuelType 'wood' | 'propane' | 'oil' | 'electric' | 'other'
UnitPreferences Configuration for measurement units and display formats
SapCollection Record of sap volume, Brix reading, tap/tree IDs, and collection timestamp
BoilSession Record of input/output volumes, fuel usage, duration, and date
EfficiencyMetrics Calculated boil ratio, yield percentage, and fuel efficiency
SeasonSummary Aggregated totals and averages for a complete production season
MapleTapConfig Application configuration options
MapleTapStore Immutable data store containing all collections and sessions

Store Management

createStore(): MapleTapStore

const store = createStore();

addSapCollection(store, collection): MapleTapStore

  • Parameters: store — current store; collection — SapCollection data (id auto-generated)
  • Throws: RangeError if volume ≤ 0 or Brix outside 0-100; Error if date invalid
const newStore = addSapCollection(store, {
  tapId: "tap-1" as TapId,
  treeId: "tree-1" as TreeId,
  volume: 10 as Volume,
  brix: 2.5 as BrixReading,
  collectedAt: new Date()
});

addBoilSession(store, session): MapleTapStore

  • Parameters: store — current store; session — BoilSession data (id auto-generated)
  • Throws: RangeError if volumes invalid, fuel negative, or duration ≤ 0
const newStore = addBoilSession(store, {
  date: new Date(),
  sapInputVolume: 40 as Volume,
  syrupOutputVolume: 1 as Volume,
  fuelType: "wood",
  fuelUsed: 5,
  durationMinutes: 180 as DurationMinutes
});

getCollectionsByTap(store, tapId): readonly SapCollection[]

const collections = getCollectionsByTap(store, "tap-1" as TapId);

getCollectionsByTree(store, treeId): readonly SapCollection[]

const collections = getCollectionsByTree(store, "tree-1" as TreeId);

getBoilSessions(store): readonly BoilSession[]

const sessions = getBoilSessions(store);

removeCollection(store, collectionId): MapleTapStore

const newStore = removeCollection(store, "uuid-string");

removeBoilSession(store, sessionId): MapleTapStore

const newStore = removeBoilSession(store, "uuid-string" as SessionId);

Calculations & Utilities

ruleOf86YieldLiters(sapVolume, sapBrix): number

  • Parameters: sapVolume — liters of raw sap; sapBrix — sugar content (0-100)
  • Returns: Theoretical syrup yield in liters
  • Throws: RangeError if volume negative or Brix outside 0-100
const yield = ruleOf86YieldLiters(100 as Volume, 2.0 as BrixReading); // ~3.03 liters

brixToSugarPercent(brix): number

  • Throws: RangeError if brix outside 0-100
const percent = brixToSugarPercent(2.5 as BrixReading); // 2.5

convertVolume(volume, fromUnit, toUnit): number

  • Throws: RangeError if volume negative; Error if invalid unit combination
const gallons = convertVolume(10 as Volume, "liters", "gallons"); // ~2.64

calculateEfficiencyMetrics(session, averageBrix): EfficiencyMetrics

  • Parameters: session — BoilSession data; averageBrix — average sugar content of input sap
  • Returns: Metrics including boil ratio, yield percentage, and fuel consumption rates
  • Throws: RangeError for invalid inputs
const metrics = calculateEfficiencyMetrics(session, 2.2 as BrixReading);

calculateSeasonSummary(seasonId, collections, sessions): SeasonSummary

  • Throws: Error if both arrays are empty
const summary = calculateSeasonSummary("2024" as SeasonId, collections, sessions);

Advanced Usage

import { 
  createStore, 
  addSapCollection, 
  addBoilSession, 
  ruleOf86YieldLiters,
  calculateEfficiencyMetrics,
  calculateSeasonSummary,
  convertVolume 
} from "@adametherzlab/maple-tap";
// REMOVED external import: import type { TapId, TreeId, Volume, BrixReading, SeasonId } from "@adametherzlab/maple-tap";

// Initialize season
let store = createStore();
const seasonId = "2024" as SeasonId;

// Early season collections (lower sugar content)
store = addSapCollection(store, {
  tapId: "tap-north-01" as TapId,
  treeId: "tree-001" as TreeId,
  volume: 15 as Volume,
  brix: 1.8 as BrixReading,
  collectedAt: new Date("2024-03-01")
});

store = addSapCollection(store, {
  tapId: "tap-north-02" as TapId,
  treeId: "tree-002" as TreeId,
  volume: 22 as Volume,
  brix: 2.2 as BrixReading,
  collectedAt: new Date("2024-03-01")
});

// Peak season (higher sugar)
store = addSapCollection(store, {
  tapId: "tap-north-01" as TapId,
  treeId: "tree-001" as TreeId,
  volume: 18 as Volume,
  brix: 3.1 as BrixReading,
  collectedAt: new Date("2024-03-15")
});

// First boil session: 55 liters total input
store = addBoilSession(store, {
  date: new Date("2024-03-02"),
  sapInputVolume: 37 as Volume, // 15 + 22
  syrupOutputVolume: 1.25 as Volume,
  fuelType: "wood",
  fuelUsed: 8, // cords or arbitrary units
  durationMinutes: 240 as DurationMinutes
});

// Calculate theoretical vs actual for the first boil
const theoreticalYield = ruleOf86YieldLiters(37 as Volume, 2.0 as BrixReading); // ~1.12 liters
console.log(`Theoretical: ${theoreticalYield.toFixed(2)}L, Actual: 1.25L`);

// Season summary
const collections = [...store.collections];
const sessions = [...store.boilSessions];
const summary = calculateSeasonSummary(seasonId, collections, sessions);

console.log(`Total sap collected: ${summary.totalSapVolume} liters`);
console.log(`Total syrup produced: ${summary.totalSyrupVolume} liters`);
console.log(`Average sugar content: ${summary.averageBrix.toFixed(1)}°Bx`);
console.log(`Overall efficiency: ${(summary.efficiency.yieldPercentage * 100).toFixed(1)}%`);

// Convert to gallons for the US market
const totalGallons = convertVolume(summary.totalSyrupVolume as Volume, "liters", "gallons");
console.log(`Season yield: ${totalGallons.toFixed(2)} gallons`);

Contributing

See CONTRIBUTING.md

License

MIT (c) AdametherzLab

About

Maple syrup tracker — sap volume, Brix, boil ratio, yield calculation

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors