feat: add workforce evidence and sourced economic context map layers

This commit is contained in:
2026-11-04 11:27:37 +02:00
parent 21ebad73e7
commit a9241ff26b
11 changed files with 358 additions and 16 deletions
+3 -2
View File
@@ -538,11 +538,12 @@ class InvestmentCatalogue
}
$public = app(ProactiveInvestment::class)->publicData($record);
$evidence = array_values(array_filter(StructuredData::collectionItems($public['evidence'] ?? []),
fn ($proof) => in_array($proof['category'] ?? null, ['resource', 'infrastructure'], true) && ($proof['purpose'] ?? null) === 'local'));
fn ($proof) => in_array($proof['category'] ?? null, ['resource', 'infrastructure', 'workforce', 'activity', 'supply', 'demand'], true) && ($proof['purpose'] ?? null) === 'local'));
if ($evidence !== []) {
$thematic[] = ['id' => $record->id, 'title' => $record->title, 'url' => $properties['url'], 'location' => $properties['location'],
'state' => $public['state'] ?? 'hypothesis', 'limitations' => $public['limitations'] ?? null,
'evidence' => array_map(fn ($proof) => array_intersect_key($proof, array_flip(['id', 'category', 'statement', 'source', 'locator', 'period', 'scope', 'coverage', 'verified_on'])), $evidence)];
'evidence' => array_map(fn ($proof) => [...array_intersect_key($proof, array_flip(['id', 'statement', 'source', 'locator', 'period', 'scope', 'coverage', 'verified_on'])),
'source_category' => $proof['category'], 'category' => in_array($proof['category'], ['activity', 'supply', 'demand'], true) ? 'economic_potential' : $proof['category']], $evidence)];
}
$sites = collect($points->get($record->id) ?? []);
if ($sites->isNotEmpty()) {
+1 -1
View File
@@ -33,7 +33,7 @@ final class ProactiveInvestment
'proactive.evidence.*.observation_id' => 'sometimes|nullable|string|size:64',
'proactive.supply_comparisons' => 'sometimes|array|max:40',
'proactive.evidence.*.id' => 'required|uuid|distinct',
'proactive.evidence.*.category' => 'required|in:activity,resource,infrastructure,supply,demand',
'proactive.evidence.*.category' => 'required|in:activity,resource,infrastructure,supply,demand,workforce',
'proactive.evidence.*.purpose' => 'required|in:local,methodology',
'proactive.evidence.*.access' => 'required|in:internal,public',
'proactive.evidence.*.document_id' => 'nullable|uuid',
@@ -2243,3 +2243,18 @@ Delta aprovado: oito testes de BD/438 asserções (quatro novos e quatro de filt
Conferência visual no Edge exclusivamente em `127.0.0.2:18644`, base `main_visual_20261104_test`: duas fixtures claramente sintéticas criadas apenas em QA, uma por categoria; nenhuma coordenada/geometria criada. Hash de todas as oportunidades anteriores idêntico antes/depois. Alternância entre ambas conserva pesquisa provincial e duas fichas gerais; cada camada mostra a sua ficha, fonte/período/âmbito/limites e ausência de ponto. Infra-estruturas recuperada após recarregar. Recibo: `docs/investmoz/unification/thematic-map-local-20261104.json`; inventário, hashes, DOM e capturas em `outputs/thematic-map-20261104/`. Demonstração sintética não constitui cobertura económica factual ou publicação de dados reais.
**ACT-02 permanece parcial.** Cobertura factual de recursos/infra-estruturas no destino, georreferenciação própria quando documentada, mão-de-obra, potencial económico quantitativo, restantes fontes temáticas e lacunas cartográficas continuam pendentes. Não se inferem estas camadas de descrições nem do número de oportunidades. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, alteração activa, migração, publicação automática ou alteração de permissões/serviços.
### 04/11/2026 — ACT-20261029-02: mão-de-obra documentada e contexto económico
Incremento qualitativo na main local sobre `21ebad73`, reutilizando mapa, evidências, síntese publicada, divulgação expressa e fontes/períodos existentes. Inventário na cópia QA: duas evidências públicas locais de actividade anteriores (fichas sintéticas 350/383), nenhuma de mão-de-obra/oferta/procura; recursos/infra-estruturas da demonstração anterior conservados. As declarações labour_demands/training_capacities têm acesso EducationAccess interno; emprego/indicadores financeiros canónicos não fazem parte da publicProjection. Observações de zonas conservam divulgação/unidades/metodologia próprias. Não foram expostas nem agregadas essas fontes privadas por existir revisão interna. As fontes oficiais aceites e as decisões de divulgação anteriores não foram reabertas.
Acrescentada categoria explícita Mão-de-obra no editor e validação da evidência existente, sem migração ou reclassificação automática dos textos anteriores. Guardar, recuperar, pré-visualizar e confirmar publicação usam o percurso existente. A camada económica reutiliza apenas evidências públicas locais de actividade/oferta/procura, mantendo `source_category` original, facto, fonte, localizador, período, verificação, âmbito, cobertura e limitações. Agrupá-las para consulta é uma escolha de apresentação, não aprovação de viabilidade nem índice de potencial.
Ambas as opções entram no selector do mapa único; a escolha é recuperada na sessão. Recursos/infra-estruturas, geometrias públicas, filtros territoriais, fichas sem ponto e contagens anteriores mantêm-se. Nenhuma posição é criada ou atribuída ao recurso/mão-de-obra. Necessidade ou qualificação não equivale a trabalhador disponível; previsão de emprego não é emprego efectivo. Contexto económico não calcula retorno, score ou viabilidade. Períodos/fontes incompatíveis permanecem separados, sem totais ou zero presumido.
Delta aprovado: sete testes de BD/77 asserções (cinco do mapa, incluindo o novo caso; dois de publicação, um novo e um de acesso/fonte privada já existente), 22 testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Teste do percurso: criar evidência workforce → recuperar → confirmar pela publicação existente → consultar mapa → alterar rascunho privado sem substituir a categoria/fonte públicas. Testes mantêm metodologia/internos fora do mapa, categorias originais, períodos distintos e ausência de quantidades/índices inferidos. Não se repetiu a bateria geral aceite.
Conferência visual no Edge exclusivamente na QA `127.0.0.2:18644`/`main_visual_20261104_test`: contexto económico reutiliza a evidência de actividade já publicada da ficha 383, sintética e explicitamente identificada. Mão-de-obra usa uma única fixture nova, também sintética; escolha e fonte recuperadas após recarregar. Nenhuma coordenada/geometria criada; hash de todas as oportunidades anteriores idêntico antes/depois. Conferidos 36 artefactos dos três recibos anteriores, todos com SHA256 inalterado. Recibo: `docs/investmoz/unification/workforce-economic-map-local-20261104.json`; inventário, DOM, capturas e hashes em `outputs/workforce-economic-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual laboral/económica, séries, unidades e metodologia quantitativa comparável, georreferenciação própria documentada e cobertura cartográfica continuam pendentes. A demonstração sintética não mede oferta laboral nem potencial regional real. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, migração, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
@@ -9139,3 +9139,18 @@ Delta aprovado: oito testes de BD/438 asserções (quatro novos e quatro de filt
Conferência visual no Edge exclusivamente em `127.0.0.2:18644`, base `main_visual_20261104_test`: duas fixtures claramente sintéticas criadas apenas em QA, uma por categoria; nenhuma coordenada/geometria criada. Hash de todas as oportunidades anteriores idêntico antes/depois. Alternância entre ambas conserva pesquisa provincial e duas fichas gerais; cada camada mostra a sua ficha, fonte/período/âmbito/limites e ausência de ponto. Infra-estruturas recuperada após recarregar. Recibo: `docs/investmoz/unification/thematic-map-local-20261104.json`; inventário, hashes, DOM e capturas em `outputs/thematic-map-20261104/`. Demonstração sintética não constitui cobertura económica factual ou publicação de dados reais.
**ACT-02 permanece parcial.** Cobertura factual de recursos/infra-estruturas no destino, georreferenciação própria quando documentada, mão-de-obra, potencial económico quantitativo, restantes fontes temáticas e lacunas cartográficas continuam pendentes. Não se inferem estas camadas de descrições nem do número de oportunidades. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, alteração activa, migração, publicação automática ou alteração de permissões/serviços.
### 04/11/2026 — ACT-20261029-02: mão-de-obra documentada e contexto económico
Incremento qualitativo na main local sobre `21ebad73`, reutilizando mapa, evidências, síntese publicada, divulgação expressa e fontes/períodos existentes. Inventário na cópia QA: duas evidências públicas locais de actividade anteriores (fichas sintéticas 350/383), nenhuma de mão-de-obra/oferta/procura; recursos/infra-estruturas da demonstração anterior conservados. As declarações labour_demands/training_capacities têm acesso EducationAccess interno; emprego/indicadores financeiros canónicos não fazem parte da publicProjection. Observações de zonas conservam divulgação/unidades/metodologia próprias. Não foram expostas nem agregadas essas fontes privadas por existir revisão interna. As fontes oficiais aceites e as decisões de divulgação anteriores não foram reabertas.
Acrescentada categoria explícita Mão-de-obra no editor e validação da evidência existente, sem migração ou reclassificação automática dos textos anteriores. Guardar, recuperar, pré-visualizar e confirmar publicação usam o percurso existente. A camada económica reutiliza apenas evidências públicas locais de actividade/oferta/procura, mantendo `source_category` original, facto, fonte, localizador, período, verificação, âmbito, cobertura e limitações. Agrupá-las para consulta é uma escolha de apresentação, não aprovação de viabilidade nem índice de potencial.
Ambas as opções entram no selector do mapa único; a escolha é recuperada na sessão. Recursos/infra-estruturas, geometrias públicas, filtros territoriais, fichas sem ponto e contagens anteriores mantêm-se. Nenhuma posição é criada ou atribuída ao recurso/mão-de-obra. Necessidade ou qualificação não equivale a trabalhador disponível; previsão de emprego não é emprego efectivo. Contexto económico não calcula retorno, score ou viabilidade. Períodos/fontes incompatíveis permanecem separados, sem totais ou zero presumido.
Delta aprovado: sete testes de BD/77 asserções (cinco do mapa, incluindo o novo caso; dois de publicação, um novo e um de acesso/fonte privada já existente), 22 testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Teste do percurso: criar evidência workforce → recuperar → confirmar pela publicação existente → consultar mapa → alterar rascunho privado sem substituir a categoria/fonte públicas. Testes mantêm metodologia/internos fora do mapa, categorias originais, períodos distintos e ausência de quantidades/índices inferidos. Não se repetiu a bateria geral aceite.
Conferência visual no Edge exclusivamente na QA `127.0.0.2:18644`/`main_visual_20261104_test`: contexto económico reutiliza a evidência de actividade já publicada da ficha 383, sintética e explicitamente identificada. Mão-de-obra usa uma única fixture nova, também sintética; escolha e fonte recuperadas após recarregar. Nenhuma coordenada/geometria criada; hash de todas as oportunidades anteriores idêntico antes/depois. Conferidos 36 artefactos dos três recibos anteriores, todos com SHA256 inalterado. Recibo: `docs/investmoz/unification/workforce-economic-map-local-20261104.json`; inventário, DOM, capturas e hashes em `outputs/workforce-economic-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual laboral/económica, séries, unidades e metodologia quantitativa comparável, georreferenciação própria documentada e cobertura cartográfica continuam pendentes. A demonstração sintética não mede oferta laboral nem potencial regional real. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, migração, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
@@ -0,0 +1,168 @@
{
"date": "2026-11-04",
"base_commit": "21ebad73",
"requirement": "ACT-20261029-02; V2-07/V2-10",
"scope": "Incremento qualitativo de mão-de-obra e contexto do potencial económico; main local",
"source_inventory": {
"database": "main_visual_20261104_test",
"public_local_evidence": {
"resource": 1,
"infrastructure": 1,
"workforce": 0,
"activity": 2,
"supply": 0,
"demand": 0
},
"private_sources_not_disclosed": [
"labour_demands/training_capacities: EducationAccess authorization",
"canonical employment and financial payloads: not part of publicProjection",
"zone observations: separate disclosure/units/methodology; not automatically mixed with opportunity evidence"
],
"existing_economic_sources": [
{
"opportunity_id": 383,
"title": "SYNTHETIC QA — ideia de apoio em Cahora Bassa",
"category": "activity",
"source": "SYNTHETIC QA exemplar público da interface",
"period": "2026 — cenário sintético",
"scope": "Cahora Bassa — referência distrital existente"
},
{
"opportunity_id": 350,
"title": "SYNTHETIC QA — ideia de apoio em Cahora Bassa",
"category": "activity",
"source": "SYNTHETIC QA exemplar público da interface",
"period": "2026 — cenário sintético",
"scope": "Cahora Bassa — referência distrital existente"
}
]
},
"implementation": [
"Categoria workforce no editor/validação existentes; mesmos campos de fonte, período, âmbito e divulgação expressa.",
"Mapeamento de activity/supply/demand já públicos para a camada economic_potential, conservando source_category e cada fonte/período.",
"Apenas evidência publicada, seleccionada e de propósito local; rascunhos privados e metodologia excluídos.",
"Escolha de ambas as novas camadas recuperável na sessão; pontos/áreas limitados aos locais existentes das oportunidades.",
"Sem unidades, quantidades, disponibilidade laboral, índices ou retorno calculados a partir dos textos; sem agregação de períodos/fontes incompatíveis."
],
"existing_private_sources": [
"EducationAccess protege labour_demands/training_capacities; revisão interna não é publicação.",
"OpportunityCanonicalContract::publicProjection não divulga emprego/indicadores financeiros privados.",
"Observações de zonas conservam divulgação, unidades e metodologia próprias; não foram automaticamente misturadas."
],
"contract": {
"route": "GET /api/v1/investment/map",
"field": "thematic_records.evidence",
"new_categories": [
"workforce",
"economic_potential"
],
"source_category": "categoria original explícita; recursos/infra-estruturas mantêm categoria anterior",
"version_compatibility": "Campos/fichas/históricos anteriores conservados; adição de categoria no rascunho existente, sem migração"
},
"checks": {
"database_tests": 7,
"database_assertions": 77,
"map_tests": 5,
"publication_tests": 2,
"frontend_tests": 22,
"phpstan_errors": 0,
"types": "passed",
"pint": "passed",
"lint": "passed",
"qa_build": "passed; avisos de fonte opcional e dimensão de chunks"
},
"visual": {
"url_base": "http://127.0.0.2:18644",
"database": "main_visual_20261104_test",
"economic_existing_opportunity_id": 383,
"economic_source_reused": "Evidência de actividade já pública, explicitamente sintética no snapshot QA; original preservado.",
"new_workforce_fixture_ids": [
534
],
"workforce_recovered_after_reload": true,
"new_coordinates": false,
"new_geometry": false,
"existing_opportunities_sha256": "90aaee1f9e47bb7793e18746fb1f6935bfb0a21329deca9c569707fadd2c04c6"
},
"preservation": {
"prior_artifact_hashes_verified_unchanged": 36,
"accepted_full_database_tests_preserved_not_reexecuted": 1325,
"previous_pending_items_retained": true
},
"pending": [
"Cobertura factual laboral e económica real, séries comparáveis, unidades e metodologia quantitativa permanecem pendentes.",
"A camada económica é contexto documental, sem classificação de viabilidade ou score regional.",
"Procura de trabalho, formação, empregos previstos e trabalhadores disponíveis não são intercambiáveis.",
"Conferência remota, aceitação da equipa e cobertura cartográfica permanecem pendentes; ACT-02/V2-07/V2-10 não encerrados."
],
"safety": {
"push": false,
"active_database_changed": false,
"migration": false,
"permissions_changed": false,
"automatic_publication": false,
"services_restarted": false
},
"proofs": {
"outputs/workforce-economic-map-20261104/source-inventory-before-fixtures.json": {
"sha256": "cd8a8943bf86778e041cc3f2ced51467d44e6e83147c8a8215024a27431d2964",
"bytes": 1313
},
"outputs/workforce-economic-map-20261104/fixture-receipt.json": {
"sha256": "7d680fed74a70d4367f1a7d8e0af60a0b7c5e5f89bc3ff4a17be5e119d294dc0",
"bytes": 405
},
"outputs/workforce-economic-map-20261104/existing-economic-context.jpg": {
"sha256": "6f81b29cbefad611b035fe26324d4b876d5dd5a9499a6fbd8ee75e8b4416aec5",
"bytes": 157445
},
"outputs/workforce-economic-map-20261104/existing-economic-context.dom.txt": {
"sha256": "1eb2687e9a3e73f4b3ed9808a9328930c54ca08dd6270e7d2e7bff1cfbef7227",
"bytes": 20535
},
"outputs/workforce-economic-map-20261104/workforce-reopened.jpg": {
"sha256": "b4c919ca4bfc344cd4feb5adcbe4a483d241d549d75450ee62a8b3854ccc6f34",
"bytes": 161166
},
"outputs/workforce-economic-map-20261104/workforce-reopened.dom.txt": {
"sha256": "01c6081409ea36d88e01d1c8b9af558eb497e68b596fe8be1b45cdec437c7515",
"bytes": 13566
},
"outputs/main-db-qa-20261104/workforce-economic-map.log": {
"sha256": "c6fb7cddb76a2589f3c41dc3d144b0387573417ddd9afcff96f331b801f53c0f",
"bytes": 94
},
"outputs/main-db-qa-20261104/workforce-economic-map.junit.xml": {
"sha256": "5824abb4aaa07c37e9420802d82a8ed08c4cd1babc76c337043016eb098f37ec",
"bytes": 2374
},
"outputs/main-db-qa-20261104/workforce-publication.log": {
"sha256": "3b0cb219fb68177a1a026ac7c076fe0da81f40c20aa6a8415a54866a08895c9d",
"bytes": 95
},
"outputs/main-db-qa-20261104/workforce-publication.junit.xml": {
"sha256": "7ad274166f6520990241a82530c51ac662540907bcc831c83889190176a80eaf",
"bytes": 958
},
"outputs/main-db-qa-20261104/workforce-economic-frontend.log": {
"sha256": "02dc96a1acd6887ac3fdc282562e8e5579e05bcf29283330effdf5689619ee09",
"bytes": 2811
},
"outputs/main-db-qa-20261104/workforce-economic-phpstan.log": {
"sha256": "1997495ca8f04c2ec09798d55607d1a2f85e3338b772f258666465bf658032d2",
"bytes": 49
},
"outputs/main-db-qa-20261104/workforce-economic-types.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/workforce-economic-lint.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/workforce-economic-build.log": {
"sha256": "ec0f15282f2dfe3c3c8dbe349773a88adbd87efba7e26332cd55c34b8f21bf25",
"bytes": 31606
}
}
}
@@ -3447,3 +3447,18 @@ Delta aprovado: oito testes de BD/438 asserções (quatro novos e quatro de filt
Conferência visual no Edge exclusivamente em `127.0.0.2:18644`, base `main_visual_20261104_test`: duas fixtures claramente sintéticas criadas apenas em QA, uma por categoria; nenhuma coordenada/geometria criada. Hash de todas as oportunidades anteriores idêntico antes/depois. Alternância entre ambas conserva pesquisa provincial e duas fichas gerais; cada camada mostra a sua ficha, fonte/período/âmbito/limites e ausência de ponto. Infra-estruturas recuperada após recarregar. Recibo: `docs/investmoz/unification/thematic-map-local-20261104.json`; inventário, hashes, DOM e capturas em `outputs/thematic-map-20261104/`. Demonstração sintética não constitui cobertura económica factual ou publicação de dados reais.
**ACT-02 permanece parcial.** Cobertura factual de recursos/infra-estruturas no destino, georreferenciação própria quando documentada, mão-de-obra, potencial económico quantitativo, restantes fontes temáticas e lacunas cartográficas continuam pendentes. Não se inferem estas camadas de descrições nem do número de oportunidades. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, alteração activa, migração, publicação automática ou alteração de permissões/serviços.
### 04/11/2026 — ACT-20261029-02: mão-de-obra documentada e contexto económico
Incremento qualitativo na main local sobre `21ebad73`, reutilizando mapa, evidências, síntese publicada, divulgação expressa e fontes/períodos existentes. Inventário na cópia QA: duas evidências públicas locais de actividade anteriores (fichas sintéticas 350/383), nenhuma de mão-de-obra/oferta/procura; recursos/infra-estruturas da demonstração anterior conservados. As declarações labour_demands/training_capacities têm acesso EducationAccess interno; emprego/indicadores financeiros canónicos não fazem parte da publicProjection. Observações de zonas conservam divulgação/unidades/metodologia próprias. Não foram expostas nem agregadas essas fontes privadas por existir revisão interna. As fontes oficiais aceites e as decisões de divulgação anteriores não foram reabertas.
Acrescentada categoria explícita Mão-de-obra no editor e validação da evidência existente, sem migração ou reclassificação automática dos textos anteriores. Guardar, recuperar, pré-visualizar e confirmar publicação usam o percurso existente. A camada económica reutiliza apenas evidências públicas locais de actividade/oferta/procura, mantendo `source_category` original, facto, fonte, localizador, período, verificação, âmbito, cobertura e limitações. Agrupá-las para consulta é uma escolha de apresentação, não aprovação de viabilidade nem índice de potencial.
Ambas as opções entram no selector do mapa único; a escolha é recuperada na sessão. Recursos/infra-estruturas, geometrias públicas, filtros territoriais, fichas sem ponto e contagens anteriores mantêm-se. Nenhuma posição é criada ou atribuída ao recurso/mão-de-obra. Necessidade ou qualificação não equivale a trabalhador disponível; previsão de emprego não é emprego efectivo. Contexto económico não calcula retorno, score ou viabilidade. Períodos/fontes incompatíveis permanecem separados, sem totais ou zero presumido.
Delta aprovado: sete testes de BD/77 asserções (cinco do mapa, incluindo o novo caso; dois de publicação, um novo e um de acesso/fonte privada já existente), 22 testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Teste do percurso: criar evidência workforce → recuperar → confirmar pela publicação existente → consultar mapa → alterar rascunho privado sem substituir a categoria/fonte públicas. Testes mantêm metodologia/internos fora do mapa, categorias originais, períodos distintos e ausência de quantidades/índices inferidos. Não se repetiu a bateria geral aceite.
Conferência visual no Edge exclusivamente na QA `127.0.0.2:18644`/`main_visual_20261104_test`: contexto económico reutiliza a evidência de actividade já publicada da ficha 383, sintética e explicitamente identificada. Mão-de-obra usa uma única fixture nova, também sintética; escolha e fonte recuperadas após recarregar. Nenhuma coordenada/geometria criada; hash de todas as oportunidades anteriores idêntico antes/depois. Conferidos 36 artefactos dos três recibos anteriores, todos com SHA256 inalterado. Recibo: `docs/investmoz/unification/workforce-economic-map-local-20261104.json`; inventário, DOM, capturas e hashes em `outputs/workforce-economic-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual laboral/económica, séries, unidades e metodologia quantitativa comparável, georreferenciação própria documentada e cobertura cartográfica continuam pendentes. A demonstração sintética não mede oferta laboral nem potencial regional real. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, migração, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
@@ -399,6 +399,8 @@ export function ProactiveInvestmentEditor({
infrastructure: 'Infra-estrutura',
supply: 'Oferta',
demand: 'Procura',
workforce:
'Mão-de-obra — evidência documentada',
},
(v) => change({ category: v }),
)}
@@ -14,7 +14,12 @@ export type ThematicMapRecord = {
limitations: string | null;
evidence: {
id: string;
category: 'resource' | 'infrastructure';
category:
| 'resource'
| 'infrastructure'
| 'workforce'
| 'economic_potential';
source_category?: string;
statement?: string | null;
source?: string | null;
locator?: string | null;
@@ -27,6 +32,16 @@ export type ThematicMapRecord = {
const labels = {
resource: 'Recursos documentados',
infrastructure: 'Infra-estruturas documentadas',
workforce: 'Mão-de-obra documentada',
economic_potential: 'Potencial económico — contexto documentado',
};
const sourceLabels: Record<string, string> = {
activity: 'Actividade',
supply: 'Oferta',
demand: 'Procura',
workforce: 'Mão-de-obra',
resource: 'Recurso',
infrastructure: 'Infra-estrutura',
};
const value = (text?: string | null) => text?.trim() || 'Não indicado';
@@ -49,8 +64,7 @@ export default function ThematicInvestmentMap({
const saved = window.sessionStorage.getItem(
'investmoz-map-theme-v1',
);
if (saved === 'resource' || saved === 'infrastructure')
setTheme(saved);
if (saved && Object.hasOwn(labels, saved)) setTheme(saved);
} catch {
// The server and first client render use the same default layer.
}
@@ -129,9 +143,12 @@ export default function ThematicInvestmentMap({
pesquisa geral mantém os seus filtros e resultados.
</p>
<p>
Mão-de-obra e potencial económico: sem camada quantitativa
nesta consulta; não são inferidos de descrições ou do número
de oportunidades.
Mão-de-obra: a evidência pode descrever necessidades ou
qualificações; não mede trabalhadores disponíveis. Potencial
económico: contexto de actividade, oferta e procura, sem
índice, previsão de retorno ou viabilidade calculados.
Fontes e períodos não são agregados como se fossem
comparáveis.
</p>
{truncated && (
<p role="note">
@@ -152,13 +169,14 @@ export default function ThematicInvestmentMap({
).length
}{' '}
sem ponto público utilizável. Ausência de evidência
não significa ausência do recurso.
não significa ausência de recursos ou actividade
económica.
</p>
{selected.length === 0 && (
<p>
Sem evidência pública local de recursos ou
infra-estruturas para esta camada e estes
filtros. Não foram criadas posições no mapa.
Sem evidência pública local para esta camada e
estes filtros. Não foram criadas posições no
mapa.
</p>
)}
{selected.length > 0 && (
@@ -207,6 +225,15 @@ export default function ThematicInvestmentMap({
<p className="font-semibold">
{labels[proof.category]}
</p>
{proof.source_category && (
<p>
Categoria de origem:{' '}
{sourceLabels[
proof
.source_category
] ?? 'Não indicada'}
</p>
)}
<p>
{value(proof.statement)}
</p>
+20 -1
View File
@@ -55,15 +55,34 @@ class InvestmentThematicMapTest extends TestCase
$record = $this->record($prefix, [$this->proof(), $this->proof('infrastructure'), $this->proof('resource', 'methodology'), $this->proof('demand')]);
$before = $record->fresh()->getRawOriginal('workflow_context');
$response = $this->getJson($this->url($prefix))->assertOk()->assertJsonPath('matched_count', 1)
->assertJsonPath('thematic_records.0.id', $record->id)->assertJsonCount(2, 'thematic_records.0.evidence')
->assertJsonPath('thematic_records.0.id', $record->id)->assertJsonCount(3, 'thematic_records.0.evidence')
->assertJsonPath('thematic_records.0.evidence.0.source', 'Synthetic public source')
->assertJsonPath('thematic_records.0.evidence.0.period', '2026')
->assertJsonPath('thematic_records.0.evidence.0.scope', 'Synthetic district')
->assertJsonPath('thematic_records.0.evidence.2.category', 'economic_potential')
->assertJsonPath('thematic_records.0.evidence.2.source_category', 'demand')
->assertJsonPath('thematic_records.0.state', 'hypothesis')->assertJsonCount(0, 'features')->assertJsonCount(1, 'unlocated');
$this->assertStringNotContainsString('PRIVATE-DRAFT-MUST-NOT-LEAK', $response->getContent());
$this->assertSame($before, $record->fresh()->getRawOriginal('workflow_context'));
}
public function test_workforce_is_explicit_and_economic_context_preserves_each_source_without_inferred_totals(): void
{
$prefix = 'THEME-'.Str::uuid();
$this->record($prefix, [$this->proof('workforce'), $this->proof('activity'), $this->proof('supply'), $this->proof('demand'), $this->proof('workforce', 'methodology')]);
$r = $this->getJson($this->url($prefix))->assertOk()->assertJsonCount(4, 'thematic_records.0.evidence')
->assertJsonPath('thematic_records.0.evidence.0.category', 'workforce')
->assertJsonPath('thematic_records.0.evidence.0.source_category', 'workforce')
->assertJsonPath('thematic_records.0.state', 'hypothesis')->assertJsonCount(0, 'features')->json('thematic_records.0');
foreach (['activity', 'supply', 'demand'] as $i => $source) {
$this->assertSame('economic_potential', $r['evidence'][$i + 1]['category']);
$this->assertSame($source, $r['evidence'][$i + 1]['source_category']);
$this->assertSame('2026', $r['evidence'][$i + 1]['period']);
}
$this->assertArrayNotHasKey('available_workers', $r);
$this->assertArrayNotHasKey('potential_score', $r);
}
public function test_unpublished_future_restricted_and_unselected_private_evidence_are_not_disclosed(): void
{
$prefix = 'THEME-'.Str::uuid();
+20
View File
@@ -62,6 +62,26 @@ final class ProactiveInvestmentTest extends TestCase
return $input;
}
public function test_workforce_evidence_saves_recovers_and_requires_existing_explicit_publication(): void
{
$input = F::input();
$input['proactive']['evidence'][0]['category'] = 'workforce';
$id = $this->postJson('/minhas-oportunidades/drafts', $input)->assertCreated()->json('id');
$o = Opportunity::findOrFail($id);
$analysis = app(OpportunityManagement::class)->values($o)['proactive'];
$this->assertSame('workforce', $analysis['evidence'][0]['category']);
$this->assertNull(app(ProactiveInvestment::class)->publicData($o));
$this->publish($o);
$this->getJson('/api/v1/investment/map?opportunity_id='.$id)->assertOk()
->assertJsonPath('thematic_records.0.evidence.0.category', 'workforce')
->assertJsonPath('thematic_records.0.evidence.0.source', $analysis['evidence'][0]['source'])
->assertJsonCount(1, 'thematic_records.0.evidence')->assertJsonCount(0, 'features');
$analysis['evidence'][0]['category'] = 'demand';
$this->putJson('/minhas-oportunidades/'.$id.'/draft', ['etag' => app(OpportunityManagement::class)->etag($o->fresh()), 'proactive' => $analysis,
'reason' => 'SYNTHETIC QA later private changes must not replace the published workforce source'])->assertOk();
$this->getJson('/api/v1/investment/map?opportunity_id='.$id)->assertOk()->assertJsonPath('thematic_records.0.evidence.0.category', 'workforce');
}
public function test_legacy_records_without_analysis_add_no_publication_lookup(): void
{
$o = new Opportunity;
@@ -137,7 +137,7 @@ test('default preserves every opportunity and one map, with sourced thematic evi
'Partial coverage',
'2026-10-01',
'não representam a posição exacta',
'não são inferidos',
'sem índice',
])
assert.ok(html.includes(text), text);
assert.match(html, /2 fichas com evidência temática pública/);
@@ -216,7 +216,7 @@ test('changing query data never retains previous evidence; loading and incomplet
);
assert.match(html, /Sem evidência pública local/);
assert.match(html, /A resposta está limitada/);
assert.match(html, /não significa ausência do recurso/);
assert.match(html, /não significa ausência de recursos/);
});
test('theme selection survives reload in the tab and unavailable or invalid storage does not break the map', () => {
@@ -276,3 +276,63 @@ test('theme selection survives reload in the tab and unavailable or invalid stor
else globalThis.window = previous;
}
});
test('workforce and economic context preserve source categories and incompatible periods without inferred totals or borrowed points', () => {
const Component = setup();
const extra = [
{
...records[0],
id: 4,
title: 'Workforce evidence',
evidence: [{ ...proof('workforce'), source_category: 'workforce' }],
},
{
...records[1],
id: 5,
title: 'Economic context',
evidence: [
{
...proof('economic_potential'),
id: 'supply',
source_category: 'supply',
period: '2023',
},
{
...proof('economic_potential'),
id: 'demand',
source_category: 'demand',
period: '2026',
},
],
},
];
const data = { ...props, records: [...records, ...extra] };
flat(Component(data))
.find((node) => node.type === 'select')
.props.onChange({ target: { value: 'workforce' } });
let tree = Component(data);
assert.deepEqual(
flat(tree).find((node) => node.type === Map).props.points,
[],
);
let html = renderToStaticMarkup(tree);
assert.match(html, /Workforce evidence/);
assert.match(html, /Categoria de origem: Mão-de-obra/);
assert.match(html, /não mede trabalhadores disponíveis/);
flat(tree)
.find((node) => node.type === 'select')
.props.onChange({ target: { value: 'economic_potential' } });
tree = Component(data);
html = renderToStaticMarkup(tree);
assert.match(html, /Economic context/);
assert.match(html, /Categoria de origem: Oferta/);
assert.match(html, /Categoria de origem: Procura/);
assert.match(html, /2023/);
assert.match(html, /2026/);
assert.match(html, /sem índice, previsão de retorno ou viabilidade/);
assert.doesNotMatch(html, /Workforce evidence/);
assert.deepEqual(
flat(tree).find((node) => node.type === Map).props.points,
[],
);
});