Skip to content

Efficency on Stoptbapp - #394

Merged
drtechie merged 8 commits into
feature/stoptb-sync-changes-feedfrom
vb/stoptb-worklist-batch
Oct 29, 2026
Merged

drtechie merged 8 commits into
feature/stoptb-sync-changes-feedfrom
vb/stoptb-worklist-batch

Conversation

@vishwab1

Copy link
Copy Markdown
Member

📋 Description

JIRA ID:

Please provide a summary of the change and the motivation behind it. Include relevant context and details.


✅ Type of Change

  • 🐞 Bug fix (non-breaking change which resolves an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 🔥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 🛠 Refactor (change that is neither a fix nor a new feature)
  • ⚙️ Config change (configuration file or build script updates)
  • 📚 Documentation (updates to docs or readme)
  • 🧪 Tests (adding new or updating existing tests)
  • 🎨 UI/UX (changes that affect the user interface)
  • 🚀 Performance (improves performance)
  • 🧹 Chore (miscellaneous changes that don't modify src or test files)

ℹ️ Additional Information

Please describe how the changes were tested, and include any relevant screenshots, logs, or other information that provides additional context.

vishwab1 and others added 8 commits October 28, 2026 13:41
Load mapping, details, account, image, address, contact, beneficiary ID,
rmnch, born-birth, household, flow, anthropometry, vitals and user lookups
once per page instead of once per beneficiary. Read ABHA from
m_benhealthidmapping/t_healthid directly instead of calling FHIR-API per
beneficiary. Falls back to the existing per-row queries if the batch load
fails or a single-result lookup finds more than one row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
door-to-door-page-size 10 -> 50 so a village pull needs 5x fewer requests.
Log one [WORKLIST-TIMING] line per Stop TB village worklist page with the
time spent in count, ids, address, prefetch, build and JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Count village flow rows without the mapping join, and page with a deferred
join so OFFSET skips on idx_flow_psm_village_reg only and just the page's
rows are joined to the mapping. Ties on registrationDate are ordered by
ben_flow_id so a page boundary cannot skip or repeat a beneficiary. An
in-range page with no mapped rows returns an empty page instead of
"No record found".

Add ApiTimingFilter: one [API-TIMING] line per request with method, path,
status and time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TB suspected getAll looked up the full user detail (role + facility
queries) for every confirmed row only to read the state for the incentive
check; look it up once per user. The incentive rules and calls are
unchanged.

Stop TB general exam, screening, OPD and diagnostics getAll built the JSON
text, parsed it back and wrote it again; build the tree once via
OutputResponse.setResponse(JsonElement). Load those rows read-only.

Responses are byte-identical to before (Stop TB getAlls and the FLW app
worklist/suspected paths compared on the 3307 test DB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd OPD lists

Diagnostic status summary read each beneficiary's latest order five times,
once per status bucket; read beneficiaryId + status once and bucket in
Java with the same status groups (case-insensitive, like the database
comparison). Bucket contents are unchanged; order inside a bucket was never
fixed by the query.

TB suspected getAll copies the DTO fields directly instead of ModelMapper
per row, and its village subquery hints idx_flow_psm_village_reg (MySQL
ignores the hint where that index is absent).

Standard OPD records for a village start from the village's beneficiaries
instead of every visit of the PSM; the same visits match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…only

Restore IncentiveLogicService/IncentiveLogicImpl to their original code and
run the FLW (ashaId) path of TB suspected getAll exactly as before. The
Stop TB village path reads each user's state once and calls the unchanged
incentiveForTbSuspected only for CG users, the only state where it acts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The index-only count also counted flow rows that have no
i_beneficiarymapping row (423 on the reference DB, mostly PSM 1725),
so villages with such rows got extra or empty pages and a fully
unmapped village got an empty success instead of "No record found".
Put countVillageWorklist, getVillageWorklistAddressIds and the empty-page
handling back to the release-3.12.0 behaviour. The batch loader, page
size and timing logs stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved:
- BeneficiaryServiceImpl: keep the feed's buildBeneficiaryRecords /
  getBeneficiaryRecordsByRegIds split; the batched page loader stays inside
  buildBeneficiaryRecords, and getMappingsForAddressIDs keeps the page
  timing line.
- TBSuspectedServiceImpl: keep both the Stop TB village fast path and the
  feed's getRecordsByIds as written.
- StopTBPrescriptionService: take the feed's version (drops the village-first
  OPD query from this branch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 29, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a65e7a55-29c4-4136-a5ce-23673804386e
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@drtechie
drtechie merged commit cd298ff into feature/stoptb-sync-changes-feed Oct 29, 2026
2 checks passed
drtechie added a commit that referenced this pull request Nov 2, 2026
* fix(user): append village ID into villageName so duplicate names are distinguishable

Two different villages can legitimately share the same name (confirmed
live: Nikshay village IDs 275135 and 275136 are both named
"Sanapindapadar", under two different facilities in Boriguma block,
Koraput). The mobile app's post-login village dropdown displays only
villageName, so a user has no way to tell which entry is which -
selecting the wrong one loads zero HH records for that village, since
worklists filter by villageID, not by name.

getUserDetail now appends the ID into each name segment, e.g.
"Sanapindapadar,Sanapindapadar" + "275135,275136" becomes
"Sanapindapadar (275135),Sanapindapadar (275136)". villageId and the
response shape (two parallel comma-separated strings) are unchanged.

Verified this requires zero mobile app changes: the village dropdown
(ServiceLocationViewModel -> BindingUtils.setSpinnerItems) is a plain
ArrayAdapter that displays villageName verbatim, and selection is by
array position + villageId, not by matching name text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump version to 3.8.4

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* trace: add diagnostic logging to getBeneficiaryData for count-mismatch investigation

Adds temporary [TRACE getBeneficiaryData] log lines to trace which
caller/device is hitting the Stop TB worklist endpoint, how many
pages/rows it gets back, and the household/non-household split
actually sent to the client per call:

- Controller: log real caller username (from JWT) + providerServiceMapID/
  villageID/pageNo per request, instead of the previous useless default
  toString() of the request DTO.
- Service: log totalCount/totalPage/addressIds per Stop TB page fetch.
- Service: warn when getByAddressID() or getDetailsByRegID() return more
  than one row for a single address/beneficiary (both do .get(0) with no
  ORDER BY) — logs the distinct houseoldId values seen among duplicates,
  to confirm whether duplicate detail rows actually carry conflicting
  household links in production (not reproduced on the .198 demo DB,
  where duplicates found were exact clones with identical houseoldId).
- Service: log a per-call summary of household-linked vs non-household
  counts in the response, for direct comparison against what each
  device's Home screen shows.

Intended to be deployed to UAT StopTB and removed once the root cause
of Nurse vs Registrar count mismatches (Achuthapuram, village 5821) is
confirmed from real traffic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: order duplicate-row lookups in getBeneficiaryData so .get(0) is deterministic and correct

BenAddressId (i_beneficiarymapping.getByAddressID) and BenRegId
(i_beneficiarydetails_rmnch.getDetailsByRegID) can both have more than
one matching row, but the queries had no ORDER BY and the calling code
in BeneficiaryServiceImpl just takes .get(0) — whichever row MySQL
happened to return first, not necessarily the correct one.

Confirmed live against the UAT StopTB database (Achuthapuram, village
5821): 36+ address IDs are shared between an old test-account
registration and a real one, and some beneficiaries have duplicate
detail rows where only the newer one has the household link recorded.
Picking an arbitrary row instead of the latest one is why the Nurse
and Registrar apps showed different Household/Non-Household counts
for the same village.

- getByAddressID: ORDER BY benMapId DESC, same "latest wins"
  convention already used in findByBenRegIdFromMapping (a5e5734).
- getDetailsByRegID: ORDER BY beneficiaryDetails_RmnchId DESC.

Verified locally against the real UAT DB: household/non-household
split now matches the true numbers (computed from
i_ben_flow_outreach, the actual source getBeneficiaryData's village
worklist is built from) within a 1-record margin from an unrelated,
pre-existing "mapping not available" skip.

Does not touch the underlying duplicate rows themselves — those still
need a data cleanup decision (which record to keep) separately from
this code fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Revert "trace: add diagnostic logging to getBeneficiaryData for count-mismatch investigation"

This reverts commit 911e2be.

* Dropping duplicate columns in table tb_stoptb_general_examination

* Adding migration applier and removing column migration file

* feat: add four new columns to TB screening form

* feat: add dynamic form migrator and contact tracing migration

* feat: change the flw-api version to 3.9.1

* feat: update Stop TB service implementation

* feat: add dynamic form migrator with new form version creation

* feat: refactored code

* feat: changed flw-api version to 3.12.0

* chore: bump pom version to 3.9.1

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

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* feat: stop-584 dynamic form backward compatibility changes

* fix(diagnostic): stop the poll scheduler from nulling vanSerialNo

pollOnce saved a stale copy of the order loaded before createAndPushOrder's
updateVanSerialNo() ran, writing vanSerialNo back to NULL. MMU sync keys
tb_diagnostic_order by vanSerialNo, so those rows could never sync and the
Stop TB sync group stayed partial.

- refill vanSerialNo after the scheduler's save in pollOnce
- mark vanSerialNo updatable=false on the diagnostic entities so entity
  saves can no longer overwrite it (updateVanSerialNo still sets it)

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

* fix(diagnostic): stamp vanSerialNo right after the order is first saved

Fill vanSerialNo before the (possibly slow) EMR Lite push instead of only
after it, so a crash or restart mid-push can't leave the order without
its sync key. Same value (= id); no change to status, push or response.

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

* feat: stop-542 extra fields consumer changes

* Revert "feat: stop-542 extra fields consumer changes"

This reverts commit 50dc8ba.

* feat: stop-542 extra fields consumer changes

* feat(stoptb): save OPD prescription to standard tables and dispense stock

Same endpoint stoptb/nurse/generalOpd/save. When the app sends the new
fields (submissionId, chiefComplaints[], prescription.drugs[] with drugID
and qtyPrescribed) the save writes t_benchiefcomplaint, t_prescription,
t_prescribeddrug, t_patientissue and t_itemstockexit and reduces
t_itemstockentry from the van's store, like TM and Inventory-API. One
transaction: insufficient stock rejects the whole submit. submissionId
makes retries safe. Old payload behaves as before.

The configured stoptb.van.id is the source of truth; a different vanID from
the app is rejected. Parking place and store are read from m_van.

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

* fix(stoptb): close dispensing gaps found in review

- Always use stoptb.van.id; a vanID sent by the app is ignored (logged).
- Reduced batch gets Processed='N' so the new quantity uploads to central;
  removed chief complaints are re-sent the same way.
- Batches with no expiry date are usable, as in v_drugforprescription.
- Drug must be mapped to the store on its own service line, same join as
  the drug list, so the save accepts exactly what the list shows.
- Patient name, age and gender filled on t_patientissue for the
  Inventory dispense screen.

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

* feat(stoptb): General OPD on AMRIT standard tables only

- New payload (chiefComplaints[] / prescription.drugs[]) no longer writes
  tb_stoptb_general_opd; complaints, prescription and dispensing live only
  in t_benchiefcomplaint, t_prescription, t_prescribeddrug, t_patientissue,
  t_itemstockexit.
- generalOpd/getAll reads the standard tables first and falls back to
  tb_stoptb_general_opd only for older visits that exist only there.
  Response fields and formats are unchanged for the app.
- Old app payload keeps its current behaviour.

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

* feat(stoptb): calculate tablet/capsule quantity when the app sends none

qtyPrescribed from the app is used as is. When it is missing, for
tablets and capsules (MMU's prescription screen asks quantity only for
other forms) it is doses per day x days from frequency and duration;
Single/Stat dose = 1, Once in a Week = 1 per 7 days. SOS or other forms
without a quantity are rejected. The calculated value is saved in
t_prescribeddrug.QtyPrescribed and dispensed.

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

* fix(stoptb): OPD save validations from final review

- chiefComplaintID is required for each chief complaint (getAll and
  reports identify standard-table records by it).
- prescription.instruction is saved in t_prescription even when no drug
  is given, as MMU does for the visit instruction.
- providerServiceMapID and createdBy give a clear error when missing
  instead of a database error.

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

* feat(stoptb): default quantity 1 for non-tablet medicines when the app sends none

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

* feat(stoptb): getAll drugs list with form; SOS quantity default

- generalOpd/getAll returns a "drugs" list per record (drugID, drugName,
  itemFormID, drugForm, dose, frequency, duration, durationUnit,
  qtyPrescribed, instructions) from t_prescribeddrug, so a saved
  prescription can be shown with form and quantity. Existing fields are
  unchanged; old free-text records return an empty list.
- SOS tablets/capsules without a quantity: 1 per day of duration
  (1 when no duration) instead of rejecting the save.

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

* updated version to 3.9.2

* Removing gpsLatitude and gpsLongitude, and setting values into latitude and longitude

* Adding locking for multiple order creation

* Adding locking for multiple order creation

* fix(stoptb): fill missing service line on the day's visit

TB suspected / confirmed / diagnostic saves create the visit with
ProviderServiceMapID null. When one of them syncs before TB screening
or OPD, the visit keeps null and getAll (filters by psm) skips it.
A later save that has a service line now fills it in.

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

* Adding locking for multiple order creation

* fix(stoptb): MMU dose-based quantity; no batch re-upload after dispense

- Quantity for tablets uses the dose like MMU (Half Tab 0.5, One Tab 1,
  One & Half Tab 1.5, Two Tabs 2 per intake), rounded up. No dose sent =
  one tablet, so today's results are unchanged.
- Dispense no longer marks the stock batch Processed='N'. Central gets the
  stock-out rows (standard MMU); re-sending a batch could insert a
  duplicate batch on central.

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

* fix(stoptb): send the batch's new stock to central after an OPD dispense

The dispensed batch is marked Processed='N' again so the next upload updates
central's copy (matched by VanSerialNo + store, now kept by central MMU-API).

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

* feat(stoptb): save OPD prescription to standard tables and dispense stock

Same endpoint stoptb/nurse/generalOpd/save. When the app sends the new
fields (submissionId, chiefComplaints[], prescription.drugs[] with drugID
and qtyPrescribed) the save writes t_benchiefcomplaint, t_prescription,
t_prescribeddrug, t_patientissue and t_itemstockexit and reduces
t_itemstockentry from the van's store, like TM and Inventory-API. One
transaction: insufficient stock rejects the whole submit. submissionId
makes retries safe. Old payload behaves as before.

The configured stoptb.van.id is the source of truth; a different vanID from
the app is rejected. Parking place and store are read from m_van.

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

* fix(stoptb): close dispensing gaps found in review

- Always use stoptb.van.id; a vanID sent by the app is ignored (logged).
- Reduced batch gets Processed='N' so the new quantity uploads to central;
  removed chief complaints are re-sent the same way.
- Batches with no expiry date are usable, as in v_drugforprescription.
- Drug must be mapped to the store on its own service line, same join as
  the drug list, so the save accepts exactly what the list shows.
- Patient name, age and gender filled on t_patientissue for the
  Inventory dispense screen.

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

* feat(stoptb): General OPD on AMRIT standard tables only

- New payload (chiefComplaints[] / prescription.drugs[]) no longer writes
  tb_stoptb_general_opd; complaints, prescription and dispensing live only
  in t_benchiefcomplaint, t_prescription, t_prescribeddrug, t_patientissue,
  t_itemstockexit.
- generalOpd/getAll reads the standard tables first and falls back to
  tb_stoptb_general_opd only for older visits that exist only there.
  Response fields and formats are unchanged for the app.
- Old app payload keeps its current behaviour.

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

* feat(stoptb): calculate tablet/capsule quantity when the app sends none

qtyPrescribed from the app is used as is. When it is missing, for
tablets and capsules (MMU's prescription screen asks quantity only for
other forms) it is doses per day x days from frequency and duration;
Single/Stat dose = 1, Once in a Week = 1 per 7 days. SOS or other forms
without a quantity are rejected. The calculated value is saved in
t_prescribeddrug.QtyPrescribed and dispensed.

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

* fix(stoptb): OPD save validations from final review

- chiefComplaintID is required for each chief complaint (getAll and
  reports identify standard-table records by it).
- prescription.instruction is saved in t_prescription even when no drug
  is given, as MMU does for the visit instruction.
- providerServiceMapID and createdBy give a clear error when missing
  instead of a database error.

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

* feat(stoptb): default quantity 1 for non-tablet medicines when the app sends none

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

* feat(stoptb): getAll drugs list with form; SOS quantity default

- generalOpd/getAll returns a "drugs" list per record (drugID, drugName,
  itemFormID, drugForm, dose, frequency, duration, durationUnit,
  qtyPrescribed, instructions) from t_prescribeddrug, so a saved
  prescription can be shown with form and quantity. Existing fields are
  unchanged; old free-text records return an empty list.
- SOS tablets/capsules without a quantity: 1 per day of duration
  (1 when no duration) instead of rejecting the save.

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

* Dropping duplicate columns in table tb_stoptb_general_examination

* Adding migration applier and removing column migration file

* feat: add dynamic form migrator and contact tracing migration

* feat: change the flw-api version to 3.9.1

* feat: update Stop TB service implementation

* feat: add dynamic form migrator with new form version creation

* feat: refactored code

* feat: changed flw-api version to 3.12.0

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* Updated the device integration according to updated workflow

* feat: stop-584 dynamic form backward compatibility changes

* feat: stop-542 extra fields consumer changes

* Revert "feat: stop-542 extra fields consumer changes"

This reverts commit 50dc8ba.

* feat: stop-542 extra fields consumer changes

* fix(stoptb): fill missing service line on the day's visit

TB suspected / confirmed / diagnostic saves create the visit with
ProviderServiceMapID null. When one of them syncs before TB screening
or OPD, the visit keeps null and getAll (filters by psm) skips it.
A later save that has a service line now fills it in.

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

* Removing gpsLatitude and gpsLongitude, and setting values into latitude and longitude

* Adding locking for multiple order creation

* Adding locking for multiple order creation

* fix(stoptb): MMU dose-based quantity; no batch re-upload after dispense

- Quantity for tablets uses the dose like MMU (Half Tab 0.5, One Tab 1,
  One & Half Tab 1.5, Two Tabs 2 per intake), rounded up. No dose sent =
  one tablet, so today's results are unchanged.
- Dispense no longer marks the stock batch Processed='N'. Central gets the
  stock-out rows (standard MMU); re-sending a batch could insert a
  duplicate batch on central.

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

* fix(stoptb): send the batch's new stock to central after an OPD dispense

The dispensed batch is marked Processed='N' again so the next upload updates
central's copy (matched by VanSerialNo + store, now kept by central MMU-API).

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

* Changes for STOP-711

* Making changes for STOP-735

* feat(stoptb): /stoptb/sync/changes feed on the hub change log

One endpoint replaces the 15+ getAll calls a STOP-TB pull makes. With no token it pages
through everything in the requested villages (keyset, type by type, beneficiaries first);
after that it returns only records with t_sync_change_log lines after the tablet's token,
in their current state, deduplicated, with deletes. Tokens carry the hub's server_uuid,
so a token from another hub or older than the log's retention starts a fresh snapshot.

Records keep the exact JSON each existing endpoint returns, via by-id variants of the
existing builders (beneficiary, screening, examination, OPD, diagnostics, suspected,
confirmed, form responses, diagnostic orders), so the app reuses its parsers.

The feed is served only where the change log exists and every source table has its
triggers (checked on the hub); elsewhere it answers {supported:false} and the app keeps
the full pull. StopTBSyncCoverageTest fails when a mapped table is neither synced nor
listed as deliberately not synced.

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

* feat(stoptb): let the app choose which record types the changes feed returns

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

* fix(stoptb): treat t_prescription as a source of the OPD record

The OPD record's notes come from the visit's latest t_prescription instruction. A
remarks-only save writes only that table, so without it in the list the change log
missed the edit and tablets kept the old note. Paired with the t_prescription trigger
in the AMRIT-DB hub migration.

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

* fix(stoptb): bound beneficiaries per feed page and bind tokens to record types

- A page carries at most 25 beneficiary records. Each costs several identity lookups
  and a FHIR call, so 200-500 per page could outlast the tablet's HTTP timeout. In a
  snapshot the token resumes after the last one sent; in a delta the row list stops
  before the one over the cap and the token resumes from the last row kept, so
  nothing is skipped.
- Tokens carry the set of record types they were issued for. A request for a
  different set starts a new snapshot instead of missing the newly requested types'
  existing records.

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

* docs(stoptb): the change log now comes from AMRIT-DB main migrations V132 / V32

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

* Efficency on Stoptbapp (#394)

* perf(worklist): batch per-page lookups in getBeneficiaryData

Load mapping, details, account, image, address, contact, beneficiary ID,
rmnch, born-birth, household, flow, anthropometry, vitals and user lookups
once per page instead of once per beneficiary. Read ABHA from
m_benhealthidmapping/t_healthid directly instead of calling FHIR-API per
beneficiary. Falls back to the existing per-row queries if the batch load
fails or a single-result lookup finds more than one row.

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

* perf(worklist): page size 50 and per-page timing log

door-to-door-page-size 10 -> 50 so a village pull needs 5x fewer requests.
Log one [WORKLIST-TIMING] line per Stop TB village worklist page with the
time spent in count, ids, address, prefetch, build and JSON.

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

* perf(worklist): index-only count and page query; per-request timing log

Count village flow rows without the mapping join, and page with a deferred
join so OFFSET skips on idx_flow_psm_village_reg only and just the page's
rows are joined to the mapping. Ties on registrationDate are ordered by
ben_flow_id so a page boundary cannot skip or repeat a beneficiary. An
in-range page with no mapped rows returns an empty page instead of
"No record found".

Add ApiTimingFilter: one [API-TIMING] line per request with method, path,
status and time.

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

* perf(stoptb): faster TB suspected and Stop TB getAll responses

TB suspected getAll looked up the full user detail (role + facility
queries) for every confirmed row only to read the state for the incentive
check; look it up once per user. The incentive rules and calls are
unchanged.

Stop TB general exam, screening, OPD and diagnostics getAll built the JSON
text, parsed it back and wrote it again; build the tree once via
OutputResponse.setResponse(JsonElement). Load those rows read-only.

Responses are byte-identical to before (Stop TB getAlls and the FLW app
worklist/suspected paths compared on the 3307 test DB).

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

* perf(stoptb): one-query diagnostic status summary; leaner suspected and OPD lists

Diagnostic status summary read each beneficiary's latest order five times,
once per status bucket; read beneficiaryId + status once and bucket in
Java with the same status groups (case-insensitive, like the database
comparison). Bucket contents are unchanged; order inside a bucket was never
fixed by the query.

TB suspected getAll copies the DTO fields directly instead of ModelMapper
per row, and its village subquery hints idx_flow_psm_village_reg (MySQL
ignores the hint where that index is absent).

Standard OPD records for a village start from the village's beneficiaries
instead of every visit of the PSM; the same visits match.

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

* perf(stoptb): keep TB suspected speed-up on the Stop TB village path only

Restore IncentiveLogicService/IncentiveLogicImpl to their original code and
run the FLW (ashaId) path of TB suspected getAll exactly as before. The
Stop TB village path reads each user's state once and calls the unchanged
incentiveForTbSuspected only for CG users, the only state where it acts.

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

* perf(worklist): restore original village count and page queries

The index-only count also counted flow rows that have no
i_beneficiarymapping row (423 on the reference DB, mostly PSM 1725),
so villages with such rows got extra or empty pages and a fully
unmapped village got an empty success instead of "No record found".
Put countVillageWorklist, getVillageWorklistAddressIds and the empty-page
handling back to the release-3.12.0 behaviour. The batch loader, page
size and timing logs stay.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(stoptb): never move the feed past a change whose transaction is still open

The safe point was the newest log line older than 5 s. seq is taken at insert, not at
commit, so a transaction open longer than that could commit a lower seq after the feed
had moved past it, and tablets would miss the change. The safe point is now below the
oldest open transaction (information_schema.innodb_trx), minus the same 5 s margin;
without the PROCESS privilege it falls back to the fixed lag and logs a warning once.

Reproduced on MySQL 8.0: with A open holding seq 5 and B committed at seq 6, the old
rule returned 6 (skipping 5); the new one returns 0 until A commits, then 6.

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

* fix(diagnostics): polls that change nothing don't rewrite the order or result

Every pending order was saved on every poll (every ~35 s): processed reset to 'N' so MMU
re-uploaded it, and the change-log trigger re-sent it to every tablet. Now, when the
vendor reports the same status, error, provider order id and result fields, only
last_polled_at (and the retry count) is updated; a repeated poll error is recorded the
same way. Real changes are saved as before. Pairs with AMRIT-DB V134 (PSMRI/AMRIT-DB#227).

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

* feat(stoptb): send a beneficiary's older records when they come into scope

AMRIT-DB V134 logs flow-table scope changes (new flow row, village / service / deleted
change) with op 'S'. When a delta page has such a line for a beneficiary, the feed now
also sends that beneficiary's existing records of every requested type: they were logged
before the beneficiary was in the tablet's villages, so they never passed the scope check
and the token had already moved past them.

Each type's query for this is derived from its snapshot query (village scope swapped for
the given BenRegIds, paging dropped); StopTBSyncTypeTest checks every one.

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

* feat(stoptb): changes feed only serves the user's own villages, all of them by default

- The feed reads the user from the JwtToken header (required) and their services and
  villages from every active m_userservicerolemapping row. A service or village the user
  isn't mapped to is rejected; no villageIds means all of the user's villages for that
  service. Cached 5 minutes per user.
- Tokens carry a hash of the service and village list, so a different scope starts a new
  snapshot instead of continuing from another scope's position.
- getUserDetail no longer takes whichever mapping row came first: it uses the STOP-TB
  (Nikshay) row when there is one, merges villages across all rows for that service, and
  fails clearly when the user has no mapping instead of IndexOutOfBounds.

UserSyncScopeTest covers merging, defaults and rejection; SyncTokenTest the new token.

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

* fix(diagnostic): don't treat a failed result update as a lost race

processResult caught every DataIntegrityViolationException on the result
save as a lost insert race and went on to mark the order COMPLETED. When
the save of an existing result failed (V134 trigger NULL row_pk), the order
completed with its result still PENDING. Only a first insert can race;
rethrow otherwise so the order is left as it was.

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

* feat(stoptb): save OPD prescription without dispensing by default (#395)

Inventory is not part of this release, so the Stop TB OPD save no longer
checks the camp store or stock, writes no patient issue and leaves stock
unchanged. Drugs from the app's own list are saved as sent (DrugID, name,
form, frequency, duration, instructions); quantity is worked out when the
app sends 0. A new save for the visit replaces its earlier prescription,
so an edit or retry leaves one copy.

The existing dispensing path is kept behind stoptb.dispense.enabled
(default false, no config needed) for the release that brings inventory.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: vishwab1 <vishwanath@navadhiti.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Vishwanath Balkur <118195001+vishwab1@users.noreply.github.com>
Co-authored-by: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com>
Co-authored-by: chetansaroya <chetan.saroya@unthinkable.co>
Co-authored-by: Mithun James <1007084+drtechie@users.noreply.github.com>
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.

2 participants