What this is

TPA Recon (reconciliation) compares Nhance enrolment data with a TPA API dump stored in tpa_api_data for a given batch file. HR can see who exists only in Nhance, only in the TPA file, or in both with field differences, then take actions: inception upload for missing members, direct DB updates for mismatches, and a deletion pipeline when the TPA marks rows for deletion.

The flow is intentionally split across EmployeeController (report, proceed, heavy logic), EmployeePolicyModel::getTPADataVariationReport (SQL slices), and EmployeeServiceController (Excel pipelines and queued follow-up jobs). Read this page top to bottom once; use the checklist at the end when you touch production data.

Glossary

TermMeaning here
batch_files.id (often called file_id in code) The batch row for the TPA import / variation context. tpa_api_data.file_id points at the same id.
tpa_api_data One row per TPA member line for that file: emp_code, name, dob, gender, relation, optional ref (FK to employee_polices.id), rec_type, action_flag_status (e.g. D for deletion intent), is_active.
rec_type Snapshot classification on tpa_api_data: matched, need_to_review, or not_in_nhance. Used to speed up repeat UI loads after the first full compute.
ref When set, links a TPA row to employee_polices.id after strict matching in reconTpaApiDataWithEmployeepolicies.
Not in TPA Nhance has an active policy member for the client/policy, but no TPA row with that emp_code in the dump.
Not in Nhance TPA has an emp_code not present in the Nhance “master” list for that client/policy (see model method with $all = true).
Need to review / mismatch Same emp_code and same relation line can be paired, but name, DOB, or gender differ; or relation-level pairing failed and TPA rows for that code need manual attention.

Key files and routes

End-to-end flow

flowchart TB A[TPA rows in tpa_api_data] --> B[getTPADataVariationReport] B --> C{rec_type snapshot exists?} C -->|no| D[Compute and persist rec_type] C -->|yes| E[Load from rec_type] D --> F[reconTpaApiDataWithEmployeepolicies] F --> G[Report UI / Excel] E --> G B --> H[not_in_tpa from SQL] G --> I[proceedTPADataVariationNextStep] I -->|not_in_nhance| J[Inception Excel + files] I -->|need_to_review| K[updateEmployeeDataFromTpa] J --> L[Excel pipelines] K --> L L --> M[Queued jobs: sync + recon + deletion init]

“Proceed” runs immediate actions; ref sync and deletion initialization also run as queued jobs after inception or correction Excel workflows complete in EmployeeServiceController.

Variation report — getTPADataVariationReport

Inputs: $file_id (batch file id), $type:

The controller loads batch_files for client_id, client_policy_id, then decides compute vs cached mode:

Compute path — high level:

  1. Load all active tpa_api_data for file_id; index by emp_code.
  2. Initialize every TPA row id to rec_type = matched as a default.
  3. Load Nhance rows from EmployeePolicyModel::getTPADataVariationReport($client_id, $client_policy_id, $file_id) (default branch: members with tpa_id null — the “not yet linked / review” slice).
  4. For each Nhance row, run reconcileDbWithTpa (see next section). Update rec_type on the matched TPA id(s) accordingly.
  5. Load master emp_code list with getTPADataVariationReport(..., [], true). Any TPA row whose code is not in that list → not_in_nhance.
  6. updateBatch all rec_type values in a transaction.
  7. Call reconTpaApiDataWithEmployeepolicies(['file_id' => $file_id]) to populate ref where possible.

Cached path: Reads not_in_nhance and need_to_review rows from tpa_api_data by rec_type, then rebuilds mismatch_data for the UI without re-running the full reconciliation loop.

not_in_tpa (always “live”): Built from TPA emp_codes for the file and getTPADataVariationReport(..., $tpa_emp_codes) — Nhance members whose emp_code is not in the TPA set. This list is not driven from the rec_type snapshot by design.

The response also includes counts and button flags for the “Not in Nhance” proceed action (inception vs deletion tallies based on empty ref and action_flag_status === 'D').

Classifying rec_typereconcileDbWithTpa

Given one Nhance row ($db) and all TPA rows for the same emp_code ($tpaRows), the controller walks TPA rows in order:

The report layer uses that result to set rec_type on TPA ids: perfect match → matched; matched with diffs → need_to_review; no relation-level match → mark candidate TPA rows for that emp_code as need_to_review.

Linking refreconTpaApiDataWithEmployeepolicies

Parameter shape: ['file_id' => $batchFileId] (same id as the TPA batch).

This is stricter than reconcileDbWithTpa (which only compares three fields after relation match) because it must pick a single policy row to link.

Proceed next step — proceedTPADataVariationNextStep

