Quality gates / quality (pull_request) Successful in 1m45s
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
94 lines
3.8 KiB
JavaScript
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.`,
|
|
};
|
|
}
|