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
Three types of regularization exist: - Full cancellation (negative-only): all care acts from the original payment are cancelled - Amendment (negative + positive): negative care acts are matched 1:1 with positive counterparts (same care code, corrected data) - Drop-and-replace (negative + positive): - Amendment: some negative care acts match positive ones () - Cancellation: some negative care acts don't match any positive - Replacement: some positive care acts don't match any negative
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 CAPIs?}
C -->|Yes| D[Find original CAPIs in DB]
C -->|No| G[Positive CAPIs extracted normally]
D --> E[Compute cancellation decision]
E --> F{Has replacement care acts?}
F -->|Yes| F1[Downgrade auto to manual cancellations]
F1 --> F2[Replacement CAPIs extracted normally]
F2 --> G
F -->|No| F3[Execute automatic cancellations]
F3 --> 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: _match_phases()
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->>R: Downgrade autoβmanual if replacements exist
R-->>E: tuple[list[CareActToCancelDecision], list[InMemoryCareActInfo]]
Step 1: Mini-matching β identify negative-only CAPIs¶
_match_phases() returns a RegularizationPhaseMatchingResult with three lists:
- to_cancel_care_act_infos β negative CAPIs with no positive counterpart (care acts to cancel)
- amended_care_act_infos β positive CAPIs matched 1:1 with a negative counterpart (amendments)
- replacement_care_act_infos β positive CAPIs with no matching negative (drop-and-replace)
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, their positive counterparts go to
amended_care_act_infos - Remaining negatives =
to_cancel_care_act_infos - Unmatched positives are logged as a warning and returned as
replacement_care_act_infos
When replacement_care_act_infos is non-empty (drop-and-replace), all to_cancel_automatically decisions are downgraded to to_cancel_manually. This prevents accidental overpayment when care codes change between original and replacement.
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 |
RegularizationDecision |
entities/regularization.py |
Groups decisions per original_payment_archiving_link |
RegularizationPhaseMatchingResult |
entities/regularization.py |
Result of _match_phases(): to_cancel_care_act_infos, replacement_care_act_infos, amended_care_act_infos |
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] |