Files
Investidor-V2/tools/portal-audit-consolidate.cjs
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

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,
},
};
};