# Delmia / DNC Integration API — document download & WW recipe-download notification

Reference for the **`delmiaintegration`** solution (`~/Desktop/delmiaintegration`), the legacy
bridge that pulls "proven" manufacturing documents (NC programs / recipes) out of **DELMIA Apriso**
and notifies **AVEVA Wonderware** that a recipe was downloaded for a machine. The recipe/NC-program
push to machines is classic **DNC** (Distributed Numerical Control), hence this lives under
`former-api-specs/dnc/`.

There are **two distinct API surfaces** in this solution:

| # | Surface | Direction | Transport |
|---|---------|-----------|-----------|
| A | **Delmia document web service** | integration → Delmia (consumed) | form-urlencoded POST, **XML** response |
| B | **WW recipe-download notification** | `WWNotifier` CLI → Wonderware receiver | **JSON** POST (`/notify`) |

> Surface B is the **legacy predecessor of the ScadaBridge `DelmiaRecipeDownload` inbound method** —
> same field set and result shape (see *ScadaBridge equivalent* below).

Source files (under `~/Desktop/delmiaintegration`):
- `DelmiaIntegration/DelmiaClient.cs` — Delmia HTTP client (Surface A)
- `DelmiaContracts/*.cs` — XML contracts (`DownloadResult`, `SearchResults`/`SearchResult`, + unused `MachineInfo`/`MachineSearchResults`/`UserInfo`)
- `WWNotifier/Program.cs`, `WWNotifier/CommandLineOptions.cs`, `WWNotifier/Models/RecipeDownload*.cs` — the CLI notifier (Surface B)
- `WWNotifier/App.config` — `NotifyURL` / `NotifyTimeout`

### End-to-end flow

```
DELMIA Apriso  ──(A) RequestProvenDocument / RequestDocument / Search──►  DelmiaClient
   (intercim "ruleset" web service, XML)                                    │ downloads proven doc/recipe to a path
                                                                            ▼
                                          WWNotifier.exe  ──(B) POST /notify {RecipeDownload}──►  Wonderware "WW receiver"
                                          (CLI, exit code + YES/NO)         ◄── {RecipeDownloadResult} ──
```

---

## Surface A — Delmia document web service (consumed by `DelmiaClient`)

`DelmiaClient` (`DelmiaClient.cs`) is a thin `HttpClient` wrapper. The **base URL is supplied by
the caller** (constructor / `URL` property — there is no default in `DelmiaIntegration/App.config`);
`Timeout` defaults to **30 s**. Every call is an `application/x-www-form-urlencoded` POST to
`{URL}/<Operation>`, and every response is **XML** deserialized with `XmlSerializer` (root
namespace `http://intercim.com/ruleset` — the Apriso/InterCIM heritage).

| Verb | Path | Form fields | Response (XML) | Client method |
|------|------|-------------|----------------|---------------|
| `POST` | `{URL}/RequestProvenDocument` | `username`, `machineID`, `partNumber`, `operationNumber`, `workOrderNumber` | `DownloadResult` | `RequestProvenDocument[Async]` |
| `POST` | `{URL}/RequestDocument` | + `documentKey` | `DownloadResult` | `RequestDocument[Async]` |
| `POST` | `{URL}/Search` | `username`, `machineID`, `partNumber`, `operationNumber` | `SearchResults` | `Search[Async]` |

- **`RequestProvenDocument`** — fetch the single *proven* (released/approved) document for the
  part + operation, logging it against `username` / `workOrderNumber`.
- **`Search`** — list candidate documents matching the part/operation (each carries a
  `DocumentKey` + `DocumentURL`).
- **`RequestDocument`** — fetch one specific document chosen from a search by `documentKey`.

`username` here is **identity/audit only** — it is a form field, not an authentication credential.
There is **no API key, no `Authorization` header, no TLS requirement** on this surface (see gotchas).

### Response — `DownloadResult` (metadata about the download)

