evaluate(['demand_quantity' => '100', 'capacity_quantity' => '50']); $this->assertContains('demand_quantity_comparison_incomplete', array_column($missing[1]['risks'], 'code')); $facts = ['demand_quantity' => '100', 'capacity_quantity' => '50', 'quantity_unit' => 'toneladas', 'quantity_period' => 'ano', 'resources_required' => '12', 'resources_available' => '0', 'resource_unit' => 'pessoas', 'funding_required' => '100.50', 'funding_committed' => '100.49', 'funding_currency' => 'MZN', 'expected_start_date' => '2026-12-01', 'funding_ready_date' => '2027-01-01', 'completion_date' => '2026-11-01']; $phases = $rules->evaluate($facts); foreach ([1 => 'demand_quantity_declared_gap', 3 => 'resources_required_declared_gap', 4 => 'funding_required_declared_gap'] as $i => $code) { $this->assertContains($code, array_column($phases[$i]['risks'], 'code')); foreach ($phases[$i]['risks'] as $risk) { $this->assertFalse($risk['restriction']); } } $this->assertCount(2, $phases[5]['risks']); $facts['funding_committed'] = '100.50'; $this->assertNotContains('funding_required_declared_gap', array_column($rules->evaluate($facts)[4]['risks'], 'code')); } public function test_missing_declared_and_attached_information_are_not_verified(): void { $rules = new InvestorGuidanceRules; $this->assertSame('absent', $rules->evaluate([])[2]['evidence'][0]['state']); $declared = $rules->evaluate(['location_evidence' => 'Declaração QA']); $this->assertSame('investor_declared_not_verified', $declared[2]['evidence'][0]['state']); $attached = $rules->evaluate([], ['location_evidence' => ['state' => 'attached_unchecked', 'documents' => []]]); $this->assertSame('attached_unchecked', $attached[2]['evidence'][0]['state']); $this->assertNotEmpty($attached[2]['risks']); } public function test_scoped_internal_verification_removes_only_the_corresponding_pending_alert(): void { $evidence = ['location_evidence' => ['state' => 'internally_verified', 'documents' => [['review' => ['author' => 'Verificador QA', 'date' => '2026-10-08', 'scope' => 'Acesso descrito no documento QA']]]]]; $phases = (new InvestorGuidanceRules)->evaluate([], $evidence); $this->assertSame([], $phases[2]['risks']); $this->assertNotEmpty($phases[3]['risks']); $this->assertSame($evidence['location_evidence']['documents'], $phases[2]['evidence'][0]['documents']); $this->assertStringContainsString('não aprova', $phases[2]['recommendations'][0]['limitations'][0]); } public function test_contradictions_expiration_and_replacement_are_specific_non_blocking_alerts(): void { foreach (['contradiction', 'expired', 'superseded'] as $state) { $phase = (new InvestorGuidanceRules)->evaluate([], ['market_evidence' => ['state' => $state, 'documents' => []]])[1]; $this->assertSame('market_evidence_'.$state, $phase['risks'][0]['code']); $this->assertFalse($phase['risks'][0]['restriction']); $this->assertSame($state === 'contradiction' ? 'documented_contradiction' : 'insufficient_information', $phase['risks'][0]['classification']); } } }