rate and prefer: understanding, readability, friendliness, page load
Readability is counted (LIX), not judged. Understanding is three yes-or-no questions per persona, with a gutted twin of every page as the control: an instrument that still says yes to "Option 1, Option 2" is not measuring the page. Friendliness is a preference between two versions shown in both orders; a pick that flips with the order counts as no preference. Page load (options offered, required fields in the step) flags a page that should be split into a further question or have fields moved to a later stage. First run on the generated contractor site, qwen3:8b as the visitor: control passed on every need; "what happens next" was 0/2 everywhere and 2/2 everywhere once the page said who reads it and when (LIX 28 to 30); an official-voice control reads LIX 63 against 27. Friendliness mostly came back "no preference" at two personas per need: directionally right (never preferred the cold version), too thin to rely on. 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
parent
612745bc66
commit
b9f2acb7b8
@@ -0,0 +1,720 @@
|
||||
{
|
||||
"buckets": {
|
||||
"applicants": {
|
||||
"desks": [
|
||||
{
|
||||
"group": "project_managers",
|
||||
"moves": [
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Invite to interview",
|
||||
"to": "interviewing"
|
||||
},
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Decline",
|
||||
"to": "declined"
|
||||
},
|
||||
{
|
||||
"from": "interviewing",
|
||||
"label": "Hire",
|
||||
"to": "hired"
|
||||
},
|
||||
{
|
||||
"from": "interviewing",
|
||||
"label": "Decline",
|
||||
"to": "declined"
|
||||
}
|
||||
],
|
||||
"page": "/desk"
|
||||
}
|
||||
],
|
||||
"initial": "open",
|
||||
"self_moves": [],
|
||||
"transitions": {
|
||||
"interviewing": [
|
||||
"hired",
|
||||
"declined"
|
||||
],
|
||||
"open": [
|
||||
"interviewing",
|
||||
"declined"
|
||||
]
|
||||
}
|
||||
},
|
||||
"enquiries": {
|
||||
"desks": [
|
||||
{
|
||||
"group": "project_managers",
|
||||
"moves": [
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Looked at the building",
|
||||
"to": "surveyed"
|
||||
},
|
||||
{
|
||||
"from": "surveyed",
|
||||
"label": "Quote sent",
|
||||
"to": "quoted"
|
||||
},
|
||||
{
|
||||
"from": "surveyed",
|
||||
"label": "Not worth quoting",
|
||||
"to": "lost"
|
||||
},
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Not for us",
|
||||
"to": "lost"
|
||||
},
|
||||
{
|
||||
"from": "quoted",
|
||||
"label": "Won",
|
||||
"to": "won"
|
||||
},
|
||||
{
|
||||
"from": "quoted",
|
||||
"label": "Lost",
|
||||
"to": "lost"
|
||||
}
|
||||
],
|
||||
"page": "/desk"
|
||||
}
|
||||
],
|
||||
"initial": "open",
|
||||
"self_moves": [],
|
||||
"transitions": {
|
||||
"open": [
|
||||
"surveyed",
|
||||
"lost"
|
||||
],
|
||||
"quoted": [
|
||||
"won",
|
||||
"lost"
|
||||
],
|
||||
"surveyed": [
|
||||
"quoted",
|
||||
"lost"
|
||||
]
|
||||
}
|
||||
},
|
||||
"site_issues": {
|
||||
"desks": [
|
||||
{
|
||||
"group": "safety_officer",
|
||||
"moves": [
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Look into it",
|
||||
"to": "in_hand"
|
||||
},
|
||||
{
|
||||
"from": "in_hand",
|
||||
"label": "Mark fixed",
|
||||
"to": "closed"
|
||||
}
|
||||
],
|
||||
"page": "/safety"
|
||||
}
|
||||
],
|
||||
"initial": "open",
|
||||
"self_moves": [],
|
||||
"transitions": {
|
||||
"in_hand": [
|
||||
"closed"
|
||||
],
|
||||
"open": [
|
||||
"in_hand"
|
||||
]
|
||||
}
|
||||
},
|
||||
"subcontractors": {
|
||||
"desks": [
|
||||
{
|
||||
"group": "project_managers",
|
||||
"moves": [
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Ask for missing papers",
|
||||
"to": "awaiting_papers"
|
||||
},
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Approve",
|
||||
"to": "approved"
|
||||
},
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Decline",
|
||||
"to": "declined"
|
||||
},
|
||||
{
|
||||
"from": "awaiting_papers",
|
||||
"label": "Approve",
|
||||
"to": "approved"
|
||||
},
|
||||
{
|
||||
"from": "awaiting_papers",
|
||||
"label": "Decline",
|
||||
"to": "declined"
|
||||
}
|
||||
],
|
||||
"page": "/desk"
|
||||
}
|
||||
],
|
||||
"initial": "open",
|
||||
"self_moves": [],
|
||||
"transitions": {
|
||||
"awaiting_papers": [
|
||||
"approved",
|
||||
"declined"
|
||||
],
|
||||
"open": [
|
||||
"awaiting_papers",
|
||||
"approved",
|
||||
"declined"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"needs": [
|
||||
{
|
||||
"done_when": [
|
||||
"won",
|
||||
"lost"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "project_managers",
|
||||
"id": "survey-enquiry",
|
||||
"lands_in": "enquiries",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Get someone to look at a building",
|
||||
"expect": [
|
||||
"Get someone to look at a building"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Your name",
|
||||
"name": "name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"name": "email",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"label": "Address of the building",
|
||||
"name": "address",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "What's wrong",
|
||||
"name": "problem",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "Submission of requests pertaining to structural or condition-related evaluation of existing properties.",
|
||||
"name": "Building condition assessment request"
|
||||
},
|
||||
{
|
||||
"description": "Applicants must provide documentation of intended works for the purposes of cost estimation.",
|
||||
"name": "Construction quotation request"
|
||||
},
|
||||
{
|
||||
"description": "Registration of enterprises for prequalification subject to verification of mandatory documentation.",
|
||||
"name": "Subcontractor prequalification"
|
||||
},
|
||||
{
|
||||
"description": "Unsolicited applications are processed in accordance with applicable recruitment procedures.",
|
||||
"name": "Employment enquiries"
|
||||
},
|
||||
{
|
||||
"description": "Mandatory registration of nonconformities in accordance with internal control requirements.",
|
||||
"name": "Deviation and incident reporting"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "Please select the applicable category for your submission"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"won"
|
||||
],
|
||||
"wants": "someone from Nordvik Bygg to come and look at the building and tell them what a fix would cost",
|
||||
"who": "a housing board member or private owner with a problem in a building"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"won",
|
||||
"lost"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "project_managers",
|
||||
"id": "build-price-enquiry",
|
||||
"lands_in": "enquiries",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Get a price for a build",
|
||||
"expect": [
|
||||
"Get a price for a build"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Your name",
|
||||
"name": "name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"name": "email",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"label": "Address of the site",
|
||||
"name": "address",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "What you want built",
|
||||
"name": "project",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
},
|
||||
{
|
||||
"label": "When you'd like to start",
|
||||
"name": "timeframe",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"As soon as possible",
|
||||
"Within a year",
|
||||
"Just exploring"
|
||||
],
|
||||
"type": "select"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "Submission of requests pertaining to structural or condition-related evaluation of existing properties.",
|
||||
"name": "Building condition assessment request"
|
||||
},
|
||||
{
|
||||
"description": "Applicants must provide documentation of intended works for the purposes of cost estimation.",
|
||||
"name": "Construction quotation request"
|
||||
},
|
||||
{
|
||||
"description": "Registration of enterprises for prequalification subject to verification of mandatory documentation.",
|
||||
"name": "Subcontractor prequalification"
|
||||
},
|
||||
{
|
||||
"description": "Unsolicited applications are processed in accordance with applicable recruitment procedures.",
|
||||
"name": "Employment enquiries"
|
||||
},
|
||||
{
|
||||
"description": "Mandatory registration of nonconformities in accordance with internal control requirements.",
|
||||
"name": "Deviation and incident reporting"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "Please select the applicable category for your submission"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"won"
|
||||
],
|
||||
"wants": "drawings, a permit application and a price for the work",
|
||||
"who": "an owner who already knows what they want built"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"approved",
|
||||
"declined"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "project_managers",
|
||||
"id": "subcontractor-application",
|
||||
"lands_in": "subcontractors",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Join our subcontractor list",
|
||||
"expect": [
|
||||
"Join our subcontractor list"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Company name",
|
||||
"name": "company_name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"name": "contact_email",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"label": "Trade",
|
||||
"name": "trade",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"Electrical",
|
||||
"Plumbing",
|
||||
"Scaffolding"
|
||||
],
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"label": "Company registration number",
|
||||
"name": "registration_number",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Insurance policy details",
|
||||
"name": "insurance",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Trade approval reference",
|
||||
"name": "trade_approval",
|
||||
"optional": true,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "Submission of requests pertaining to structural or condition-related evaluation of existing properties.",
|
||||
"name": "Building condition assessment request"
|
||||
},
|
||||
{
|
||||
"description": "Applicants must provide documentation of intended works for the purposes of cost estimation.",
|
||||
"name": "Construction quotation request"
|
||||
},
|
||||
{
|
||||
"description": "Registration of enterprises for prequalification subject to verification of mandatory documentation.",
|
||||
"name": "Subcontractor prequalification"
|
||||
},
|
||||
{
|
||||
"description": "Unsolicited applications are processed in accordance with applicable recruitment procedures.",
|
||||
"name": "Employment enquiries"
|
||||
},
|
||||
{
|
||||
"description": "Mandatory registration of nonconformities in accordance with internal control requirements.",
|
||||
"name": "Deviation and incident reporting"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "Please select the applicable category for your submission"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"approved"
|
||||
],
|
||||
"wants": "to get on Nordvik Bygg's list of subcontractors they call for work",
|
||||
"who": "a trade firm doing electrical, plumbing or scaffolding work"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"hired",
|
||||
"declined"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "project_managers",
|
||||
"id": "job-application",
|
||||
"lands_in": "applicants",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Ask about working with us",
|
||||
"expect": [
|
||||
"Ask about working with us"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Your name",
|
||||
"name": "name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"name": "email",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"label": "What you do",
|
||||
"name": "role",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"Carpenter",
|
||||
"Apprentice carpenter",
|
||||
"Other"
|
||||
],
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"label": "Anything else we should know",
|
||||
"name": "message",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "Submission of requests pertaining to structural or condition-related evaluation of existing properties.",
|
||||
"name": "Building condition assessment request"
|
||||
},
|
||||
{
|
||||
"description": "Applicants must provide documentation of intended works for the purposes of cost estimation.",
|
||||
"name": "Construction quotation request"
|
||||
},
|
||||
{
|
||||
"description": "Registration of enterprises for prequalification subject to verification of mandatory documentation.",
|
||||
"name": "Subcontractor prequalification"
|
||||
},
|
||||
{
|
||||
"description": "Unsolicited applications are processed in accordance with applicable recruitment procedures.",
|
||||
"name": "Employment enquiries"
|
||||
},
|
||||
{
|
||||
"description": "Mandatory registration of nonconformities in accordance with internal control requirements.",
|
||||
"name": "Deviation and incident reporting"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "Please select the applicable category for your submission"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"hired"
|
||||
],
|
||||
"wants": "to find out if Nordvik Bygg is hiring and put themselves forward",
|
||||
"who": "a carpenter or apprentice looking for work"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"closed"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "safety_officer",
|
||||
"id": "report-site-issue",
|
||||
"lands_in": "site_issues",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Report something from site",
|
||||
"expect": [
|
||||
"Report something from site"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "What kind of issue",
|
||||
"name": "issue_type",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"Hazard",
|
||||
"Near miss",
|
||||
"Wrong materials delivered"
|
||||
],
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"label": "Where on site",
|
||||
"name": "location",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "What happened",
|
||||
"name": "details",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "Submission of requests pertaining to structural or condition-related evaluation of existing properties.",
|
||||
"name": "Building condition assessment request"
|
||||
},
|
||||
{
|
||||
"description": "Applicants must provide documentation of intended works for the purposes of cost estimation.",
|
||||
"name": "Construction quotation request"
|
||||
},
|
||||
{
|
||||
"description": "Registration of enterprises for prequalification subject to verification of mandatory documentation.",
|
||||
"name": "Subcontractor prequalification"
|
||||
},
|
||||
{
|
||||
"description": "Unsolicited applications are processed in accordance with applicable recruitment procedures.",
|
||||
"name": "Employment enquiries"
|
||||
},
|
||||
{
|
||||
"description": "Mandatory registration of nonconformities in accordance with internal control requirements.",
|
||||
"name": "Deviation and incident reporting"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "Please select the applicable category for your submission"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"closed"
|
||||
],
|
||||
"wants": "to flag a hazard, a near miss or wrong materials fast, from their phone",
|
||||
"who": "a crew member on site, ours or a subcontractor's"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"won",
|
||||
"lost"
|
||||
],
|
||||
"enters_as": "project_managers",
|
||||
"handled_by": "project_managers",
|
||||
"id": "log-phone-enquiry",
|
||||
"lands_in": "enquiries",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Log this enquiry",
|
||||
"expect": [
|
||||
"Log this enquiry"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Their name",
|
||||
"name": "caller_name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Phone or email",
|
||||
"name": "contact",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Address of the building",
|
||||
"name": "address",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "What they told you",
|
||||
"name": "problem",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
},
|
||||
{
|
||||
"label": "How soon",
|
||||
"name": "urgency",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"This week",
|
||||
"This month",
|
||||
"No rush"
|
||||
],
|
||||
"type": "select"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "",
|
||||
"name": "Log this enquiry"
|
||||
}
|
||||
],
|
||||
"page": "/desk/phone-enquiry",
|
||||
"question": "Please select the applicable category for your submission"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"won"
|
||||
],
|
||||
"wants": "to get the enquiry into the system before it gets lost",
|
||||
"who": "a project manager who just took a call, email or text about a building"
|
||||
}
|
||||
],
|
||||
"personas": [
|
||||
{
|
||||
"id": "housing-board-cracks",
|
||||
"need": "survey-enquiry",
|
||||
"text": "There are cracks running up the outside wall of our building and none of us know if it's serious. We just want someone qualified to come look at it and tell us what we're dealing with."
|
||||
},
|
||||
{
|
||||
"id": "homeowner-leaking-roof",
|
||||
"need": "survey-enquiry",
|
||||
"text": "Water's coming through the ceiling every time it rains. I don't know if it's the roof or something else, I just want someone to come out and have a look before it gets worse."
|
||||
},
|
||||
{
|
||||
"id": "homeowner-extension",
|
||||
"need": "build-price-enquiry",
|
||||
"text": "We want to add a room above the garage. We know roughly what we want, we just need someone to draw it up, sort the paperwork with the municipality, and tell us what it will cost."
|
||||
},
|
||||
{
|
||||
"id": "owner-basement-conversion",
|
||||
"need": "build-price-enquiry",
|
||||
"text": "I'd like to turn the basement into a flat. Need drawings, the permit sorted, and a proper price before I commit to anything."
|
||||
},
|
||||
{
|
||||
"id": "electrician-more-work",
|
||||
"need": "subcontractor-application",
|
||||
"text": "We're a small electrical firm and we'd like to pick up more building work. We're registered and insured, just need to know who to talk to about getting on your books."
|
||||
},
|
||||
{
|
||||
"id": "scaffolding-firm",
|
||||
"need": "subcontractor-application",
|
||||
"text": "We put up and take down scaffolding for building sites. We'd like to be one of the firms you call when a job needs it."
|
||||
},
|
||||
{
|
||||
"id": "apprentice-asking",
|
||||
"need": "job-application",
|
||||
"text": "I'm in my second year as an apprentice carpenter and looking for a firm that'll keep me on after. Are you taking anyone on right now?"
|
||||
},
|
||||
{
|
||||
"id": "experienced-carpenter",
|
||||
"need": "job-application",
|
||||
"text": "I've been doing carpentry for eight years, mostly older buildings. Wondered if you had any openings on site."
|
||||
},
|
||||
{
|
||||
"id": "crew-loose-scaffold",
|
||||
"need": "report-site-issue",
|
||||
"text": "Noticed a scaffold board that's not sitting right, feels loose when you step on it. Wanted to flag it straight away before someone gets hurt, but I've got gloves on and no time for a long form."
|
||||
},
|
||||
{
|
||||
"id": "subcontractor-wrong-delivery",
|
||||
"need": "report-site-issue",
|
||||
"text": "The delivery this morning was the wrong type of insulation, not what was on the drawings. Someone needs to know before it gets used."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
{
|
||||
"buckets": {
|
||||
"applicants": {
|
||||
"desks": [
|
||||
{
|
||||
"group": "project_managers",
|
||||
"moves": [
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Invite to interview",
|
||||
"to": "interviewing"
|
||||
},
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Decline",
|
||||
"to": "declined"
|
||||
},
|
||||
{
|
||||
"from": "interviewing",
|
||||
"label": "Hire",
|
||||
"to": "hired"
|
||||
},
|
||||
{
|
||||
"from": "interviewing",
|
||||
"label": "Decline",
|
||||
"to": "declined"
|
||||
}
|
||||
],
|
||||
"page": "/desk"
|
||||
}
|
||||
],
|
||||
"initial": "open",
|
||||
"self_moves": [],
|
||||
"transitions": {
|
||||
"interviewing": [
|
||||
"hired",
|
||||
"declined"
|
||||
],
|
||||
"open": [
|
||||
"interviewing",
|
||||
"declined"
|
||||
]
|
||||
}
|
||||
},
|
||||
"enquiries": {
|
||||
"desks": [
|
||||
{
|
||||
"group": "project_managers",
|
||||
"moves": [
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Looked at the building",
|
||||
"to": "surveyed"
|
||||
},
|
||||
{
|
||||
"from": "surveyed",
|
||||
"label": "Quote sent",
|
||||
"to": "quoted"
|
||||
},
|
||||
{
|
||||
"from": "surveyed",
|
||||
"label": "Not worth quoting",
|
||||
"to": "lost"
|
||||
},
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Not for us",
|
||||
"to": "lost"
|
||||
},
|
||||
{
|
||||
"from": "quoted",
|
||||
"label": "Won",
|
||||
"to": "won"
|
||||
},
|
||||
{
|
||||
"from": "quoted",
|
||||
"label": "Lost",
|
||||
"to": "lost"
|
||||
}
|
||||
],
|
||||
"page": "/desk"
|
||||
}
|
||||
],
|
||||
"initial": "open",
|
||||
"self_moves": [],
|
||||
"transitions": {
|
||||
"open": [
|
||||
"surveyed",
|
||||
"lost"
|
||||
],
|
||||
"quoted": [
|
||||
"won",
|
||||
"lost"
|
||||
],
|
||||
"surveyed": [
|
||||
"quoted",
|
||||
"lost"
|
||||
]
|
||||
}
|
||||
},
|
||||
"site_issues": {
|
||||
"desks": [
|
||||
{
|
||||
"group": "safety_officer",
|
||||
"moves": [
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Look into it",
|
||||
"to": "in_hand"
|
||||
},
|
||||
{
|
||||
"from": "in_hand",
|
||||
"label": "Mark fixed",
|
||||
"to": "closed"
|
||||
}
|
||||
],
|
||||
"page": "/safety"
|
||||
}
|
||||
],
|
||||
"initial": "open",
|
||||
"self_moves": [],
|
||||
"transitions": {
|
||||
"in_hand": [
|
||||
"closed"
|
||||
],
|
||||
"open": [
|
||||
"in_hand"
|
||||
]
|
||||
}
|
||||
},
|
||||
"subcontractors": {
|
||||
"desks": [
|
||||
{
|
||||
"group": "project_managers",
|
||||
"moves": [
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Ask for missing papers",
|
||||
"to": "awaiting_papers"
|
||||
},
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Approve",
|
||||
"to": "approved"
|
||||
},
|
||||
{
|
||||
"from": "open",
|
||||
"label": "Decline",
|
||||
"to": "declined"
|
||||
},
|
||||
{
|
||||
"from": "awaiting_papers",
|
||||
"label": "Approve",
|
||||
"to": "approved"
|
||||
},
|
||||
{
|
||||
"from": "awaiting_papers",
|
||||
"label": "Decline",
|
||||
"to": "declined"
|
||||
}
|
||||
],
|
||||
"page": "/desk"
|
||||
}
|
||||
],
|
||||
"initial": "open",
|
||||
"self_moves": [],
|
||||
"transitions": {
|
||||
"awaiting_papers": [
|
||||
"approved",
|
||||
"declined"
|
||||
],
|
||||
"open": [
|
||||
"awaiting_papers",
|
||||
"approved",
|
||||
"declined"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"needs": [
|
||||
{
|
||||
"done_when": [
|
||||
"won",
|
||||
"lost"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "project_managers",
|
||||
"id": "survey-enquiry",
|
||||
"lands_in": "enquiries",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Get someone to look at a building",
|
||||
"expect": [
|
||||
"Get someone to look at a building"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Your name",
|
||||
"name": "name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"name": "email",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"label": "Address of the building",
|
||||
"name": "address",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "What's wrong",
|
||||
"name": "problem",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "For housing boards and private owners with a problem in a building who want us to come and take a look. A project manager reads it and calls you within two working days to agree a time to come.",
|
||||
"name": "Get someone to look at a building"
|
||||
},
|
||||
{
|
||||
"description": "For owners who already know what they want built and need drawings, a permit application and a price. A project manager reads it and replies within two working days.",
|
||||
"name": "Get a price for a build"
|
||||
},
|
||||
{
|
||||
"description": "For electrical, plumbing and scaffolding firms who want to be one of the firms we call when a job needs them. A project manager checks your papers and answers within a week.",
|
||||
"name": "Join our subcontractor list"
|
||||
},
|
||||
{
|
||||
"description": "For carpenters and apprentices who want to know if we're hiring. A project manager reads every one and answers within a week.",
|
||||
"name": "Ask about working with us"
|
||||
},
|
||||
{
|
||||
"description": "For crew on site, ours or a subcontractor's, who've spotted a hazard, a near miss or the wrong materials. The safety officer sees it at once.",
|
||||
"name": "Report something from site"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "What brings you here?"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"won"
|
||||
],
|
||||
"wants": "someone from Nordvik Bygg to come and look at the building and tell them what a fix would cost",
|
||||
"who": "a housing board member or private owner with a problem in a building"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"won",
|
||||
"lost"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "project_managers",
|
||||
"id": "build-price-enquiry",
|
||||
"lands_in": "enquiries",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Get a price for a build",
|
||||
"expect": [
|
||||
"Get a price for a build"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Your name",
|
||||
"name": "name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"name": "email",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"label": "Address of the site",
|
||||
"name": "address",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "What you want built",
|
||||
"name": "project",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
},
|
||||
{
|
||||
"label": "When you'd like to start",
|
||||
"name": "timeframe",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"As soon as possible",
|
||||
"Within a year",
|
||||
"Just exploring"
|
||||
],
|
||||
"type": "select"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "For housing boards and private owners with a problem in a building who want us to come and take a look. A project manager reads it and calls you within two working days to agree a time to come.",
|
||||
"name": "Get someone to look at a building"
|
||||
},
|
||||
{
|
||||
"description": "For owners who already know what they want built and need drawings, a permit application and a price. A project manager reads it and replies within two working days.",
|
||||
"name": "Get a price for a build"
|
||||
},
|
||||
{
|
||||
"description": "For electrical, plumbing and scaffolding firms who want to be one of the firms we call when a job needs them. A project manager checks your papers and answers within a week.",
|
||||
"name": "Join our subcontractor list"
|
||||
},
|
||||
{
|
||||
"description": "For carpenters and apprentices who want to know if we're hiring. A project manager reads every one and answers within a week.",
|
||||
"name": "Ask about working with us"
|
||||
},
|
||||
{
|
||||
"description": "For crew on site, ours or a subcontractor's, who've spotted a hazard, a near miss or the wrong materials. The safety officer sees it at once.",
|
||||
"name": "Report something from site"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "What brings you here?"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"won"
|
||||
],
|
||||
"wants": "drawings, a permit application and a price for the work",
|
||||
"who": "an owner who already knows what they want built"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"approved",
|
||||
"declined"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "project_managers",
|
||||
"id": "subcontractor-application",
|
||||
"lands_in": "subcontractors",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Join our subcontractor list",
|
||||
"expect": [
|
||||
"Join our subcontractor list"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Company name",
|
||||
"name": "company_name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"name": "contact_email",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"label": "Trade",
|
||||
"name": "trade",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"Electrical",
|
||||
"Plumbing",
|
||||
"Scaffolding"
|
||||
],
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"label": "Company registration number",
|
||||
"name": "registration_number",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Insurance policy details",
|
||||
"name": "insurance",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Trade approval reference",
|
||||
"name": "trade_approval",
|
||||
"optional": true,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "For housing boards and private owners with a problem in a building who want us to come and take a look. A project manager reads it and calls you within two working days to agree a time to come.",
|
||||
"name": "Get someone to look at a building"
|
||||
},
|
||||
{
|
||||
"description": "For owners who already know what they want built and need drawings, a permit application and a price. A project manager reads it and replies within two working days.",
|
||||
"name": "Get a price for a build"
|
||||
},
|
||||
{
|
||||
"description": "For electrical, plumbing and scaffolding firms who want to be one of the firms we call when a job needs them. A project manager checks your papers and answers within a week.",
|
||||
"name": "Join our subcontractor list"
|
||||
},
|
||||
{
|
||||
"description": "For carpenters and apprentices who want to know if we're hiring. A project manager reads every one and answers within a week.",
|
||||
"name": "Ask about working with us"
|
||||
},
|
||||
{
|
||||
"description": "For crew on site, ours or a subcontractor's, who've spotted a hazard, a near miss or the wrong materials. The safety officer sees it at once.",
|
||||
"name": "Report something from site"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "What brings you here?"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"approved"
|
||||
],
|
||||
"wants": "to get on Nordvik Bygg's list of subcontractors they call for work",
|
||||
"who": "a trade firm doing electrical, plumbing or scaffolding work"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"hired",
|
||||
"declined"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "project_managers",
|
||||
"id": "job-application",
|
||||
"lands_in": "applicants",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Ask about working with us",
|
||||
"expect": [
|
||||
"Ask about working with us"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Your name",
|
||||
"name": "name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"name": "email",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"label": "What you do",
|
||||
"name": "role",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"Carpenter",
|
||||
"Apprentice carpenter",
|
||||
"Other"
|
||||
],
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"label": "Anything else we should know",
|
||||
"name": "message",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "For housing boards and private owners with a problem in a building who want us to come and take a look. A project manager reads it and calls you within two working days to agree a time to come.",
|
||||
"name": "Get someone to look at a building"
|
||||
},
|
||||
{
|
||||
"description": "For owners who already know what they want built and need drawings, a permit application and a price. A project manager reads it and replies within two working days.",
|
||||
"name": "Get a price for a build"
|
||||
},
|
||||
{
|
||||
"description": "For electrical, plumbing and scaffolding firms who want to be one of the firms we call when a job needs them. A project manager checks your papers and answers within a week.",
|
||||
"name": "Join our subcontractor list"
|
||||
},
|
||||
{
|
||||
"description": "For carpenters and apprentices who want to know if we're hiring. A project manager reads every one and answers within a week.",
|
||||
"name": "Ask about working with us"
|
||||
},
|
||||
{
|
||||
"description": "For crew on site, ours or a subcontractor's, who've spotted a hazard, a near miss or the wrong materials. The safety officer sees it at once.",
|
||||
"name": "Report something from site"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "What brings you here?"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"hired"
|
||||
],
|
||||
"wants": "to find out if Nordvik Bygg is hiring and put themselves forward",
|
||||
"who": "a carpenter or apprentice looking for work"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"closed"
|
||||
],
|
||||
"enters_as": null,
|
||||
"handled_by": "safety_officer",
|
||||
"id": "report-site-issue",
|
||||
"lands_in": "site_issues",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Report something from site",
|
||||
"expect": [
|
||||
"Report something from site"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "What kind of issue",
|
||||
"name": "issue_type",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"Hazard",
|
||||
"Near miss",
|
||||
"Wrong materials delivered"
|
||||
],
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"label": "Where on site",
|
||||
"name": "location",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "What happened",
|
||||
"name": "details",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "For housing boards and private owners with a problem in a building who want us to come and take a look. A project manager reads it and calls you within two working days to agree a time to come.",
|
||||
"name": "Get someone to look at a building"
|
||||
},
|
||||
{
|
||||
"description": "For owners who already know what they want built and need drawings, a permit application and a price. A project manager reads it and replies within two working days.",
|
||||
"name": "Get a price for a build"
|
||||
},
|
||||
{
|
||||
"description": "For electrical, plumbing and scaffolding firms who want to be one of the firms we call when a job needs them. A project manager checks your papers and answers within a week.",
|
||||
"name": "Join our subcontractor list"
|
||||
},
|
||||
{
|
||||
"description": "For carpenters and apprentices who want to know if we're hiring. A project manager reads every one and answers within a week.",
|
||||
"name": "Ask about working with us"
|
||||
},
|
||||
{
|
||||
"description": "For crew on site, ours or a subcontractor's, who've spotted a hazard, a near miss or the wrong materials. The safety officer sees it at once.",
|
||||
"name": "Report something from site"
|
||||
}
|
||||
],
|
||||
"page": "/",
|
||||
"question": "What brings you here?"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"closed"
|
||||
],
|
||||
"wants": "to flag a hazard, a near miss or wrong materials fast, from their phone",
|
||||
"who": "a crew member on site, ours or a subcontractor's"
|
||||
},
|
||||
{
|
||||
"done_when": [
|
||||
"won",
|
||||
"lost"
|
||||
],
|
||||
"enters_as": "project_managers",
|
||||
"handled_by": "project_managers",
|
||||
"id": "log-phone-enquiry",
|
||||
"lands_in": "enquiries",
|
||||
"steps": [
|
||||
{
|
||||
"alternative": "Log this enquiry",
|
||||
"expect": [
|
||||
"Log this enquiry"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"label": "Their name",
|
||||
"name": "caller_name",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Phone or email",
|
||||
"name": "contact",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "Address of the building",
|
||||
"name": "address",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"label": "What they told you",
|
||||
"name": "problem",
|
||||
"optional": false,
|
||||
"options": [],
|
||||
"type": "textarea"
|
||||
},
|
||||
{
|
||||
"label": "How soon",
|
||||
"name": "urgency",
|
||||
"optional": false,
|
||||
"options": [
|
||||
"This week",
|
||||
"This month",
|
||||
"No rush"
|
||||
],
|
||||
"type": "select"
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"description": "",
|
||||
"name": "Log this enquiry"
|
||||
}
|
||||
],
|
||||
"page": "/desk/phone-enquiry",
|
||||
"question": "What did they tell you?"
|
||||
}
|
||||
],
|
||||
"success": [
|
||||
"won"
|
||||
],
|
||||
"wants": "to get the enquiry into the system before it gets lost",
|
||||
"who": "a project manager who just took a call, email or text about a building"
|
||||
}
|
||||
],
|
||||
"personas": [
|
||||
{
|
||||
"id": "housing-board-cracks",
|
||||
"need": "survey-enquiry",
|
||||
"text": "There are cracks running up the outside wall of our building and none of us know if it's serious. We just want someone qualified to come look at it and tell us what we're dealing with."
|
||||
},
|
||||
{
|
||||
"id": "homeowner-leaking-roof",
|
||||
"need": "survey-enquiry",
|
||||
"text": "Water's coming through the ceiling every time it rains. I don't know if it's the roof or something else, I just want someone to come out and have a look before it gets worse."
|
||||
},
|
||||
{
|
||||
"id": "homeowner-extension",
|
||||
"need": "build-price-enquiry",
|
||||
"text": "We want to add a room above the garage. We know roughly what we want, we just need someone to draw it up, sort the paperwork with the municipality, and tell us what it will cost."
|
||||
},
|
||||
{
|
||||
"id": "owner-basement-conversion",
|
||||
"need": "build-price-enquiry",
|
||||
"text": "I'd like to turn the basement into a flat. Need drawings, the permit sorted, and a proper price before I commit to anything."
|
||||
},
|
||||
{
|
||||
"id": "electrician-more-work",
|
||||
"need": "subcontractor-application",
|
||||
"text": "We're a small electrical firm and we'd like to pick up more building work. We're registered and insured, just need to know who to talk to about getting on your books."
|
||||
},
|
||||
{
|
||||
"id": "scaffolding-firm",
|
||||
"need": "subcontractor-application",
|
||||
"text": "We put up and take down scaffolding for building sites. We'd like to be one of the firms you call when a job needs it."
|
||||
},
|
||||
{
|
||||
"id": "apprentice-asking",
|
||||
"need": "job-application",
|
||||
"text": "I'm in my second year as an apprentice carpenter and looking for a firm that'll keep me on after. Are you taking anyone on right now?"
|
||||
},
|
||||
{
|
||||
"id": "experienced-carpenter",
|
||||
"need": "job-application",
|
||||
"text": "I've been doing carpentry for eight years, mostly older buildings. Wondered if you had any openings on site."
|
||||
},
|
||||
{
|
||||
"id": "crew-loose-scaffold",
|
||||
"need": "report-site-issue",
|
||||
"text": "Noticed a scaffold board that's not sitting right, feels loose when you step on it. Wanted to flag it straight away before someone gets hurt, but I've got gloves on and no time for a long form."
|
||||
},
|
||||
{
|
||||
"id": "subcontractor-wrong-delivery",
|
||||
"need": "report-site-issue",
|
||||
"text": "The delivery this morning was the wrong type of insulation, not what was on the drawings. Someone needs to know before it gets used."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"build-price-enquiry": {
|
||||
"control_ok": true,
|
||||
"control_which": 0,
|
||||
"lix": 25.593406593406595,
|
||||
"options": 5,
|
||||
"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": 26.44899072558647,
|
||||
"options": 5,
|
||||
"overloaded": false,
|
||||
"personas": 2,
|
||||
"required_fields": 4,
|
||||
"steps": 1,
|
||||
"understanding": {
|
||||
"asked": 2,
|
||||
"next": 0,
|
||||
"which": 2
|
||||
}
|
||||
},
|
||||
"report-site-issue": {
|
||||
"control_ok": true,
|
||||
"control_which": 0,
|
||||
"lix": 26.557413600891863,
|
||||
"options": 5,
|
||||
"overloaded": false,
|
||||
"personas": 2,
|
||||
"required_fields": 3,
|
||||
"steps": 1,
|
||||
"understanding": {
|
||||
"asked": 2,
|
||||
"next": 0,
|
||||
"which": 2
|
||||
}
|
||||
},
|
||||
"subcontractor-application": {
|
||||
"control_ok": true,
|
||||
"control_which": 0,
|
||||
"lix": 30.64138678223185,
|
||||
"options": 5,
|
||||
"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.867430441898527,
|
||||
"options": 5,
|
||||
"overloaded": false,
|
||||
"personas": 2,
|
||||
"required_fields": 4,
|
||||
"steps": 1,
|
||||
"understanding": {
|
||||
"asked": 2,
|
||||
"next": 0,
|
||||
"which": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"build-price-enquiry": {
|
||||
"control_ok": true,
|
||||
"control_which": 0,
|
||||
"lix": 28.462059620596207,
|
||||
"options": 5,
|
||||
"overloaded": false,
|
||||
"personas": 2,
|
||||
"required_fields": 5,
|
||||
"steps": 1,
|
||||
"understanding": {
|
||||
"asked": 2,
|
||||
"next": 2,
|
||||
"which": 2
|
||||
}
|
||||
},
|
||||
"job-application": {
|
||||
"control_ok": true,
|
||||
"control_which": 0,
|
||||
"lix": 29.146007816862088,
|
||||
"options": 5,
|
||||
"overloaded": false,
|
||||
"personas": 2,
|
||||
"required_fields": 4,
|
||||
"steps": 1,
|
||||
"understanding": {
|
||||
"asked": 2,
|
||||
"next": 2,
|
||||
"which": 2
|
||||
}
|
||||
},
|
||||
"report-site-issue": {
|
||||
"control_ok": true,
|
||||
"control_which": 0,
|
||||
"lix": 29.2562358276644,
|
||||
"options": 5,
|
||||
"overloaded": false,
|
||||
"personas": 2,
|
||||
"required_fields": 3,
|
||||
"steps": 1,
|
||||
"understanding": {
|
||||
"asked": 2,
|
||||
"next": 2,
|
||||
"which": 2
|
||||
}
|
||||
},
|
||||
"subcontractor-application": {
|
||||
"control_ok": true,
|
||||
"control_which": 0,
|
||||
"lix": 32.111111111111114,
|
||||
"options": 5,
|
||||
"overloaded": false,
|
||||
"personas": 2,
|
||||
"required_fields": 5,
|
||||
"steps": 1,
|
||||
"understanding": {
|
||||
"asked": 2,
|
||||
"next": 2,
|
||||
"which": 2
|
||||
}
|
||||
},
|
||||
"survey-enquiry": {
|
||||
"control_ok": true,
|
||||
"control_which": 0,
|
||||
"lix": 30.151032942490225,
|
||||
"options": 5,
|
||||
"overloaded": false,
|
||||
"personas": 2,
|
||||
"required_fields": 4,
|
||||
"steps": 1,
|
||||
"understanding": {
|
||||
"asked": 2,
|
||||
"next": 2,
|
||||
"which": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ use std::process::{Command, Stdio};
|
||||
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
|
||||
mod rate;
|
||||
mod simulate;
|
||||
|
||||
const REFERENCE: &str = include_str!("../generator/reference.md");
|
||||
@@ -43,6 +44,8 @@ fn main() -> anyhow::Result<()> {
|
||||
match argv.first().map(String::as_str) {
|
||||
Some("simulate") => return simulate::run(&argv),
|
||||
Some("compare") => return simulate::compare(&argv),
|
||||
Some("rate") => return rate::rate(&argv),
|
||||
Some("prefer") => return rate::prefer(&argv),
|
||||
_ => {}
|
||||
}
|
||||
if argv.first().map(String::as_str) == Some("answer") {
|
||||
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
//! Understanding, readability, friendliness: three scores to iterate
|
||||
//! wording against, each measured the way that keeps it from sticking
|
||||
//! at its maximum.
|
||||
//!
|
||||
//! - **Readability** is counted, not judged: LIX, sentence length plus
|
||||
//! the share of words over six letters. Below 30 reads very easily,
|
||||
//! 30-40 easily, 40-50 takes effort, above 50 is official prose.
|
||||
//! - **Understanding** is three yes-or-no questions put to each persona
|
||||
//! about the page in front of them. `rate` also puts them to a
|
||||
//! deliberately gutted twin of the page (headings replaced by
|
||||
//! "Option 1", descriptions removed). An instrument that still says
|
||||
//! yes to the twin is not measuring the page, and `rate` says so.
|
||||
//! - **Friendliness** is a preference between two versions, never an
|
||||
//! absolute mark: `prefer` shows each persona both versions in both
|
||||
//! orders; a pick that flips with the order counts as no preference.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::simulate::ask;
|
||||
|
||||
fn flag(argv: &[String], name: &str) -> Option<String> {
|
||||
argv.iter().position(|a| a == name).and_then(|i| argv.get(i + 1)).cloned()
|
||||
}
|
||||
|
||||
fn load(path: &str) -> anyhow::Result<Value> {
|
||||
Ok(serde_json::from_str(&std::fs::read_to_string(path).with_context(|| format!("reading {path}"))?)?)
|
||||
}
|
||||
|
||||
/// LIX = words per sentence + 100 * long words / words.
|
||||
pub fn lix(text: &str) -> f64 {
|
||||
let words: Vec<&str> = text
|
||||
.split(|c: char| !(c.is_alphanumeric() || c == '\'' || c == '-'))
|
||||
.filter(|w| w.chars().any(char::is_alphabetic))
|
||||
.collect();
|
||||
if words.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let sentences = text.split(['.', '!', '?', ':', '\n']).filter(|s| s.chars().any(char::is_alphabetic)).count().max(1);
|
||||
let long = words.iter().filter(|w| w.chars().count() > 6).count();
|
||||
words.len() as f64 / sentences as f64 + 100.0 * long as f64 / words.len() as f64
|
||||
}
|
||||
|
||||
pub fn lix_band(score: f64) -> &'static str {
|
||||
match score {
|
||||
s if s < 30.0 => "very easy",
|
||||
s if s < 40.0 => "easy",
|
||||
s if s < 50.0 => "takes effort",
|
||||
_ => "official prose",
|
||||
}
|
||||
}
|
||||
|
||||
/// The page as the visitor meets it: the question, every alternative,
|
||||
/// and the form behind the one that serves them.
|
||||
fn page_text(step: &Value, gutted: bool) -> String {
|
||||
let mut text = format!("{}\n", step["question"].as_str().unwrap_or_default());
|
||||
for (i, option) in step["options"].as_array().into_iter().flatten().enumerate() {
|
||||
if gutted {
|
||||
text.push_str(&format!("\n- Option {}\n", i + 1));
|
||||
} else {
|
||||
text.push_str(&format!(
|
||||
"\n- {}\n {}\n",
|
||||
option["name"].as_str().unwrap_or_default(),
|
||||
option["description"].as_str().unwrap_or_default()
|
||||
));
|
||||
}
|
||||
}
|
||||
let fields: Vec<&str> = step["fields"].as_array().into_iter().flatten().filter_map(|f| f["label"].as_str()).collect();
|
||||
if !gutted && !fields.is_empty() {
|
||||
text.push_str(&format!(
|
||||
"\nThe form under \"{}\" asks for: {}.\n",
|
||||
step["alternative"].as_str().unwrap_or_default(),
|
||||
fields.join(", ")
|
||||
));
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
/// What one page may ask of one person before it should be split into
|
||||
/// a further question, or the fields moved to a later stage of the
|
||||
/// case where they are actually needed. Complexity belongs in the
|
||||
/// number of steps and states, not in the weight of any one page.
|
||||
const MAX_OPTIONS: usize = 5;
|
||||
const MAX_REQUIRED: usize = 5;
|
||||
|
||||
/// (alternatives offered, required fields in the form that serves them)
|
||||
fn page_load(step: &Value) -> (usize, usize) {
|
||||
(
|
||||
step["options"].as_array().map_or(0, Vec::len),
|
||||
step["fields"].as_array().into_iter().flatten().filter(|f| f["optional"] != true).count(),
|
||||
)
|
||||
}
|
||||
|
||||
const QUESTIONS: [(&str, &str); 3] = [
|
||||
("which", "Is it clear from the page which option is meant for someone in your situation?"),
|
||||
("asked", "Can you tell from the page what you will be asked to provide?"),
|
||||
("next", "Does the page tell you what happens after you send it, or who will read it?"),
|
||||
];
|
||||
|
||||
fn understanding(cmd: &str, who: &str, page: &str, seed: usize) -> anyhow::Result<[bool; 3]> {
|
||||
let prompt = format!(
|
||||
"You are this person: {who}\n\nThis is the whole of a web page you have just opened:\n---\n{page}---\n\nAnswer three questions about it as yourself. Say yes only when the page text itself gives you that; if you would be guessing, say no.\n{}\nReply as JSON: {{\"which\": true or false, \"asked\": true or false, \"next\": true or false}}",
|
||||
QUESTIONS.iter().map(|(k, q)| format!("- {k}: {q}")).collect::<Vec<_>>().join("\n")
|
||||
);
|
||||
let reply = ask(cmd, &prompt, &seed.to_string())?;
|
||||
Ok([reply["which"] == true, reply["asked"] == true, reply["next"] == true])
|
||||
}
|
||||
|
||||
pub fn rate(argv: &[String]) -> anyhow::Result<()> {
|
||||
let usage = "usage: portal-trainer rate --model <model.json> --out <dir> [--llm 'cmd']";
|
||||
let model = load(&flag(argv, "--model").ok_or_else(|| anyhow!(usage))?)?;
|
||||
let out = std::path::PathBuf::from(flag(argv, "--out").ok_or_else(|| anyhow!(usage))?);
|
||||
let cmd = flag(argv, "--llm").unwrap_or_else(|| "claude -p".to_string());
|
||||
std::fs::create_dir_all(&out)?;
|
||||
|
||||
let mut report = BTreeMap::new();
|
||||
println!("{:<28} {:>6} {:<14} {:>8} {:>8} {:>8} {:>16} {:<22}", "need", "LIX", "", "which", "asked", "next", "gutted twin says", "page load");
|
||||
for need in model["needs"].as_array().into_iter().flatten() {
|
||||
let id = need["id"].as_str().unwrap_or_default();
|
||||
let Some(step) = need["steps"].as_array().and_then(|s| s.first()) else { continue };
|
||||
let personas: Vec<&Value> = model["personas"].as_array().into_iter().flatten().filter(|p| p["need"] == id).collect();
|
||||
if personas.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let page = page_text(step, false);
|
||||
let readability = lix(&page);
|
||||
let (mut yes, mut control) = ([0usize; 3], [0usize; 3]);
|
||||
for (i, persona) in personas.iter().enumerate() {
|
||||
let who = persona["text"].as_str().unwrap_or_default();
|
||||
for (tally, gutted) in [(&mut yes, false), (&mut control, true)] {
|
||||
let answers = understanding(&cmd, who, &page_text(step, gutted), i)?;
|
||||
for (slot, answer) in tally.iter_mut().zip(answers) {
|
||||
*slot += answer as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
let n = personas.len();
|
||||
let share = |k: usize| format!("{}/{n}", yes[k]);
|
||||
// The twin has no headings and no descriptions: "which" must
|
||||
// collapse on it, or the instrument is agreeing with anything.
|
||||
let control_ok = control[0] * 2 < n.max(1);
|
||||
let (options, required) = page_load(step);
|
||||
let steps = need["steps"].as_array().map_or(1, Vec::len);
|
||||
println!(
|
||||
"{:<28} {:>6.0} {:<14} {:>8} {:>8} {:>8} {:>16} {:<22}",
|
||||
id,
|
||||
readability,
|
||||
lix_band(readability),
|
||||
share(0),
|
||||
share(1),
|
||||
share(2),
|
||||
format!("{}/{n} {}", control[0], if control_ok { "ok" } else { "CONTROL FAILED" }),
|
||||
format!(
|
||||
"{options} options, {required} required{}",
|
||||
if options > MAX_OPTIONS || required > MAX_REQUIRED { " - SPLIT" } else { "" }
|
||||
),
|
||||
);
|
||||
report.insert(
|
||||
id.to_string(),
|
||||
json!({
|
||||
"lix": readability, "personas": n, "steps": steps,
|
||||
"options": options, "required_fields": required,
|
||||
"overloaded": options > MAX_OPTIONS || required > MAX_REQUIRED,
|
||||
"understanding": {"which": yes[0], "asked": yes[1], "next": yes[2]},
|
||||
"control_which": control[0], "control_ok": control_ok,
|
||||
}),
|
||||
);
|
||||
}
|
||||
std::fs::write(out.join("rate.json"), serde_json::to_string_pretty(&report)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn prefer(argv: &[String]) -> anyhow::Result<()> {
|
||||
let usage = "usage: portal-trainer prefer --a <model-a.json> --b <model-b.json> [--llm 'cmd']";
|
||||
let a = load(&flag(argv, "--a").ok_or_else(|| anyhow!(usage))?)?;
|
||||
let b = load(&flag(argv, "--b").ok_or_else(|| anyhow!(usage))?)?;
|
||||
let cmd = flag(argv, "--llm").unwrap_or_else(|| "claude -p".to_string());
|
||||
|
||||
println!("{:<28} {:>5} {:>5} {:>14} {:>5} {:>5} {:>14} {:>9}", "need", "A", "B", "no preference", "A", "B", "no preference", "LIX A->B");
|
||||
println!("{:<28} {:^26} {:^26}", "", "friendlier", "clearer");
|
||||
for need_a in a["needs"].as_array().into_iter().flatten() {
|
||||
let id = need_a["id"].as_str().unwrap_or_default();
|
||||
let Some(need_b) = b["needs"].as_array().into_iter().flatten().find(|n| n["id"] == id) else { continue };
|
||||
let (Some(step_a), Some(step_b)) = (need_a["steps"].as_array().and_then(|s| s.first()), need_b["steps"].as_array().and_then(|s| s.first())) else { continue };
|
||||
let (text_a, text_b) = (page_text(step_a, false), page_text(step_b, false));
|
||||
if text_a == text_b {
|
||||
continue;
|
||||
}
|
||||
// [friendlier, clearer] x [A, B, none]
|
||||
let mut wins = [[0usize; 3]; 2];
|
||||
for (i, persona) in a["personas"].as_array().into_iter().flatten().filter(|p| p["need"] == id).enumerate() {
|
||||
let who = persona["text"].as_str().unwrap_or_default();
|
||||
let mut picks: [[&str; 2]; 2] = [["", ""], ["", ""]];
|
||||
for (order, (first, second)) in [(&text_a, &text_b), (&text_b, &text_a)].into_iter().enumerate() {
|
||||
let prompt = format!(
|
||||
"You are this person: {who}\n\nTwo versions of the same web page. They lead to the same thing; only the wording differs.\n\n=== VERSION 1 ===\n{first}\n=== VERSION 2 ===\n{second}\n\nAs yourself: which version makes you feel more welcome and at ease, and which one makes it clearer what to do?\nReply as JSON: {{\"friendlier\": 1 or 2, \"clearer\": 1 or 2}}"
|
||||
);
|
||||
let reply = ask(&cmd, &prompt, &(i * 2 + order).to_string())?;
|
||||
for (k, key) in ["friendlier", "clearer"].iter().enumerate() {
|
||||
let first_won = reply[key] == 1;
|
||||
// In the second order, version 1 is B.
|
||||
picks[k][order] = if first_won == (order == 0) { "A" } else { "B" };
|
||||
}
|
||||
}
|
||||
for k in 0..2 {
|
||||
let slot = match picks[k] {
|
||||
["A", "A"] => 0,
|
||||
["B", "B"] => 1,
|
||||
_ => 2,
|
||||
};
|
||||
wins[k][slot] += 1;
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{:<28} {:>5} {:>5} {:>14} {:>5} {:>5} {:>14} {:>9}",
|
||||
id, wins[0][0], wins[0][1], wins[0][2], wins[1][0], wins[1][1], wins[1][2],
|
||||
format!("{:.0}->{:.0}", lix(&text_a), lix(&text_b))
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn lix_separates_plain_words_from_official_prose() {
|
||||
let plain = "Get someone to look at a building. We come and see it. Then we tell you what it takes.";
|
||||
let official = "Submission of documentation regarding rehabilitation requirements necessitates comprehensive structural evaluation procedures.";
|
||||
assert!(lix(plain) < 30.0, "{}", lix(plain));
|
||||
assert!(lix(official) > 60.0, "{}", lix(official));
|
||||
assert_eq!(lix_band(lix(plain)), "very easy");
|
||||
assert_eq!(lix(""), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_gutted_twin_keeps_the_question_and_loses_the_wording() {
|
||||
let step = json!({
|
||||
"question": "What brings you here?",
|
||||
"options": [{"name": "Get a price", "description": "For owners."}, {"name": "Work with us", "description": "For crews."}],
|
||||
"alternative": "Get a price",
|
||||
"fields": [{"label": "Email"}, {"label": "The build"}]
|
||||
});
|
||||
let page = page_text(&step, false);
|
||||
assert!(page.contains("Get a price") && page.contains("asks for: Email, The build"));
|
||||
let twin = page_text(&step, true);
|
||||
assert!(twin.contains("What brings you here?") && twin.contains("Option 2"));
|
||||
assert!(!twin.contains("Get a price") && !twin.contains("Email"));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -327,7 +327,7 @@ fn resolve_choice(names: &[&str], raw: &str) -> String {
|
||||
/// One JSON-answering call. The backend is asked for JSON where it can
|
||||
/// be (`FORMAT=json`, honoured by engines/ollama.sh) and the first
|
||||
/// object in the reply is taken either way.
|
||||
fn ask(cmd: &str, prompt: &str, seed: &str) -> anyhow::Result<Value> {
|
||||
pub(crate) fn ask(cmd: &str, prompt: &str, seed: &str) -> anyhow::Result<Value> {
|
||||
let reply = llm_with(cmd, prompt, &[("FORMAT", "json"), ("SEED", seed), ("TEMPERATURE", "0.7")])?;
|
||||
Ok(first_object(&reply).unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user