`DownloadResult` describes *who/what/which order+document* was downloaded — it does **not** carry
the file bytes (the file lands at a download path on the Delmia side). Fields:

| Group | Fields |
|-------|--------|
| User | `UserKey` (int), `UserName`, `UserSite` |
| Machine | `MachineKey` (int), `MachineID`, `MachineSite` |
| Order | `WorkOrderNumber`, `ShopOrderKey` (int), `ShopOrderID`, `ShopOrderStatus`, `ShopOrderOperKey` (int), `ShopOrderOperID`, `ShopOrderOperStatus` |
| Document | `DocumentKey` (int), `DocumentName`, `DocumentRev`, `DocumentStatus` |
| Part | `PartID`, `PartRev` |
| Outcome | **`TransferSuccessful`** (bool), `ErrorMessage` (string) |

> **Success is `TransferSuccessful`, not the HTTP status.** Treat `TransferSuccessful == true` as the
> only success signal; `ErrorMessage` carries the reason otherwise.

### Response — `SearchResults` / `SearchResult`

`SearchResults` = `{ List<SearchResult> Results; string ErrorMessage }`. Each `SearchResult`:
`ShopOrderKey`/`ShopOrderID`/`ShopOrderStatus`, `ShopOrderOperKey`/`ShopOrderOperID`/`ShopOrderOperStatus`,
`DocumentKey` (int), `DocumentObjectID` (int, with an `XmlIgnore` `…Specified` flag),
`DocumentName`, `DocumentRev`, `DocumentStatus`, **`DocumentURL`**, `PartID`, `PartRev`.

### Quick reference (curl, Surface A)

```bash
# Search for candidate documents (values from DownloadTestUtil's example)
curl -X POST "{URL}/Search" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "username=dohertj2" \
  --data-urlencode "machineID=000005" \
  --data-urlencode "partNumber=00444455599" \
  --data-urlencode "operationNumber=0100"
# -> <SearchResults xmlns="http://intercim.com/ruleset"> … </SearchResults>

# Request the proven document for that part/operation
curl -X POST "{URL}/RequestProvenDocument" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "username=dohertj2" \
  --data-urlencode "machineID=000005" \
  --data-urlencode "partNumber=00444455599" \
  --data-urlencode "operationNumber=0100" \
  --data-urlencode "workOrderNumber=W111111"
```

---

## Surface B — WW recipe-download notification (`WWNotifier` → WW receiver)

`WWNotifier.exe` (`WWNotifier/Program.cs`) is a **command-line tool** invoked by the download
process to tell Wonderware that a recipe/NC file was placed at a path for a machine. It POSTs a
JSON `RecipeDownload` to the configured receiver and interprets the JSON `RecipeDownloadResult`.

### CLI options (`CommandLineOptions`)

| Short | Long | Required | Meaning |
|-------|------|----------|---------|
| `-d` | `--downloadpath` | **yes** | File download path |
| `-m` | `--machine` | **yes** | Machine code |
| `-w` | `--workorder` | **yes** | Work order number |
| `-p` | `--partnumber` | **yes** | Part / item number |
| `-s` | `--seqop` | no | Job step / sequence number |
| `-u` | `--username` | no | Operator username |

### Endpoint & payload

`POST {NotifyURL}` with a JSON body, expecting a JSON reply. From `WWNotifier/App.config`:
`NotifyURL = http://wonder-app-vd01.zmr.zimmer.com:9001/notify`, `NotifyTimeout = 30` (seconds).
`NotifyURL` may be a **comma-separated list** — each is tried in order and the **first success
wins** (failover).

**Request — `RecipeDownload` (JSON)**

| Field | Type | Source |
|-------|------|--------|
| `MachineCode` | string | `--machine` |
| `DownloadPath` | string | `--downloadpath` |
| `WorkOrderNumber` | string | `--workorder` |
| `PartNumber` | string | `--partnumber` |
| `JobStepNumber` | string | `--seqop` |
| `Username` | string | `--username` |

