Custom strategy
When rules are not enough (a scorecard, an external scoring service, an ML model), implement
RiskStrategy directly. It is the same seam the rule-based default and the AI strategy use,
so the rest of the system does not change.
use Gawrys\Counterparty\Risk\{RiskStrategy, RiskAssessment, Evidence};
use Gawrys\Counterparty\Enum\RiskLevel;
final class ScorecardStrategy implements RiskStrategy
{
public function assess(Counterparty $counterparty, VerificationReport $report): RiskAssessment
{
// The report is ground truth: never override a hard sanctions FAIL.
if ($report->hasAdverseFindings()) {
return new RiskAssessment(RiskLevel::Critical, 1.0, 'Adverse hard fact present.', humanReviewRequired: true);
}
$score = $this->scorecard($counterparty, $report); // your model -> 0.0-1.0
return new RiskAssessment(
level: RiskLevel::fromScore($score),
score: $score,
summary: 'Scorecard result.',
humanReviewRequired: $score >= 0.6 || $report->hasInconclusive(),
evidence: [Evidence::grounded('Scorecard v3', 'https://internal/scorecard', 0.8)],
);
}
}
Guidelines
- Respect the hard facts. The report is deterministic ground truth; a custom strategy should never downgrade an adverse finding. Treat the assessment as advice layered on top.
- Set
requiresHumanReview()honestly - on adverse facts, on inconclusive coverage, and when your own confidence is low. - Ground your evidence. Prefer
Evidence::grounded()with a real source; reserveungrounded()for low-confidence hints. - Keep it deterministic where you can, so results are cacheable and reproducible.
Composing strategies
A strategy can wrap another - e.g. run the rule-based default, then add an external score, or fall back to it when your service is unavailable:
final class HybridStrategy implements RiskStrategy
{
public function __construct(
private RiskStrategy $primary,
private RiskStrategy $fallback,
) {}
public function assess(Counterparty $c, VerificationReport $r): RiskAssessment
{
try {
return $this->primary->assess($c, $r);
} catch (\Throwable) {
return $this->fallback->assess($c, $r);
}
}
}
The AI strategy is exactly such a drop-in - it consumes the report and returns a
RiskAssessment, advisory only.