Files
portal/runs/permits-plan-2026-09-30/llm/03-prompt.md
T
Bendik Aagaard LynghaugandClaude Fable 5.1 3dabcf7c58 build: one page holds every need that answers a record; runs: the kommune planned again by Opus
Two needs about the same record wrote the same page, and the last won.
The record's page now holds one form per answering need, under
words["forms"][need].

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
2026-09-30 08:58:55 +02:00

38 KiB

plan.yaml

A plan is a needs spec that also says what each form collects. A builder turns it into a working site with no further decisions, so every structural fact about the business has to be in it.

business: <name>
language: en                  # the intake's language code
contact: { name: <who answers for the site>, email: <their email> }
about: >
  What the business does, in its own words.
mail: { from: "<name> <post@their-domain>", reply_to: <their inbox> }
                              # who every mail the site sends comes from; required as soon
                              # as any need has `mails`

does:                         # the front door's first card: what the business does, one
  - id: kebab-case            # or two features, one role each, no button. Say it for the
    for: <need id>            # role the business exists for and the next one it serves.
    text: what it does for them, in the intake's own words

shows:                        # what the site shows as it is: a live figure, the latest
  - id: kebab-case            # releases, what has been built; `resource` is portal's block
    text: one line on what it is
    resource: { public: true, source: { kind: url, url: https://... } }

doors:                        # a first question before the forms, only when the offers
  - id: kebab-case            # are more than one page holds (measured: four doors beat
    who: who comes through it # ten offers on one page; six offers on one page beat doors)

groups:                       # groups with a label the public may see: the committees
  - { id: traffic, label: Traffic, invited_by: [board] }   # a person can ask to join (optional; label defaults to the id)
invites_by: [board]           # groups whose desk may invite people into every desk

needs:
  - id: kebab-case            # ASCII only
    who: the kind of person, in plain words, in the intake's language
    wants: what they want from the business
    door: <door id>           # the door this public need sits behind; omit to put it on the front door
    enters_as: <group>        # ONLY for a role inside the business; omit for the public
    lands_in: <bucket>        # snake_case, ASCII
    handled_by: <group>       # the role that must see and move the record
    stages: [open, <stage>, ..., <end>, <end>]
                              # EVERY stage the business said a case goes through, first to
                              # last, read off their own sentences one verb at a time: "they
                              # come in, we go and look, we send a quote, we win or lose it"
                              # is [open, surveyed, quoted, won, lost]. A pipeline is not an
                              # inbox: "answered" is rarely the real outcome.
    done_when: [<end>, <end>] # every stage that counts as finished, good or bad
    success: [<end>]          # the finished stages that are a good outcome for the business
    skippable: [<stage>]      # stages a case may pass over (papers already complete)
    requires: <one sentence>    # what the person must ALREADY have or be for this to work:
                              # a membership, a reference number, the link from a mail, an
                              # account. Stated on the card above the form. Omit when there
                              # is none; never invent one
    turned_down_from: [open, <stage>]
                              # the unfinished stages a case can still be turned down from;
                              # omit for every one. An applicant is declined before they
                              # start, not after - after, they depart
    reached_from: { <stage>: [<stage>, ...] }
                              # where a stage is entered from, when the line does not say it.
                              # Two things need it and nothing else does: a case SENT BACK to
                              # a stage it was already past ("papers missing" again, after the
                              # hearing turned something up), and a GOOD ENDING partway along,
                              # where a case finishes well without walking to the end ("we
                              # changed our own decision", instead of passing it upwards).
                              # A stage named here is entered from exactly those stages and
                              # nowhere else, so it also says where a refusal may NOT come
                              # from. Omit unless the business described one of those two
    moves_by: { <stage>: [<group>, ...] }
                              # only where someone other than handled_by moves a case INTO a
                              # stage: another group, or `submitter` (the person who sent the
                              # form; the form must then collect a required `email`)
    watched_by: [<group>]     # groups that see these cases and move nothing; `public` opens a
                              # read-only page of the bucket to everyone (contact details hidden)
    mails: { open: submitter, <end>: submitter, <stage>: <address> }
                              # who hears when a case enters a stage: `submitter` (the form
                              # must then collect a required `email`) or one address. Tell the
                              # person on arrival and on every decision that is about them.
    grants: { <stage>: <group> }
                              # a stage that makes the person a member of a group when their
                              # case arrives (an approval that IS membership); the form must
                              # then collect a required `name` and a required `email`
    deadlines: { <stage>: { days: 30, to: <stage> } }
                              # a stage a clock moves on from when a case sits in it that long
                              # (a notice that expires, a hearing with no answer); only along
                              # a move the stages already allow
    public_states: [<stage>]  # with `public` in watched_by: the stages the public list shows
                              # (a noticeboard shows what is up, not what was taken down)
    stages_text: { open: <words>, <stage>: <words> }
                              # each stage in the intake's language, where the ASCII id
                              # would read wrong to a person: "ikke_lost" is "ikke løst"
    events: { open: applied } # ONLY when rebuilding a live site: the event names its
                              # aggregates.yaml already has, so its records keep replaying
    attended_by: >            # ONLY for a stage no desk button moves: the automation that does
      what moves it, in a sentence
    max_fields: 4             # decide this BEFORE writing collects, from what the intake
                              # says about the person; a budget that equals the form's size
                              # is counted, not decided, and is reported as such
                              # how many required fields THIS person will tolerate, from what
                              # the intake says about them: someone on a phone with gloves
                              # on gets 3, a first enquiry 4 to 6, a firm's paperwork 5 to 6
    collects:                 # the form; name is snake_case ASCII, type one of
                              # text (default), email, tel, textarea, date, select, location
                              # (a pin on a map, stored as lat/lon/zoom; optional: true,
                              # since a map needs a browser that can draw one)
      - { name: name }
      - { name: phone, type: tel }
      - { name: problem, type: textarea }
      - { name: urgency, type: select, options: [This week, This month, No rush] }
      - { name: can_do, type: select, multiple: true, options: [Drive, Cook, Carry] }   # any number
      - { name: committee, type: select, options_from: groups }   # the labelled groups above
      - { name: address, optional: true, private: true }          # never on a public page

What a portal site can do - use what serves this business, leave the rest. Each is one line in the plan, and the builder does the wiring:

  • Show, not only ask. A bucket with watched_by: [public] becomes a public list: its newest few records sit inline on the front door, linked to the whole list, contact details pruned. What neighbours reported and where it stands, which changes were approved, what is on the noticeboard: a visitor who sees the place working trusts it, and a person who reported something sees it being dealt with. public_states shows only the stages worth showing.
  • Stay in dialogue. mails tells the person at the stages that are about them, and a reply to that mail comes back to the case as a note. moves_by: { <stage>: [submitter] } gives them a move of their own through their link: confirm, withdraw, say it is solved.
  • Let a decision do the work. grants makes an approval into membership: the person gets an account and a place in the group the moment the case gets there; deadlines lets a stage end on its own (a notice that expires, a request nobody took up); invites_by lets a desk bring a person in without anyone touching an admin tool.
  • Let one record answer another. A need about another need is one answer per record of it, from that record's own row on the public list: a neighbour saying "I can lend you that" to a notice, a bid to a request. mails: { open: owner } sends the answer straight to whoever posted the record, and the site keeps no queue - the poster's inbox is the queue. The site never becomes a go-between.
  • Keep some things for members. A need with door and enters_as is offered to everyone on the door and taken on the group's page behind sign-in, with name and address filled from the account: a seat on a committee for members, a discount for customers. The group is one a decision grants, so nobody has to be waved in.
  • Answer for the pages as a group. contact: { name, email, group } makes the contact a group's inbox rather than a person: no account is made for it, and every member of the group may suggest changes to the pages from the page itself.
  • Make the choices real. groups with labels, and a select with options_from: groups, so a form offers exactly the committees or teams that exist; multiple: true where a person can pick several.
  • Place things. A location field puts a pin on a map (where the hazard is, where the job is), when the site has maps.
  • Open with the business itself. does says what it does for the roles it exists for; shows puts its live content beside that.

Rules:

  • One need per (kind of person, thing they want). Work inside the business counts: a role recording or posting something is a need with enters_as. The intake's "what slows us down" usually names one.
  • Groups are the roles the intake names, as snake_case identifiers.
  • The front door opens with does (at most two features, one role each) and shows, then holds at most seven offers: doors plus needs with no door. A door with no need behind it is refused; a door on a need with enters_as is the way to the members' page. A role does speaks for must have a move of its own after sending (moves_by with submitter) or a public view, or it is a spectator.
  • Every need whose form collects an email should tell the person on arrival and on the decisions about them (mails); the plan then needs mail: { from }.
  • Every feature on a card explains itself: a list says what one row is and what its buttons do, a form says what to have ready. If an alternative only works for someone who already has something, say so in requires - a condition the reader meets only after filling the form in has already cost them the filling in.
  • Needs that land in the same bucket must have the same stages, done_when, skippable, moves_by, watched_by and handled_by.
  • Every required field is one the business will use to act on the record. If the intake gives no reason to need it, make it optional or leave it out. The required fields must not exceed max_fields.
  • Only select lists options, and it must list them.
  • Everything a person reads (who, wants, about, select options) is in the intake's language. Everything a machine reads (ids, buckets, groups, stages, field names) is plain ASCII: no æ, ø, å.
  • Write no personas.

Task

A business filled in this intake. Write its plan.yaml: every need the intake implies, for the public and for each role inside the business, with the stages read off how the business says things progress, the form each need collects, and how much each person will tolerate.

Intake

A building authority's intake: the hardest sector the trainer has

been given. Cases that pause and resume, cases that belong to other

cases, a decision two roles sign, a deadline nobody in the building

controls, and an appeal that reopens something already finished.

Written the way the department head would write it, in plain words.

business: Vestmark kommune - Building and Planning contact: { name: Solveig Hauge, email: byggesak@vestmark.example } language: en

what_we_do: > We are the building authority for a municipality of twenty-one thousand people. Anyone who wants to build, demolish, divide a property or change what a building is used for has to come to us first. We check the drawings against the zoning map and the building regulations, we let the neighbours and the other authorities have their say, and then we approve or refuse. We also decide exemptions from the zoning rules, we inspect what is actually built, we sign off finished work, and we deal with building that was never applied for.

who_comes_to_us: > Homeowners who want a garage, an extension or a new window, and who have never read a regulation in their life. Architects and engineering firms who file on behalf of owners; they have to be approved by us before they may file anything. Neighbours who got a notice in the post and do not agree with what is planned next door. The other authorities we are obliged to ask: the road authority, the fire service, the heritage office, the county. People who ring to say something is going up behind their fence without permission. And anyone who wants to sit down with us before they spend eighty thousand on drawings.

what_we_keep_track_of: > Pre-application meetings: someone asks for one, we find a time, we meet, and we write down what we told them so they can hold us to it. Applications: they arrive, someone checks whether the papers are all there, and if something is missing we write and ask for it, and the twelve-week clock stops until they answer. Often we have to ask twice. When it is complete we send it out to the neighbours and to the agencies and wait six weeks. Then a case officer writes a recommendation, and the head of section signs the decision: approved, approved with conditions, or refused. People can withdraw at any point, and quite a few do once they hear from the neighbours. Exemptions: if what someone wants breaks the zoning rules they can ask for an exemption. It rides along with the application but it is a decision of its own, and we are not allowed to grant one before the county has been asked. Objections: a neighbour who got a notice writes to us within the hearing period. Each objection belongs to an application. We register it, the applicant gets to answer it, and the case officer has to address every single one in the decision, by name. Hearing answers: we ask an agency, they answer, or they do not, and when the deadline has passed we go on without them. Appeals: for three weeks after a decision anyone with an interest can appeal it. We go through it ourselves first, and if we change our own mind that is the end of it. If we do not, it goes to the appeals board, who meet once a month and either uphold us or overturn us. Inspections: we pick cases and go and look, on site. Either it is as approved, or we write up what is wrong and set a date for it to be put right, and then we go back. Finishing: when the work is done the firm asks for a completion certificate. If they want to move in before everything is done they can ask for a temporary permission instead. We check what was inspected and what is still outstanding before we sign anything. Illegal building: someone reports it, we go and look, we write to the owner and they have to explain themselves, and then they either apply for it properly, take it down, or we fine them weekly until they do. Approved firms: a firm sends us their registration, their insurance and what they are qualified for, we check it, and then they are on the list and may file applications.

who_is_involved: > The front desk logs what comes in over the counter and on the phone, and keeps the archive. Four case officers carry the cases. The head of section signs every decision and every exemption; a case officer may not sign their own. Two inspectors are out on site most of the week and write up what they find on a phone in a cold wind. The appeals board is seven elected politicians who meet on the third Tuesday of the month and read what we send them. The agencies are outside the building altogether and only ever answer hearings. The approved firms file applications and answer objections on behalf of their clients.

what_slows_us_down: > The twelve-week clock, and the fact that stopping it and starting it again is done by hand in a spreadsheet. Applications that arrive missing half the drawings. Objections that come in on paper and get stapled to the wrong case. Neighbours ringing to ask what is happening with the case behind their house, which we are obliged to tell them and which takes ten minutes every time. Agencies that never answer at all. And nobody outside this building can see a case without asking us for it.

Your last attempt was refused

need "inspection": stages_text names "then we go back", which is not one of its stages

Fix that and return the whole plan again.

business: Vestmark kommune - Building and Planning
language: en
contact: { name: Solveig Hauge, email: byggesak@vestmark.example }
about: >
  We are the building authority for a municipality of twenty-one
  thousand people. Anyone who wants to build, demolish, divide a
  property or change what a building is used for has to come to us
  first. We check the drawings against the zoning map and the building
  regulations, we let the neighbours and the other authorities have
  their say, and then we approve or refuse. We also decide exemptions
  from the zoning rules, we inspect what is actually built, we sign off
  finished work, and we deal with building that was never applied for.
mail: { from: "Vestmark kommune Building and Planning <post@vestmark.example>", reply_to: byggesak@vestmark.example }

does:
  - id: check-and-decide
    for: application
    text: >
      We check your drawings against the zoning map and the building
      regulations, let the neighbours and the other authorities have
      their say, and then approve or refuse. Every case is on the list
      here, so you and your neighbours can follow it the whole way,
      and object to it or appeal it from its own row.
  - id: firms-file-here
    for: firm-application
    text: >
      Approved firms file applications for their clients here, see what
      is missing and ask for the completion certificate when the work is done.

doors:
  - id: build
    who: You want to build, demolish, divide a property or change what a building is used for
  - id: firm
    who: You are an architect or an engineering firm filing on behalf of owners

invites_by: [head_of_section]

needs:
  - id: pre-application-meeting
    who: Anyone who wants to sit down with us before they spend money on drawings
    wants: A meeting with a case officer, and a written note of what we told them
    door: build
    lands_in: pre_application_meetings
    handled_by: case_officers
    stages: [open, scheduled, met, written_up, cancelled]
    done_when: [written_up, cancelled]
    success: [written_up]
    turned_down_from: [open, scheduled]
    moves_by: { cancelled: [submitter, case_officers] }
    watched_by: [front_desk]
    mails: { open: submitter, scheduled: submitter, written_up: submitter, cancelled: submitter }
    stages_text: { open: asked for, scheduled: time found, met: meeting held, written_up: written down and sent, cancelled: cancelled }
    max_fields: 4
    collects:
      - { name: name }
      - { name: email, type: email }
      - { name: property_address }
      - { name: what_you_plan, type: textarea }
      - { name: phone, type: tel, optional: true }
      - { name: place, type: location, optional: true }

  - id: application
    who: A homeowner who wants a garage, an extension, a new window or another change to their property
    wants: Permission to build, demolish, divide a property or change what a building is used for
    door: build
    lands_in: applications
    handled_by: case_officers
    stages: [open, papers_missing, papers_received, complete, in_hearing, hearing_over, recommended, approved, approved_with_conditions, refused, withdrawn]
    done_when: [approved, approved_with_conditions, refused, withdrawn]
    success: [approved, approved_with_conditions]
    skippable: [papers_missing, papers_received]
    reached_from:
      papers_missing: [open, papers_received, complete, in_hearing]
      refused: [recommended]
    moves_by:
      papers_received: [submitter]
      approved: [head_of_section]
      approved_with_conditions: [head_of_section]
      refused: [head_of_section]
      withdrawn: [submitter, case_officers]
    watched_by: [public, front_desk, inspectors]
    public_states: [open, papers_missing, papers_received, complete, in_hearing, hearing_over, recommended, approved, approved_with_conditions, refused]
    mails:
      open: submitter
      papers_missing: submitter
      complete: submitter
      in_hearing: submitter
      approved: submitter
      approved_with_conditions: submitter
      refused: submitter
      withdrawn: submitter
    deadlines: { in_hearing: { days: 42, to: hearing_over } }
    stages_text:
      open: received
      papers_missing: papers missing, clock stopped
      papers_received: missing papers sent in
      complete: complete, clock running
      in_hearing: out to neighbours and agencies
      hearing_over: hearing over
      recommended: recommendation written
      approved: approved
      approved_with_conditions: approved with conditions
      refused: refused
      withdrawn: withdrawn
    max_fields: 5
    collects:
      - { name: name, private: true }
      - { name: email, type: email, private: true }
      - { name: property_address }
      - { name: kind_of_work, type: select, options: [New building, Extension, Garage or outbuilding, Window or facade change, Demolition, Dividing a property, Change of use] }
      - { name: what_you_want_to_do, type: textarea }
      - { name: enclosed, type: select, multiple: true, optional: true, options: [Site plan, Floor plans, Elevations, Section drawing, Neighbour notices sent, Statement from the owner] }
      - { name: phone, type: tel, optional: true, private: true }
      - { name: place, type: location, optional: true }

  - id: firm-application
    who: An approved architect or engineering firm filing on behalf of an owner
    wants: To file a building application for a client and see what is missing
    door: firm
    enters_as: approved_firms
    requires: Your firm must be on our list of approved firms before it may file anything.
    lands_in: applications
    handled_by: case_officers
    stages: [open, papers_missing, papers_received, complete, in_hearing, hearing_over, recommended, approved, approved_with_conditions, refused, withdrawn]
    done_when: [approved, approved_with_conditions, refused, withdrawn]
    success: [approved, approved_with_conditions]
    skippable: [papers_missing, papers_received]
    reached_from:
      papers_missing: [open, papers_received, complete, in_hearing]
      refused: [recommended]
    moves_by:
      papers_received: [submitter]
      approved: [head_of_section]
      approved_with_conditions: [head_of_section]
      refused: [head_of_section]
      withdrawn: [submitter, case_officers]
    watched_by: [public, front_desk, inspectors]
    public_states: [open, papers_missing, papers_received, complete, in_hearing, hearing_over, recommended, approved, approved_with_conditions, refused]
    mails:
      open: submitter
      papers_missing: submitter
      complete: submitter
      in_hearing: submitter
      approved: submitter
      approved_with_conditions: submitter
      refused: submitter
      withdrawn: submitter
    deadlines: { in_hearing: { days: 42, to: hearing_over } }
    stages_text:
      open: received
      papers_missing: papers missing, clock stopped
      papers_received: missing papers sent in
      complete: complete, clock running
      in_hearing: out to neighbours and agencies
      hearing_over: hearing over
      recommended: recommendation written
      approved: approved
      approved_with_conditions: approved with conditions
      refused: refused
      withdrawn: withdrawn
    max_fields: 6
    collects:
      - { name: name, private: true }
      - { name: email, type: email, private: true }
      - { name: property_address }
      - { name: kind_of_work, type: select, options: [New building, Extension, Garage or outbuilding, Window or facade change, Demolition, Dividing a property, Change of use] }
      - { name: what_you_want_to_do, type: textarea }
      - { name: enclosed, type: select, multiple: true, options: [Site plan, Floor plans, Elevations, Section drawing, Neighbour notices sent, Statement from the owner] }
      - { name: owner_name, optional: true, private: true }
      - { name: phone, type: tel, optional: true, private: true }
      - { name: place, type: location, optional: true }

  - id: exemption
    who: An applicant whose plans break the zoning rules
    wants: An exemption from the zoning rules, decided alongside their application
    about: application
    requires: An application with us; the exemption rides along with it and is asked for from its row on the list of cases.
    lands_in: exemptions
    handled_by: case_officers
    stages: [open, county_asked, county_heard, granted, refused]
    done_when: [granted, refused]
    success: [granted]
    moves_by: { granted: [head_of_section], refused: [head_of_section] }
    watched_by: [front_desk]
    mails: { open: submitter, county_asked: submitter, granted: submitter, refused: submitter }
    deadlines: { county_asked: { days: 42, to: county_heard } }
    stages_text: { open: received, county_asked: county asked, county_heard: county has answered or its deadline has passed, granted: granted, refused: refused }
    max_fields: 4
    collects:
      - { name: name }
      - { name: email, type: email }
      - { name: rule_it_breaks }
      - { name: why_it_should_be_allowed, type: textarea }

  - id: objection
    who: A neighbour who got a notice in the post and does not agree with what is planned next door
    wants: To object to an application and have the objection answered in the decision
    about: application
    requires: The application must still be out for hearing; objections are taken until the six weeks are up.
    lands_in: objections
    handled_by: case_officers
    stages: [open, registered, answered, addressed]
    done_when: [addressed]
    success: [addressed]
    skippable: [answered]
    moves_by: { registered: [front_desk, case_officers], answered: [approved_firms, case_officers] }
    watched_by: [front_desk, head_of_section]
    mails: { open: submitter, registered: owner, addressed: submitter }
    stages_text: { open: received, registered: registered on the case, answered: answered by the applicant, addressed: addressed in the decision }
    max_fields: 4
    collects:
      - { name: name }
      - { name: email, type: email }
      - { name: your_property }
      - { name: objection, type: textarea }
      - { name: phone, type: tel, optional: true }

  - id: logged-objection
    who: The front desk, when an objection comes in on paper or over the counter
    wants: To put the objection on the right case straight away, instead of stapling it to a folder
    enters_as: front_desk
    lands_in: paper_objections
    handled_by: case_officers
    stages: [open, registered, answered, addressed]
    done_when: [addressed]
    success: [addressed]
    skippable: [answered]
    moves_by: { registered: [front_desk, case_officers], answered: [approved_firms, case_officers] }
    watched_by: [front_desk, head_of_section]
    stages_text: { open: received, registered: registered on the case, answered: answered by the applicant, addressed: addressed in the decision }
    max_fields: 5
    collects:
      - { name: application_reference }
      - { name: name }
      - { name: objection, type: textarea }
      - { name: received_on, type: date }
      - { name: your_property, optional: true }
      - { name: phone, type: tel, optional: true }

  - id: hearing-request
    who: A case officer who is obliged to ask another authority about an application
    wants: To ask the road authority, the fire service, the heritage office or the county, and go on when the deadline has passed
    enters_as: case_officers
    lands_in: hearing_answers
    handled_by: case_officers
    stages: [open, answered, no_answer]
    done_when: [answered, no_answer]
    success: [answered]
    moves_by: { answered: [agencies, case_officers] }
    watched_by: [agencies, front_desk]
    deadlines: { open: { days: 42, to: no_answer } }
    stages_text: { open: asked, answered: answered, no_answer: deadline passed without an answer }
    max_fields: 4
    collects:
      - { name: application_reference }
      - { name: agency, type: select, options: [Road authority, Fire service, Heritage office, County] }
      - { name: what_we_ask, type: textarea }
      - { name: answer_by, type: date, optional: true }

  - id: appeal
    who: Anyone with an interest in a decision, the applicant or a neighbour
    wants: To appeal a decision we have made
    about: application
    requires: A decision on the application, made no more than three weeks ago.
    lands_in: appeals
    handled_by: case_officers
    stages: [open, under_review, decision_changed, sent_to_board, upheld, overturned, dismissed]
    done_when: [decision_changed, upheld, overturned, dismissed]
    success: [decision_changed, upheld, overturned]
    turned_down_from: [open, under_review]
    reached_from: { decision_changed: [under_review] }
    moves_by: { decision_changed: [head_of_section], upheld: [appeals_board], overturned: [appeals_board] }
    watched_by: [appeals_board, head_of_section, front_desk]
    mails: { open: submitter, decision_changed: submitter, sent_to_board: submitter, upheld: submitter, overturned: submitter, dismissed: submitter }
    stages_text: { open: received, under_review: we are going through it ourselves, decision_changed: we changed our own decision, sent_to_board: sent to the appeals board, upheld: decision upheld by the board, overturned: decision overturned by the board, dismissed: dismissed }
    max_fields: 4
    collects:
      - { name: name }
      - { name: email, type: email }
      - { name: your_interest, type: select, options: [I am the applicant, I am a neighbour, Other] }
      - { name: grounds, type: textarea }
      - { name: phone, type: tel, optional: true }

  - id: illegal-building-report
    who: Someone who sees something going up behind their fence without permission
    wants: To tell us, so that we go and look
    lands_in: illegal_building
    handled_by: case_officers
    stages: [open, inspected, owner_written_to, explained, fined, applied_for, taken_down, nothing_illegal]
    done_when: [applied_for, taken_down, nothing_illegal]
    success: [applied_for, taken_down]
    skippable: [fined]
    turned_down_from: [open, inspected, explained]
    moves_by: { inspected: [inspectors], fined: [head_of_section] }
    watched_by: [front_desk, inspectors]
    stages_text: { open: reported, inspected: we have been and looked, owner_written_to: owner written to, explained: owner has explained, fined: weekly fine running, applied_for: applied for properly, taken_down: taken down, nothing_illegal: nothing illegal found }
    max_fields: 3
    collects:
      - { name: where }
      - { name: what_is_being_built, type: textarea }
      - { name: place, type: location, optional: true }
      - { name: name, optional: true, private: true }
      - { name: phone, type: tel, optional: true, private: true }

  - id: logged-report
    who: The front desk, when someone rings or comes in about building without permission
    wants: To log the report so an inspector goes and looks
    enters_as: front_desk
    lands_in: illegal_building
    handled_by: case_officers
    stages: [open, inspected, owner_written_to, explained, fined, applied_for, taken_down, nothing_illegal]
    done_when: [applied_for, taken_down, nothing_illegal]
    success: [applied_for, taken_down]
    skippable: [fined]
    turned_down_from: [open, inspected, explained]
    moves_by: { inspected: [inspectors], fined: [head_of_section] }
    watched_by: [front_desk, inspectors]
    stages_text: { open: reported, inspected: we have been and looked, owner_written_to: owner written to, explained: owner has explained, fined: weekly fine running, applied_for: applied for properly, taken_down: taken down, nothing_illegal: nothing illegal found }
    max_fields: 3
    collects:
      - { name: where }
      - { name: what_is_being_built, type: textarea }
      - { name: place, type: location, optional: true }
      - { name: name, optional: true, private: true }
      - { name: phone, type: tel, optional: true, private: true }

  - id: inspection
    who: An inspector on site, writing on a phone in a cold wind
    wants: To write up what was found on a case and set a date for what must be put right
    enters_as: inspectors
    lands_in: inspections
    handled_by: inspectors
    stages: [open, to_put_right, as_approved]
    done_when: [as_approved]
    success: [as_approved]
    skippable: [to_put_right]
    watched_by: [case_officers, head_of_section]
    stages_text: { open: inspected, to_put_right: to be put right, then we go back, as_approved: as approved }
    max_fields: 3
    collects:
      - { name: application_reference }
      - { name: finding, type: select, options: [As approved, Something is wrong] }
      - { name: what_is_wrong, type: textarea, optional: true }
      - { name: put_right_by, type: date, optional: true }
      - { name: place, type: location, optional: true }

  - id: completion-certificate
    who: An approved firm whose work on a case is done
    wants: A completion certificate for the finished work
    door: firm
    enters_as: approved_firms
    requires: Your firm must be on our list of approved firms, and the application must be approved; have its reference ready.
    lands_in: completion_certificates
    handled_by: case_officers
    stages: [open, checked, issued, refused]
    done_when: [issued, refused]
    success: [issued]
    moves_by: { issued: [head_of_section], refused: [head_of_section] }
    watched_by: [inspectors, front_desk]
    mails: { open: submitter, issued: submitter, refused: submitter }
    stages_text: { open: asked for, checked: inspections and outstanding work checked, issued: certificate signed, refused: refused }
    max_fields: 5
    collects:
      - { name: name }
      - { name: email, type: email }
      - { name: application_reference }
      - { name: finished_on, type: date }
      - { name: what_was_built, type: textarea, optional: true }
      - { name: still_outstanding, type: textarea, optional: true }

  - id: temporary-permission
    who: An approved firm whose client wants to move in before everything is done
    wants: A temporary permission to use the building
    door: firm
    enters_as: approved_firms
    requires: Your firm must be on our list of approved firms, and the application must be approved; have its reference ready.
    lands_in: temporary_permissions
    handled_by: case_officers
    stages: [open, checked, issued, refused]
    done_when: [issued, refused]
    success: [issued]
    moves_by: { issued: [head_of_section], refused: [head_of_section] }
    watched_by: [inspectors, front_desk]
    mails: { open: submitter, issued: submitter, refused: submitter }
    stages_text: { open: asked for, checked: inspections and outstanding work checked, issued: temporary permission signed, refused: refused }
    max_fields: 6
    collects:
      - { name: name }
      - { name: email, type: email }
      - { name: application_reference }
      - { name: move_in_on, type: date }
      - { name: still_outstanding, type: textarea }
      - { name: finished_by, type: date, optional: true }

  - id: firm-approval
    who: An architect or engineering firm that wants to file applications for owners
    wants: To be approved by us and put on the list of firms that may file
    door: firm
    lands_in: approved_firm_requests
    handled_by: case_officers
    stages: [open, checked, listed, refused]
    done_when: [listed, refused]
    success: [listed]
    moves_by: { listed: [head_of_section], refused: [head_of_section] }
    watched_by: [public, front_desk]
    public_states: [listed]
    mails: { open: submitter, listed: submitter, refused: submitter }
    grants: { listed: approved_firms }
    stages_text: { open: received, checked: registration, insurance and qualifications checked, listed: on the list, refused: refused }
    max_fields: 6
    collects:
      - { name: firm }
      - { name: name, private: true }
      - { name: email, type: email, private: true }
      - { name: registration_number }
      - { name: insurance, private: true }
      - { name: qualified_for, type: select, multiple: true, options: [Applying on behalf of owners, Architectural design, Structural engineering, Fire safety design, Geotechnical engineering, Carrying out the work, Independent control] }
      - { name: phone, type: tel, optional: true, private: true }

Reply with the YAML only, no prose, no code fences.