From 20a4099d10400496e19f03589b6383f512b48d9d Mon Sep 17 00:00:00 2001 From: nala Date: Fri, 23 Oct 2026 09:56:22 +0200 Subject: [PATCH] Present 34 agribusiness opportunities in existing corridor sections --- app/Services/CorridorAgribusiness.php | 76 ++++++++ app/Services/LegacyContent.php | 3 + .../corridor-investment-opportunities.json | 181 ++++++++++++++++++ .../unification/traceability-and-manual.md | 8 + tests/Unit/CorridorAgribusinessTest.php | 48 +++++ tools/verify-corridor-agribusiness.php | 33 ++++ 6 files changed, 349 insertions(+) create mode 100644 app/Services/CorridorAgribusiness.php create mode 100644 database/data/corridor-investment-opportunities.json create mode 100644 tests/Unit/CorridorAgribusinessTest.php create mode 100644 tools/verify-corridor-agribusiness.php diff --git a/app/Services/CorridorAgribusiness.php b/app/Services/CorridorAgribusiness.php new file mode 100644 index 00000000..015813cd --- /dev/null +++ b/app/Services/CorridorAgribusiness.php @@ -0,0 +1,76 @@ +loadHTML(''.$html.'', LIBXML_NONET); + $body = $doc->getElementsByTagName('body')->item(0); + if (! $body) { + return $html; + } + $changed = false; + foreach ($data['groups'] as $index => $group) { + $marker = 'agronegocio-oportunidades-'.($index + 1); + if ($doc->getElementById($marker)) { + continue; + } + foreach (iterator_to_array($body->childNodes) as $heading) { + if (! $heading instanceof DOMElement) { + continue; + } + $name = preg_replace('/^[a-f]\)\s*/u', '', trim($heading->textContent)); + if ($name !== $group['name']) { + continue; + } + $chains = $heading->nextSibling; + while ($chains && ! $chains instanceof DOMElement) { + $chains = $chains->nextSibling; + } + // Insert only into the exact named section and its existing priority-chain paragraph. + if (! $chains || ! str_starts_with(trim($chains->textContent), 'Cadeias de valor prioritárias:')) { + continue; + } + $fragment = $doc->createDocumentFragment(); + $label = $doc->createElement('p'); + $strong = $doc->createElement('strong'); + $strong->appendChild($doc->createTextNode('Oportunidades de investimento em agronegócio:')); + $label->appendChild($strong); + $fragment->appendChild($label); + $list = $doc->createElement('ul'); + $list->setAttribute('id', $marker); + foreach ($group['opportunities'] as $opportunity) { + $item = $doc->createElement('li'); + $chain = $doc->createElement('strong'); + $chain->appendChild($doc->createTextNode($opportunity['chain'].': ')); + $item->appendChild($chain); + $item->appendChild($doc->createTextNode($opportunity['description'])); + $list->appendChild($item); + } + $fragment->appendChild($list); + $source = $doc->createElement('p'); + $source->appendChild($doc->createTextNode('Fonte das oportunidades: '.$data['source'])); + $fragment->appendChild($source); + $body->insertBefore($fragment, $chains->nextSibling); + $changed = true; + break; + } + } + + return $changed ? implode('', array_map(fn ($node) => $doc->saveHTML($node), iterator_to_array($body->childNodes))) : $html; + } finally { + libxml_clear_errors(); + libxml_use_internal_errors($previous); + } + } +} diff --git a/app/Services/LegacyContent.php b/app/Services/LegacyContent.php index d14e6c17..f20e3640 100644 --- a/app/Services/LegacyContent.php +++ b/app/Services/LegacyContent.php @@ -78,6 +78,9 @@ final class LegacyContent $image = $this->publicResources()->where('r.id', $adaptation['image_resource_id'])->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp'])->first(); } } + if ($slug === 'agronegocio') { + $localHtml = app(CorridorAgribusiness::class)->enrich($localHtml); + } preg_match_all('~href="([^"]+)"~i', $localHtml, $existingLinks); $alreadyLinked = array_map(fn ($href) => html_entity_decode($href, ENT_QUOTES | ENT_HTML5, 'UTF-8'), $existingLinks[1]); $relatedArticles = []; diff --git a/database/data/corridor-investment-opportunities.json b/database/data/corridor-investment-opportunities.json new file mode 100644 index 00000000..c002b9c3 --- /dev/null +++ b/database/data/corridor-investment-opportunities.json @@ -0,0 +1,181 @@ +{ + "source": "investimentos nos corredores.docx", + "source_sha256": "1473b06c48aa234e771660e3f2a51b9d2e4e1943bd52d878e0ae854ee6657055", + "introduction": "Os corredores de desenvolvimento integram cadeias de valor prioritárias que podem ser apresentadas no portal InvesteMoz como oportunidades concretas de investimento na produção, transformação, logística e comercialização. Para cada corredor, são destacadas abaixo as oportunidades associadas aos produtos com maior potencial.", + "segments": [ + "Produção primária e fornecimento de insumos", + "Processamento e transformação agro-industrial", + "Armazenagem, cadeia de frio e logística", + "Embalagem, distribuição, comercialização e exportação", + "Infraestruturas e serviços de apoio às cadeias de valor" + ], + "groups": [ + { + "name": "Corredor de Pemba–Lichinga", + "opportunities": [ + { + "chain": "Batata, trigo, feijão, milho e soja", + "description": "Produção comercial, armazenamento e processamento de batata, trigo, feijão, milho e soja." + }, + { + "chain": "Algodão", + "description": "Descaroçamento, fiação e produção de óleo e derivados de algodão." + }, + { + "chain": "Sésamo", + "description": "Limpeza, prensagem, embalagem e exportação de sésamo." + }, + { + "chain": "Florestas", + "description": "Viveiros, plantações comerciais, madeira processada e produtos florestais sustentáveis." + }, + { + "chain": "Avicultura", + "description": "Produção de rações, incubação, criação, abate e cadeia de frio para avicultura." + } + ] + }, + { + "name": "Corredor de Nacala", + "opportunities": [ + { + "chain": "Mandioca", + "description": "Produção e transformação de mandioca em farinha, amido e outros derivados." + }, + { + "chain": "Milho, amendoim e sésamo", + "description": "Produção, armazenagem e processamento de milho, amendoim e sésamo." + }, + { + "chain": "Algodão", + "description": "Descaroçamento e transformação de algodão." + }, + { + "chain": "Frutas e caju", + "description": "Pomares, processamento, embalagem e exportação de frutas e caju." + }, + { + "chain": "Florestas", + "description": "Silvicultura comercial, madeira processada e produtos florestais." + }, + { + "chain": "Avicultura", + "description": "Produção de rações, criação, abate e distribuição de produtos avícolas." + } + ] + }, + { + "name": "Corredor do Vale do Zambeze", + "opportunities": [ + { + "chain": "Arroz", + "description": "Produção irrigada, moagem, embalagem e comercialização de arroz." + }, + { + "chain": "Milho e batata", + "description": "Produção, secagem, armazenagem e transformação de milho e batata." + }, + { + "chain": "Sésamo", + "description": "Limpeza, prensagem e exportação de sésamo." + }, + { + "chain": "Algodão", + "description": "Descaroçamento e transformação de algodão." + }, + { + "chain": "Avicultura", + "description": "Produção de rações, incubação, criação e abate de aves." + }, + { + "chain": "Bovinos e caprinos", + "description": "Criação, engorda, abate, processamento de carne e cadeia de frio para bovinos e caprinos." + } + ] + }, + { + "name": "Corredor da Beira", + "opportunities": [ + { + "chain": "Milho, trigo e arroz", + "description": "Produção, silos, moagem e processamento de milho, trigo e arroz." + }, + { + "chain": "Soja", + "description": "Esmagamento de soja, produção de óleo vegetal e fabrico de rações." + }, + { + "chain": "Hortícolas", + "description": "Produção irrigada, conservação, embalagem e distribuição de hortícolas." + }, + { + "chain": "Sésamo", + "description": "Limpeza, prensagem e exportação de sésamo." + }, + { + "chain": "Açúcar", + "description": "Produção e refinação de açúcar e aproveitamento de subprodutos." + }, + { + "chain": "Florestas", + "description": "Plantações comerciais, processamento de madeira e produtos florestais." + }, + { + "chain": "Carne bovina", + "description": "Criação, engorda, abate, processamento e cadeia de frio para carne bovina." + } + ] + }, + { + "name": "Corredor do Limpopo", + "opportunities": [ + { + "chain": "Arroz", + "description": "Produção irrigada, moagem, embalagem e comercialização de arroz." + }, + { + "chain": "Hortícolas", + "description": "Produção em estufas e campo aberto, conservação, embalagem e distribuição de hortícolas." + }, + { + "chain": "Carne vermelha", + "description": "Criação, engorda, abate, processamento e cadeia de frio para carne vermelha." + }, + { + "chain": "Avicultura", + "description": "Produção de rações, incubação, criação, abate e distribuição de produtos avícolas." + }, + { + "chain": "Infraestruturas e serviços de apoio", + "description": "Infraestruturas de irrigação, armazenagem, transporte refrigerado e centros de comercialização." + } + ] + }, + { + "name": "Corredor de Maputo", + "opportunities": [ + { + "chain": "Arroz", + "description": "Produção irrigada, moagem, embalagem e fornecimento de arroz aos mercados urbanos e regionais." + }, + { + "chain": "Hortícolas", + "description": "Produção, conservação, embalagem, transformação e distribuição de hortícolas." + }, + { + "chain": "Carne vermelha", + "description": "Criação, engorda, abate, processamento, distribuição e exportação regional de carne vermelha." + }, + { + "chain": "Avicultura", + "description": "Produção de rações, incubação, criação, abate e comercialização de produtos avícolas." + }, + { + "chain": "Infraestruturas e serviços de apoio", + "description": "Plataformas logísticas, centros de distribuição, cadeia de frio e serviços de exportação." + } + ] + } + ], + "sector": "Agronegócio" +} diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 9fe7bc99..2ee97733 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -1,5 +1,13 @@ # Rastreabilidade, utilização e formação +## Agronegócio nos corredores — complemento de apresentação, 23/10/2026 + +Entrega QA recebida em `entrega-agronegocio-corredores-20261023`: cinco ficheiros conferidos pelos hashes do manifesto. Complementa exclusivamente `/agronegocio`, nas seis secções existentes: Pemba–Lichinga (5), Nacala (6), Vale do Zambeze (6), Beira (7), Limpopo (5) e Maputo (5), total 34 entradas. Reutilizadas as provas QA entregues (dois testes, 63 asserções e verificador offline do exemplar preservado); não constituem prova HTTP do destino. + +Delta: serviço `CorridorAgribusiness`, fonte JSON com hash do DOCX e chamada adicional em `LegacyContent::article`. O ficheiro anterior de `LegacyContent` na homologação corresponde exactamente à base local; não há alterações posteriores a sobrescrever. Sem migration, importação, escrita de conteúdo, publicação de dossiers ou alteração dos nomes/cadastros de corredores. Originais e versões da biblioteca permanecem preservados; estas entradas são conteúdo informativo do artigo, não 34 novos dossiers executáveis. + +Disponibilização pelo Git e mecanismo automático existente, com cópia privada do código/recibo anteriores em `/var/backups/investmoz-agronegocio-corredores-20261023`. Backup/restauro de dados já verificados nesta sessão em `/var/backups/investmoz-preservation-inventory-20261023/backup`; esta entrega não escreve na base. Reversão delimitada: retirar apenas a chamada de complemento, preservando alterações posteriores; não restaurar a base de dados. Conferir apenas o delta HTTP após a promoção da revisão. + ## Preservação do concluído — inventário e reposição, 23/10/2026 **Prioridade expressa da Tempo:** inventariar o que já estava concluído, identificar o que desapareceu e repor, antes de avançar com outras rondas de testes/validações. Actualizado o [inventário existente dos 43 componentes](#saldo-dos-43-componentes) com uma coluna de saldo actual; conservada também a matriz executável dos 17 percursos. Os componentes de interface ali referenciados existem no código actual. As entregas posteriores prevalecem sobre estados históricos: por exemplo, os incrementos 36–39 já não podem ser apresentados como ausentes, e o acervo editorial já não está genericamente desabilitado. Não se criou outra matriz/backlog nem se usou o inventário para exigir novamente aceitações anteriores. diff --git a/tests/Unit/CorridorAgribusinessTest.php b/tests/Unit/CorridorAgribusinessTest.php new file mode 100644 index 00000000..98622460 --- /dev/null +++ b/tests/Unit/CorridorAgribusinessTest.php @@ -0,0 +1,48 @@ + $group) { + $original .= '

'.chr(97 + $index).') '.$group['name'].'

Cadeias de valor prioritárias:
Conteúdo original '.$index.'

Descrição preservada '.$index.'

'; + } + $original .= '

4. Âmbito de Investimento

Outros sectores serão acrescentados.

'; + $service = new CorridorAgribusiness; + $html = $service->enrich($original); + $doc = new DOMDocument; + $doc->loadHTML(''.$html); + $xpath = new DOMXPath($doc); + $this->assertCount(6, $xpath->query('//ul[starts-with(@id,"agronegocio-oportunidades-")]')); + $this->assertCount(34, $xpath->query('//ul[starts-with(@id,"agronegocio-oportunidades-")]/li')); + foreach ($data['groups'] as $index => $group) { + $list = $doc->getElementById('agronegocio-oportunidades-'.($index + 1)); + $this->assertSame(count($group['opportunities']), $list->getElementsByTagName('li')->length); + foreach ($group['opportunities'] as $opportunity) { + $this->assertStringContainsString($opportunity['description'], $list->textContent); + } + $this->assertStringContainsString(chr(97 + $index).') '.$group['name'], $html); + $this->assertStringContainsString('Conteúdo original '.$index, $html); + $this->assertStringContainsString('Descrição preservada '.$index, $html); + } + $this->assertStringContainsString('4. Âmbito de Investimento', $html); + $this->assertSame($html, $service->enrich($html)); + } + + public function test_broader_corridor_names_and_incomplete_sections_are_left_unchanged(): void + { + $html = '

Corredor de Nacala e Norte

Cadeias de valor prioritárias: preservadas

' + .'

Corredor Maputo–Limpopo

Cadeias de valor prioritárias: preservadas

' + .'

Corredor de Nacala

Sem parágrafo de cadeias prioritárias.

'; + $this->assertSame($html, (new CorridorAgribusiness)->enrich($html)); + } +} diff --git a/tools/verify-corridor-agribusiness.php b/tools/verify-corridor-agribusiness.php new file mode 100644 index 00000000..0fc28a18 --- /dev/null +++ b/tools/verify-corridor-agribusiness.php @@ -0,0 +1,33 @@ +sanitize($source['content_html']); +$html = (new App\Services\CorridorAgribusiness)->enrich($original); +$doc = new DOMDocument; +$doc->loadHTML(''.$html); +$xpath = new DOMXPath($doc); +$groups = $xpath->query('//ul[starts-with(@id,"agronegocio-oportunidades-")]'); +$entries = $xpath->query('//ul[starts-with(@id,"agronegocio-oportunidades-")]/li'); +if ($groups->length !== 6 || $entries->length !== 34 || $sourceHash !== hash_file('sha256', $path)) { + throw new RuntimeException('Incomplete integration or source changed.'); +} +// Removing only the additions must recover the exact sanitized original. +foreach (iterator_to_array($groups) as $group) { + $group->parentNode->removeChild($group->previousSibling); + $group->parentNode->removeChild($group->nextSibling); + $group->parentNode->removeChild($group); +} +$body = $doc->getElementsByTagName('body')->item(0); +$recovered = implode('', array_map(fn ($node) => $doc->saveHTML($node), iterator_to_array($body->childNodes))); +if ($recovered !== $original) { + throw new RuntimeException('Original displayed content was changed.'); +} +echo json_encode(['source_sha256' => $sourceHash, 'source_unchanged' => true, 'original_display_preserved' => true, + 'corridors' => 6, 'agribusiness_entries' => 34, 'database_writes' => 0], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE).PHP_EOL;