118 lines
4.7 KiB
JavaScript
118 lines
4.7 KiB
JavaScript
// Consolidates the existing register; keeps every source row and its divergent criteria.
|
|
const crypto = require('node:crypto');
|
|
const normalize = (s) => String(s).normalize('NFC').replace(/\s+/g, ' ').trim();
|
|
module.exports = function consolidate(sourceRows) {
|
|
const groups = new Map();
|
|
for (const row of sourceRows) {
|
|
const field = /^OP-FIELD-([^-]+)-(.+)$/.exec(row.id);
|
|
const definition = field?.[1].replace(/(?:Create|Patch|Response)$/, '');
|
|
const key = field
|
|
? `field:${definition}.${field[2]}`
|
|
: `clause:${row.modulo}:${normalize(row.pedido)}`;
|
|
if (!groups.has(key)) groups.set(key, []);
|
|
groups.get(key).push(row);
|
|
}
|
|
const rank = [
|
|
'concluído e demonstrado',
|
|
'parcial',
|
|
'implementado mas não ativado',
|
|
'dependente de decisão concreta',
|
|
'não implementado',
|
|
'com falha',
|
|
];
|
|
const unique = (values) => [...new Set(values)];
|
|
const result = [];
|
|
for (const [key, members] of groups) {
|
|
const field = key.startsWith('field:');
|
|
const canonical = field
|
|
? 'OP-FIELD-' + key.slice(6).replace('.', '-')
|
|
: members[0].id;
|
|
const selected = members.find((r) => r.id === canonical) || members[0];
|
|
const states = unique(members.map((r) => r.estado));
|
|
const stages = Object.fromEntries(
|
|
['interface', 'api', 'gravacao', 'recuperacao', 'resultado'].map(
|
|
(stage) => [
|
|
stage,
|
|
unique(
|
|
members.map(
|
|
(r) =>
|
|
r.percurso?.[stage] ||
|
|
'Não demonstrado individualmente; localizar critério e executar em QA.',
|
|
),
|
|
).join(' | '),
|
|
],
|
|
),
|
|
);
|
|
result.push({
|
|
...selected,
|
|
id: canonical,
|
|
pedido: field
|
|
? `Persistir, validar e recuperar ${key.slice(6)}; critérios de Create/Patch/Response discriminados por origem`
|
|
: selected.pedido,
|
|
fontes: unique(members.flatMap((r) => r.fontes)),
|
|
rotas: unique(members.flatMap((r) => r.rotas)),
|
|
estado: states.sort((a, b) => rank.indexOf(b) - rank.indexOf(a))[0],
|
|
prioridade: members.some((r) => r.prioridade === 'P0')
|
|
? 'P0'
|
|
: members.some((r) => r.prioridade === 'P1')
|
|
? 'P1'
|
|
: 'P2',
|
|
aceitacao: unique(
|
|
members.map((r) => `${r.id}: ${r.aceitacao}`),
|
|
).join(' | '),
|
|
variantes: unique(
|
|
members.map((r) => `${r.id}: ${r.variantes}`),
|
|
).join(' | '),
|
|
aliases: members.map((r) => r.id),
|
|
linhas_origem: members.length,
|
|
origens: members.map((r) => ({
|
|
id: r.id,
|
|
pedido: r.pedido,
|
|
fontes: r.fontes,
|
|
aceitacao: r.aceitacao,
|
|
estado: r.estado,
|
|
variantes: r.variantes,
|
|
modulo: r.modulo,
|
|
})),
|
|
percurso: stages,
|
|
consolidacao: field
|
|
? 'Mesmo campo canónico; variantes de operação conservadas'
|
|
: 'Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada',
|
|
});
|
|
}
|
|
const ids = new Set(result.map((r) => r.id));
|
|
const aliases = result.flatMap((r) => r.aliases);
|
|
if (
|
|
ids.size !== result.length ||
|
|
new Set(aliases).size !== sourceRows.length ||
|
|
aliases.length !== sourceRows.length
|
|
)
|
|
throw Error('Consolidation lost or duplicated source identities');
|
|
const byState = {};
|
|
for (const row of result) {
|
|
byState[row.estado] ??= { requisitos: 0, linhas_origem: 0 };
|
|
byState[row.estado].requisitos++;
|
|
byState[row.estado].linhas_origem += row.linhas_origem;
|
|
}
|
|
return {
|
|
rows: result,
|
|
summary: {
|
|
requisitos: result.length,
|
|
linhas_matriz: result.length,
|
|
linhas_origem: sourceRows.length,
|
|
por_estado: byState,
|
|
conservation_sha256: crypto
|
|
.createHash('sha256')
|
|
.update(
|
|
aliases
|
|
.slice()
|
|
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
|
|
.join('\n'),
|
|
)
|
|
.digest('hex'),
|
|
metodo: 'Deduplicação exata por módulo e campo canónico entre operações; diferenças preservadas, sem considerar catálogo/API como aceitação de UI.',
|
|
browser_current_version_verified: false,
|
|
},
|
|
};
|
|
};
|