Expose calendar duration and overlap before comparing territorial observations
tests / ci (push) Waiting to run
tests / ci (push) Waiting to run
This commit is contained in:
@@ -9849,3 +9849,12 @@ Backup/restauro fresco de 193 tabelas/411 ficheiros, com contagens e hashes conf
|
||||
Reutilizadas as provas aceites: três testes backend/21 asserções, cinco frontend afectados, análise/tipos/lint/build e conferência visual sintética do componente; o CI obrigatório do executor correu normalmente, sem nova ronda manual dos percursos aceites. HTTPS do mapa, instituições, demografia e entrada privada conferido. Os originais e a pesquisa, suporte e relatórios de 8854b292 foram preservados. Não foram criadas observações sintéticas na homologação para fazer aparecer a comparação. Recibo: `outputs/observation-nature-20261113/homologation-installed.json`; pacote QA selado inalterado.
|
||||
|
||||
Estado do incremento: instalado. ACT-02/V2-07/V2-10 permanecem parciais quanto à cobertura factual, séries estatisticamente comparáveis, oferta laboral, classificação factual do legado e metodologia económica. Esta entrega fecha a capacidade de declarar/recuperar/consultar a natureza das observações, não esses requisitos amplos.
|
||||
|
||||
|
||||
## 13/11/2026 — ACT-02: conferência temporal da comparação, QA
|
||||
|
||||
Continuidade após a natureza das observações instalada em 42b9324a. A comparação existente mostra a duração dos períodos de referência em dias de calendário, com ambos os limites incluídos. Durações diferentes exigem conferência; períodos distintos com sobreposição, incluindo um único dia comum, mostram aviso próprio. Datas incompletas, inválidas ou invertidas continuam por confirmar, sem duração inferida da data da fonte. Não anualiza, calcula taxas/evolução, soma observações ou declara comparabilidade estatística. Valores textuais, zeros, fontes, versões, filtros e âmbito territorial preservados.
|
||||
|
||||
QA: dois testes novos de duração/ano bissexto/sobreposição/limites e datas inválidas; sete testes afectados passaram. Tipos, lint e compilação aprovados. Edge com componente real compilado e dados sintéticos: duração inclusiva de 366/365 dias e aviso de sobreposição de um dia conferidos. Sem API, base de dados, migração, importação ou publicação. Provas: outputs/observation-period-20261113/. Provas aceites dos restantes percursos reutilizadas.
|
||||
|
||||
Estado: implementado em QA, sem instalação. ACT-02 permanece parcial: cobertura factual, classificação documental do legado, séries estatisticamente comparáveis, oferta laboral e metodologia económica conservam o saldo próprio. Esta entrega encerra a conferência automática de duração e sobreposição das datas recebidas, não a comparabilidade factual das séries.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -68,6 +68,21 @@ const completeDate = (value?: string | null) =>
|
||||
);
|
||||
|
||||
type MapObservation = QuantitativeMapContext['records'][number];
|
||||
// Calendar metadata only: inclusive source dates, never a rate or annualisation.
|
||||
function periodDays(row: MapObservation): number | null {
|
||||
if (
|
||||
!completeDate(row.period_start) ||
|
||||
!completeDate(row.period_end) ||
|
||||
row.period_start! > row.period_end!
|
||||
)
|
||||
return null;
|
||||
return (
|
||||
(Date.parse(row.period_end!) - Date.parse(row.period_start!)) /
|
||||
86_400_000 +
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
const observationKey = (row: MapObservation) =>
|
||||
JSON.stringify([row.zone_id, row.id, row.zone_version]);
|
||||
|
||||
@@ -123,6 +138,23 @@ export function compareMapObservations(
|
||||
`${label} observação: data da fonte incompleta ou inválida.`,
|
||||
);
|
||||
}
|
||||
const firstDays = periodDays(first);
|
||||
const secondDays = periodDays(second);
|
||||
if (firstDays !== null && secondDays !== null) {
|
||||
if (firstDays !== secondDays)
|
||||
issues.push(
|
||||
'Duração dos períodos diferente; não trate os valores como bases temporais equivalentes.',
|
||||
);
|
||||
if (
|
||||
(first.period_start !== second.period_start ||
|
||||
first.period_end !== second.period_end) &&
|
||||
first.period_start! <= second.period_end! &&
|
||||
second.period_start! <= first.period_end!
|
||||
)
|
||||
issues.push(
|
||||
'Os períodos sobrepõem-se; as observações podem abranger os mesmos dias.',
|
||||
);
|
||||
}
|
||||
if (measureKind(first) !== measureKind(second))
|
||||
issues.push(
|
||||
'Natureza do valor diferente: observado, planeado e projectado não são equivalentes.',
|
||||
@@ -175,6 +207,13 @@ export function MapObservationComparison({
|
||||
'Período',
|
||||
(row) => `${text(row.period_start)} — ${text(row.period_end)}`,
|
||||
],
|
||||
[
|
||||
'Duração do período (dias, limites incluídos)',
|
||||
(row) => {
|
||||
const days = periodDays(row);
|
||||
return days === null ? 'Por confirmar' : String(days);
|
||||
},
|
||||
],
|
||||
['Âmbito', (row) => text(row.scope)],
|
||||
['População/base', (row) => text(row.population)],
|
||||
['Metodologia', (row) => text(row.methodology)],
|
||||
|
||||
@@ -360,3 +360,31 @@ test('comparison delta: progressive selection stays inside the received filters
|
||||
const single = render({ ...context, records: [row] });
|
||||
assert.ok(!single.includes('Comparar duas observações'));
|
||||
});
|
||||
|
||||
|
||||
test('temporal delta: unequal duration and overlapping boundaries require review without annualising values', () => {
|
||||
const first = {...row, description:'Synthetic source', measure_kind:'observed', period_start:'2024-01-01', period_end:'2024-12-31', value:'123.4500'};
|
||||
const second = {...first, id:'second', period_start:'2025-01-01', period_end:'2025-12-31', value:'0'};
|
||||
const before = JSON.stringify([first,second]);
|
||||
assert.ok(compareObservations(first,second).issues.some(x=>x.includes('Duração dos períodos diferente')));
|
||||
const boundary = {...second, period_start:'2024-12-31'};
|
||||
assert.ok(compareObservations(first,boundary).issues.some(x=>x.includes('sobrepõem-se')));
|
||||
const identical = {...first,id:'same-period'};
|
||||
assert.equal(compareObservations(first,identical).state,'metadata_aligned');
|
||||
const html = renderToStaticMarkup(React.createElement(Comparison,{first,second}));
|
||||
assert.match(html,/Duração do período/);assert.match(html,/>366</);assert.match(html,/>365</);
|
||||
assert.match(html,/123\.4500/);assert.match(html,/>0</);
|
||||
assert.equal(JSON.stringify([first,second]),before);
|
||||
});
|
||||
|
||||
test('temporal delta: missing invalid and inverted dates never become a duration', () => {
|
||||
const first = {...row, description:'Synthetic', measure_kind:'observed'};
|
||||
for (const change of [{period_start:null},{period_start:'2025'},{period_start:'2025-02-30'},{period_start:'2026-01-01'}]) {
|
||||
const second = {...first,id:'invalid',...change};
|
||||
const result = compareObservations(first,second);
|
||||
assert.ok(result.issues.some(x=>x.includes('período incompleto ou inválido')));
|
||||
assert.ok(!result.issues.some(x=>x.includes('Duração dos períodos diferente')));
|
||||
const html = renderToStaticMarkup(React.createElement(Comparison,{first,second}));
|
||||
assert.match(html,/Por confirmar/);assert.ok(!html.includes('NaN'));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user