npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

seasonal-forecast

v1.2.0

Published

Zero-dependency demand forecasting for seasonal businesses. Pure statistics, no ML, no cloud costs.

Readme

seasonal-forecast

Zero-dependency demand forecasting for seasonal businesses. Pure statistics, no ML, no cloud costs.

npm version CI License: MIT TypeScript Built by TourOperation.com

Built and battle-tested at TourOperation.com — powering demand predictions for tourism operators processing thousands of bookings across multiple years.

Why?

Most forecasting libraries are either:

  • Too heavy — TensorFlow, Prophet, ARIMA models requiring Python, GPU, or cloud APIs
  • Too generic — Built for stock prices or IoT, not seasonal businesses

seasonal-forecast is purpose-built for businesses where:

  • Demand follows weekly patterns (weekdays vs weekends)
  • Demand follows seasonal patterns (summer vs winter)
  • You have 1-5 years of historical data
  • You need predictions now, not after training a model

Features

| Feature | Description | |---------|-------------| | DOY-Window Forecasting | Compares same time-of-year across all historical years using a configurable day-of-year window | | 3-Tier Historical Fallback | DOY → Month → Seasonal ratio — always produces a value, even with sparse data | | DOW-Aware | Respects day-of-week patterns (e.g., restaurants closed Mondays, tours only on weekends) | | Recency Weighting | Recent years count more than older ones (2024 data matters more than 2021) | | Completion Rate Prediction | "15 people booked 7 days out → predicted final: 38 people" based on historical booking velocity | | Season Multipliers | Define high/low seasons with custom multipliers | | Demand Matrix | Grid view of items × dates with color-coded demand levels | | Anomaly Detection | Z-score based detection with severity classification (mild/moderate/extreme) | | Time Series Decomposition | Additive decomposition into trend, seasonal, and residual components | | CSV/JSON Import | Auto-detecting importers with flexible column mapping and date format handling | | State Serialization | Export/import forecaster state for caching patterns to file or database | | Last Year Comparison | DOW-aware comparison with same period last year | | Forecast Accuracy | Score predictions with MAPE, RMSE, MAE, bias, and hit rate metrics | | Multi-Item Correlation | Pearson correlation to identify complementary and substitute products | | Holiday Calendar | Built-in holiday presets (US, EU, TR, international) with proximity matching | | Incremental Updates | Stream new data without full reload via addRecords() | | CLI Tool | Command-line interface for quick analysis without writing code | | Zero Dependencies | Pure TypeScript, works in Node.js, Bun, Deno, and browsers |

Install

npm install seasonal-forecast

Quick Start

import { SeasonalForecaster } from 'seasonal-forecast';

const forecaster = new SeasonalForecaster({
  windowDays: 3,     // ±3 days around target day-of-year
  minDataPoints: 3,  // minimum data points to produce a forecast
});

// Load your historical data
forecaster.loadHistory([
  { itemId: 'sunset-cruise', date: '2023-06-15', quantity: 42 },
  { itemId: 'sunset-cruise', date: '2023-06-16', quantity: 38 },
  { itemId: 'sunset-cruise', date: '2024-06-14', quantity: 45 },
  { itemId: 'sunset-cruise', date: '2024-06-15', quantity: 51 },
  // ... years of daily data
]);

// Get a single forecast
const forecast = forecaster.forecast('sunset-cruise', '2026-06-15');
console.log(forecast);
// {
//   itemId: 'sunset-cruise',
//   date: '2026-06-15',
//   predicted: 48,
//   confidenceLow: 34,
//   confidenceHigh: 62,
//   confidence: 'high',
//   method: 'doy_weighted',
//   factors: { base: 46.2, window: '+-3d', ... }
// }

Core Concepts

1. Pattern Analysis

Analyzes your entire dataset to extract demand patterns per item:

const patterns = forecaster.analyze();

for (const pattern of patterns) {
  console.log(`${pattern.itemId}:`);
  console.log(`  Average: ${pattern.overallAverage} per day`);
  console.log(`  DOW coefficients: ${pattern.dowCoefficients}`);
  //  [0.6, 1.1, 1.0, 0.9, 1.0, 1.2, 1.4]
  //   Sun  Mon  Tue  Wed  Thu  Fri  Sat
  console.log(`  YoY trend: ${pattern.yoyTrend > 0 ? '+' : ''}${(pattern.yoyTrend * 100).toFixed(0)}%`);
}

2. Historical Averages (3-Tier Fallback)

Gets the best available historical comparison for any date:

