From 7e351348af90b8b4b24e3109cd5b84bfdbbb8721 Mon Sep 17 00:00:00 2001 From: nala Date: Wed, 4 Nov 2026 12:55:03 +0200 Subject: [PATCH] feat: filter public map indicators by indicator and reference period --- .../consolidated-review-matrix-2026-10-03.md | 10 ++ ...antitative-map-filters-local-20261104.json | 78 ++++++++++ .../unification/traceability-and-manual.md | 10 ++ docs/investmoz/v2/auditoria-portal/backlog.md | 10 ++ .../components/investment-map-indicators.tsx | 142 +++++++++++++++++- .../investment-map-indicators.test.mjs | 73 +++++++++ 6 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 docs/investmoz/unification/quantitative-map-filters-local-20261104.json diff --git a/docs/investmoz/consolidated-review-matrix-2026-10-03.md b/docs/investmoz/consolidated-review-matrix-2026-10-03.md index 4ea38595..d378a28f 100644 --- a/docs/investmoz/consolidated-review-matrix-2026-10-03.md +++ b/docs/investmoz/consolidated-review-matrix-2026-10-03.md @@ -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. diff --git a/docs/investmoz/unification/quantitative-map-filters-local-20261104.json b/docs/investmoz/unification/quantitative-map-filters-local-20261104.json new file mode 100644 index 00000000..6ef5f8b5 --- /dev/null +++ b/docs/investmoz/unification/quantitative-map-filters-local-20261104.json @@ -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" + } + ] +} diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index a4e2cf51..823a91ad 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -9171,3 +9171,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. diff --git a/docs/investmoz/v2/auditoria-portal/backlog.md b/docs/investmoz/v2/auditoria-portal/backlog.md index 1d2ed86f..138bc315 100644 --- a/docs/investmoz/v2/auditoria-portal/backlog.md +++ b/docs/investmoz/v2/auditoria-portal/backlog.md @@ -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. diff --git a/resources/js/components/investment-map-indicators.tsx b/resources/js/components/investment-map-indicators.tsx index 6ea190b1..2093dfdb 100644 --- a/resources/js/components/investment-map-indicators.tsx +++ b/resources/js/components/investment-map-indicators.tsx @@ -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 = { 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 (
) : ( <> +
+ + Filtrar observações do contexto territorial + + + + + +
+

+ 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. +

+ {invalidPeriod && ( +

+ O início do período deve ser anterior ou igual ao + fim. +

+ )}

{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.

{context.truncated && ( @@ -114,8 +244,10 @@ export default function InvestmentMapIndicators({ )} {rows.length === 0 ? (

- 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.

) : (
diff --git a/tests/frontend/investment-map-indicators.test.mjs b/tests/frontend/investment-map-indicators.test.mjs index f07d6ad9..73df0224 100644 --- a/tests/frontend/investment-map-indicators.test.mjs +++ b/tests/frontend/investment-map-indicators.test.mjs @@ -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/); +});