portal-trainer: the loop around portal's needs verifier
The trainer procedure as an agent skill, the engine contract, two
sector specs (software-studio: UHHM's own, green; construction: a
target with no pages yet), and the first wording run against uhhm.no.
The verifier itself is portal's question_lint (uhhm/portal 188f9bf).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
This commit is contained in:
co-authored by
Claude Fable 5.1
commit
7caaa27ba7
@@ -0,0 +1,123 @@
|
||||
# portal-trainer
|
||||
|
||||
Onboarding a business onto [portal](https://project.uhhm.no/uhhm/portal)
|
||||
is mostly a wording problem: the engine is generic, and whether it
|
||||
works for a given business depends on whether its pages say, in the
|
||||
visitor's words, the thing the business needs them to answer. This
|
||||
repo is where that gets tested and, eventually, learned.
|
||||
|
||||
## The loop
|
||||
|
||||
```
|
||||
interview ─► needs.yaml ─► pages + aggregates + desks
|
||||
│ │
|
||||
│ question_lint (structure: pass/fail)
|
||||
│ │
|
||||
└─► personas ─► tasks.jsonl ─► answering engine ─► answers.jsonl
|
||||
│
|
||||
question_lint --needs-score ◄──┘
|
||||
│
|
||||
misroutes = wording defects ─► revise pages
|
||||
```
|
||||
|
||||
- **`needs.yaml`** lives in the business's own content repo. It says
|
||||
who wants what, which bucket the answer lands in, which group
|
||||
carries it to a finished state, and what the visitor will tolerate
|
||||
in submissions and required fields.
|
||||
- **The verifier is portal's `question_lint`**, not code in this repo.
|
||||
It reads portal's own content types and ships in every portal
|
||||
release, so a content repo's CI enforces its needs with no new
|
||||
dependency. Structure is deterministic and gates CI. Wording is
|
||||
graded, and gates only if you pass `--min-accuracy`.
|
||||
- **This repo holds what sits around the verifier**: the trainer
|
||||
procedure (`skill/SKILL.md`), sector specs to start from
|
||||
(`sectors/`), and the runs (`runs/`).
|
||||
|
||||
```
|
||||
question_lint --path <repo>/questions # structure
|
||||
question_lint --path <repo>/questions --needs-tasks tasks.jsonl # full wording
|
||||
question_lint --path <repo>/questions --needs-tasks skim.jsonl --skim # headings only
|
||||
question_lint --path <repo>/questions --needs-score answers.jsonl [--min-accuracy 0.8]
|
||||
```
|
||||
|
||||
## The engine contract
|
||||
|
||||
A task is one page on one persona's path, as a typed `choice`:
|
||||
|
||||
```json
|
||||
{"id":"architecture-firm@/","persona":"architecture-firm","page":"/",
|
||||
"state":{"body":"We run a small architecture firm and ..."},
|
||||
"questions":{"pick":{"type":"choice",
|
||||
"instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?",
|
||||
"criteria":{"<alternative heading>":"<its description>", "...":"..."}}},
|
||||
"expect":["<every alternative that still meets the need>"]}
|
||||
```
|
||||
|
||||
Strip `expect` (and `need`) before an engine sees it. An answer is one
|
||||
line: `{"id": "...", "choice": "<heading, verbatim>", "confidence": 0.0-1.0}`.
|
||||
`state` and `questions` are exactly the arguments of
|
||||
[laya](https://github.com/NandhaKishorM/laya)'s
|
||||
`router.predict(state, questions)`, so a laya adapter is a loop over
|
||||
the file; an LLM adapter is a prompt around the same two fields.
|
||||
|
||||
## Engines, and what is known about them
|
||||
|
||||
| Engine | Status | What it is good for |
|
||||
|---|---|---|
|
||||
| LLM, blind (no `expect`) | used for the first run | Reads like a visitor; slow and costs per task, so use it to find defects, not to gate CI. |
|
||||
| laya `english` (ModernBERT-large, 421M) | not run yet | 33 ms per question, so it can gate CI and score thousands of generated variants. Its authors measure 0.78 intent accuracy in English. |
|
||||
| laya `multilingual` | not run yet | Its authors' own sweep puts Norwegian Bokmål at **0.53 accuracy with 0.86 mean confidence**. It is confidently wrong too often to grade Norwegian wording without fine-tuning. Test a Norwegian site in English translation, or with an LLM. |
|
||||
|
||||
An engine is a measuring instrument. Before trusting one on a sector,
|
||||
score it on tasks whose right answer nobody disputes; an engine that
|
||||
misses those is measuring itself, not the page.
|
||||
|
||||
## First run: uhhm.no, 2026-09-21
|
||||
|
||||
Nine personas against the live uhhm.no front door, answered by a small
|
||||
LLM that never saw `expect`. One run each; eight tasks; treat as a
|
||||
lead, not a measurement.
|
||||
|
||||
| What the engine could read | Routed right |
|
||||
|---|---|
|
||||
| Headings and descriptions | 7 of 8 |
|
||||
| Headings only (`--skim`) | 5 of 8 |
|
||||
|
||||
- A founder who wants a first version built picked **"Work happens
|
||||
through dialogue, not tickets"** - the collaborator door - in both
|
||||
modes. With headings only, so did a firm wanting to pay for storage
|
||||
and backups. The heading reads as how UHHM works with clients.
|
||||
- With headings only, a student seeking an internship picked
|
||||
**"Attention is earned"**.
|
||||
- Structure: four needs met. The site-improvement flow is advertised
|
||||
and `disabled`, so it is declared `planned`.
|
||||
|
||||
## Sectors
|
||||
|
||||
A sector file is a needs spec with no pages behind it: a failing test
|
||||
suite for the trainer to make pass, carrying what the sector demands
|
||||
(why a lead is a pipeline, why a deviation report gets three fields).
|
||||
|
||||
- `sectors/software-studio/needs.yaml` - UHHM's own, and green against
|
||||
`uhhm/questions`.
|
||||
- `sectors/construction/needs.yaml` - a contractor with project
|
||||
managers, a safety officer, crew and subcontractors: winning survey
|
||||
and design work, vetting subcontractors, requesting and answering
|
||||
bids, hiring, deviation reporting. No pages yet.
|
||||
|
||||
## Toward a trained model
|
||||
|
||||
Nothing here trains weights yet, on purpose. The order that makes
|
||||
training possible:
|
||||
|
||||
1. The verifier has to be trusted first. It is the reward. A generator
|
||||
trained against a judge that is wrong half the time learns the
|
||||
judge.
|
||||
2. Every onboarding leaves a run: needs, the pages at each revision,
|
||||
the scores. That corpus - what wording routed which persona right,
|
||||
per sector - is the training data, and it does not exist until the
|
||||
loop has been run by hand a few dozen times.
|
||||
3. With a fast engine (laya-class) as the scorer, generating and
|
||||
scoring many page variants per need is cheap, which is what makes
|
||||
search, and later fine-tuning a small generator on the winners,
|
||||
practical.
|
||||
@@ -0,0 +1,8 @@
|
||||
{"id":"architecture-firm@/","choice":"Good software begins with a clear argument","confidence":0.9}
|
||||
{"id":"climbing-gym-founder@/","choice":"Work happens through dialogue, not tickets","confidence":0.75}
|
||||
{"id":"municipality-buyer@/","choice":"Good software begins with a clear argument","confidence":0.95}
|
||||
{"id":"exhibition-visitor@/","choice":"Attention is earned","confidence":0.95}
|
||||
{"id":"tech-journalist@/","choice":"Attention is earned","confidence":0.9}
|
||||
{"id":"rust-developer@/","choice":"Work happens through dialogue, not tickets","confidence":0.8}
|
||||
{"id":"interaction-designer@/","choice":"Work happens through dialogue, not tickets","confidence":0.85}
|
||||
{"id":"student-intern@/","choice":"Work happens through dialogue, not tickets","confidence":0.65}
|
||||
@@ -0,0 +1,8 @@
|
||||
{"id":"architecture-firm@/","choice":"Work happens through dialogue, not tickets","confidence":0.70}
|
||||
{"id":"climbing-gym-founder@/","choice":"Work happens through dialogue, not tickets","confidence":0.85}
|
||||
{"id":"municipality-buyer@/","choice":"Good software begins with a clear argument","confidence":0.60}
|
||||
{"id":"exhibition-visitor@/","choice":"Attention is earned","confidence":0.80}
|
||||
{"id":"tech-journalist@/","choice":"Attention is earned","confidence":0.80}
|
||||
{"id":"rust-developer@/","choice":"Work happens through dialogue, not tickets","confidence":0.75}
|
||||
{"id":"interaction-designer@/","choice":"Work happens through dialogue, not tickets","confidence":0.80}
|
||||
{"id":"student-intern@/","choice":"Attention is earned","confidence":0.55}
|
||||
@@ -0,0 +1,8 @@
|
||||
{"id":"architecture-firm@/","persona":"architecture-firm","need":"commission","page":"/","state":{"body":"We run a small architecture firm and our project files are scattered across three cloud drives. We want to pay someone to set up proper storage and backups we control ourselves."},"questions":{"pick":{"type":"choice","instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?","criteria":{"Attention is earned":"Considered notes on what we're building, and why — sent only when there's something worth saying.","Good software begins with a clear argument":"Infrastructure, tools, or a product — bring the problem, and we read the whole brief before answering.","Work happens through dialogue, not tickets":"What you bring shapes what gets built. We onboard collaborators the same deliberate way we build — by hand, through a real conversation."}}},"expect":["Good software begins with a clear argument"]}
|
||||
{"id":"climbing-gym-founder@/","persona":"climbing-gym-founder","need":"commission","page":"/","state":{"body":"I have an idea for a booking app for climbing gyms and I'm looking for a team that can build the first version with me."},"questions":{"pick":{"type":"choice","instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?","criteria":{"Attention is earned":"Considered notes on what we're building, and why — sent only when there's something worth saying.","Good software begins with a clear argument":"Infrastructure, tools, or a product — bring the problem, and we read the whole brief before answering.","Work happens through dialogue, not tickets":"What you bring shapes what gets built. We onboard collaborators the same deliberate way we build — by hand, through a real conversation."}}},"expect":["Good software begins with a clear argument"]}
|
||||
{"id":"municipality-buyer@/","persona":"municipality-buyer","need":"commission","page":"/","state":{"body":"Our municipality needs an internal tool for tracking building permits. We are looking for a vendor who can scope it and deliver it."},"questions":{"pick":{"type":"choice","instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?","criteria":{"Attention is earned":"Considered notes on what we're building, and why — sent only when there's something worth saying.","Good software begins with a clear argument":"Infrastructure, tools, or a product — bring the problem, and we read the whole brief before answering.","Work happens through dialogue, not tickets":"What you bring shapes what gets built. We onboard collaborators the same deliberate way we build — by hand, through a real conversation."}}},"expect":["Good software begins with a clear argument"]}
|
||||
{"id":"exhibition-visitor@/","persona":"exhibition-visitor","need":"follow","page":"/","state":{"body":"I saw your drawing network at an exhibition and liked it. I don't want to get involved, I'd just like to know when there is news."},"questions":{"pick":{"type":"choice","instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?","criteria":{"Attention is earned":"Considered notes on what we're building, and why — sent only when there's something worth saying.","Good software begins with a clear argument":"Infrastructure, tools, or a product — bring the problem, and we read the whole brief before answering.","Work happens through dialogue, not tickets":"What you bring shapes what gets built. We onboard collaborators the same deliberate way we build — by hand, through a real conversation."}}},"expect":["Attention is earned"]}
|
||||
{"id":"tech-journalist@/","persona":"tech-journalist","need":"follow","page":"/","state":{"body":"I write about Nordic technology. Keep me posted on what you release so I can cover it."},"questions":{"pick":{"type":"choice","instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?","criteria":{"Attention is earned":"Considered notes on what we're building, and why — sent only when there's something worth saying.","Good software begins with a clear argument":"Infrastructure, tools, or a product — bring the problem, and we read the whole brief before answering.","Work happens through dialogue, not tickets":"What you bring shapes what gets built. We onboard collaborators the same deliberate way we build — by hand, through a real conversation."}}},"expect":["Attention is earned"]}
|
||||
{"id":"rust-developer@/","persona":"rust-developer","need":"collaborate","page":"/","state":{"body":"I'm a Rust developer between jobs and I like that you build in the open. I would love to contribute, and maybe join you."},"questions":{"pick":{"type":"choice","instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?","criteria":{"Attention is earned":"Considered notes on what we're building, and why — sent only when there's something worth saying.","Good software begins with a clear argument":"Infrastructure, tools, or a product — bring the problem, and we read the whole brief before answering.","Work happens through dialogue, not tickets":"What you bring shapes what gets built. We onboard collaborators the same deliberate way we build — by hand, through a real conversation."}}},"expect":["Work happens through dialogue, not tickets"]}
|
||||
{"id":"interaction-designer@/","persona":"interaction-designer","need":"collaborate","page":"/","state":{"body":"I'm an interaction designer. I want to work with you on the drawing interface."},"questions":{"pick":{"type":"choice","instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?","criteria":{"Attention is earned":"Considered notes on what we're building, and why — sent only when there's something worth saying.","Good software begins with a clear argument":"Infrastructure, tools, or a product — bring the problem, and we read the whole brief before answering.","Work happens through dialogue, not tickets":"What you bring shapes what gets built. We onboard collaborators the same deliberate way we build — by hand, through a real conversation."}}},"expect":["Work happens through dialogue, not tickets"]}
|
||||
{"id":"student-intern@/","persona":"student-intern","need":"collaborate","page":"/","state":{"body":"I'm a computer science student looking for a summer internship doing infrastructure work."},"questions":{"pick":{"type":"choice","instructions":"A visitor reads the page question \"What are you here for?\". Which alternative do they choose?","criteria":{"Attention is earned":"Considered notes on what we're building, and why — sent only when there's something worth saying.","Good software begins with a clear argument":"Infrastructure, tools, or a product — bring the problem, and we read the whole brief before answering.","Work happens through dialogue, not tickets":"What you bring shapes what gets built. We onboard collaborators the same deliberate way we build — by hand, through a real conversation."}}},"expect":["Work happens through dialogue, not tickets"]}
|
||||
@@ -0,0 +1,158 @@
|
||||
# A building contractor's needs, as a portal needs spec: a small to
|
||||
# mid-sized firm doing surveys, design and rehabilitation, run by a few
|
||||
# project managers with its own crew and a ring of subcontractors.
|
||||
#
|
||||
# This file is a target, not a description: there are no pages behind
|
||||
# it yet, so `question_lint` fails every need. The trainer's job is to
|
||||
# write the pages, aggregates.yaml and desks that turn it green, then
|
||||
# word them until the personas route right.
|
||||
#
|
||||
# Groups (Kanidm): project_managers, hse, crew, subcontractors.
|
||||
# Everyone else arrives anonymous.
|
||||
#
|
||||
# What the sector demands of the flows, and why the budgets are what
|
||||
# they are:
|
||||
# - Winning work is a pipeline, not an inbox: a lead is surveyed, then
|
||||
# quoted, then won or lost. "answered" is not an outcome.
|
||||
# - A subcontractor must be vetted before they set foot on site
|
||||
# (company registration, trade approval, insurance, tax standing,
|
||||
# site ID cards for their people). Documents go missing; the state
|
||||
# machine has to say so and let them come back.
|
||||
# - Deviation and near-miss reporting only works when it costs the
|
||||
# reporter seconds. Three required fields, one submission, from a
|
||||
# phone, and the safety officer owns closing it.
|
||||
# - People move between firms constantly; onboarding a worker is the
|
||||
# same shape as onboarding a subcontractor, one size smaller.
|
||||
needs:
|
||||
- id: win-survey-work
|
||||
who: a building owner or housing board with a problem in their building
|
||||
wants: someone qualified to look at it and say what it takes
|
||||
lands_in: leads
|
||||
handled_by: project_managers
|
||||
done_when: [won, lost]
|
||||
max_steps: 2
|
||||
max_fields: 5
|
||||
|
||||
- id: win-design-work
|
||||
who: an owner who knows what they want built or changed
|
||||
wants: drawings, a permit application, and a price
|
||||
lands_in: leads
|
||||
handled_by: project_managers
|
||||
done_when: [won, lost]
|
||||
max_steps: 2
|
||||
max_fields: 6
|
||||
|
||||
- id: vet-subcontractor
|
||||
who: a trade firm that wants to work for us
|
||||
wants: to be approved once, and then be asked
|
||||
lands_in: subcontractors
|
||||
handled_by: project_managers
|
||||
done_when: [approved, rejected]
|
||||
max_steps: 2
|
||||
max_fields: 7
|
||||
|
||||
- id: request-bids
|
||||
who: a project manager with a job to staff
|
||||
wants: approved subcontractors to see it and answer
|
||||
enters: /jobs/post
|
||||
enters_as: project_managers
|
||||
lands_in: job_requests
|
||||
handled_by: project_managers
|
||||
done_when: [awarded, withdrawn]
|
||||
max_steps: 1
|
||||
max_fields: 5
|
||||
|
||||
- id: answer-bid
|
||||
who: an approved subcontractor
|
||||
wants: to say yes to a job, with a price and a start date
|
||||
enters: /jobs
|
||||
enters_as: subcontractors
|
||||
lands_in: bids
|
||||
handled_by: project_managers
|
||||
done_when: [accepted, declined]
|
||||
max_steps: 2
|
||||
max_fields: 3
|
||||
|
||||
- id: hire-crew
|
||||
who: a tradesperson looking for work
|
||||
wants: to be considered, without writing a letter
|
||||
lands_in: applicants
|
||||
handled_by: project_managers
|
||||
done_when: [hired, declined]
|
||||
max_steps: 2
|
||||
max_fields: 4
|
||||
|
||||
- id: report-deviation
|
||||
who: anyone on site - crew or a subcontractor's people
|
||||
wants: to report a hazard, a near miss or a defect before they forget
|
||||
enters: /site
|
||||
enters_as: crew
|
||||
lands_in: deviations
|
||||
handled_by: hse
|
||||
done_when: [closed]
|
||||
max_steps: 1
|
||||
max_fields: 3
|
||||
|
||||
personas:
|
||||
- id: housing-board-damp
|
||||
need: win-survey-work
|
||||
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: win-survey-work
|
||||
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: win-design-work
|
||||
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: win-design-work
|
||||
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: vet-subcontractor
|
||||
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: vet-subcontractor
|
||||
text: >
|
||||
I run a scaffolding company. How do I get to do jobs for you?
|
||||
|
||||
- id: carpenter-jobseeker
|
||||
need: hire-crew
|
||||
text: >
|
||||
I'm a certified carpenter with eight years on rehabilitation
|
||||
jobs. Are you hiring?
|
||||
|
||||
- id: apprentice
|
||||
need: hire-crew
|
||||
text: >
|
||||
I finish vocational school this spring and need an
|
||||
apprenticeship as a bricklayer.
|
||||
|
||||
- id: loose-railing
|
||||
need: report-deviation
|
||||
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-deviation
|
||||
text: >
|
||||
The bathroom membrane delivered today is not the one in the
|
||||
specification. We stopped work on it.
|
||||
@@ -0,0 +1,127 @@
|
||||
# What UHHM, as a business, needs this site to do - stated apart from
|
||||
# how the pages word it. `question_lint` checks every need against the
|
||||
# pages and aggregates.yaml (portal `src/needs.rs`): a path of
|
||||
# submissions gets the visitor's answer into `lands_in` within the
|
||||
# effort budget, `handled_by` has a desk over that bucket, and every
|
||||
# `done_when` state is reachable through the moves the desk offers.
|
||||
#
|
||||
# The personas are visitors in their own words - never the site's.
|
||||
# `question_lint --needs-tasks` turns each page on their path into a
|
||||
# typed choice for an answering engine; `--needs-score` grades its
|
||||
# picks. A misroute means the page said one thing and they heard
|
||||
# another. The runtime never reads this file.
|
||||
needs:
|
||||
- id: commission
|
||||
who: someone with a problem software could solve, and a budget
|
||||
wants: UHHM to take the work on
|
||||
via: Good software begins with a clear argument
|
||||
lands_in: projects
|
||||
handled_by: owners
|
||||
done_when: [accepted, declined]
|
||||
max_steps: 1
|
||||
# name, email, the build, the organization - the page argues for
|
||||
# each of the four; a fifth would need the same argument.
|
||||
max_fields: 4
|
||||
|
||||
- id: follow
|
||||
who: someone curious who isn't ready to get involved
|
||||
wants: to hear when something ships
|
||||
via: Attention is earned
|
||||
lands_in: subscribers
|
||||
handled_by: owners
|
||||
# Being on the list is the finished state; leaving is theirs to do.
|
||||
done_when: [open, unsubscribed]
|
||||
max_steps: 1
|
||||
max_fields: 2
|
||||
|
||||
- id: collaborate
|
||||
who: a developer, designer or researcher
|
||||
wants: to work with UHHM
|
||||
via: Work happens through dialogue, not tickets
|
||||
lands_in: applicants
|
||||
handled_by: owners
|
||||
done_when: [active, declined]
|
||||
max_steps: 1
|
||||
max_fields: 3
|
||||
|
||||
- id: improve-site
|
||||
who: anyone using this site
|
||||
wants: the site itself to change
|
||||
enters: /develop/proposal
|
||||
# Announced, not open: the automation that turns a proposal into a
|
||||
# pull request is still being built, so the alternative is disabled.
|
||||
planned: true
|
||||
lands_in: development_proposals
|
||||
handled_by: owners
|
||||
done_when: [approved, rejected]
|
||||
max_steps: 1
|
||||
max_fields: 3
|
||||
|
||||
- id: track-prospect
|
||||
who: an owner who met an organization worth following up
|
||||
wants: the relationship written down and walked to an outcome
|
||||
enters: /review
|
||||
enters_as: owners
|
||||
via: Record a prospect
|
||||
lands_in: organizations
|
||||
handled_by: owners
|
||||
done_when: [client, past_client]
|
||||
max_steps: 1
|
||||
max_fields: 2
|
||||
|
||||
personas:
|
||||
- id: architecture-firm
|
||||
need: commission
|
||||
text: >
|
||||
We run a small architecture firm and our project files are
|
||||
scattered across three cloud drives. We want to pay someone to
|
||||
set up proper storage and backups we control ourselves.
|
||||
|
||||
- id: climbing-gym-founder
|
||||
need: commission
|
||||
text: >
|
||||
I have an idea for a booking app for climbing gyms and I'm
|
||||
looking for a team that can build the first version with me.
|
||||
|
||||
- id: municipality-buyer
|
||||
need: commission
|
||||
text: >
|
||||
Our municipality needs an internal tool for tracking building
|
||||
permits. We are looking for a vendor who can scope it and
|
||||
deliver it.
|
||||
|
||||
- id: exhibition-visitor
|
||||
need: follow
|
||||
text: >
|
||||
I saw your drawing network at an exhibition and liked it. I don't
|
||||
want to get involved, I'd just like to know when there is news.
|
||||
|
||||
- id: tech-journalist
|
||||
need: follow
|
||||
text: >
|
||||
I write about Nordic technology. Keep me posted on what you
|
||||
release so I can cover it.
|
||||
|
||||
- id: rust-developer
|
||||
need: collaborate
|
||||
text: >
|
||||
I'm a Rust developer between jobs and I like that you build in
|
||||
the open. I would love to contribute, and maybe join you.
|
||||
|
||||
- id: interaction-designer
|
||||
need: collaborate
|
||||
text: >
|
||||
I'm an interaction designer. I want to work with you on the
|
||||
drawing interface.
|
||||
|
||||
- id: student-intern
|
||||
need: collaborate
|
||||
text: >
|
||||
I'm a computer science student looking for a summer internship
|
||||
doing infrastructure work.
|
||||
|
||||
- id: site-critic
|
||||
need: improve-site
|
||||
text: >
|
||||
Your contact form never says how long a reply takes. You should
|
||||
add that.
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
---
|
||||
name: portal-trainer
|
||||
description: Onboard a business onto portal - interview them, write their needs.yaml, then write the content repo (pages, aggregates.yaml, desks) until question_lint proves every need is met and an answering engine routes the personas right. Use when someone wants a new portal site, a new flow on an existing one, or asks why visitors pick the wrong alternative.
|
||||
---
|
||||
|
||||
# Portal trainer
|
||||
|
||||
You are onboarding a business onto [portal](https://project.uhhm.no/uhhm/portal):
|
||||
a site that is one question per page, a few alternatives per question,
|
||||
each alternative optionally a form whose answer is recorded into a
|
||||
bucket with a state machine, worked from a group-gated desk.
|
||||
|
||||
You have a verifier. Use it instead of your judgment wherever it can
|
||||
speak: `question_lint --path <repo>/questions` exits non-zero until
|
||||
the structure meets every need in `<repo>/needs.yaml`.
|
||||
|
||||
## The order of work
|
||||
|
||||
1. **Interview, then write `needs.yaml` first.** Never pages first.
|
||||
Ask who comes to the business and what each of them wants, who in
|
||||
the business handles it, and what "handled" means - the states a
|
||||
case ends in. Ask what it costs the visitor to ask: how many
|
||||
submissions, how many required fields they will tolerate. If the
|
||||
business is in a sector under `sectors/`, start from that file and
|
||||
cut it down with them; do not start blank.
|
||||
2. **Write personas in the visitor's words, not the business's.** Two
|
||||
or three per need, including one who would not use the trade's own
|
||||
vocabulary. Never reuse a phrase you intend to put on a page - a
|
||||
persona that quotes the page tests nothing.
|
||||
3. **Run the lint. It fails. Read every line.** Each failure names
|
||||
what is missing: no path, a closed (`disabled`) path, a field budget
|
||||
blown, no desk held by the handling group, a finished state no desk
|
||||
move reaches, a state records strand in.
|
||||
4. **Write the least content that turns it green**: `aggregates.yaml`
|
||||
(one aggregate per bucket, states the business named, only the
|
||||
transitions they described), the visitor pages, one followup page
|
||||
per recording alternative saying what happens next, and one desk
|
||||
page per handling group with exactly the moves the state machine
|
||||
allows. Mark a need `planned: true` only when the business says
|
||||
"later" - it keeps warning.
|
||||
5. **Then word it.** `question_lint ... --needs-tasks tasks.jsonl`,
|
||||
and again with `--skim` (headings only - most visitors read nothing
|
||||
else). Give the tasks to an answering engine that never sees
|
||||
`expect`; grade with `--needs-score answers.jsonl`.
|
||||
- A **misroute** is a wording defect. Fix the heading before the
|
||||
description: a heading that needs its description has failed.
|
||||
- Two alternatives a persona confuses are usually one distinction
|
||||
the business makes and the visitor does not. Name the visitor's
|
||||
side of it.
|
||||
- **Hesitant** (right, under 0.6) means it works and is still a coin
|
||||
flip. Same treatment.
|
||||
- Change the page, never the persona, to make a score go up.
|
||||
6. **Keep the run.** Copy tasks, answers and the score into
|
||||
`runs/<business>-<date>/` with a line on which engine answered.
|
||||
Runs are the record of which wording worked, per sector.
|
||||
|
||||
## What a page is
|
||||
|
||||
Full reference: portal's `README.md` and
|
||||
`docs/design/filesystem-routes.md`; a live example is
|
||||
`uhhm/questions`. The file tree under `questions/` is the router
|
||||
(`index.yaml` is `/`, `_section.yaml` applies `qualifies` to a
|
||||
directory, nested non-index files are followups).
|
||||
|
||||
```yaml
|
||||
# questions/index.yaml
|
||||
name: What brings you here? # the question - always a question
|
||||
responsible: { name: ..., contact: ... }
|
||||
alternatives:
|
||||
- name: <heading the visitor picks> # graded alone under --skim
|
||||
description: <one or two sentences>
|
||||
action: /received # where the submission lands them
|
||||
consequence: [Send] # the button
|
||||
record_as: leads # bucket - must exist in aggregates.yaml
|
||||
features:
|
||||
- name: <form section>
|
||||
requirements:
|
||||
- { name: email, label: Email, type: email }
|
||||
- { name: problem, label: What's wrong, type: textarea }
|
||||
- { name: address, label: Address, optional: true }
|
||||
```
|
||||
|
||||
```yaml
|
||||
# aggregates.yaml
|
||||
aggregates:
|
||||
- bucket: leads
|
||||
initial: open
|
||||
states:
|
||||
open: { event: received }
|
||||
quoted: { event: quoted }
|
||||
won: { event: won }
|
||||
lost: { event: lost }
|
||||
transitions:
|
||||
open: [quoted, lost]
|
||||
quoted: [won, lost]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# questions/desk/_section.yaml
|
||||
qualifies: project_managers
|
||||
# questions/desk/index.yaml
|
||||
name: What came in?
|
||||
alternatives:
|
||||
- name: Leads
|
||||
action: /desk
|
||||
consequence: [Confirm]
|
||||
features:
|
||||
- name: ""
|
||||
resource:
|
||||
source: { kind: kv, bucket: leads }
|
||||
requires_group: project_managers
|
||||
transitions:
|
||||
- { to: quoted, label: Quote sent } # from defaults to open
|
||||
- { to: lost, label: Not for us }
|
||||
- { from: quoted, to: won, label: Won }
|
||||
- { from: quoted, to: lost, label: Lost }
|
||||
```
|
||||
|
||||
## House rules for the copy
|
||||
|
||||
- The page name is a question the visitor could ask themselves.
|
||||
- Headings say what the visitor gets, in the visitor's words. Lead
|
||||
with what a thing enables; never define it by what it isn't.
|
||||
- Every required field must be one the business will actually use to
|
||||
answer. If they can't say what they do with it, it's optional or
|
||||
gone.
|
||||
- A followup page says who reads it, when they answer, and what the
|
||||
visitor can do meanwhile.
|
||||
|
||||
## Engines
|
||||
|
||||
Any engine that reads a task's `state` and `questions` and returns
|
||||
`{"id","choice","confidence"}` per line works. Compare at least two
|
||||
before trusting a score, and re-run the same engine before trusting a
|
||||
change: one run of eight tasks is an anecdote. See the repo README for
|
||||
what has been measured about each.
|
||||
Reference in New Issue
Block a user