const avg = forecaster.historicalAverage('sunset-cruise', '2026-07-20');
// { average: 44.3, tier: 'doy', years: 3 }
//
// Tiers:
//   'doy'      — Same DOW within ±10 day-of-year window (most accurate)
//   'month'    — Same DOW within same month (reliable fallback)
//   'seasonal' — DOW seasonal ratio × annual average (always available)

3. Completion Rate Prediction

Predicts final demand from current bookings based on historical booking velocity:

forecaster.loadBookings([
  { itemId: 'sunset-cruise', serviceDate: '2025-07-20', bookedAt: '2025-07-01', quantity: 2 },
  { itemId: 'sunset-cruise', serviceDate: '2025-07-20', bookedAt: '2025-07-10', quantity: 4 },
  // ... thousands of historical bookings
]);

const prediction = forecaster.predictFinal('sunset-cruise', '2026-07-20', 15);
// { predictedFinal: 38, confidence: 'high', completionRate: 0.394, milestone: 7, samples: 24 }

4. Demand Matrix

Generate a grid view for operational planning:

const cells = forecaster.matrix(
  ['sunset-cruise', 'island-tour', 'diving'],
  '2026-07-01',
  '2026-07-14',
  { 'sunset-cruise-2026-07-01': 12, 'island-tour-2026-07-01': 8 }
);

for (const cell of cells) {
  console.log(`${cell.itemId} on ${cell.date}: ${cell.color}`);
  // sunset-cruise on 2026-07-01: orange
}

5. Anomaly Detection

Identify unusual data points in historical data:

const anomalies = forecaster.detectAnomalies({ threshold: 2.0 });

for (const a of anomalies) {
  console.log(`${a.date}: ${a.quantity} (expected ${a.expected}, ${a.severity} ${a.direction})`);
  // 2024-06-15: 200 (expected 52.3, extreme high)
}

6. Time Series Decomposition

Break down demand into trend, seasonal, and residual components:

const decompositions = forecaster.decompose();

for (const d of decompositions) {
  console.log(`${d.itemId}: seasonal strength=${d.seasonalStrength}, trend strength=${d.trendStrength}`);
  // Each point has: observed, trend, seasonal, residual
}

7. Forecast Accuracy Scoring

Measure how well your forecasts match reality:

const forecasts = forecaster.forecastAll('2025-06-01', 30);
const actuals = [
  { itemId: 'sunset-cruise', date: '2025-06-01', quantity: 45 },
  // ... actual outcomes
];

const report = forecaster.score(forecasts, actuals);
console.log(`MAPE: ${report.overall.mape}%`);   // Mean Absolute Percentage Error
console.log(`RMSE: ${report.overall.rmse}`);     // Root Mean Squared Error
console.log(`MAE: ${report.overall.mae}`);       // Mean Absolute Error
console.log(`Bias: ${report.overall.bias}`);     // Positive = over-predicting
console.log(`Hit rate: ${report.overall.hitRate}%`); // Within ±20% tolerance

8. Multi-Item Correlation

Discover which items move together:

const result = forecaster.correlate({ minOverlap: 30 });

for (const pair of result.pairs) {
  console.log(`${pair.itemA} ↔ ${pair.itemB}: ${pair.correlation} (${pair.relationship})`);
  // sunset-cruise ↔ island-tour: 0.82 (strong_positive)
  // sunset-cruise ↔ museum-pass: -0.45 (moderate_negative)
}

9. Holiday Calendar

Flag dates near holidays that may affect demand:

import { HOLIDAY_PRESETS } from 'seasonal-forecast';

forecaster.setHolidays([
  ...HOLIDAY_PRESETS.international,
  { name: 'Local Festival', monthDay: '08-15', impact: 'high' },
]);

const matches = forecaster.checkHoliday('2026-12-25');
// [{ holiday: 'Christmas Day', distance: 0, impact: 'high', isExactMatch: true }]

// Annotate a date range
const annotations = forecaster.annotateHolidays('2026-12-20', '2027-01-05');

10. Data Import

Import data from CSV or JSON with auto-detection:

import { fromCSV, fromJSON } from 'seasonal-forecast';

// CSV with auto-detected columns and delimiters
const records = fromCSV(csvString);

// JSON in various formats (array, date-keyed, nested)
const records2 = fromJSON(jsonData);

11. Incremental Updates

Add data without full reload:

forecaster.loadHistory(historicalData);

// Later, add new data points
forecaster.addRecords([
  { itemId: 'sunset-cruise', date: '2026-03-23', quantity: 35 },
]);

12. Season Multipliers

forecaster.setSeasons([
  { startMonthDay: '06-01', endMonthDay: '09-30', multiplier: 1.3 },  // Summer: +30%
  { startMonthDay: '12-01', endMonthDay: '02-28', multiplier: 0.5 },  // Winter: -50%
]);

13. State Serialization

Cache analysis results for faster startup:

// Export state
const state = forecaster.exportState();
const json = toJSON(state);
fs.writeFileSync('forecast-cache.json', json);

// Import later
const newForecaster = new SeasonalForecaster();
newForecaster.importState(json);

CLI

# Analyze patterns
seasonal-forecast analyze data.csv

# Generate forecasts
seasonal-forecast forecast data.csv --days 14 --item sunset-cruise

# Detect anomalies
seasonal-forecast anomalies data.csv --threshold 2.0

# Decompose time series
seasonal-forecast decompose data.csv

# Export state
seasonal-forecast export data.csv --format json

Standalone Functions

All algorithms are also available as standalone functions:

import {
  analyzePatterns,
  generateForecasts,
  buildHistoricalLookup,
  buildCompletionRates,
  predictFinal,
  detectAnomalies,
  decompose,
  fromCSV,
  fromJSON,
  scoreAccuracy,
  correlate,
  matchHolidays,
  HOLIDAY_PRESETS,
  calcWeightedAvg,
  classifyDemand,
  getDoy,
  doyDistance,
} from 'seasonal-forecast';

How It Works

The DOY-Window Algorithm

Instead of complex ML models, we use a simple but powerful insight: the best predictor of demand on June 15 this year is demand around June 15 in previous years.

Target: June 15, 2026 (DOY 166)
Window: ±3 days → DOY 163-169

Year 2023: DOY 163=32, 165=41, 167=38  →  avg=37.0  weight=1
Year 2024: DOY 164=39, 166=45, 168=42  →  avg=42.0  weight=2
Year 2025: DOY 163=44, 165=48, 169=46  →  avg=46.0  weight=3

Weighted average: (37×1 + 42×2 + 46×3) / (1+2+3) = 43.2
Apply DOW coefficient (Sunday=0.85): 43.2 × 0.85 = 36.7
Apply trend (+8% YoY): 36.7 × 1.08 = 39.6
→ Predicted: 40 passengers

Why Not ML?

| Approach | seasonal-forecast | ML (Prophet, etc.) | |----------|-------------------|---------------------| | Setup time | npm install | Python env, model training | | Data needed | 1 year minimum | 2+ years recommended | | Runtime cost | < 1ms per forecast | 100ms-10s per forecast | | Cloud dependency | None | Often needs GPU/API | | Explainability | Full factor breakdown | "The model says..." | | Accuracy (seasonal) | Very good | Slightly better with 5+ years | | Maintenance | Zero | Model retraining, drift monitoring |

For seasonal businesses with 1-5 years of data, the statistical approach performs within 5-10% of ML models while being orders of magnitude simpler.

Use Cases

  • Tourism: Tour operators, activity providers, cruise lines
  • Hospitality: Hotels, resorts, vacation rentals
  • Restaurants: Table/cover forecasting, staff scheduling
  • Retail: Seasonal product demand, inventory planning
  • Events: Venue capacity planning, ticket sales forecasting
  • Agriculture: Seasonal labor and resource planning

API Reference

SeasonalForecaster

| Method | Description | |--------|-------------| | loadHistory(records) | Load historical quantity data | | addRecords(records) | Add data incrementally without full reload | | loadBookings(bookings) | Load booking-level data for completion rates | | setSeasons(seasons) | Define season multipliers | | analyze() | Get pattern analysis for all items | | getPattern(itemId) | Get pattern for a specific item | | forecast(itemId, date) | Single-item single-date forecast | | forecastAll(fromDate?, days?) | Bulk forecast for all items | | historicalAverage(itemId, date) | 3-tier historical average | | predictFinal(itemId, date, current) | Completion rate prediction | | matrix(itemIds, from, to, actuals?) | Full demand matrix | | classify(actual, historical, thresholds?) | Color classification | | lastYearComparison(itemId, date) | Compare with same DOW last year | | detectAnomalies(options?) | Find unusual data points | | decompose(options?) | Trend/seasonal/residual decomposition | | exportState() / importState(data) | Serialize/restore forecaster state | | score(forecasts, actuals, options?) | Measure forecast accuracy (MAPE, RMSE, MAE) | | correlate(options?) | Cross-item Pearson correlation | | setHolidays(holidays) | Set holiday/event definitions | | checkHoliday(date, proximity?) | Check date against holiday calendar | | annotateHolidays(from, to, proximity?) | Annotate date range with holidays |

Types

See src/types.ts for full TypeScript definitions.

Contributing

Contributions are welcome! Please open an issue first to discuss what you'd like to change.

License

MIT — Built with care at TourOperation.com