Harden rank feedback duplicate ids
This commit is contained in:
@@ -477,6 +477,25 @@ function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSour
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeOptionIds(options = []) {
|
||||
const seen = new Map();
|
||||
return options.map((option, index) => {
|
||||
const baseId = cleanText(option.id || `option-${index + 1}`, 80) || `option-${index + 1}`;
|
||||
const count = seen.get(baseId) || 0;
|
||||
seen.set(baseId, count + 1);
|
||||
if (count === 0) return { ...option, id: baseId };
|
||||
return {
|
||||
...option,
|
||||
id: cleanText(`${baseId}-${count + 1}`, 80),
|
||||
provenance: {
|
||||
...(option.provenance || {}),
|
||||
originalId: baseId,
|
||||
idNormalized: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function candidateArrayFrom(...entries) {
|
||||
return entries.find(entry => Array.isArray(entry?.items)) || null;
|
||||
}
|
||||
@@ -500,12 +519,12 @@ function optionsFromBody(body = {}) {
|
||||
);
|
||||
if (rawCandidates) {
|
||||
const fallbackId = rawCandidates.sourceSection.toLowerCase().includes('action') ? 'action' : 'feature';
|
||||
return rawCandidates.items.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, fallbackId, rawCandidates.sourceSection)).filter(item => item.title);
|
||||
return normalizeOptionIds(rawCandidates.items.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, fallbackId, rawCandidates.sourceSection)).filter(item => item.title));
|
||||
}
|
||||
if (Array.isArray(body.options)) {
|
||||
return body.options.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, 'option', 'options')).filter(item => item.title);
|
||||
return normalizeOptionIds(body.options.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, 'option', 'options')).filter(item => item.title));
|
||||
}
|
||||
return parseOptionsFromText(body.optionsText || featureSet.optionsText || conceptMap.optionsText || '');
|
||||
return normalizeOptionIds(parseOptionsFromText(body.optionsText || featureSet.optionsText || conceptMap.optionsText || ''));
|
||||
}
|
||||
|
||||
function scoreOption(option, mode, context = '', decisionContext = {}) {
|
||||
@@ -664,6 +683,7 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
|
||||
|
||||
const itemTrace = ranked.map(item => {
|
||||
if (!item.provenance?.sourceSection) warnings.push(`missing source section for ${item.id}`);
|
||||
if (item.provenance?.idNormalized) warnings.push(`duplicate source id ${item.provenance.originalId} normalized to ${item.id}`);
|
||||
if (!item.factors?.evidenceNeeded && ['do', 'test'].includes(item.lane?.id)) warnings.push(`missing evidence needed for active item ${item.id}`);
|
||||
if (item.metrics?.nonGoalConflicts?.length && ['do', 'test'].includes(item.lane?.id)) warnings.push(`active item ${item.id} conflicts with source non-goals: ${item.metrics.nonGoalConflicts.join('; ')}`);
|
||||
return {
|
||||
@@ -672,6 +692,8 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
|
||||
lane: item.lane?.id || 'defer',
|
||||
sourceSection: item.provenance?.sourceSection || '',
|
||||
sourceId: item.provenance?.sourceId || '',
|
||||
originalId: item.provenance?.originalId || '',
|
||||
idNormalized: Boolean(item.provenance?.idNormalized),
|
||||
evidenceNeeded: item.factors?.evidenceNeeded || '',
|
||||
nonGoalConflicts: item.metrics?.nonGoalConflicts || [],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user