Noemie Decompte Regularization¶
Since September 2025, Ameli sends regularization decomptes that correct previously processed decomptes.
Decompte structure¶
A noemie decompte contains one or more payments, each identified by an archiving_link. Each payment contains one or more care acts. Most decomptes have a single payment, but ~5% have multiple payments with different archiving links.
A regularization targets specific payments (not necessarily the whole decompte). It references the original payment via original_payment_archiving_link and contains:
- A negative phase: care acts with inverted signs, cancelling the original payment's care acts
- Optionally a positive phase: replacement care acts with corrected data
Two types of regularization exist: - Full cancellation (negative-only): all care acts from the original payment are cancelled - Amendment (negative + positive): some care acts are cancelled and replaced with corrected ones
Each regularization carries a RegularizationReason ⧉ code (e.g. change of regime, pricing error) extracted from the NOEMIE data.
You can read more about the noemie norm in the official documentation ⧉ or see examples provided by Ameli ⧉
High-Level Flow¶
flowchart TD
A[Regularization decompte received] --> B[Extraction]
B --> C{Has negative-only CAPIs?}
C -->|Yes| D[Find original CAPIs in DB]
C -->|No| G[Positive CAPIs extracted normally]
D --> E[Compute cancellation decision]
E --> F[Execute automatic cancellations]
F --> G
G --> H[Matching]
H --> I[Consolidation]
Extraction¶
File: compute_extraction_decompte.py
When processing a decompte, each payment is checked for an original_payment_archiving_link. If present, it's a regularization payment.
Phase detection¶
_determine_regularization_phase() determines whether a payment belongs to the negative or positive phase:
| Condition | Phase |
|---|---|
original_payment_archiving_link is None |
Not a regularization |
| First occurrence of archiving link, sign = "N" | Negative |
Second occurrence (via previous_archiving_link) |
Positive |
| First occurrence but sign = "P" and non-negative NOEMIE code | Positive (rare edge case) |
Grouping¶
Care acts are grouped into RegularizationGroupingResult objects keyed by original_payment_archiving_link in DecompteExtractionResult.regularizations. Each group accumulates its negative and positive phase CAPIs separately.
After extraction, for each group:
1. check_data_conformity() validates phase/reason consistency
2. compute_care_acts_to_cancel_decision() determines which care acts to cancel
3. Positive phase CAPIs are added to the normal extraction output
Identifying Care Acts to Cancel¶
File: decompte_regularization.py
This is the core logic. The sequence below shows how we go from raw regularization data to cancellation decisions.
sequenceDiagram
participant E as Extraction
participant R as RegularizationGroupingResult
participant DB as Database
E->>R: compute_care_acts_to_cancel_decision()
R->>R: _get_cancelled_care_act_infos()
Note over R: Match positive CAPIs to negative CAPIs<br/>Remaining negatives = care acts to cancel
loop For each care act to cancel
R->>DB: _find_original_capis_for_care_act_info()
Note over DB: Query by archiving_link +<br/>insurance_profile + amounts + dates
DB-->>R: Matching original CAPIs
R->>R: _resolve_capi_to_cancel()
Note over R: 0 matches → error<br/>1 match → use it<br/>N same decompte → pick first unmatched<br/>N different decomptes → ambiguous error
R->>R: _compute_cancellation_status()
Note over R: See decision tree below
end
R-->>E: list[CareActToCancelDecision]
Step 1: Mini-matching — identify negative-only CAPIs¶
_get_cancelled_care_act_infos() separates negative CAPIs that have a positive counterpart (amendments) from those that don't (cancellations).
For each positive CAPI, it finds a matching negative CAPI via _get_matched_care_act_info() using progressive field matching. The discriminating fields (side, dental_locations) are always compared. Then combinations of start_date, secu_grouping_code, secu_reimbursement_base, total_spend are tried from most to least specific.
- Matched negatives are removed from the cancelled list
- Remaining negatives = care acts to cancel
- Unmatched positives raise an error (cannot determine which negative they replace)
Step 2: Find original CAPIs from the original decompte¶
_find_original_capis_for_care_act_info() queries the database for the original CAPIs being regularized.
Primary lookup: match on archiving_link = the regularization's original_payment_archiving_link
Fallback: for non-backfilled CAPIs (~5%), match on noemie_decompte.unique_identifier
Match fields: insurance_profile_id, secu_grouping_code, start_date, total_spend, secu_reimbursement_base, side, dental_locations
When multiple CAPIs match, _resolve_capi_to_cancel() resolves ambiguity:
| Scenario | Resolution |
|---|---|
| 0 matches | Error (strict) or warning (non-strict) |
| 1 match | Use it |
| N matches, same decompte | Pick first unmatched (GDP-1009 fix) |
| N matches, different decomptes | Ambiguous, error |
Step 3: Compute cancellation decision¶
_compute_cancellation_status() determines what to do with each matched care act:
Care act found
├── No linked CareAct → skip (return None)
├── Multiple noemie decompte sources → TO_KEEP (duplicate, other source still valid)
├── Already cancelled → ALREADY_CANCELLED
├── Single source: 1 noemie decompte only → TO_CANCEL_AUTOMATICALLY
└── Multiple source types (noemie + TP) → TO_CANCEL_MANUALLY
The auto-cancel check in _can_automatically_cancel_care_act() returns True only if the care act has exactly one source of type noemie_decompte.
Step 4: Execute automatic cancellations¶
File: process_noemie_decompte.py
_cancel_regularized_care_act_in_noemie_decompte() filters decisions for to_cancel_automatically and calls cancel_care_act() with skip_cancellation_review=True.
Matching Rule¶
File: source_care_act_extraction.py
A regularization CAPI must match a CareAct whose original CAPI has the same archiving_link as the regularization's original_payment_archiving_link.
_is_capi_regularization_of_caci()checks this against consolidated info_is_capi_regularization_of_care_act()checks this against the care act's noemie decompte CAPIs, with a fallback tonoemie_decompte.unique_identifierfor non-backfilled data
When a regularization match is confirmed, data review checks (BRSS, total_spend, care_type, start_date) are skipped via _skip_review_reasons() — differences between original and regularization data are expected and handled in consolidation.
Consolidation Rule¶
File: compute_consolidation.py
reduce_care_act_partial_infos_to_consolidate() ensures the regularization decompte's data takes precedence over the original:
- Collect all
original_payment_archiving_linkvalues from the CAPIs - Filter out any CAPI whose
archiving_linkappears in that set (i.e. the original decompte's CAPIs) - Among remaining noemie decompte CAPIs, keep only the latest (by
created_at)
This guarantees consolidated data always comes from the regularization decompte, not the original.
Key Data Structures¶
| Type | Location | Description |
|---|---|---|
RegularizationPhase |
enums/regularization.py |
negative / positive |
CareActRegularizationStatus |
enums/regularization.py |
to_cancel_automatically, to_cancel_manually, to_keep, already_cancelled, unknown |
RegularizationReason |
enums/ |
NOEMIE reason code for the regularization |
CareActToCancelDecision |
entities/regularization.py |
Links a CAPI + CareAct ID + cancellation status |
RegularizationCancellationDecision |
entities/regularization.py |
Groups decisions per original_payment_archiving_link |
RegularizationGroupingResult |
decompte_regularization.py |
Core class: holds negative/positive CAPIs and all cancellation logic |
DecompteExtractionResult |
decompte_extraction_result.py |
Extraction output: simple CAPIs + dict[str, RegularizationGroupingResult] |