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

fairnight

v0.2.1

Published

Open-source dynamic pricing engine for short-term rentals: a price, a minimum stay and an explanation for every night. Pure library, no network.

Readme

Fairnight

Open-source dynamic pricing engine for short-term rentals. Part of OpenStay.

npm install fairnight

Give it the price rules of a listing and its calendar of stays. For every night of the next N days it returns:

  • a price;
  • a minimum stay;
  • the list of factors that produced them, with what each one changed.

The engine is a pure library:

  • no network, no files, no database, no clock (you pass today), no runtime dependencies;
  • the same input always gives the same output.

This is the engine library. Hosts want fairnight-cli: it reads your calendars, runs this engine and shows a calendar page.

Usage

import { priceListing, parseEventsCsv } from "fairnight";

const listing = JSON.parse(listingJson); // see examples/annuncio-esempio.json
const { events } = parseEventsCsv(eventsCsvText); // optional: the host's own events
listing.factors.events = { enabled: true, list: events };

const result = priceListing(listing, { stays }, { today: "2026-11-01", days: 365 });
for (const night of result.nights) console.log(night.explanation);

Invalid input throws a ConfigError that lists every problem with its path. For example:

  • listing.factors.limits: floorPrice (300) is higher than ceilingPrice (200);
  • listing.factors.celing: unknown field.

Use validateListingConfig() instead if you want the list without an exception.

Factors

Every factor can be switched on and off. They are applied in this order:

| # | Factor | What it does | |---|---|---| | 1 | Base price | Starting point of every night. | | 2 | Seasons | Yearly periods (MM-DD → MM-DD, may wrap the new year) with a multiplier or a fixed price. The first matching period wins. | | 3 | Day of week | A percentage per weekday (e.g. Saturday +15%). | | 4 | Last minute | Discount steps by distance from the night: within 3 days -15%, within 7 days -8%… The tightest step applies. | | 5 | Far out | Premium steps for nights far ahead: from 120 days +5%, from 240 days +10%. The farthest step reached applies. | | 6 | Gaps | Free stretches of up to N nights between two stays (or blocks): a discount, and a minimum stay lowered to the gap length so one booking can fill it. If a last-minute discount also applies, only the larger of the two counts. | | 7 | Occupancy | Within the next N days (default 60): if the month is booked above a threshold the price goes up, below another threshold it goes down. Only nights from today count, and blocked nights are left out. | | 8 | Zone demand (off by default) | Compares each night of the zone with the zone's own average, from zone data passed in calendar.market (the engine never fetches it). If the zone is at least 10% busier (or quieter) than usual, the price moves by half of that difference, at most +15% / -10%. Nights measured on fewer than 5 comparable listings are skipped. Weight, thresholds and caps are per listing. The explanation names the source and its attribution, and says when the occupancy is an estimate. | | 9 | Events | The host's own events (CSV: date, name, impact 1–5). Each impact level has a percentage (default 0 / 5 / 12 / 25 / 40). Overlapping events do not add up: the highest impact counts. | | 10 | Stale calendar (off by default) | No new booking for N days: a discount that grows step by step up to a cap, on the next nights only. It goes back to zero as soon as a new booking arrives. Needs lastBookingReceived in the calendar input. | | 11 | Historical floor (off by default) | The price never goes below a share (e.g. 75%) of the average price sold in the same month a year earlier, when there are enough sold nights. Needs priceHistory in the calendar input. | | 12 | Floor and ceiling | The price is kept between the two. | | 13 | Rounding | To a step (1, 5, 10…), never back outside floor and ceiling. | | 14 | Date overrides | A price and/or a minimum stay set by hand for a date. They win over everything, floor and ceiling included. |

Minimum stay follows this precedence:

  1. the date override;
  2. the far-out step;
  3. the last-minute step;
  4. the default.

After that the gap rule applies, but only if it lowers the minimum (and never below an override).

Reserved and blocked nights get no price (price: null).

Explanation of a night

Real output for the invented example in examples/, with today = 2026-11-01:

Sat 2027-02-13 · 230 EUR: base 100 → season "Carnival week" = 160 (fixed price) → sat +15% = 184
→ event "Jazz weekend" (impact 4) +25% = 230 (highest impact counts; also "Street market" (impact 2))
→ within floor 60 and ceiling 260. Min stay 3 (default).
Mon 2026-11-09 · 88 EUR: base 100 → gap of 2 nights -12% = 88 (last-minute discount 4% not added)
→ within floor 60 and ceiling 260. Min stay 2 (lowered to fill the gap).

With zone data (invented here, from the host's own CSV):

Sat 2026-11-14 · 115 EUR: base 100 → zone demand "costa" +15% = 115 (zone 90% booked vs 61% average
· capped at +15% · 25 comparable listings · your file "zona.csv" · read 2026-11-01). Min stay 2 (default).

The same information is available as data: night.steps holds { factor, label, before, after, percent?, note? } for every step.

Configuration

See examples/annuncio-esempio.json for a complete listing with every factor. All data there is invented. The types are in src/engine/types.ts.

Example files:

  • examples/prenotazioni-esempio.json: the stays (checkIn = first night, checkOut = departure day, optional kind: "blocked");
  • examples/eventi-esempio.csv: an events file.

What is there and what is not (yet)

The engine is at its first step. The full list of the functions of a pricing engine, with the state of each one in Fairnight, is in docs/mappa-funzioni.md (in Italian).

Around the engine (other OpenStay packages): iCal calendar sync, CSV import of past prices, a local calendar page, CSV export and a read-only MCP server.

Phase 2:

  • rules per group and portfolio, reusable rule sets, preview before applying;
  • length-of-stay discounts, extra-guest fees, check-in/out days;
  • more channel manager connectors (Smoobu and Beds24 are done, with the daily autopilot);
  • alerts;
  • portfolio analytics and pacing against last year;
  • backtesting on a past year;
  • MCP with write access behind confirmation.

Phase 3:

  • market data (comparable listings, neighbourhood percentiles) through optional adapters that use the user's own key;
  • base-price suggestions (a first version from zone data is done);
  • computed minimum stay;
  • demand forecasting.

Not planned:

  • direct Airbnb/Vrbo connections, which need partner agreements;
  • listing-quality scoring;
  • any scraping, ever.

Development

npm install            # at the repo root
npm run check          # typecheck + lint + tests
npm run coverage -w fairnight
npm run regen:expected -w fairnight   # after an intentional change to the engine

The regression test prices 365 nights of the example and compares them with test/fixtures/expected-365.json. If you change the engine on purpose, regenerate that file and review the diff.

License

AGPL-3.0-only. See LICENSE.