# Digital Twin Products: Current State and Work Queue

Date: 2026-08-21

[Open the visual planning map](products-read-model-event-map.html)

## Two-Minute Summary

The Products UI, APIs, serialized receiving handler, Cosmos inventory positions, and Cosmos-to-SQL inventory projection exist.

The results are not yet a trustworthy complete inventory view because:

- receiving events and Cosmos inventory positions do not contain the TenantConfig root ID, so the SQL projection writes `Guid.Empty` as the tenant ID;
- Formulary changes do not reach Digital Twin;
- physical location hierarchy and container category are not reliably projected;
- non-serialized receiving, cycle-count corrections, and later inventory movements are incomplete;
- the Products query still uses a screen-specific aggregate that cannot support the required filters and count semantics.

The three immediate dependencies already have Azure DevOps stories:

1. [#327055: propagate tenant ID](https://dev.azure.com/inmar/Healthcare/_workitems/edit/327055) — Tech Grooming.
2. [#315051: consume Formulary messages](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315051) — New.
3. [#315053: consume organization/location data](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315053) — Active.

After those foundations, finish the normalized Products query under [feature #265915](https://dev.azure.com/inmar/Healthcare/_workitems/edit/265915), then finish Product Details under [feature #265916](https://dev.azure.com/inmar/Healthcare/_workitems/edit/265916).

## What Works Today

| Capability | Integrated state |
|---|---|
| Serialized receiving publication | Serialization publishes receiving outcomes. |
| Digital Twin receiving | Digital Twin consumes serialized receiving events and stores Cosmos inventory positions. |
| Inventory SQL projection | A Cosmos change-feed processor projects positions into SQL inventory facts and the existing aggregate. |
| Products APIs | List, details, facilities, and locations endpoints exist and read SQL. |
| Products UI | Initial Products and Product Details flows exist. |
| Source reference publishers | Formulary, Container Management, CycleCount, and Recalls publish relevant data. |

This is a useful foundation, not a complete end-to-end Digital Twin.

## Event and Work Map

| Input | Source status | Digital Twin status | Existing Azure DevOps work |
|---|---|---|---|
| Serialized receiving | Integrated | Integrated, but tenant is absent and SQL currently falls back to `Guid.Empty` | [#327055](https://dev.azure.com/inmar/Healthcare/_workitems/edit/327055) fixes tenant propagation; [#288332](https://dev.azure.com/inmar/Healthcare/_workitems/edit/288332) covers broader count updates |
| Formulary create/update/archive | Publisher integrated | Consumer and live projection missing | [#315051](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315051) is queued |
| Location hierarchy and container category | Publisher integrated | Consumer/projection missing from the inspected integration baseline | [#315053](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315053) is active |
| Non-serialized receiving | Publisher not complete | Consumer and quantity behavior missing | [#323919](https://dev.azure.com/inmar/Healthcare/_workitems/edit/323919) is in Tech Grooming; it covers publication only |
| Cycle-count completion | Publisher integrated | No integrated consumer applies completed counts to inventory | [#288344](https://dev.azure.com/inmar/Healthcare/_workitems/edit/288344) is New; [#327263](https://dev.azure.com/inmar/Healthcare/_workitems/edit/327263) handles payloads over 1,000 items |
| Recall changes | Publisher integrated | Consumer missing | [#315054](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315054) is Sprint Ready |
| Quarantine removal | Only a best-effort RxTransparent call was found | No durable Digital Twin event/consumer | No clear Digital Twin story found |
| Putaway, restock, adjustment, and movement | No complete integrated Digital Twin mutation path found | Missing | Mentioned across task/alert features, but no clean end-to-end movement story was found |

## Critical Path

### 1. Carry the Tenant ID Through Receiving

Use [#327055](https://dev.azure.com/inmar/Healthcare/_workitems/edit/327055). It already describes the required behavior: carry the TenantConfig root ID from Serialization through Cosmos and SQL, park invalid events, and remove the `Guid.Empty` fallback.

### 2. Project Live Reference Data

Run these in parallel where possible:

- [#315051](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315051): Formulary identity, grouping, active state, 340B attribution, and NDC mappings.
- [#315053](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315053): facility/location hierarchy and physical container category.

These are prerequisites for correct grouping, location filtering, and dispensability.

### 3. Replace the Products Read

Complete [feature #265915](https://dev.azure.com/inmar/Healthcare/_workitems/edit/265915) using normalized inventory, Formulary, mapping, and location tables.

The query must support:

- deterministic paging and total count;
- Formulary-name search;
- one selected facility: distinct-bin count and room/location filtering;
- multiple selected facilities: distinct-facility count and facility filtering;
- classification before aggregation for All, Dispensable, and Non-dispensable views;
- quantities across all qualifying NDCs in the selected product group.

### 4. Finish Product Details

Complete [feature #265916](https://dev.azure.com/inmar/Healthcare/_workitems/edit/265916) using the same tenant, facility, grouping, and inventory rules as the list.

Relevant queued work includes:

- [#287560: Create Product Details Endpoint](https://dev.azure.com/inmar/Healthcare/_workitems/edit/287560) — New, but likely stale because the endpoint already exists.
- [#327160: display proprietary names for NDCs](https://dev.azure.com/inmar/Healthcare/_workitems/edit/327160) — Tech Grooming.

### 5. Remove the Old Aggregate

Remove `FacilityFormularyAggregate` only after the normalized list and details reads are correct, tested, and measured. Until then, it remains an existing reader dependency.

## Decisions That Still Matter

1. **Persistence rule:** The stated architecture is external event → canonical Digital Twin Cosmos state → SQL projection. Stories [#315051](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315051) and [#315054](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315054) currently describe direct SQL reference-state writes. Either revise them to Cosmos-first or record an explicit exception.
2. **Product identity:** Work items alternate among Formulary item, GPI, and equivalency group. Choose one stable API identity and define cross-facility grouping.
3. **Location count:** The current feature text is inconsistent. Earlier implementation/QA work says one facility counts bins and multiple facilities count facilities; the active feature also contains language that always says distinct bins.
4. **Dispensability:** Define the exact inventory-level rule for physical categories and virtual inventory. The backlog names the tabs but does not provide one unambiguous classification contract.
5. **Unknown quantity:** Product Details explicitly allows presence with an unavailable NDC quantity. Define how that contributes to list totals, values, and category counts.
6. **Cost:** Products and Product Details require EDI-810 value/cost, but the current Digital Twin read model does not establish that source path.

## Backlog Findings

- Do not create duplicate tenant, Formulary, location, recall, cycle-count, or non-serialized publication stories; work already exists.
- [#315053](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315053) is active, but its child task references `FacilityUpsertedEvent` while the integrated Container Management source publishes a `LocationHierarchyChanged` message wrapper. Verify the contract before accepting the implementation.
- [#315051](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315051) has useful scope but no formal acceptance criteria and assumes direct SQL plus aggregate refresh. It needs architectural refinement before implementation.
- [#315054](https://dev.azure.com/inmar/Healthcare/_workitems/edit/315054) is detailed and Sprint Ready, but it intentionally writes recall snapshots directly to SQL. That is an architecture decision, not a hidden implementation detail.
- [#265918](https://dev.azure.com/inmar/Healthcare/_workitems/edit/265918), [#288332](https://dev.azure.com/inmar/Healthcare/_workitems/edit/288332), the resolved receiving story, and [#323919](https://dev.azure.com/inmar/Healthcare/_workitems/edit/323919) overlap. Reconcile serialized, pack-level, and non-serialized responsibilities before pulling more receiving work.
- Tasks [#306226–#306230](https://dev.azure.com/inmar/Healthcare/_workitems/edit/306226) remain New under removed virtual-bin story #286678. Tasks [#306231–#306235](https://dev.azure.com/inmar/Healthcare/_workitems/edit/306231) remain New under closed design story #301971. Reconcile them rather than scheduling them blindly.
- Expiration, PAR/restock, recall-task generation, and data-platform publication are real future capabilities, but they are not prerequisites for replacing the Products read query.

## Target Shape

```text
Source event or mutation
        ↓
Digital Twin handler
        ↓
Canonical, replayable Cosmos state
        ↓ change feed
Normalized SQL facts and dimensions
        ↓
Products and Product Details APIs
```

## Evidence

- `products-read-model-technical-analysis.md`: detailed code-baseline and event analysis.
- `products-page-query-findings.md`: normalized-query and aggregate findings.
- `study digital twin schema.linq`: executable normalized Products query proof.

Azure DevOps was inspected read-only. No work item was created or changed.