**Response — `RecipeDownloadResult` (JSON)**: `{ "Result": bool, "ResultText": string }`.

### Process contract (stdout + exit code)

The caller (Delmia) reads `WWNotifier`'s console output and exit code:
- On success → prints **`YES`**; exit code `0`.
- On any failure (arg parse, missing config, all receivers failed, `Result == false`) → prints
  **`NO`** plus a reason line; `Environment.ExitCode = -1`.

```bash
WWNotifier.exe -m Z28061 -d "C:\recipes\wo111111.nc" -w W111111 -p P111111 -s 0100 -u chamalas
# stdout: YES        (or: NO\n<reason>)
# POSTs {"MachineCode":"Z28061","DownloadPath":"C:\\recipes\\wo111111.nc", … } to /notify
```

---

## Other contracts (defined but unused)

`MachineInfo`, `MachineSearchResults` (`{ List<MachineInfo> Results; string ErrorMessage }`), and
`UserInfo` exist in `DelmiaContracts` (same `intercim.com/ruleset` namespace) but are **not wired to
any `DelmiaClient` operation** — scaffolding for machine-lookup / user-lookup endpoints that were
never implemented. Shapes, for reference: `MachineInfo` = `MachineKey`/`MachineID`/`MachineName`/
`DownloadPath`/`MachineDescription`/`MachineSite`/`MachineStatus`; `UserInfo` = `UserKey`/`UserName`/
`UserSite`/`IsActive`.

---

## Behavior notes & gotchas

- **No authentication on either surface.** Surface A's `username` is just a logged form field;
  Surface B sends no credential at all. Both are plaintext HTTP. (Contrast the ScadaBridge inbound
  API, which requires `X-API-Key`/`Bearer` — see `../mes/authgaps.md`.)
- **Surface A ignores HTTP status.** `DelmiaClient` never calls `EnsureSuccessStatusCode`; a non-2xx
  body is handed straight to `XmlSerializer`, which typically throws → the `catch` returns a generic
  `TransferSuccessful = false` / `"Failed to call Delmia web service at '<URL>'."`. The real HTTP
  error is lost.
- **Sync methods block on `.Result`.** `RequestProvenDocument`/`RequestDocument`/`Search` call
  `.Result` on the async POST (deadlock-prone in some contexts); async variants exist.
- **`WWNotifier` has a latent NPE in its error path.** On a notify exception it logs
  `error.InnerException.Message` (`Program.cs:129`); if `InnerException` is null this throws inside
  the `catch`, masking the original error.
- **`NotifyURL` failover is first-success-wins**, in list order; a slow first endpoint costs up to
  `NotifyTimeout` before the next is tried.
- **Surface A base URL is caller-supplied** (no config default), so the effective Delmia endpoint
  depends on whoever constructs `DelmiaClient` (e.g. `TestUI`/`DelmiaClientUI`).

---

## ScadaBridge equivalent (porting note)

- **Surface B → ScadaBridge Inbound API `DelmiaRecipeDownload`.** The legacy `WWNotifier.exe` + the
  `/notify` WW receiver are replaced by `POST /api/DelmiaRecipeDownload` (authenticated with
  `X-API-Key`/`Bearer`). The contract is identical: request `{ MachineCode, DownloadPath,
  WorkOrderNumber, PartNumber, JobStepNumber, Username }` → response `{ Result, ResultText }`. The
  inbound script routes to the site via `Route.To(MachineCode).Call("ProcessRecipeDownload", …)`.
  The CLI's comma-list failover is superseded by Traefik active-node routing; the `YES`/`NO` + exit
  code contract becomes the HTTP status + JSON body.
- **Surface A (Delmia document service) has no direct ScadaBridge equivalent** — retrieving proven
  documents from DELMIA Apriso remains an external concern (it would be an External System Gateway
  call if pulled into ScadaBridge).

This file documents the **legacy** `delmiaintegration` contracts for reference/parity during that
migration.
