Merge branch 'main' of https://git.tempo.co.mz/nala/Investidor-V2
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-11-05 12:21:50 +02:00
6 changed files with 1872 additions and 5 deletions
@@ -2275,3 +2275,13 @@ Conferência visual exclusivamente em `127.0.0.2:18644`/`main_visual_20261104_te
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral, natureza das grandezas e metodologia quantitativa de potencial económico continuam pendentes conforme dados aplicáveis. A demonstração sintética não é estatística real, índice regional ou diagnóstico do portal remoto. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, instalação, escrita activa, migração, publicação automática ou alteração de permissões/serviços.
Precisão adicional demonstrada: número JSON `1234567890123.456789` recuperado sem arredondamento. O valor é extraído como texto decimal do mesmo snapshot público carregado, sem o converter num float PHP nem misturar metadados e versão com uma leitura posterior. O teste dirigido de precisão e PHPStan passaram após esta correcção.
### 04/11/2026 — ACT-20261029-02: filtros de indicador e período das observações quantitativas
Main local sobre `3f50f666`, sem push. Inventário do concluído: mantidas as camadas temáticas, hierarquia territorial, observações públicas, precisão decimal, fonte/data/versão, estados de conhecimento e limites de cobertura já demonstrados. Conferidos por SHA256 os 63 recibos JSON anteriores da pasta de unificação, todos inalterados. Não foi identificada perda neste delta; as provas aceites, incluindo 1.325 testes/28.377 asserções, não foram reabertas. A instalação anterior de `3f50f666` mantém recibo em `outputs/main-install-20261104/installation-result.json`; os estados «sem instalação» das entradas anteriores descrevem a sua execução histórica.
Próximo recorte dos filtros previstos na matriz: selecção por indicador e período de referência sobre as observações quantitativas públicas já recebidas no mapa. Sobreposição temporal inclusiva, com início e/ou fim opcionais; intervalo invertido apresenta erro. Períodos incompletos, inválidos ou invertidos da fonte continuam consultáveis sem filtro temporal e são excluídos quando este é aplicado; a data da fonte não substitui o período. A limpeza repõe as observações. Não altera a pesquisa de oportunidades, os limites de resposta ou a cobertura territorial; não soma, converte, estima nem infere comparabilidade. Escolha de implementação delimitada aos filtros da ACT-02, sem nova regra organizacional.
Delta verificado: 12 testes frontend dirigidos aprovados, tipos globais, lint do componente e build QA aprovados. Conferência visual apenas em `127.0.0.2:18644`/`main_visual_20261104_test`, reutilizando as cinco observações sintéticas anteriores, sem novas fixtures ou escrita de dados. Produção em 31/12/2025 devolve uma linha com `125.7500` toneladas; intervalo invertido mostra erro; limpar repõe cinco linhas. As 12 oportunidades do cenário QA mantêm-se. Corrigida e conferida a actualização dos campos de data durante a introdução. Recibo e hashes: `docs/investmoz/unification/quantitative-map-filters-local-20261104.json`; DOM, captura e logs em `outputs/quantitative-filters-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral e metodologia quantitativa de potencial económico continuam pendentes conforme fontes aplicáveis. O cenário sintético não é estatística real. Conferência autenticada de Competitividade pela equipa e requisitos amplos V2-07/V2-10 preservados. Sem push, instalação deste delta, migração, importação, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
@@ -0,0 +1,78 @@
{
"date": "2026-11-04",
"requirement": "ACT-20261029-02",
"base_commit": "3f50f6663318bc084b4a233092c87c48e42df8e4",
"branch": "main",
"scope": "Filtros locais por indicador e sobreposicao inclusiva do periodo de referencia das observacoes quantitativas publicas",
"source": "quantitative_context existente, sem alteracao de API/schema/dados/permissoes",
"prior_proofs_verified_unchanged": 63,
"accepted_suite_preserved": "1325 testes / 28377 assercoes, sem repeticao integral",
"checks": {
"frontend_tests": 12,
"frontend_failures": 0,
"types": "passed",
"lint": "passed",
"qa_build": "passed",
"visual": "passed"
},
"visual": {
"url": "http://127.0.0.2:18644/oportunidades?province_id=bfde2651-2b46-5d9c-a500-733fa55a8c5c&view=map",
"database": "main_visual_20261104_test",
"synthetic_observations_reused": 5,
"fixtures_created": 0,
"production_inclusive_end": "2025-12-31",
"filtered_rows": 1,
"exact_value": "125.7500",
"inverted_period_alert": true,
"clear_restores_rows": 5,
"opportunities_unchanged": 12,
"date_input_update_verified": true
},
"limits": [
"Filtragem apenas do conjunto recebido; limites e truncagem preservados",
"Periodos incompletos, invalidos ou invertidos excluidos apenas quando existe filtro temporal",
"Nao usa data da fonte para inferir periodo; nao agrega nem converte grandezas",
"Cobertura factual, ACT-02 parcial e aceitacao da equipa mantidas",
"V2-07/V2-10 e conferencia autenticada de Competitividade preservadas"
],
"remote_push": false,
"active_writes": 0,
"artifacts": [
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\build.log",
"sha256": "8AA5789FFCD47993A524AC56D03D9BB467DFFD115741482557CC95641C946D18"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\cleared.dom.txt",
"sha256": "BB20747D18A3AEB60871A1C7E48324D87D2B90C2A49EA371DD51003A94CFD3B2"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\filtered.dom.txt",
"sha256": "15694ABA461F5AEE46B94CEEF2442A81150F12732B309060FFD48D4758DCEFF0"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\filtered.jpg",
"sha256": "68AE971C375FC2B056289E90781C4D78640583C5BBBB1B1DA95FE170C7A25FC8"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\invalid-period.dom.txt",
"sha256": "00CA11714D99D74949F6BE1FB4BD71BE1B78AE2FB9DFC1377E654C04C1A21013"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\lint.log",
"sha256": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\proofs-before.json",
"sha256": "1F7E40C076AD54E1C0AFD0711F69BD7FF13EA94A14532E791D8197A82A5D2059"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\tests.log",
"sha256": "FB22D38E948C28DF960507F125B21E8BF60769CDCA045CCC540C4EB093C8FD7B"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\types.log",
"sha256": "7505365A9FABA8ECBC6A5EEE8DEDFB464F91D43AFBC00DF22E79FF835F75C612"
}
]
}
File diff suppressed because it is too large Load Diff
@@ -3479,3 +3479,13 @@ Conferência visual exclusivamente em `127.0.0.2:18644`/`main_visual_20261104_te
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral, natureza das grandezas e metodologia quantitativa de potencial económico continuam pendentes conforme dados aplicáveis. A demonstração sintética não é estatística real, índice regional ou diagnóstico do portal remoto. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, instalação, escrita activa, migração, publicação automática ou alteração de permissões/serviços.
Precisão adicional demonstrada: número JSON `1234567890123.456789` recuperado sem arredondamento. O valor é extraído como texto decimal do mesmo snapshot público carregado, sem o converter num float PHP nem misturar metadados e versão com uma leitura posterior. O teste dirigido de precisão e PHPStan passaram após esta correcção.
### 04/11/2026 — ACT-20261029-02: filtros de indicador e período das observações quantitativas
Main local sobre `3f50f666`, sem push. Inventário do concluído: mantidas as camadas temáticas, hierarquia territorial, observações públicas, precisão decimal, fonte/data/versão, estados de conhecimento e limites de cobertura já demonstrados. Conferidos por SHA256 os 63 recibos JSON anteriores da pasta de unificação, todos inalterados. Não foi identificada perda neste delta; as provas aceites, incluindo 1.325 testes/28.377 asserções, não foram reabertas. A instalação anterior de `3f50f666` mantém recibo em `outputs/main-install-20261104/installation-result.json`; os estados «sem instalação» das entradas anteriores descrevem a sua execução histórica.
Próximo recorte dos filtros previstos na matriz: selecção por indicador e período de referência sobre as observações quantitativas públicas já recebidas no mapa. Sobreposição temporal inclusiva, com início e/ou fim opcionais; intervalo invertido apresenta erro. Períodos incompletos, inválidos ou invertidos da fonte continuam consultáveis sem filtro temporal e são excluídos quando este é aplicado; a data da fonte não substitui o período. A limpeza repõe as observações. Não altera a pesquisa de oportunidades, os limites de resposta ou a cobertura territorial; não soma, converte, estima nem infere comparabilidade. Escolha de implementação delimitada aos filtros da ACT-02, sem nova regra organizacional.
Delta verificado: 12 testes frontend dirigidos aprovados, tipos globais, lint do componente e build QA aprovados. Conferência visual apenas em `127.0.0.2:18644`/`main_visual_20261104_test`, reutilizando as cinco observações sintéticas anteriores, sem novas fixtures ou escrita de dados. Produção em 31/12/2025 devolve uma linha com `125.7500` toneladas; intervalo invertido mostra erro; limpar repõe cinco linhas. As 12 oportunidades do cenário QA mantêm-se. Corrigida e conferida a actualização dos campos de data durante a introdução. Recibo e hashes: `docs/investmoz/unification/quantitative-map-filters-local-20261104.json`; DOM, captura e logs em `outputs/quantitative-filters-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral e metodologia quantitativa de potencial económico continuam pendentes conforme fontes aplicáveis. O cenário sintético não é estatística real. Conferência autenticada de Competitividade pela equipa e requisitos amplos V2-07/V2-10 preservados. Sem push, instalação deste delta, migração, importação, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
@@ -1,3 +1,5 @@
import { useId, useState } from 'react';
export type QuantitativeMapContext = {
state: 'available' | 'unavailable' | 'not_included' | 'error';
records: {
@@ -45,6 +47,39 @@ const metadataLabels: Record<string, string> = {
source_date: 'data da fonte',
};
const text = (value?: string | null) => value?.trim() || 'Não indicado';
const completeDate = (value?: string | null) =>
Boolean(
value &&
/^\d{4}-\d{2}-\d{2}$/.test(value) &&
!Number.isNaN(Date.parse(value)) &&
new Date(value).toISOString().slice(0, 10) === value,
);
export function filterMapIndicators(
records: QuantitativeMapContext['records'],
indicator: string,
start: string,
end: string,
) {
if (start && end && start > end) return [];
return records.filter((row) => {
if (indicator !== 'all' && row.indicator !== indicator) return false;
if (!start && !end) return true;
// Incomplete or inverted source periods cannot establish overlap.
if (
!completeDate(row.period_start) ||
!completeDate(row.period_end) ||
!row.period_start ||
!row.period_end ||
row.period_start > row.period_end
)
return false;
return (
(!start || row.period_end >= start) &&
(!end || row.period_start <= end)
);
});
}
export default function InvestmentMapIndicators({
context,
@@ -53,12 +88,19 @@ export default function InvestmentMapIndicators({
context: QuantitativeMapContext | null;
theme: string;
}) {
const filterId = useId();
const [indicator, setIndicator] = useState('all');
const [start, setStart] = useState('');
const [end, setEnd] = useState('');
if (!['all', 'workforce', 'economic_potential'].includes(theme))
return null;
const rows =
const themeRows =
context?.records.filter(
(row) => theme === 'all' || row.theme === theme,
) ?? [];
const rows = filterMapIndicators(themeRows, indicator, start, end);
const invalidPeriod = Boolean(start && end && start > end);
const filtered = indicator !== 'all' || Boolean(start || end);
return (
<section
className="space-y-2 rounded border p-3"
@@ -96,13 +138,101 @@ export default function InvestmentMapIndicators({
</p>
) : (
<>
<fieldset className="grid gap-3 sm:grid-cols-3">
<legend>
Filtrar observações do contexto territorial
</legend>
<label
className="grid gap-1"
htmlFor={`${filterId}-indicator`}
>
Indicador
<select
className="bg-background w-full rounded border p-2"
id={`${filterId}-indicator`}
value={indicator}
onChange={(event) =>
setIndicator(event.target.value)
}
>
<option value="all">
Todos os indicadores
</option>
{Object.entries(labels).map(([key, label]) => (
<option key={key} value={key}>
{label}
</option>
))}
</select>
</label>
<label
className="grid gap-1"
htmlFor={`${filterId}-start`}
>
Período de referência — desde
<input
className="w-full min-w-0 rounded border p-2"
id={`${filterId}-start`}
type="date"
value={start}
onInput={(event) =>
setStart(event.currentTarget.value)
}
aria-invalid={invalidPeriod}
aria-describedby={`${filterId}-help`}
/>
</label>
<label
className="grid gap-1"
htmlFor={`${filterId}-end`}
>
Período de referência — até
<input
className="w-full min-w-0 rounded border p-2"
id={`${filterId}-end`}
type="date"
value={end}
onInput={(event) =>
setEnd(event.currentTarget.value)
}
aria-invalid={invalidPeriod}
aria-describedby={`${filterId}-help`}
/>
</label>
<button
className="w-fit rounded border px-3 py-2 text-sm underline sm:col-span-3"
type="button"
onClick={() => {
setIndicator('all');
setStart('');
setEnd('');
}}
>
Limpar filtros dos indicadores
</button>
</fieldset>
<p id={`${filterId}-help`}>
O período selecciona observações com sobreposição de
datas, incluindo os limites. Datas incompletas ou
invertidas na fonte ficam fora do filtro temporal. A
data da fonte não substitui o período de referência.
Estes filtros actuam apenas nas observações recebidas;
não alteram oportunidades ou limites de cobertura.
</p>
{invalidPeriod && (
<p role="alert">
O início do período deve ser anterior ou igual ao
fim.
</p>
)}
<p role="status">
{rows.length}{' '}
{rows.length === 1 ? 'observação' : 'observações'} nesta
camada, em {context.zones_consulted}{' '}
camada, de {themeRows.length} recebidas, em{' '}
{context.zones_consulted}{' '}
{context.zones_consulted === 1
? 'ficha territorial consultada.'
: 'fichas territoriais consultadas.'}
: 'fichas territoriais consultadas.'}{' '}
A contagem não mede cobertura nacional.
</p>
{context.truncated && (
@@ -114,8 +244,10 @@ export default function InvestmentMapIndicators({
)}
{rows.length === 0 ? (
<p>
Sem observações públicas destes indicadores na
resposta. Valor desconhecido não equivale a zero.
{filtered
? 'Nenhuma observação corresponde aos filtros dos indicadores.'
: 'Sem observações públicas destes indicadores na resposta.'}{' '}
Valor desconhecido não equivale a zero.
</p>
) : (
<details>
@@ -28,6 +28,7 @@ compileFunction(compiled, ['require', 'module', 'exports'])(
module.exports,
);
const Component = module.exports.default;
const filterIndicators = module.exports.filterMapIndicators;
const row = {
id: '1',
zone_id: 'park-a',
@@ -146,3 +147,75 @@ test('unknown values, disputes, missing metadata, incomplete coverage and exclud
/Não foi possível consultar os indicadores/,
);
});
test('indicator and inclusive temporal overlap filters preserve exact source values without mutation', () => {
const records = [
row,
{
...row,
id: 'prior',
period_start: '2024-01-01',
period_end: '2024-12-31',
},
{
...row,
id: 'production',
indicator: 'production',
value: '1234567890123.456789',
},
];
const before = JSON.stringify(records);
assert.deepEqual(
filterIndicators(records, 'employment', '2025-12-31', '2026-01-01').map(
(item) => item.id,
),
['1'],
);
assert.deepEqual(
filterIndicators(records, 'all', '', '2024-01-01').map(
(item) => item.id,
),
['prior'],
);
assert.equal(
filterIndicators(records, 'production', '2025-01-01', '').at(0).value,
'1234567890123.456789',
);
assert.equal(
filterIndicators(records, 'all', '2026-01-01', '2025-01-01').length,
0,
);
assert.equal(JSON.stringify(records), before);
});
test('missing, partial, invalid and inverted source periods are visible unfiltered but never inferred from source date', () => {
const records = [
{ ...row, id: 'missing', period_start: null },
{ ...row, id: 'partial', period_start: '2025' },
{ ...row, id: 'invalid', period_start: '2025-02-30' },
{ ...row, id: 'inverted', period_start: '2026-01-01' },
{ ...row, id: 'unknown', value: null, knowledge: 'unknown' },
{ ...row, id: 'disputed', knowledge: 'disputed' },
];
assert.equal(filterIndicators(records, 'all', '', '').length, 6);
assert.deepEqual(
filterIndicators(records, 'all', '2025-01-01', '2025-12-31').map(
(item) => item.id,
),
['unknown', 'disputed'],
);
assert.equal(
filterIndicators([row], 'all', '2026-01-15', '2026-01-15').length,
0,
);
});
test('quantitative filters are labelled, scoped and explain source date and coverage limits', () => {
const html = render(context);
assert.match(html, /Filtrar observações do contexto territorial/);
assert.match(html, /Período de referência — desde/);
assert.match(html, /Período de referência — até/);
assert.match(html, /A data da fonte não substitui o período de referência/);
assert.match(html, /não alteram oportunidades ou limites de cobertura/);
assert.match(html, /Limpar filtros dos indicadores/);
});