> For the complete documentation index, see [llms.txt](https://docs.digit.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.digit.org/health/design/architecture/low-level-design/services/health-services/beneficiary-idgen.md).

# Beneficiary IDGen

## Overview

Beneficiary IDGen is the shared ID factory and vending machine for a health campaign. Beneficiaries (and other entities) need unique, human-readable IDs — often pre-printed on cards, wristbands, or QR stickers handed out in the field. It's primarily used for beneficiaries who don't have a national ID (or equivalent government-issued identifier) to provide, giving them a system-generated identity they can be tracked and served under instead.

It has two main jobs:

1. **Generate** a tenant-specific pool of unique, human-readable beneficiary IDs from a configured format.
2. **Dispatch** a safe, non-overlapping batch of those IDs to a specific user and device in the field.

{% hint style="info" %}
Think of it as: **generate IDs ahead of time → dispatch them safely to devices → reconcile their status later**.
{% endhint %}

### At a glance

* **Used by:** setup or admin flows for generation, and **DISTRIBUTOR** users for field dispatch.
* **Input:** tenant, ID format configuration, requested counts, dispatch context, and update criteria.
* **Output:** generated pool records, dispatched ID allocations, search results, and audit logs.
* **Source of truth:** Postgres `id_pool` stores the ID pool state. Redis only enforces dispatch quotas.
* **Generate:** pre-mints IDs into the pool.
* **Dispatch:** allocates IDs to one user and one device without overlap.

The Health Campaign Management app itself is offline-first — field staff often work in areas with low or no internet connectivity, so the app can't assume it can call IDGen live every time a new beneficiary or entity needs an ID.

To work around this, IDGen supports a **pre-fetch/download model**: when the field worker has connectivity (typically right after login, or whenever a sync window is available), the app downloads a batch of pre-generated IDs in advance and caches them locally. As new beneficiaries are registered in the field — even fully offline — the app assigns IDs from this local pool instead of waiting on a live call to IDGen.

The **number of IDs downloaded per batch is configurable via MDMS**, so this can be tuned per campaign (e.g. based on expected daily registration volume, device storage constraints, or field team size) without needing a code change.

In short: IDGen isn't just a request/response ID generator — it's designed as a **vending machine that dispenses IDs in bulk upfront**, so the offline app always has a local supply to draw from until the next sync, at which point it can top up.

It prepares IDs before campaign runtime, then hands them out in controlled batches during execution.

## Dependencies

* **egov-mdms-service** — supplies the **ID format template** for each tenant. Format tokens such as `[seq...]`, `[fy:...]`, `[cy:...]`, `[city]`, `[tenantid]`, and random `[d{n}]` patterns are expanded into final IDs. A DB fallback exists if `idformat.from.mdms=false`.
* **PostgreSQL** — stores the ID pool and dispatch audit trail. Main tables are `id_pool` and `id_transaction_log`. Postgres sequences back the `[seq]` portion of configured formats.
* **Redis (via Redisson)** — stores per-user and per-device dispatch counters for total and per-day limits, with TTL-based expiry.
* **Kafka** — carries async generation requests, bulk updates, persister writes, and dispatch audit events.
* **egov-persister** — persists `save-in-id-pool`, `update-id-pool-status`, and `save-dispatch-id-log` events into Postgres.
* **health-services-common / -models** — shared producers, validators, POJOs, and `ResponseInfo` utilities.
* **mdms-client** and **tracer** — inherited support for MDMS access, correlation IDs, and exception handling.

## Data Model

The service stores two kinds of state:

* generated ID pool records
* dispatch audit records

**Key relationships:**

* each generated beneficiary ID lives in `id_pool`
* dispatch assigns IDs from that pool to a user and device
* `id_transaction_log` records who received which IDs and when

{% tabs %}
{% tab title="ID Pool" %}

```
id_pool: Stores generated beneficiary IDs and their current state

- one record per generated ID
- tenant-scoped
- status-driven lifecycle
- populated by the generate flow
- consumed by the dispatch flow
```

{% endtab %}

{% tab title="Dispatch Log" %}

```
id_transaction_log: Stores dispatch audit history

- which user requested IDs
- which device received them
- when dispatch happened
- which IDs were allocated
- used for traceability and reconciliation
```

{% endtab %}
{% endtabs %}

Flyway migrations for these tables live under `db/migration/main`.

### Web Sequence Diagrams

{% tabs %}
{% tab title="Generate" %}

```
/beneficiary-idgen/id/id_pool/_generate
```

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeUe2xZlLiZmiTpt9DNCr0EJk6Nt_nEPOP7U8Bbm4DMAuB7wELcmMuhJR6QZ8ww0XKCHdI9n9Hgpcui3rm7NKeKmiYrDIIscclb3qgpa_Pibfyx3Ix_myEmtZEjVTOxBEtRMAOeWg?key=h6xj56uLHjrcNj0msKKRCQ" alt=""><figcaption><p>Pool generation flow</p></figcaption></figure>
{% endtab %}

{% tab title="Dispatch" %}

```
/beneficiary-idgen/id/id_pool/_dispatch
```

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/yGgKvtwgkWlsdlkOmPaJ/image.png" alt=""><figcaption><p>Dispatch and allocation flow</p></figcaption></figure>
{% endtab %}

{% tab title="Update / Reconcile" %}

```
/beneficiary-idgen/id/id_pool/_update
```

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/r88CUDPe6u32HKAERTvq/image.png" alt=""><figcaption><p>Status update and reconciliation flow</p></figcaption></figure>
{% endtab %}
{% endtabs %}

## Business Flow

The service follows a simple four-step flow:

1. **Generate pool.** A setup or admin flow calls `_generate` with the tenant and requested count.
2. **Dispatch IDs.** A field device calls `_dispatch` to receive a reserved batch.
3. **Re-sync or search.** The client can call `_dispatch` with `fetchAllocatedIds=true`, or use `_search` to inspect pool records.
4. **Update status.** The client calls `_update` later to reconcile the issued IDs.

Generation is asynchronous because large pools can take time to mint.

Dispatch is online and allocation-safe.

The service ensures that two devices do not receive the same ID.

## API Details

```
Base Path: /beneficiary-idgen/id
```

From a client's point of view:

* `_generate` is asynchronous
* `_dispatch` and `_search` are synchronous
* `_update` is asynchronous bulk processing

<table><thead><tr><th width="231.359375">Endpoint</th><th>Purpose</th></tr></thead><tbody><tr><td><code>POST /id_pool/_generate</code></td><td>Queue <strong>asynchronous</strong> generation of a tenant-specific pool of IDs. Returns immediately after the work is chunked and queued.</td></tr><tr><td><code>POST /id_pool/_dispatch</code></td><td>Allocate a block of unused IDs to one <strong>user + device</strong>, subject to total and daily limits. With <code>fetchAllocatedIds=true</code>, returns IDs already assigned to that device instead.</td></tr><tr><td><code>POST /id_pool/_search</code></td><td>Search ID pool records by ID list, status, and tenant.</td></tr><tr><td><code>POST /id_pool/_update</code></td><td>Accept an async <strong>bulk</strong> status update request for pool records, such as marking IDs as used.</td></tr></tbody></table>

### Internal Flow

This section explains how the service implements the API behaviour internally.

**Generation path.** `_generate` splits the requested count into chunks and publishes them to `id-gen-consumer-bulk-create-topic`. The service's own consumer mints IDs from the configured format, then emits `save-in-id-pool` for persistence.

**Dispatch path.** `_dispatch` selects unused IDs from Postgres using locking semantics, checks total and daily quotas in Redis, assigns the IDs to the user and device, and emits audit events on `save-dispatch-id-log`.

**Update path.** `_update` publishes to `id-gen-consumer-bulk-update-topic`. The consumer validates the request and emits status changes on `update-id-pool-status`.

There is no published Swagger contract for this service under `docs/health-api-specs/contracts/`.

#### Kafka Topics

<table><thead><tr><th width="322.54296875">Topic</th><th width="90.46484375">Dir</th><th>Purpose</th></tr></thead><tbody><tr><td><code>id-gen-consumer-bulk-create-topic</code></td><td>in</td><td>Bulk id-pool create requests</td></tr><tr><td><code>id-gen-consumer-bulk-update-topic</code></td><td>in</td><td>Bulk id-pool update requests</td></tr><tr><td><code>save-in-id-pool</code></td><td>out</td><td>Persist generated IDs into the pool</td></tr><tr><td><code>update-id-pool-status</code></td><td>out</td><td>Update ID status after dispatch or later reconciliation</td></tr><tr><td><code>save-dispatch-id-log</code></td><td>out</td><td>Persist dispatch audit logs</td></tr></tbody></table>

### Process Flow

Use this reading order:

1. **Queue generation.** `_generate` validates the request, splits it into chunks, and queues the work.
2. **Mint and persist.** The consumer expands the format, generates unique IDs, and persists them into `id_pool`.
3. **Dispatch atomically.** `_dispatch` draws from the unused pool, applies limits, and assigns IDs to one user and device.
4. **Inspect or reconcile.** `_search` reads back pool state, and `_update` applies later lifecycle changes.
5. **Audit the action.** Dispatch and update flows write audit data for tracing and reconciliation.

The generate and update paths are asynchronous.

Dispatch happens in-line, but uses DB locking and Redis counters to stay safe.

<figure><img src="https://2077406040-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FI0KFbSBTySIQ7TCOKnJF%2Fuploads%2F1oYpXRQOcEpA84e4oa2a%2Fimage.png?alt=media&amp;token=084d6b5c-cf36-47c3-81bd-ce1cce99da58" alt=""><figcaption><p>End-to-end request lifecycle</p></figcaption></figure>

### Failure & Retry Handling

* **Generation never blocks the caller and never half-fails the request.** `_generate` returns once chunks are queued; the actual minting happens in the consumer. If a chunk fails, it is logged and the request is **not** retried automatically.
* **Dispatch is concurrency-safe.** The pool draw uses `FOR UPDATE SKIP LOCKED`, so simultaneous dispatches skip locked rows rather than deadlock or double-allocate.
* **Dispatch limits are enforced.** Per-user and per-device **total** and **per-day** caps are checked against Redis counters. Exceeding them returns `USER_DEVICE_LIMIT_EXCEEDED`.
* **No IDs left** returns `NO IDS AVAILABLE` — a signal that the pool needs topping up via `_generate`.
* **Random-format buffer.** When a format contains a random pattern, the batch is over-generated by a configurable buffer percentage to absorb collisions. The pool insert uses `ON CONFLICT (id) DO NOTHING`, so duplicate random IDs are dropped rather than failing the batch.
* **Persister dependency trap.** If persister wiring for these topics is missing or stale in an environment, the API can accept generate or update work while rows silently never appear in Postgres.

### Known Risks & Limitations

* **The pool must be filled before it can be drawn from.** `_dispatch` only hands out IDs that `_generate` has already minted. An empty or exhausted pool returns `NO IDS AVAILABLE`.
* **Two stores must agree.** The authoritative pool is in **Postgres**, but dispatch quotas live in **Redis**. If Redis is flushed or lost, quota tracking resets even though Postgres still shows IDs as dispatched.
* **Limits are per user + device, keyed by date in a configured timezone.** "Per day" boundaries follow `id.timezone` or `app.timezone`. A bad timezone shifts when a quota resets.
* **Generation failures are swallowed in the consumer** — logged, but not retried. There is no dead-letter queue today, so a failed chunk simply means fewer IDs in the pool.
* **`_update` validation assumes a single tenant per bulk request.** It reads `tenantId` from the first record, so mixed-tenant batches are not supported.
* **ID status and format correctness are largely convention-driven.** A malformed MDMS `IdFormat` surfaces during generation time, not at configuration time.

### Recent Changes

{% hint style="info" %}
HCM v2.1: Dependency & Plumbing Update

* **No functional, API, or database migration changes** in this window — updates are limited to dependencies and plumbing.
* **Version bump**: `1.1.0-SNAPSHOT` → `1.1.1-SNAPSHOT`.
* **Tracer upgrade**: Bumped to `2.9.2` for `DataAccessException` handling; direct tracer dependency removed in favour of the transitive one from `health-services-common`.
* **Shared library bumps**: `health-services-common` `1.1.1` → `1.1.3`, `health-services-models` `1.0.29` → `1.0.30`.
* **OpenTelemetry BOMs added**: Added to dependency management, with OTEL exporters explicitly disabled in `application.properties` (`otel.traces/logs/metrics.exporter=none`).
* **No changes** to controllers, services, repositories, the ID-pool/dispatch logic, or Flyway migrations.
  {% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.digit.org/health/design/architecture/low-level-design/services/health-services/beneficiary-idgen.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
