humanish 0.63.0 → 0.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/run.d.ts CHANGED
@@ -920,6 +920,10 @@ export interface StudyTaskFunnel {
920
920
  sessions: number;
921
921
  /** False when the task declared no success criterion: asked for, never measurable. */
922
922
  observable: boolean;
923
+ /** Sessions where this task's criteria were never evaluated, because the observations they
924
+ * read never arrived. Counted apart from failures: "0/3 completed" with 3 unmeasured is a
925
+ * statement about our instrument, not about the participants (#514). */
926
+ unmeasured: number;
923
927
  }>;
924
928
  }
925
929
  /** Roll per-participant funnels up into the study funnel. Undefined when nothing measured one. */
package/dist/run.js CHANGED
@@ -92,7 +92,7 @@ export function aggregateTaskFunnels(funnels) {
92
92
  for (const task of funnel.tasks) {
93
93
  let entry = byId.get(task.id);
94
94
  if (entry === undefined) {
95
- entry = { completed: 0, sessions: 0, observable: false };
95
+ entry = { completed: 0, sessions: 0, observable: false, unmeasured: 0 };
96
96
  byId.set(task.id, entry);
97
97
  order.push(task.id);
98
98
  }
@@ -101,13 +101,21 @@ export function aggregateTaskFunnels(funnels) {
101
101
  entry.completed += 1;
102
102
  if (task.observable)
103
103
  entry.observable = true;
104
+ if (task.inputsObserved === false)
105
+ entry.unmeasured += 1;
104
106
  }
105
107
  }
106
108
  return {
107
109
  sessions: funnels.length,
108
110
  tasks: order.map((id) => {
109
111
  const entry = byId.get(id);
110
- return { id, completed: entry.completed, sessions: entry.sessions, observable: entry.observable };
112
+ return {
113
+ id,
114
+ completed: entry.completed,
115
+ sessions: entry.sessions,
116
+ observable: entry.observable,
117
+ unmeasured: entry.unmeasured
118
+ };
111
119
  })
112
120
  };
113
121
  }
@@ -116,9 +124,17 @@ export function formatStudyTaskFunnel(funnel) {
116
124
  if (funnel.tasks.length === 0)
117
125
  return "no tasks declared";
118
126
  return funnel.tasks
119
- .map((task) => task.observable
120
- ? `${task.id} ${task.completed}/${task.sessions}`
121
- : `${task.id} (no completion criterion)`)
127
+ .map((task) => {
128
+ if (!task.observable)
129
+ return `${task.id} (no completion criterion)`;
130
+ // A count that is entirely unmeasured must not render as a bare "0/3": that reads as a
131
+ // participant failure when it is our observer that produced nothing (#514).
132
+ if (task.unmeasured === task.sessions) {
133
+ return `${task.id} (never measured in ${task.sessions})`;
134
+ }
135
+ const caveat = task.unmeasured > 0 ? ` (${task.unmeasured} never measured)` : "";
136
+ return `${task.id} ${task.completed}/${task.sessions}${caveat}`;
137
+ })
122
138
  .join(" · ");
123
139
  }
124
140
  /** One line a stakeholder can read, with the denominator attached to every number. */