Skip to main content

Dressing a demo as another town

This page is for whoever runs a demo installation. A demo installation (CHIBAN_DEMO_MODE=true) is reset to the fictional Town of Alder Pond by pnpm demo:reset, or node dist/demo-reset.mjs inside the application image, which you run on a schedule. A demo overlay is a JSON file that dresses that reset as a particular town: its name, the office's contact lines, the starters it uses, its fees and where its boards post notice. Set CHIBAN_DEMO_OVERLAY_FILE to the file's path and every reset applies it.

When demo mode is off, the overlay is ignored and a line in the log says so. It never touches a real installation.

What a reset does with it​

Before the database is dropped, the reset reads the file and checks it against the overlay schema and against the starters shipped in the build. A file that fails either check stops the reset with every failing path listed, and the database is left as it was.

The rest goes through the same draft and publish steps the settings screens use, as the seeded administrator, so every value meets the same checks and lands in the configuration history and the audit log:

  1. The seed writes the town name from town.name wherever it would write Alder Pond.
  2. Before any demo case is created, the listed starters are adopted, and their forms and fee schedules are published with the overlay's changes. Seeded cases use those versions, so their fees match what a new application is charged.
  3. After the seed, boards are renamed, and the town profile, payment settings and board notice settings are merged over what the seed published.

A value refused at this stage fails the reset with the overlay path, for example demo overlay: townProfile.boards.housing_commission: there is no active housing_commission board. So does a seed that cannot run with the overlay, for example when an added required field is missing from the seeded building permits, or when a fee change leaves a seeded payment with nothing due. Either way, the reset then seeds the plain demo so the site stays usable, and exits with a non-zero status.

Checking a file​

pnpm demo:overlay:check path/to/overlay.json
node dist/demo-overlay-check.mjs path/to/overlay.json

Neither needs a database or any other setting. Both exit with a non-zero status and print each failing path when the file is invalid, and they name any starters this build does not have.

Format​

Every key is optional except version, and an unknown key at any depth is an error.

KeyWhat it sets
versionAlways 1
town.nameThe town name in the header, page titles and merge fields
townProfileofficeName, addressLines, phone, email, website, officeHours, letterheadFooter, and boards.<board kind> with officeName, addressLines and footerText for that board's letterhead
paymentSettingschecksPayableTo, receiptFooter, convenienceFee
startersApplication type keys to adopt, such as BP or VAR. A key this build does not ship is reported and skipped, so an overlay can name a starter before it is released
boards.<board kind>.name, shortNameRenames the one active board of that kind through the board admin, as the settings screen does
boards.<board kind>.noticeposting, publication, newspaper, bulletinBoards, websiteUrl for every active board of that kind
boards.<board kind>.noticeFeesperAbutterCents and advertisingCents. They set the notice_mail and notice_newspaper lines of every listed starter on that board, adding a line the starter lacks
applicationTypes.<key>.formFieldsFields appended to that type's form, in the form field format
applicationTypes.<key>.fees.<code>expr, in the fee expression format, and optionally label and due. An existing line keeps its label and stage unless given. A new code needs both

Every key under applicationTypes must also be listed in starters, so a mistyped key fails the check instead of being skipped. Amounts are in cents.

Example​

This example uses every section. It is the file the overlay tests run against.

demo-overlay.example.json
{
"version": 1,
"town": {
"name": "Town of Alder Pond"
},
"townProfile": {
"officeName": "Alder Pond Land Use Department",
"addressLines": [
"Alder Pond Town Hall",
"1 Main Street",
"Alder Pond, NH 03000"
],
"phone": "603-555-0199",
"email": "",
"website": "https://alderpond.example",
"officeHours": "Monday to Friday, 8 to 4",
"letterheadFooter": "Town of Alder Pond Land Use Department.",
"boards": {
"planning_board": {
"officeName": "Alder Pond Planning Board"
},
"zba": {
"officeName": "Alder Pond Zoning Board of Adjustment"
}
}
},
"paymentSettings": {
"checksPayableTo": "Town of Alder Pond",
"receiptFooter": "Questions about this receipt: Alder Pond Land Use Department."
},
"starters": [
"BP",
"EL",
"SG",
"SUB",
"SPR",
"VAR",
"AAD"
],
"boards": {
"planning_board": {
"notice": {
"newspaper": "Alder Pond Courier",
"bulletinBoards": [
"Alder Pond Town Hall lobby"
]
},
"noticeFees": {
"perAbutterCents": 900,
"advertisingCents": 8000
}
},
"zba": {
"notice": {
"newspaper": "Alder Pond Courier",
"bulletinBoards": [
"Alder Pond Town Hall lobby"
]
},
"noticeFees": {
"perAbutterCents": 900,
"advertisingCents": 8000
}
}
},
"applicationTypes": {
"BP": {
"fees": {
"building": {
"expr": {
"op": "sum",
"of": [
{
"op": "per_unit",
"source": {
"kind": "field",
"field": "finished_sq_ft"
},
"centsPerUnit": 32
},
{
"op": "per_unit",
"source": {
"kind": "field",
"field": "unfinished_sq_ft"
},
"centsPerUnit": 22
},
{
"op": "per_unit",
"source": {
"kind": "field",
"field": "commercial_sq_ft"
},
"centsPerUnit": 32
}
]
}
},
"plan_review": {
"label": "Plan review, set by staff when the building official requires it",
"due": "at_approval",
"expr": {
"op": "flat",
"cents": 0
}
}
}
},
"EL": {
"fees": {
"el": {
"expr": {
"op": "flat",
"cents": 6000
}
}
}
},
"SG": {
"formFields": [
{
"key": "sign_duration",
"label": "Temporary or permanent sign",
"type": "select",
"required": true,
"options": [
{
"value": "temporary",
"label": "Temporary"
},
{
"value": "permanent",
"label": "Permanent"
}
]
}
],
"fees": {
"sg": {
"expr": {
"op": "conditional_multiplier",
"when": {
"field": "sign_duration",
"op": "equals",
"value": "temporary"
},
"factor": 0.5,
"of": {
"op": "flat",
"cents": 3000
}
}
}
}
},
"SUB": {
"fees": {
"application": {
"expr": {
"op": "sum",
"of": [
{
"op": "flat",
"cents": 35000
},
{
"op": "per_unit",
"source": {
"kind": "field",
"field": "lot_count"
},
"centsPerUnit": 7500
}
]
}
}
}
},
"VAR": {
"fees": {
"application": {
"expr": {
"op": "flat",
"cents": 12500
}
}
}
},
"AAD": {
"fees": {
"application": {
"expr": {
"op": "flat",
"cents": 15000
}
},
"hearing": {
"label": "Hearing fee",
"due": "at_intake",
"expr": {
"op": "flat",
"cents": 15000
}
}
}
}
}
}

What it cannot set​

The overlay reaches configuration and board names. The seeded people, parcels and cases keep their Alder Pond values, and so does any board the overlay does not rename. A fee that depends on another application, or one that staff decide case by case, has no expression. The nearest is a zero line that staff set per application with a fee override.