['model' => 'm1'], 'answers' => [ 'category' => ['type' => 'choice', 'choice' => 'billing', 'confidence' => 0.9, 'distribution' => ['billing' => 0.9, 'bug' => 0.1]], 'urgency' => ['type' => 'score', 'score' => '2', 'confidence' => 0.5], 'angry' => ['type' => 'noul', 'noul' => 0.93], ], ]; } it('parses each answer into a decision', function () { $set = DecisionSet::fromResponse(sampleResponse()); expect($set->routedModel)->toBe('m1') ->and($set->get('category')->choice())->toBe('billing') ->and($set->get('category')->type)->toBe('choice') ->and($set->get('category')->confidence())->toBe(0.9) ->and($set->get('category')->distribution())->toBe(['billing' => 0.9, 'bug' => 0.1]) ->and($set->get('urgency')->score())->toBe(2.0) ->and($set->get('angry')->probability())->toBe(0.93); }); it('returns null for fields a decision does not have', function () { $d = DecisionSet::fromResponse(sampleResponse())->get('angry'); expect($d->choice())->toBeNull() ->and($d->score())->toBeNull() ->and($d->confidence())->toBeNull(); }); it('handles a response with no answers or routing', function () { $set = DecisionSet::fromResponse([]); expect($set->routedModel)->toBeNull(); $set->get('x'); })->throws(OutOfBoundsException::class, "No answer for question 'x'"); it('throws for an unknown question name', function () { DecisionSet::fromResponse(sampleResponse())->get('missing'); })->throws(OutOfBoundsException::class); it('uses unknown as the type when the server omits it', function () { $set = DecisionSet::fromResponse(['answers' => ['q' => ['choice' => 'a']]]); expect($set->get('q')->type)->toBe('unknown'); }); it('returns only the distribution field, not the whole payload', function () { $d = DecisionSet::fromResponse(['answers' => ['q' => ['type' => 'choice', 'choice' => 'a']]])->get('q'); expect($d->distribution())->toBe([]); })->todo('CODE_REVIEW #8: distribution() currently falls back to the whole answer');