rate: the desks are scored on their buttons
The inside of the business reads button labels and nothing else. For every desk and bucket with two or more buttons, a worker is told what just happened to a case, in the state's own words, and picks the button; scored right of total with what each miss was taken for. The vel's neighbour-help desk scores 2 of 4 and the notice board 1 of 2: the first numbers a desk has ever had. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5e313b7d40
commit
9089503524
@@ -324,6 +324,17 @@ sentences and would pull every page down to "very easy". The
|
|||||||
`rate.json` in `runs/contractor-build-2026-09-21/` predates this and
|
`rate.json` in `runs/contractor-build-2026-09-21/` predates this and
|
||||||
its `asked` and `next` columns are the old, pinned ones.
|
its `asked` and `next` columns are the old, pinned ones.
|
||||||
|
|
||||||
|
`rate --repo` also scores the **desks**, the inside of the business,
|
||||||
|
which reads button labels and nothing else: for every desk and bucket
|
||||||
|
with two or more buttons, a worker is told what has just happened to
|
||||||
|
a case (in the state's own words, never the label's) and picks the
|
||||||
|
button among that desk's, in a rotated order. Scored per desk as
|
||||||
|
right of total, with what each miss was mistaken for. Measured on
|
||||||
|
2026-09-24: the vel's neighbour-help desk 2 of 4 ("not solved" taken
|
||||||
|
for "connect a neighbour"), the notice board 1 of 2, the planned
|
||||||
|
contractor's subcontractor desk 4 of 7 (checking, approved and
|
||||||
|
rejected all crossed). A desk that scores full marks has labels a
|
||||||
|
stranger to the business could press right.
|
||||||
## Measured so far
|
## Measured so far
|
||||||
|
|
||||||
**Generated: a building contractor** (`runs/contractor-2026-09-21/`),
|
**Generated: a building contractor** (`runs/contractor-2026-09-21/`),
|
||||||
|
|||||||
+77
-2
@@ -156,8 +156,57 @@ fn followup_page(repo: Option<&Path>, step: &Value) -> Option<Value> {
|
|||||||
load_page(repo, action)
|
load_page(repo, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `/` is `questions/index.yaml`; `/a/b` is `questions/a/b.yaml` or
|
/// Every desk's buttons per bucket, from the simulation model:
|
||||||
/// `questions/a/b/index.yaml`.
|
/// (page, group, bucket, [(from, to, label)]). A desk with one button
|
||||||
|
/// for a bucket has nothing to confuse and is left out.
|
||||||
|
fn desk_buttons(model: &Value) -> Vec<(String, String, String, Vec<(String, String, String)>)> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for (bucket, spec) in model["buckets"].as_object().into_iter().flatten() {
|
||||||
|
for desk in spec["desks"].as_array().into_iter().flatten() {
|
||||||
|
let moves: Vec<(String, String, String)> = desk["moves"]
|
||||||
|
.as_array()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|m| (s(&m["from"]), s(&m["to"]), s(&m["label"])))
|
||||||
|
.filter(|(_, _, l)| !l.is_empty())
|
||||||
|
.collect();
|
||||||
|
if moves.len() >= 2 {
|
||||||
|
out.push((s(&desk["page"]), s(&desk["group"]), bucket.clone(), moves));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.sort();
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn s(v: &Value) -> String {
|
||||||
|
v.as_str().unwrap_or_default().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A worker on the desk, told what just happened to a case, picks
|
||||||
|
/// the button for it. The outcome is put in plain words from the
|
||||||
|
/// state's own name, never from the label, and the buttons are listed
|
||||||
|
/// in a seeded shuffle.
|
||||||
|
fn button_pick(cmd: &str, group: &str, bucket: &str, from: &str, to: &str, labels: &[&str], seed: usize) -> anyhow::Result<String> {
|
||||||
|
let plain = |id: &str| id.replace(['_', '-'], " ");
|
||||||
|
let mut order: Vec<&str> = labels.to_vec();
|
||||||
|
// A fixed rotation per move: the right answer is not always first.
|
||||||
|
let by = seed % order.len().max(1);
|
||||||
|
order.rotate_left(by);
|
||||||
|
let listed: Vec<String> = order.iter().enumerate().map(|(i, l)| format!("{}. {l}", i + 1)).collect();
|
||||||
|
let prompt = format!(
|
||||||
|
"You work on the {} desk of a small organisation. A case in \"{}\" was \"{}\" and has just become \"{}\". The desk shows these buttons for it:\n\n{}\n\nWhich button records what just happened? Reply as JSON: {{\"choice\": <the button's number>}}",
|
||||||
|
plain(group),
|
||||||
|
plain(bucket),
|
||||||
|
plain(from),
|
||||||
|
plain(to),
|
||||||
|
listed.join("\n")
|
||||||
|
);
|
||||||
|
let reply = ask(cmd, &prompt, &seed.to_string())?;
|
||||||
|
let n = reply["choice"].as_u64().unwrap_or(0) as usize;
|
||||||
|
Ok(order.get(n.wrapping_sub(1)).map(|l| l.to_string()).unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
/// Every `mail:` on a state in the repo's aggregates.yaml, as
|
/// Every `mail:` on a state in the repo's aggregates.yaml, as
|
||||||
/// (`bucket:state`, subject, body) with the `{placeholders}` taken
|
/// (`bucket:state`, subject, body) with the `{placeholders}` taken
|
||||||
/// out, so they read as the person will read them.
|
/// out, so they read as the person will read them.
|
||||||
@@ -193,6 +242,8 @@ pub(crate) fn mail_texts(repo: &Path) -> Option<Vec<(String, String, String)>> {
|
|||||||
Some(out)
|
Some(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `/` is `questions/index.yaml`; `/a/b` is `questions/a/b.yaml` or
|
||||||
|
/// `questions/a/b/index.yaml`.
|
||||||
pub(crate) fn load_page(repo: &Path, route: &str) -> Option<Value> {
|
pub(crate) fn load_page(repo: &Path, route: &str) -> Option<Value> {
|
||||||
let rel = route.trim_matches('/');
|
let rel = route.trim_matches('/');
|
||||||
let candidates = if rel.is_empty() {
|
let candidates = if rel.is_empty() {
|
||||||
@@ -248,6 +299,30 @@ pub fn rate(argv: &[String]) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut report = BTreeMap::new();
|
let mut report = BTreeMap::new();
|
||||||
|
// The desks: the inside of the business reads button labels and
|
||||||
|
// nothing else, so each desk is scored on whether a worker told
|
||||||
|
// what has just happened to a case picks the right button among
|
||||||
|
// that desk's buttons for the bucket.
|
||||||
|
let desks = desk_buttons(&model);
|
||||||
|
if !desks.is_empty() {
|
||||||
|
println!("{:<20} {:<24} {:>8} {}", "desk", "bucket", "buttons", "mistaken for");
|
||||||
|
for (page, group, bucket, moves) in &desks {
|
||||||
|
let labels: Vec<&str> = moves.iter().map(|(_, _, l)| l.as_str()).collect();
|
||||||
|
let mut right = 0;
|
||||||
|
let mut mistaken = Vec::new();
|
||||||
|
for (i, (from, to, label)) in moves.iter().enumerate() {
|
||||||
|
let picked = button_pick(&cmd, group, bucket, from, to, &labels, i)?;
|
||||||
|
if picked == *label {
|
||||||
|
right += 1;
|
||||||
|
} else {
|
||||||
|
mistaken.push(format!("{to} as {picked:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("{:<20} {:<24} {:>3} of {:<2} {}", page, bucket, right, moves.len(), mistaken.join("; "));
|
||||||
|
report.insert(format!("desk {page} {bucket}"), json!({ "right": right, "of": moves.len(), "mistaken": mistaken }));
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
// The mails first, when the repo carries any: a person reads a
|
// The mails first, when the repo carries any: a person reads a
|
||||||
// mail alone, with no card around it, so its prose is rated on
|
// mail alone, with no card around it, so its prose is rated on
|
||||||
// its own - LIX and length.
|
// its own - LIX and length.
|
||||||
|
|||||||
Reference in New Issue
Block a user