Country-specific scoring

  1. Example: different logic for DE and PL
  2. The source is the join key
  3. Going further: a different strategy per country
  4. Tips

Yes - you can score one country differently from another, using the data your own registry driver returns. Two facts make this work:

  1. The data you put in LookupResult::found(data: [...]) flows into CheckResult::$raw (via RegistryCheck), tagged with the source you gave that check.
  2. A RiskRule receives a RiskContext with both the counterparty (so the country) and the full report (so every result, including your raw data).
LookupResult.data  ─►  RegistryCheck  ─►  CheckResult { source, raw = data, proofId }
                                                 │
                                                 ▼
                                       RiskRule->evaluate(RiskContext)
                                  (counterparty->country  +  report.fromSource(...))

So a rule can branch on the country and read the country-specific payload.

Example: different logic for DE and PL

use Gawrys\Counterparty\Risk\{RiskRule, RiskContext, RiskSignal, Evidence};

// Only applies to DE, reads the German registry payload.
final class GermanInsolvencyRule implements RiskRule
{
    public function evaluate(RiskContext $ctx): iterable
    {
        if ($ctx->counterparty->country !== 'DE') {
            return; // not applicable elsewhere
        }

        foreach ($ctx->report->fromSource('de.handelsregister') as $r) {
            if (($r->raw['status'] ?? null) === 'INSOLVENT') {
                yield new RiskSignal('de.insolvent', 0.9, adverse: true,
                    evidence: Evidence::grounded(
                        'Insolvency flagged in the Handelsregister.',
                        $r->proofId ?? 'https://handelsregister.de/',
                        0.95,
                    ));
            }
        }
    }
}

// Only applies to PL, uses the White List / KRS.
final class PolishVatRule implements RiskRule
{
    public function evaluate(RiskContext $ctx): iterable
    {
        if ($ctx->counterparty->country !== 'PL') {
            return;
        }
        // ... read Source::WHITE_LIST / Source::KRS results ...
    }
}

Both live in the same strategy; each guards on country:

$strategy = new RuleBasedRiskStrategy([
    new GermanInsolvencyRule(),
    new PolishVatRule(),
    new \Gawrys\Counterparty\Risk\Rule\SanctionsHitRule(), // applies everywhere
]);

The source is the join key

When you register a RegistryCheck for your country, you pass a source string (e.g. 'de.handelsregister'). That is exactly what the rule queries with $ctx->report->fromSource('de.handelsregister'). Keep a small set of constants for your sources, the way the core does in Report\Source.

new RegistryCheck(
    $registries,
    RegistryCapability::LegalEntityData,
    $clock,
    source: 'de.handelsregister',
    name: 'DE Handelsregister',
);

Going further: a different strategy per country

Country-guarded rules cover most needs. If two countries need fundamentally different models (weights, thresholds, even an ML scorecard), select the whole strategy up front:

$strategy = match ($counterparty->country) {
    'DE'    => $germanStrategy,   // RuleBasedRiskStrategy with DE rules + threshold
    'PL'    => $polishStrategy,
    default => RuleBasedRiskStrategy::withDefaultRules(),
};

$verifier = new Verifier($checks, $strategy, $clock);

See Custom strategy for replacing the engine entirely.

Tips

  • Keep country logic in rules, not in checks - checks gather facts, rules interpret them.
  • Put structured, rule-friendly fields into LookupResult::data (e.g. status, incorporatedAt, pkd), not just a raw blob.
  • Prefer grounded Evidence (with the registry’s URL / proof id) for adverse signals so the outcome is auditable.

Counterparty Verification - a due-diligence aid, not a compliance product. MIT licensed.

This site uses Just the Docs, a documentation theme for Jekyll.