Route: GET employee/proceedTPADataVariationNextStep/{file_id}?tab=...

tabBehaviour
not_in_nhance Calls generateEmployeeUploadFromNotInNhance. On success, an inception-style file exists in files and format validation has run. Follow-up ref/deletion jobs are not invoked inline here (see background jobs).
need_to_review Calls updateEmployeeDataFromTpa(['batch_file_id' => (int) $file_id]). This applies TPA-sourced values onto employees for reconciled mismatches (no correction Excel in this path). The handler checks $generationResult['success'] (not status).
other / missing Still logs “proceed” and returns a generic success message.
i
Fix applied: The need_to_review branch previously passed the wrong parameter shape to updateEmployeeDataFromTpa and read a non-existent status key. It now passes batch_file_id and honours success, matching how queued jobs and QA utilities call the same method.

Not in Nhance → inception file — generateEmployeeUploadFromNotInNhance

Purpose: Turn TPA-only members into an Employee Upload with Events Excel so the normal onboarding pipeline can create them in Nhance.

  1. Validate client_id, client_policy_id, client_branch_id on the batch file.
  2. Master codes = getTPADataVariationReport(..., [], true)emp_code list.
  3. Select active tpa_api_data for this file_id whose emp_code is not in the master list.
  4. Build headers from EmployeeServiceController::getInceptionExcelColumns(); map each TPA row (relation synonyms, change_event = addition, dates as d-M-Y, etc.).
  5. Save XLSX under WRITEPATH/uploads/excel/, insert files row (action = addition, status = inprogress).
  6. Run excelFileFormatValidation with ['file_id' => newFileId, 'batch_file_id' => batchFileId].

Need to review → DB sync — updateEmployeeDataFromTpa

Expects ['batch_file_id' => int]. Loads the same Nhance slice as the report, loads TPA rows per emp_code, reuses reconcileDbWithTpa. When status is matched and not_matching is non-empty, writes allowed fields on employees (name, dob, gender, relationship from TPA relation, corporate email when present). Returns success, message, and counts in data.

Deletion initialization — initializeDeletionProcessForTpaApiData

Parameter: ['file_id' => $batchFileId] (same batch / TPA file id).

Business logic (as implemented):

  1. Resolve the batch file; require client, policy, and branch.
  2. From tpa_api_data, select distinct non-empty ref values where file_id matches, rows are active, and action_flag_status = 'D' (deletion intent from TPA).
  3. Those ref values are employee_polices.id values already linked to TPA.
  4. Load active Nhance members in the same client/policy scope whose employee_polices.id is in that set (whereIn on policy id). These are the rows that will appear on the generated deletion sheet.
  5. Build a deletion-format .xls, create a files row with action = deletion, run EmployeeServiceController::employeeDisembark to create endorsements from the sheet.
  6. Build a separate import-format workbook from export helpers, create a batch_files row, run EmpDataServiceController::importDeletionValidation for the batch import path.

A commented rec_type = matched filter exists in the query; it is intentionally not applied — do not assume rec_type gates deletion eligibility unless you change the code deliberately.

Background jobs chain

After a successful inception-style onboarding from grouped family data (employeesOnboardProcess path in EmployeeServiceController) when batch_file_id is present in params, three jobs are enqueued in order:

  1. updateEmployeeDataFromTpa — payload includes file_id (newly created processing file where applicable) and batch_file_id
  2. reconTpaApiDataWithEmployeepolicies — payload ['file_id' => batch_file_id]
  3. initializeDeletionProcessForTpaApiData — payload ['file_id' => batch_file_id]

A similar trio is queued after employeesCorrectionProcess when batch_file_id is passed. This is how ref linking and deletion initialization catch up after Excel-driven workflows finish, even when the “Proceed” controller path does not call them inline.

New developer checklist

  1. Identify the batch file id you are debugging; confirm matching rows exist in tpa_api_data with the same file_id.
  2. Open Variation report in view mode first — inspect not_in_tpa, not_in_nhance, mismatch_data separately.
  3. If rec_type looks stale, remember the controller only recomputes when no snapshot exists unless you clear or adjust rec_type in DB (there is no public “job” route in production docs).
  4. When changing matching rules, update both reconcileDbWithTpa and reconTpaApiDataWithEmployeepolicies if they must stay aligned, or document intentional differences.
  5. Before testing deletion flows on real clients, trace action_flag_status and ref on TPA rows — deletion candidates are rows explicitly flagged D with a populated ref.
  6. Watch myLogger entries prefixed with TPA / TPA RECON for operational breadcrumbs.

For QA-only utilities (guarded in production), see internal notes such as public/dev_logs/2026-04-03.md for updateEmployeeDataFromTpa and related routes.