llms.txt
# Writing a CalPal sheet
CalPal (https://trycalpal.app) is a calculator you type into in plain language.
A sheet is a list of lines; each line is either a "# " comment or an expression,
and each expression answers on its own line. Values carry units, dates and times
are values you can do arithmetic with, and a reader can type "total", "count" or
"split 4 ways" under any section to aggregate what is above it.
These are the rules for writing one that answers correctly. They exist because
each of them has been got wrong — usually by a model that knew the syntax and
not the consequences. A sheet that breaks them still computes; it just computes
something other than what was asked.
## A heading separates the inputs from the amounts
A "# " comment starts a new section, and the running aggregates — "total", "count", "split 4 ways" — see only the lines since the last one. The reader types those underneath what you wrote, so a sheet written as one block hands your own parameters to the sum. Put the values a calculation is driven by under one heading and the amounts themselves under the next. The same split applies to a rate, a headcount, a percentage or a unit price.
```
# Inputs
guests = 120 120
plate_cost = 850 850
# Event budget
catering = guests * plate_cost 102,000
venue = 40000 40,000
total 142,000
```
Not this — The 120 guests and the 850 per plate counted as two more costs.
```
# Event budget
guests = 120 120
plate_cost = 850 850
catering = guests * plate_cost 102,000
venue = 40000 40,000
total 142,970
```
## Put the currency on every amount, or on none of them
Where the request names a currency, write it on each amount: "850 INR", "$1200", "1200 USD". It carries through the arithmetic and it labels the answer. Half a currency is worse than none: a labelled amount and a bare one are two different kinds of number, so a total has to keep one kind and report the other as left out.
```
# Budget
rent = 1200 USD 1,200 USD
food = 400 USD 400 USD
total 1,600 USD
```
Not this — The unlabelled 400 is not money as far as the engine can tell, so the total dropped it.
```
# Budget
rent = 1200 USD 1,200 USD
food = 400 400
total 1,200 USD
```
## Leave "total", "count" and "results" to the reader
Those three names are the running aggregates. Assigning to one takes it away from the reader for the rest of the sheet, and they have no way to get it back. "total_cost", "guest_count" and "monthly_total" are all free.
```
Flight 45000 45,000
Hotel 30000 30,000
total_cost = Flight + Hotel 75,000
total 75,000
```
## A subtotal is safe to write
A line that is nothing but names already on the sheet, added or subtracted, is read as a view of those lines rather than as a new amount. It is shown, and it does not count itself into the total, so you can name a running subtotal without doubling the answer. Anything that introduces a quantity of its own — a literal, a rate, a percentage — is a new amount and does count.
```
# Event budget
venue = 12000 12,000
catering = 28000 28,000
total_cost = venue + catering 40,000
total 40,000
```
## Every line either answers or starts with "# "
There is no third kind of line, and a line that is neither does not fail loudly. It produces nothing and sits there looking like part of the sheet. If a line is a note, comment it. If it is a calculation, check it computes.
Not this — The note is not commented, so it sits in the sheet as a line that failed quietly.
```
# Notes
subtotal = 10 10
ask finance about the venue
total 10
```
## Never hedge a number
"about 900", "roughly 40", "approximately 12". A bare word in front of a number is read as a label for it, which is how "Dinner 2400" works and is usually what someone meant. A hedge is not a label. It becomes a variable called "about" holding 900, and the 900 joins the total as though it were certain. Write the number, or name the estimate: "venue_estimate = 900".
```
# Estimate
venue_estimate = 900 900
catering_estimate = 40 40
total 940
```
Not this — Two variables called "roughly" and "approximately" now exist, and the total reads as exact.
```
# Estimate
roughly 900 900
approximately 40 40
total 940
```
## Never pick a random number yourself
Write randomInt(), random() or pickRandom() and let the engine draw. A number you choose is a number you chose: asked for a number between one and ten, a language model says seven, because its training data is full of people saying seven, and seven is what it will say again tomorrow. That is a fine imitation of a person and a bad random number. The engine's draw is uniform, it is the same for everyone opening the sheet, and the reader can take another one.
```
# Roll a die
roll(sides) = randomInt(1, sides + 1) ƒ roll(sides)
roll(6) 4
```
Not this — Four forever. The sheet cannot roll again, because nothing in it rolls.
```
# Roll a die
roll = 4 4
```
## Never work out by hand what the engine already knows
Timezone offsets, working days, month ends, unit conversions and ratios are all built in, and they are right for the whole year. An offset written as a number is correct in January and an hour wrong in September, and the sheet stays confident about it the whole time. Write "9am New York to Kolkata", not the arithmetic.
```
# When the call lands
9am New York to Kolkata 9:00 AM America/New_York = 6:30 PM Asia/Kolkata
```
## Count people and items with ceil()
Nobody sells two thirds of a ticket or books 4.4 taxis. Wrap a count in ceil(), or in floor() where the remainder is genuinely discarded. Left alone the engine will answer 66.6667 and mean it.
```
# Tickets needed to cover the costs
total_cost = 40000 40,000
ticket_price = 600 600
# Tickets to sell
tickets_to_sell = ceil(total_cost / ticket_price) 67
```
## Name things in spelled-out snake_case
Never single letters: "e" and "i" are built-in constants, so a parameter called "e" silently shadows Euler's number. Separate arguments with a comma and a space — "min(5, 3)" — because a comma tight against three digits reads as a thousands separator.
## Check the sheet before you hand it over
Every one of these produces a sheet that computes. That is what makes them worth
writing down: nothing goes red, and the answer is simply a different number from
the one that was asked for. So read the answers, not just the lines. A total
larger than the amounts above it is a parameter being counted; a total smaller
than them is something being left out; an answer with no unit where the others
have one is the currency rule. Paste the sheet into https://trycalpal.app and
the answers appear beside the lines, which is the fastest way to see it.