From aef0fe13965913c7ed3be609e108a2f6e704aa8d Mon Sep 17 00:00:00 2001 From: Bendik Aagaard Lynghaug Date: Mon, 21 Sep 2026 21:08:08 +0200 Subject: [PATCH] build: slots instead of placeholders, and the first worded run Fallback words read like finished copy, and nemotron handed the front door straight back. The prompt now shows every string as a slot saying what belongs there; a slot echoed back counts as no answer and keeps the fallback. runs/contractor-build-2026-09-21: nemotron-3.5-lightning:30b-a3b, the model that produced no loadable site free-form, words a complete contractor site in 130 s. Verifier green by construction; held-out personas route 8/8 with descriptions and 8/8 on headings alone (qwen3:8b as the visitor). "What happens next" is still 0/2 on the front door: no heading or description says who reads a submission. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH --- README.md | 32 ++++ runs/contractor-build-2026-09-21/a-full.jsonl | 8 + runs/contractor-build-2026-09-21/a-skim.jsonl | 8 + .../aggregates.yaml | 75 +++++++++ runs/contractor-build-2026-09-21/needs.yaml | 149 ++++++++++++++++++ .../questions/crew/_section.yaml | 1 + .../questions/crew/index.yaml | 11 ++ .../crew/report-site-issue-sent.yaml | 10 ++ .../questions/crew/report-site-issue.yaml | 30 ++++ .../questions/index.yaml | 123 +++++++++++++++ .../questions/project_managers/_section.yaml | 1 + .../questions/project_managers/index.yaml | 98 ++++++++++++ .../log-phone-enquiry-sent.yaml | 10 ++ .../project_managers/log-phone-enquiry.yaml | 28 ++++ .../questions/received/build-enquiry.yaml | 10 ++ .../questions/received/job-application.yaml | 10 ++ .../received/subcontractor-application.yaml | 10 ++ .../questions/received/survey-enquiry.yaml | 10 ++ .../questions/safety_officer/_section.yaml | 1 + .../questions/safety_officer/index.yaml | 26 +++ runs/contractor-build-2026-09-21/rate.json | 77 +++++++++ runs/contractor-build-2026-09-21/run.log | 22 +++ runs/contractor-build-2026-09-21/site.yaml | 2 + src/build.rs | 35 +++- 24 files changed, 784 insertions(+), 3 deletions(-) create mode 100644 runs/contractor-build-2026-09-21/a-full.jsonl create mode 100644 runs/contractor-build-2026-09-21/a-skim.jsonl create mode 100644 runs/contractor-build-2026-09-21/aggregates.yaml create mode 100644 runs/contractor-build-2026-09-21/needs.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/crew/_section.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/crew/index.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/crew/report-site-issue-sent.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/crew/report-site-issue.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/index.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/project_managers/_section.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/project_managers/index.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/project_managers/log-phone-enquiry-sent.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/project_managers/log-phone-enquiry.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/received/build-enquiry.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/received/job-application.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/received/subcontractor-application.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/received/survey-enquiry.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/safety_officer/_section.yaml create mode 100644 runs/contractor-build-2026-09-21/questions/safety_officer/index.yaml create mode 100644 runs/contractor-build-2026-09-21/rate.json create mode 100644 runs/contractor-build-2026-09-21/run.log create mode 100644 runs/contractor-build-2026-09-21/site.yaml diff --git a/README.md b/README.md index e5e1648..1179226 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,38 @@ series for every role, the state machines behind them, and a desk per handling role. No software is written for the business; the only thing produced is content, and every line of it is held to a verifier. +## Build: structure from the plan, words from the model + +``` +portal-trainer build --plan plans/contractor.yaml --out out/nordvik \ + --lint question_lint --llm "MODEL=nemotron-3.5-lightning:30b-a3b engines/ollama.sh" +``` + +A `plan.yaml` is a needs spec that also says what each form collects. +Everything structural is derived from it with no model involved: the +state machines (read off `stages`: walk the line, be turned down at +any point, succeed only from the end, pass over `skippable` stages), +the front door, a followup page per need, a gated directory per group +with its desk and the pages only that group may open, and every button +the machine allows. The verifier accepts the result by construction. + +The model writes words only, one page at a time, into a JSON object +whose shape is fixed. Its strings are merged over built-in fallbacks +key by key, so a bad reply costs wording, never a working site. + +| Same contractor, same local model (nemotron 30B-A3B on ergo) | Free-form `generate` | `build` | +|---|---|---| +| A site the verifier accepts | never, in 6 rounds and 16 min | first try, 130 s | +| Held-out personas, with descriptions | - | 8 of 8 | +| Held-out personas, headings only | - | 8 of 8 | + +For comparison, a Sonnet-class model writing everything free-form +scored 8 of 10 on headings only and silently dropped a stage the owner +had described. `build` cannot drop a stage: it never decides one. + +`generate` (below) remains for turning a plain-words intake into a +first plan; what it should hand over is the plan, not the pages. + ## How it works ``` diff --git a/runs/contractor-build-2026-09-21/a-full.jsonl b/runs/contractor-build-2026-09-21/a-full.jsonl new file mode 100644 index 0000000..a964019 --- /dev/null +++ b/runs/contractor-build-2026-09-21/a-full.jsonl @@ -0,0 +1,8 @@ +{"id":"housing-board-damp@/","choice":"Get a professional building survey","confidence":0.95} +{"id":"buyer-before-purchase@/","choice":"Get a professional building survey","confidence":0.95} +{"id":"attic-conversion@/","choice":"Plan your building project with us","confidence":0.95} +{"id":"shop-to-flat@/","choice":"Plan your building project with us","confidence":0.95} +{"id":"electrician-firm@/","choice":"Become a trusted subcontractor","confidence":0.95} +{"id":"scaffolder@/","choice":"Become a trusted subcontractor","confidence":0.95} +{"id":"carpenter-jobseeker@/","choice":"Join our carpentry team","confidence":0.95} +{"id":"apprentice@/","choice":"Join our carpentry team","confidence":0.95} diff --git a/runs/contractor-build-2026-09-21/a-skim.jsonl b/runs/contractor-build-2026-09-21/a-skim.jsonl new file mode 100644 index 0000000..a964019 --- /dev/null +++ b/runs/contractor-build-2026-09-21/a-skim.jsonl @@ -0,0 +1,8 @@ +{"id":"housing-board-damp@/","choice":"Get a professional building survey","confidence":0.95} +{"id":"buyer-before-purchase@/","choice":"Get a professional building survey","confidence":0.95} +{"id":"attic-conversion@/","choice":"Plan your building project with us","confidence":0.95} +{"id":"shop-to-flat@/","choice":"Plan your building project with us","confidence":0.95} +{"id":"electrician-firm@/","choice":"Become a trusted subcontractor","confidence":0.95} +{"id":"scaffolder@/","choice":"Become a trusted subcontractor","confidence":0.95} +{"id":"carpenter-jobseeker@/","choice":"Join our carpentry team","confidence":0.95} +{"id":"apprentice@/","choice":"Join our carpentry team","confidence":0.95} diff --git a/runs/contractor-build-2026-09-21/aggregates.yaml b/runs/contractor-build-2026-09-21/aggregates.yaml new file mode 100644 index 0000000..07fe78f --- /dev/null +++ b/runs/contractor-build-2026-09-21/aggregates.yaml @@ -0,0 +1,75 @@ +aggregates: +- bucket: applicants + initial: open + states: + declined: + event: declined + hired: + event: hired + interviewing: + event: interviewing + open: + event: received + transitions: + interviewing: + - hired + - declined + open: + - interviewing + - declined +- bucket: enquiries + initial: open + states: + lost: + event: lost + open: + event: received + quoted: + event: quoted + surveyed: + event: surveyed + won: + event: won + transitions: + open: + - surveyed + - lost + quoted: + - won + - lost + surveyed: + - quoted + - lost +- bucket: site_issues + initial: open + states: + closed: + event: closed + in_hand: + event: in_hand + open: + event: received + transitions: + in_hand: + - closed + open: + - in_hand +- bucket: subcontractors + initial: open + states: + approved: + event: approved + awaiting_papers: + event: awaiting_papers + declined: + event: declined + open: + event: received + transitions: + awaiting_papers: + - approved + - declined + open: + - awaiting_papers + - approved + - declined diff --git a/runs/contractor-build-2026-09-21/needs.yaml b/runs/contractor-build-2026-09-21/needs.yaml new file mode 100644 index 0000000..8d86929 --- /dev/null +++ b/runs/contractor-build-2026-09-21/needs.yaml @@ -0,0 +1,149 @@ +needs: +- done_when: + - won + - lost + enters: / + handled_by: project_managers + id: survey-enquiry + lands_in: enquiries + max_fields: 4 + max_steps: 1 + stages: + - open + - surveyed + - quoted + - won + - lost + success: + - won + via: Get a professional building survey + wants: someone qualified to come and look at it and say what it takes + who: a housing board or private owner with a problem in a building +- done_when: + - won + - lost + enters: / + handled_by: project_managers + id: build-enquiry + lands_in: enquiries + max_fields: 5 + max_steps: 1 + stages: + - open + - surveyed + - quoted + - won + - lost + success: + - won + via: Plan your building project with us + wants: drawings, a permit application and a price + who: an owner who already knows what they want built or changed +- done_when: + - won + - lost + enters: /project_managers/log-phone-enquiry + enters_as: project_managers + handled_by: project_managers + id: log-phone-enquiry + lands_in: enquiries + max_fields: 4 + max_steps: 1 + stages: + - open + - surveyed + - quoted + - won + - lost + success: + - won + via: Log a phone enquiry + wants: the enquiry written down before it gets lost + who: a project manager who just took a call or a text about a building +- done_when: + - approved + - declined + enters: / + handled_by: project_managers + id: subcontractor-application + lands_in: subcontractors + max_fields: 5 + max_steps: 1 + stages: + - open + - awaiting_papers + - approved + - declined + success: + - approved + via: Become a trusted subcontractor + wants: to be approved once, and then be called when a job needs them + who: a trade firm doing electrical, plumbing or scaffolding work +- done_when: + - hired + - declined + enters: / + handled_by: project_managers + id: job-application + lands_in: applicants + max_fields: 3 + max_steps: 1 + stages: + - open + - interviewing + - hired + - declined + success: + - hired + via: Join our carpentry team + wants: to be considered, without writing a letter + who: a carpenter, bricklayer or apprentice looking for work +- done_when: + - closed + enters: /crew/report-site-issue + enters_as: crew + handled_by: safety_officer + id: report-site-issue + lands_in: site_issues + max_fields: 3 + max_steps: 1 + stages: + - open + - in_hand + - closed + success: + - closed + via: Report a safety issue on site + wants: to report a hazard, a near miss or wrong materials before they forget + who: anyone on site, ours or a subcontractor's, on a phone with gloves on +personas: +- id: housing-board-damp + need: survey-enquiry + text: I chair the board of a housing cooperative from the seventies. Two ground-floor flats have damp in the walls and we don't know if it's drainage or the facade. We need someone to tell us. +- id: buyer-before-purchase + need: survey-enquiry + text: We are about to buy an old timber house. Before we sign we want a professional to go through it and tell us what is wrong with it. +- id: attic-conversion + need: build-enquiry + text: We want to turn our attic into two bedrooms and a bathroom. We need drawings and someone who handles the application to the municipality. +- id: shop-to-flat + need: build-enquiry + text: I own a former shop on the ground floor and want to make it a flat. I have no idea what is allowed. Can you sort that out and give me a price? +- id: electrician-firm + need: subcontractor-application + text: We are a four-person electrical firm, fully certified. We would like to get on your list so you call us when you need electricians. +- id: scaffolder + need: subcontractor-application + text: I run a scaffolding company. How do I get to do jobs for you? +- id: carpenter-jobseeker + need: job-application + text: I'm a certified carpenter with eight years on rehabilitation jobs. Are you hiring? +- id: apprentice + need: job-application + text: I finish vocational school this spring and need an apprenticeship as a bricklayer. +- id: loose-railing + need: report-site-issue + text: The railing on the third level of the scaffold on the north side is loose. Nobody got hurt but someone will. +- id: wrong-membrane + need: report-site-issue + text: The bathroom membrane delivered today is not the one in the specification. We stopped work on it. diff --git a/runs/contractor-build-2026-09-21/questions/crew/_section.yaml b/runs/contractor-build-2026-09-21/questions/crew/_section.yaml new file mode 100644 index 0000000..1f962a5 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/crew/_section.yaml @@ -0,0 +1 @@ +qualifies: crew diff --git a/runs/contractor-build-2026-09-21/questions/crew/index.yaml b/runs/contractor-build-2026-09-21/questions/crew/index.yaml new file mode 100644 index 0000000..7e3fa29 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/crew/index.yaml @@ -0,0 +1,11 @@ +alternatives: +- action: /crew/report-site-issue + consequence: + - Report issue + description: Write down a hazard, near miss or wrong materials from the site. + name: Report a site issue +description: A desk for the project managers and safety officer. Move cases through stages, approve subs, and log issues. +name: What do you need to do today? +responsible: + contact: ingrid@nordvikbygg.example + name: Ingrid Nordvik diff --git a/runs/contractor-build-2026-09-21/questions/crew/report-site-issue-sent.yaml b/runs/contractor-build-2026-09-21/questions/crew/report-site-issue-sent.yaml new file mode 100644 index 0000000..1159716 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/crew/report-site-issue-sent.yaml @@ -0,0 +1,10 @@ +alternatives: +- description: A safety officer will assess the issue and assign it to the right person. + name: What happens next +- action: /crew + consequence: + - Back to form + description: '' + name: Back to form +description: Your report has been received. We will review it and get back to you. +name: What type of issue did you report? diff --git a/runs/contractor-build-2026-09-21/questions/crew/report-site-issue.yaml b/runs/contractor-build-2026-09-21/questions/crew/report-site-issue.yaml new file mode 100644 index 0000000..ba1c02a --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/crew/report-site-issue.yaml @@ -0,0 +1,30 @@ +alternatives: +- action: /crew/report-site-issue-sent + consequence: + - Send report + description: Use this form to report a safety issue, near miss, or wrong materials while on site. + features: + - name: Report a site issue + requirements: + - label: Issue type + name: kind + options: + - Hazard + - Near miss + - Wrong materials + - Other + type: select + - label: Where on site + name: where + type: text + - label: What happened + name: what_happened + type: textarea + name: Report a safety issue on site + record_as: site_issues +description: Report hazards, near misses, or wrong materials so they can be fixed. +followup: false +name: What happened on site? +responsible: + contact: ingrid@nordvikbygg.example + name: Ingrid Nordvik diff --git a/runs/contractor-build-2026-09-21/questions/index.yaml b/runs/contractor-build-2026-09-21/questions/index.yaml new file mode 100644 index 0000000..20b3ae2 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/index.yaml @@ -0,0 +1,123 @@ +alternatives: +- action: /received/survey-enquiry + consequence: + - Book a survey + description: We visit, assess and give you a quote for the repair work. + features: + - name: Survey enquiry + requirements: + - label: Your name + name: name + type: text + - label: Phone number + name: phone + type: tel + - label: Building address + name: address + type: text + - label: Describe the problem + name: problem + type: textarea + - label: Site access + name: access + optional: true + type: text + name: Get a professional building survey + record_as: enquiries +- action: /received/build-enquiry + consequence: + - Start project + description: We handle drawings, permits and price for your building plans. + features: + - name: Building project + requirements: + - label: Your name + name: name + type: text + - label: Phone number + name: phone + type: tel + - label: Project address + name: address + type: text + - label: Project description + name: project + type: textarea + - label: When do you want it done + name: timeframe + options: + - This year + - Next year + - No fixed date + type: select + name: Plan your building project with us + record_as: enquiries +- action: /received/subcontractor-application + consequence: + - Apply now + description: Register your trade firm so we can call you for jobs. + features: + - name: Subcontractor sign-up + requirements: + - label: Company name + name: company + type: text + - label: Org number + name: registration_number + type: text + - label: Your trade + name: trade + options: + - Electrical + - Plumbing + - Scaffolding + - Sheet metal + - Other + type: select + - label: Contact email + name: contact_email + type: email + - label: Insurance confirmed + name: insurance_confirmed + options: + - Yes + - Not yet + type: select + - label: Trade approval + name: trade_approval + optional: true + type: text + name: Become a trusted subcontractor + record_as: subcontractors +- action: /received/job-application + consequence: + - Apply for work + description: Share your trade and skills so we can find you a job. + features: + - name: Job application + requirements: + - label: Your name + name: name + type: text + - label: Phone number + name: phone + type: tel + - label: Your trade + name: trade + options: + - Carpenter + - Bricklayer + - Apprentice + - Other + type: select + - label: Your experience + name: experience + optional: true + type: textarea + name: Join our carpentry team + record_as: applicants +description: 'Pick what you need: a building check, a new project, or joining the team.' +name: What brings you to Nordvik Bygg today? +responsible: + contact: ingrid@nordvikbygg.example + name: Ingrid Nordvik diff --git a/runs/contractor-build-2026-09-21/questions/project_managers/_section.yaml b/runs/contractor-build-2026-09-21/questions/project_managers/_section.yaml new file mode 100644 index 0000000..d8acdff --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/project_managers/_section.yaml @@ -0,0 +1 @@ +qualifies: project_managers diff --git a/runs/contractor-build-2026-09-21/questions/project_managers/index.yaml b/runs/contractor-build-2026-09-21/questions/project_managers/index.yaml new file mode 100644 index 0000000..0d55d8e --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/project_managers/index.yaml @@ -0,0 +1,98 @@ +alternatives: +- action: /project_managers + consequence: + - Continue + description: A carpenter, bricklayer or apprentice looking for their next job can be added here. The team reviews their skills and experience. + features: + - description: '' + name: '' + resource: + requires_group: project_managers + source: + bucket: applicants + kind: kv + transitions: + - from: interviewing + label: Hired + to: hired + - from: interviewing + label: Declined + to: declined + - from: open + label: Start interview + to: interviewing + - from: open + label: Declined + to: declined + name: Job applicants +- action: /project_managers + consequence: + - Continue + description: A housing board or private owner with a building problem, or an owner who wants something built or changed. Track the case from first contact to final result. + features: + - description: '' + name: '' + resource: + requires_group: project_managers + source: + bucket: enquiries + kind: kv + transitions: + - from: open + label: Start survey + to: surveyed + - from: open + label: Mark lost + to: lost + - from: quoted + label: Won + to: won + - from: quoted + label: Mark lost + to: lost + - from: surveyed + label: Make quote + to: quoted + - from: surveyed + label: Mark lost + to: lost + name: Building enquiries +- action: /project_managers + consequence: + - Continue + description: A trade firm doing electrical, plumbing or scaffolding work can be approved once and then called when a job needs them. + features: + - description: '' + name: '' + resource: + requires_group: project_managers + source: + bucket: subcontractors + kind: kv + transitions: + - from: awaiting_papers + label: Approve + to: approved + - from: awaiting_papers + label: Decline + to: declined + - from: open + label: Await papers + to: awaiting_papers + - from: open + label: Approve + to: approved + - from: open + label: Decline + to: declined + name: Subcontractors +- action: /project_managers/log-phone-enquiry + consequence: + - Log call + description: Write down a phone call or text about a building issue before it gets lost. + name: Log phone enquiry +description: Use the buckets below to move cases through their stages. Each bucket is a list of jobs waiting for action. +name: What needs doing today? +responsible: + contact: ingrid@nordvikbygg.example + name: Ingrid Nordvik diff --git a/runs/contractor-build-2026-09-21/questions/project_managers/log-phone-enquiry-sent.yaml b/runs/contractor-build-2026-09-21/questions/project_managers/log-phone-enquiry-sent.yaml new file mode 100644 index 0000000..0f8b8cc --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/project_managers/log-phone-enquiry-sent.yaml @@ -0,0 +1,10 @@ +alternatives: +- description: Ingrid and the team will review the details and get back to you within one business day. + name: What happens next +- action: /project_managers + consequence: + - Back to form + description: '' + name: Back to form +description: Your phone call or text has been recorded. It will not be forgotten. +name: Did the message go through? diff --git a/runs/contractor-build-2026-09-21/questions/project_managers/log-phone-enquiry.yaml b/runs/contractor-build-2026-09-21/questions/project_managers/log-phone-enquiry.yaml new file mode 100644 index 0000000..105297c --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/project_managers/log-phone-enquiry.yaml @@ -0,0 +1,28 @@ +alternatives: +- action: /project_managers/log-phone-enquiry-sent + consequence: + - Log phone call + description: Write down the enquiry before it gets lost. + features: + - name: Log phone enquiry + requirements: + - label: Your name + name: caller + type: text + - label: Phone number + name: phone + type: tel + - label: Address + name: address + type: text + - label: What happened? + name: problem + type: textarea + name: Log a phone enquiry + record_as: enquiries +description: Quickly record a building enquiry from a phone call or text. +followup: false +name: What happened on the phone? +responsible: + contact: ingrid@nordvikbygg.example + name: Ingrid Nordvik diff --git a/runs/contractor-build-2026-09-21/questions/received/build-enquiry.yaml b/runs/contractor-build-2026-09-21/questions/received/build-enquiry.yaml new file mode 100644 index 0000000..6a369bb --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/received/build-enquiry.yaml @@ -0,0 +1,10 @@ +alternatives: +- description: We will contact you within two business days to discuss your project and send a quote. + name: 'Next steps: quote and timeline' +- action: / + consequence: + - Back to form + description: '' + name: Back to form +description: Your build enquiry has been received. A project manager will review it. +name: What happens next? diff --git a/runs/contractor-build-2026-09-21/questions/received/job-application.yaml b/runs/contractor-build-2026-09-21/questions/received/job-application.yaml new file mode 100644 index 0000000..73f94e9 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/received/job-application.yaml @@ -0,0 +1,10 @@ +alternatives: +- description: Project managers will review your profile and contact you if they want to interview. + name: Next steps +- action: / + consequence: + - Back to form + description: '' + name: Back to form +description: Your job application has been received. +name: What are you looking for? diff --git a/runs/contractor-build-2026-09-21/questions/received/subcontractor-application.yaml b/runs/contractor-build-2026-09-21/questions/received/subcontractor-application.yaml new file mode 100644 index 0000000..e7220c4 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/received/subcontractor-application.yaml @@ -0,0 +1,10 @@ +alternatives: +- description: Once approved, you will be added to our approved subcontractor list and contacted when relevant projects arise. + name: Approved and added to list +- action: / + consequence: + - Back to form + description: '' + name: Back to form +description: Subcontractor applications are reviewed by our project managers to verify credentials and suitability before approval. +name: What is your trade? diff --git a/runs/contractor-build-2026-09-21/questions/received/survey-enquiry.yaml b/runs/contractor-build-2026-09-21/questions/received/survey-enquiry.yaml new file mode 100644 index 0000000..e2eaa00 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/received/survey-enquiry.yaml @@ -0,0 +1,10 @@ +alternatives: +- description: A project manager will review the details and contact you to schedule a site visit. + name: 'Next: Site survey' +- action: / + consequence: + - Back to form + description: '' + name: Back to form +description: You sent a survey enquiry. This page tells you who will handle it and what happens next. +name: Who will handle your enquiry? diff --git a/runs/contractor-build-2026-09-21/questions/safety_officer/_section.yaml b/runs/contractor-build-2026-09-21/questions/safety_officer/_section.yaml new file mode 100644 index 0000000..d7ea7a1 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/safety_officer/_section.yaml @@ -0,0 +1 @@ +qualifies: safety_officer diff --git a/runs/contractor-build-2026-09-21/questions/safety_officer/index.yaml b/runs/contractor-build-2026-09-21/questions/safety_officer/index.yaml new file mode 100644 index 0000000..989b8f3 --- /dev/null +++ b/runs/contractor-build-2026-09-21/questions/safety_officer/index.yaml @@ -0,0 +1,26 @@ +alternatives: +- action: /safety_officer + consequence: + - Submit + description: Report hazards, near misses, or wrong materials before you forget them. + features: + - description: '' + name: '' + resource: + requires_group: safety_officer + source: + bucket: site_issues + kind: kv + transitions: + - from: in_hand + label: Mark as closed + to: closed + - from: open + label: Report issue + to: in_hand + name: Report a site issue +description: Log a safety concern or material issue from the site. +name: What issue do you need to report? +responsible: + contact: ingrid@nordvikbygg.example + name: Ingrid Nordvik diff --git a/runs/contractor-build-2026-09-21/rate.json b/runs/contractor-build-2026-09-21/rate.json new file mode 100644 index 0000000..60398c9 --- /dev/null +++ b/runs/contractor-build-2026-09-21/rate.json @@ -0,0 +1,77 @@ +{ + "build-enquiry": { + "control_ok": true, + "control_which": 0, + "lix": 27.477272727272727, + "options": 4, + "overloaded": false, + "personas": 2, + "required_fields": 5, + "steps": 1, + "understanding": { + "asked": 2, + "next": 0, + "which": 2 + } + }, + "job-application": { + "control_ok": true, + "control_which": 0, + "lix": 23.909090909090907, + "options": 4, + "overloaded": false, + "personas": 2, + "required_fields": 3, + "steps": 1, + "understanding": { + "asked": 2, + "next": 0, + "which": 2 + } + }, + "report-site-issue": { + "control_ok": true, + "control_which": 0, + "lix": 15.618181818181819, + "options": 1, + "overloaded": false, + "personas": 2, + "required_fields": 3, + "steps": 1, + "understanding": { + "asked": 2, + "next": 0, + "which": 1 + } + }, + "subcontractor-application": { + "control_ok": true, + "control_which": 0, + "lix": 29.01581027667984, + "options": 4, + "overloaded": false, + "personas": 2, + "required_fields": 5, + "steps": 1, + "understanding": { + "asked": 2, + "next": 0, + "which": 2 + } + }, + "survey-enquiry": { + "control_ok": true, + "control_which": 0, + "lix": 27.92885375494071, + "options": 4, + "overloaded": false, + "personas": 2, + "required_fields": 4, + "steps": 1, + "understanding": { + "asked": 2, + "next": 0, + "which": 2 + } + } +} \ No newline at end of file diff --git a/runs/contractor-build-2026-09-21/run.log b/runs/contractor-build-2026-09-21/run.log new file mode 100644 index 0000000..5284c59 --- /dev/null +++ b/runs/contractor-build-2026-09-21/run.log @@ -0,0 +1,22 @@ +content repo written to /tmp/claude-1000/-home-bl-repos-portal/b7802cea-728f-42d2-a751-680a973ba17d/scratchpad/det/nemotron2 +OK: 12 question(s), 5 aggregate(s) valid +need "survey-enquiry": / [Get a professional building survey] -> "enquiries" +need "build-enquiry": / [Plan your building project with us] -> "enquiries" +need "log-phone-enquiry": /project_managers/log-phone-enquiry [Log a phone enquiry] -> "enquiries" +need "subcontractor-application": / [Become a trusted subcontractor] -> "subcontractors" +need "job-application": / [Join our carpentry team] -> "applicants" +need "report-site-issue": /crew/report-site-issue [Report a safety issue on site] -> "site_issues" +OK: 6 need(s) structurally met, 0 planned, 10 persona(s) +BUILD_EXIT=0 in 130s +=== held-out personas, qwen3:8b, full === +wording: 8/8 routed right (100%) +=== held-out personas, qwen3:8b, skim === +wording: 8/8 routed right (100%) +=== rate === +need LIX which asked next gutted twin says page load +survey-enquiry 28 very easy 2/2 2/2 0/2 0/2 ok 4 options, 4 required +build-enquiry 27 very easy 2/2 2/2 0/2 0/2 ok 4 options, 5 required +subcontractor-application 29 very easy 2/2 2/2 0/2 0/2 ok 4 options, 5 required +job-application 24 very easy 2/2 2/2 0/2 0/2 ok 4 options, 3 required +report-site-issue 16 very easy 1/2 2/2 0/2 0/2 ok 1 options, 3 required +ALL_DONE diff --git a/runs/contractor-build-2026-09-21/site.yaml b/runs/contractor-build-2026-09-21/site.yaml new file mode 100644 index 0000000..17b5f54 --- /dev/null +++ b/runs/contractor-build-2026-09-21/site.yaml @@ -0,0 +1,2 @@ +lang: en +title: Nordvik Bygg diff --git a/src/build.rs b/src/build.rs index 77ee04c..0af89e1 100644 --- a/src/build.rs +++ b/src/build.rs @@ -291,11 +291,40 @@ pub fn merge_words(fallback: &Value, given: &Value) -> Value { (Value::Object(f), Value::Object(g)) => { Value::Object(f.iter().map(|(k, v)| (k.clone(), g.get(k).map_or_else(|| v.clone(), |gv| merge_words(v, gv)))).collect()) } - (Value::String(_), Value::String(s)) if !s.trim().is_empty() => Value::String(s.trim().to_string()), + // An unfilled slot echoed back is no answer. + (Value::String(_), Value::String(s)) if !s.trim().is_empty() && !s.contains(SLOT) => { + Value::String(s.trim().to_string()) + } _ => fallback.clone(), } } +const SLOT: &str = " Value { + match value { + Value::Object(map) => Value::Object(map.iter().map(|(k, v)| (k.clone(), slots(v, k))).collect()), + Value::String(_) => Value::String(format!( + "{SLOT}: {}>", + match key { + "question" => "the one question this page asks its reader".to_string(), + "heading" | "next_heading" => "a heading, at most eight words, saying what the reader gets".to_string(), + "description" | "page_description" | "next_description" => "one or two short sentences".to_string(), + "button" | "back" | "confirm" => "a button, one to three words".to_string(), + "form_title" => "a title for the form, two to four words".to_string(), + other if other.contains("->") => format!("a button, two to four words, for a case going {other}"), + other => format!("a form label for the field `{other}`"), + } + )), + other => other.clone(), + } +} + fn s(v: &Value) -> &str { v.as_str().unwrap_or_default() } @@ -470,14 +499,14 @@ fn words_prompt(plan: &Plan, path: &str, fallback: &Value) -> String { .map(|n| format!("- {}: {} - wants: {}. Handled by {}. Stages: {}.", n.id, n.who, n.wants, n.handled_by, n.stages.join(" -> "))) .collect(); format!( - "You write the words for one page of a website for this business.\n\nBusiness: {}\nAbout: {}\nContact: {} <{}>\nLanguage to write in: {}\n\nEveryone the site serves:\n{}\n\nThe page: {path}\nIt is {purpose}\n\nBelow is the page's wording as JSON, filled with placeholder text. Return the same JSON with every string rewritten. Keep every key exactly as it is; change only the string values.\n\nRules for the words:\n- `question` is a question the reader could ask themselves.\n- A heading says what the reader gets, in the reader's own words, and must work without its description.\n- Lead with what a thing enables. No negations in headings. No trade jargon the reader would not use.\n- Descriptions are one or two short sentences. Labels and buttons are a few words.\n- Write the way people in this trade and country actually speak.\n\n{}\n\nReply with the JSON object only.", + "You write the words for one page of a website for this business.\n\nBusiness: {}\nAbout: {}\nContact: {} <{}>\nLanguage to write in: {}\n\nEveryone the site serves:\n{}\n\nThe page: {path}\nIt is {purpose}\n\nBelow is the page as JSON. Every value is a slot that says what to write there. Return the same JSON with every slot replaced by your words. Keep every key exactly as it is. A slot left as it is counts as no answer.\n\nRules for the words:\n- `question` is a question the reader could ask themselves.\n- A heading says what the reader gets, in the reader's own words, and must work without its description.\n- Lead with what a thing enables. No negations in headings. No trade jargon the reader would not use.\n- Descriptions are one or two short sentences. Labels and buttons are a few words.\n- Write the way people in this trade and country actually speak.\n\n{}\n\nReply with the JSON object only.", plan.business, plan.about.trim(), plan.contact.name, plan.contact.email, plan.language, context.join("\n"), - serde_json::to_string_pretty(fallback).unwrap_or_default(), + serde_json::to_string_pretty(&slots(fallback, "")).unwrap_or_default(), ) }