Files
timmy-talking-turd/src/suggestion.js
T
timmy 5aee7436f2
Quality gates / quality (pull_request) Successful in 1m45s
feat: make AI suggestions visibly provisional and correction-first (#13)
Suggested, confirmed, and corrected AI states are now distinct in the log
sheet, and correcting a suggested value clears the AI-confirmed state while
keeping every user-entered field. Screen-reader copy states the AI confidence
and who is in control.

- src/suggestion.js: provenance state machine (pending/confirmed/corrected/manual)
- app.js: renders provenance on the scan result and log steps, tracks corrections
- tests/suggestion.test.js: unit coverage for the state machine and a11y copy
- tests/provisional-suggestion.acceptance.mjs: browser acceptance + screenshots
- record_release_demo.mjs: demo now reviews, confirms, and corrects the suggestion
- CI, release builder, and release notes gate the new contract
2026-10-08 08:09:10 +00:00

94 lines
3.8 KiB
JavaScript

// Tracks where a log entry's visual fields came from, so an AI suggestion is never
// shown as if the user had confirmed it.
export const SUGGESTION_STATUS = {
none: 'none',
pending: 'pending',
confirmed: 'confirmed',
corrected: 'corrected',
manual: 'manual',
};
const VISUAL_FIELDS = ['bristolType', 'color'];
export function createSuggestionState(suggestion = null) {
if (!suggestion || suggestion.status !== 'suggestion') {
return { status: SUGGESTION_STATUS.none, suggestion: null, affirmed: false, correctedFields: [] };
}
return {
status: SUGGESTION_STATUS.pending,
suggestion: {
bristolType: suggestion.bristolType,
color: suggestion.color,
confidence: Number(suggestion.confidence),
},
affirmed: false,
correctedFields: [],
};
}
export function confirmSuggestion(state = {}) {
if (!state.suggestion || state.status !== SUGGESTION_STATUS.pending) return { ...state };
return { ...state, status: SUGGESTION_STATUS.confirmed, affirmed: true };
}
export function changeVisualField(state = {}, field, value) {
const correctedFields = [...(state.correctedFields || [])];
if (!VISUAL_FIELDS.includes(field)) return { ...state, correctedFields };
const suggested = state.suggestion ? state.suggestion[field] : undefined;
if (suggested === undefined) {
return {
...state,
status: state.status === SUGGESTION_STATUS.none ? SUGGESTION_STATUS.manual : state.status,
correctedFields,
};
}
if (value === suggested) return { ...state, correctedFields };
if (!correctedFields.includes(field)) correctedFields.push(field);
return { ...state, status: SUGGESTION_STATUS.corrected, affirmed: false, correctedFields };
}
function percent(value) {
return Math.round(Number(value) * 100);
}
export function provenanceCopy(state = {}, current = {}) {
const suggestion = state.suggestion;
const status = suggestion || state.status === SUGGESTION_STATUS.manual
? state.status
: SUGGESTION_STATUS.none;
if (status === SUGGESTION_STATUS.none) return { status, label: '', detail: '', aria: '' };
if (status === SUGGESTION_STATUS.manual) {
return {
status,
label: 'YOUR OWN ENTRY',
detail: 'You chose every field yourself. No AI suggestion is applied.',
aria: `You chose Type ${current.bristolType}, ${current.color} yourself. No AI suggestion is applied.`,
};
}
const confidence = percent(suggestion.confidence);
const suggestedSummary = `Type ${suggestion.bristolType}, ${suggestion.color}`;
if (status === SUGGESTION_STATUS.pending) {
return {
status,
label: 'AI SUGGESTION',
detail: `Suggested ${suggestedSummary} — provisional and not applied yet. You choose whether to use it.`,
aria: `AI suggestion with ${confidence} percent confidence: ${suggestedSummary}. This is a visual suggestion only, not a diagnosis, and it is not saved until you confirm it.`,
};
}
if (status === SUGGESTION_STATUS.corrected) {
const chosen = `Type ${current.bristolType}, ${current.color}`;
return {
status,
label: 'CORRECTED BY YOU',
detail: `AI suggested ${suggestedSummary}. You changed it to ${chosen} — your values are saved, not the suggestion. Urgency, discomfort, notes, and symptoms must come from you.`,
aria: `You changed the AI suggestion. It suggested ${suggestedSummary}; you chose ${chosen}. Your values are saved, not the suggestion.`,
};
}
return {
status,
label: 'AI PREFILLED',
detail: 'AI suggested only form and visible color, and you confirmed it. Urgency, discomfort, notes, and symptoms must come from you.',
aria: `You confirmed the AI suggestion with ${confidence} percent confidence: ${suggestedSummary}. You can still change any of it; the suggestion is a visual aid, not a diagnosis.`,
};
}