Functional Weave
Code in Rust

hospitality.room-rate@1.0.0

README.md

1,958 bytes · view raw

# hospitality.room-rate

Prices a hotel stay night by night from a rate table the caller supplies, then
takes off the length-of-stay discount the stay qualifies for. The result lists
every night with its date, season and price, then the subtotal, discount and
total.

## The rate table is the hotel's

Rates are commercial decisions, not published rules, so there is no data file
here. Each row covers a range of nights (`firstNight` to `lastNight`,
inclusive) and, optionally, only some days of the week:

```
low          2026-01-01 .. 2026-03-31  Sun-Thu  90.00
low weekend  2026-01-01 .. 2026-03-31  Fri-Sat 110.00
high         2026-04-01 .. 2026-09-30  any     140.00
```

A night is named by the date it starts, so "Friday night" is the night of a
Friday, with the ISO weekday of that date (1 Monday to 7 Sunday).

**Exactly one row must match each night.** If none does, or two do, the
function fails and names the night. The obvious alternatives, "the last row
wins" or "the narrowest range wins", both quietly price a stay wrong the day
someone adds an event rate on top of a season without removing the season.
Make the rows disjoint, for example by splitting the season around the event.

## Length-of-stay discounts

`stayDiscounts` is a list of "at least n nights, x% off". The stay gets the
discount with the largest `minNights` it reaches (7 nights beats 3 nights), and
only that one. The percentage is taken off the subtotal once, rounded half-up,
so there is no per-night rounding drift.

## Edge cases

- A stay across 29 February, a month end or a year end steps through the
  calendar with `dates.add-days`.
- Every row must be in the same currency as the others the stay uses.
- A malformed or impossible date anywhere in the table or the arrival is an
  error.

## Not covered

Per-person pricing, occupancy supplements, children's rates, packages and taxes
(see `hospitality.tourist-tax` and `finance.tax.add-vat`) are separate steps.