Skip to content

AML(#78) M3: flag companies that changed owner before onboarding (KYB_OWNER_CHANGED) - #1694

Closed
stenehrlich-tuleva wants to merge 1 commit into
masterfrom
aml-78-m3-owner-change
Closed

AML(#78) M3: flag companies that changed owner before onboarding (KYB_OWNER_CHANGED)#1694
stenehrlich-tuleva wants to merge 1 commit into
masterfrom
aml-78-m3-owner-change

Conversation

@stenehrlich-tuleva

@stenehrlich-tuleva stenehrlich-tuleva commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Milestone M3 of the company AML risk-scoring rollout (issue TulevaEE/tuleva#78, Phase A). Self-contained spec: the rev-3 comment on tuleva#78 and the plan file in TulevaEE/tuleva#79.

Stacked on M2 (#1693) — base is aml-78-m2-company-age, so this PR shows only M3's diff. It reuses KybCheckType.isOnboardingGate() introduced by M2's gate fix. Merge M2 first, then retarget this to master.

Rule

Methodology rule (KKR): "Kas juriidiline isik on vahetanud omanikku enne on-boardimist kunagi" — has the company ever changed owner before onboarding. A company that changed hands before coming to us is an acquired-shell red flag (pairs with M2's company-age signal).

Why now / what it replaces

The methodology notes this signal's data (getCompanyRelationships endDate history) was "fetched but not used." The existing DATA_CHANGED check inferred change from screening-to-screening diffs keyed by the representative's personal code, which contaminates a person who runs multiple companies. M3 reads the change from the authoritative Äriregister relationship history instead — per company, with dates.

Change

  • OwnershipChangeDetector flags a company when an owner (shareholder OSAN or beneficial owner) appears in the full history but is not a current owner — i.e. someone exited. A re-recorded same-person owner and board-only changes do not trigger it.
  • The flag rides in KybCompanyData (computed in LegalEntityScreener from getCompanyRelationships, like M2's foundingDate); a new OwnerChangeScreener emits KYB_OWNER_CHANGED.
  • Risk signal, not a gate: OWNER_CHANGED is isOnboardingGate()=false, so it's persisted and scored by M7 without rejecting the company (depends on the M2 fix).

Tests (TDD)

  • OwnershipChangeDetectorTest — exit detected; sole-owner no-change; same-person record split (not a false positive); beneficial-owner exit; board-only change ignored; empty history.
  • OwnerChangeScreenerTest — emits failing/passing OWNER_CHANGED.
  • KybScreeningIntegrationTest — a changed-owner company persists a failing KYB_OWNER_CHANGED.
  • Green under pg/Testcontainers: KybEndToEndTest 28/28, KybScreeningIntegrationTest 6/6, + 149 kyb/aml unit tests, spotless.

For Maria's sign-off (fine print of the rule)

  • Scope = owners only — this is methodology rule 16 (planned_owner_changed_ever, "Omanikku vahetanud kunagi", status ⏳), which is owner-only by definition. Board-member changes are a separate, already-active rule — rule 5 kkr_related_changed (the existing DATA_CHANGED since-last-screening check, ✓) — not part of M3. So this is not a widen-to-board toggle; the two are distinct rules.
  • "Owner exited" = a former owner with no current owner relationship. Same-person re-recording is excluded; a single ended owner-relationship with churn could still need refinement.
  • One extra getCompanyRelationships SOAP call per screen/validate (could be deduplicated against the active-relationships fetch later).

Refs TulevaEE/tuleva#78.

🤖 Generated with Claude Code

Screens the methodology rule "juriidiline isik on vahetanud omanikku enne
on-boardimist kunagi" (KKR) from the authoritative Äriregister
relationship history (getCompanyRelationships, with start/end dates),
rather than the person-keyed screening-comparison that contaminated
multi-company representatives.

OwnershipChangeDetector flags a company when an owner (shareholder or
beneficial owner) appears in its history but is no longer a current owner
- i.e. someone exited, so ownership changed at some point before
onboarding. A re-recorded same-person owner and board-only changes do not
trigger it. The signal rides in KybCompanyData (computed in
LegalEntityScreener from full history); a new OwnerChangeScreener emits
KYB_OWNER_CHANGED. It is a risk signal (non-gating via isOnboardingGate),
so it is scored by M7 without blocking onboarding.

Stacked on M2 (reuses KybCheckType.isOnboardingGate from the M2 gate fix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.30%. Comparing base (d7b275a) to head (d32bde8).
⚠️ Report is 395 commits behind head on master.

Files with missing lines Patch % Lines
...tuleva/onboarding/kyb/OwnershipChangeDetector.java 84.61% 0 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##             master    #1694   +/-   ##
=========================================
  Coverage     91.30%   91.30%           
- Complexity     6131     6144   +13     
=========================================
  Files           949      951    +2     
  Lines         22456    22481   +25     
  Branches       1669     1670    +1     
=========================================
+ Hits          20503    20526   +23     
  Misses         1300     1300           
- Partials        653      655    +2     
Files with missing lines Coverage Δ
...in/java/ee/tuleva/onboarding/aml/AmlCheckType.java 100.00% <100.00%> (ø)
...uleva/onboarding/aml/AmlKybCheckEventListener.java 100.00% <100.00%> (ø)
...in/java/ee/tuleva/onboarding/kyb/KybCheckType.java 100.00% <100.00%> (ø)
.../java/ee/tuleva/onboarding/kyb/KybCompanyData.java 100.00% <ø> (ø)
...ee/tuleva/onboarding/kyb/KybCompanyDataMapper.java 100.00% <100.00%> (ø)
.../ee/tuleva/onboarding/kyb/LegalEntityScreener.java 100.00% <100.00%> (ø)
...a/onboarding/kyb/screener/OwnerChangeScreener.java 100.00% <100.00%> (ø)
...tuleva/onboarding/kyb/survey/KybSurveyService.java 93.49% <100.00%> (ø)
...tuleva/onboarding/kyb/OwnershipChangeDetector.java 84.61% <84.61%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@stenehrlich-tuleva

Copy link
Copy Markdown
Contributor Author

Closing: the AML(#78) M-series stalled after #1747 (2026-06-30) and #1709 was closed unmerged on 2026-08-19. This branch is 568 commits behind and conflicting. If rule 16 (owner changed before onboarding) is revived it is cheaper to re-cut than to rebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant