Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 28 additions & 52 deletions skills/uipath-process-mining/SKILL.md

Large diffs are not rendered by default.

20 changes: 16 additions & 4 deletions skills/uipath-process-mining/references/data-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ to `Cases` is treated as disconnected and rejected at query time

| Model | Endpoint | Shape | Role |
|-------|----------|-------|------|
| **Data model** (structural) | `/apps/{id}/{stage}/dataModel` | `tables[]` of `{ type, name, primaryKey, foreignKeys }` | What tables exist + how they link. **`add-table` edits this.** `apps model get` reads it. |
| **Semantic model** | `/apps/{id}/{stage}/model` | `Processes` / `Metrics` / `Tables[].Fields[]` | Field-level view `query info` reads. **Derived** from the data model by `applyCurrentDatamodel` — do not hand-edit for add-table. |
| **Data model** (structural) | `/apps/{id}/{stage}/dataModel` | `tables[]` of `{ type, name, primaryKey, foreignKeys }` | What tables exist + how they link. **`apps data-model add-table` edits this; `apps data-model get` reads it.** |
| **Semantic model** | `/apps/{id}/{stage}/model` | `Processes` / `Metrics` / `Tables[].Fields[]` | Field-level view `query info` (and `apps model get`) reads; edited by `apps model fields …` ([`model-editing.md`](model-editing.md)). `applyCurrentDatamodel` **reconciles** structural changes into it, preserving your semantic edits (calculated fields, metrics, data-kind overrides) — so add-table won't wipe them; just don't hand-author its per-column fields, edit the structural model. |

Edit the structural data model; `applyCurrentDatamodel` regenerates the semantic
model (its per-column fields) from it. `add-table` does both.
Expand Down Expand Up @@ -84,6 +84,13 @@ The table isn't one-row-per-case, but must still reach `Cases`. Give it a **surr
PK** and a **nullable `Case_ID`** carrying the FK — a null FK is enough to satisfy the
case-centric graph; aggregate queries don't need it to resolve to real cases.

**Caveat — a null `Case_ID` makes the table analytically disconnected.** Case-level
filters/selections (how PM dashboards normally scope data) won't propagate to it, and
it can't be joined back to real cases. Use the null-FK loose link **only** for a
genuinely case-independent aggregate (a weekly total, a cross-case study). If its rows
*do* correspond to real cases, populate `Case_ID` with the real key so case filtering
flows through.

1. Author the dbt model. First two selected columns:

```sql
Expand All @@ -98,7 +105,7 @@ case-centric graph; aggregate queries don't need it to resolve to real cases.
```bash
uip pm transformations create <app> models/Workload_weekly.sql --file ./Workload_weekly.sql
uip pm transformations apply <app> --wait
uip pm apps model add-table <app> --file ./Workload_weekly.table.json # edits /dev/dataModel + applyCurrentDatamodel
uip pm apps data-model add-table <app> --file ./Workload_weekly.table.json # edits /dev/dataModel + applyCurrentDatamodel
uip pm ingestions create <app> --wait # REQUIRED — materializes the table
uip pm query run <app> --group-by Service_Component --metric Closed_Interactions:sum --output table
```
Expand All @@ -107,7 +114,12 @@ case-centric graph; aggregate queries don't need it to resolve to real cases.

`add-table` GETs `/dev/dataModel` (with its ETag), **upserts** the table by name
(replace if present, else append), PUTs it back `If-Match`-guarded, then POSTs
`applyCurrentDatamodel`. A concurrent edit surfaces as `412`. It returns
`applyCurrentDatamodel`. Because it merges into exactly the document it just read,
that ETag is a genuine compare-and-swap — so **`add-table` takes no `--etag`**, unlike
`data-mapping update` / `model update` / `transformations update`, which replace a file
you edited locally and therefore require it. A concurrent edit surfaces as `412`; **just
re-run** — `add-table` re-reads and re-applies on top of the other write. (A data model
returned without an ETag fails the command rather than writing unguarded.) It returns
`IngestionNeeded: true` — the entity is not queryable until the re-ingest completes.

## Publish vs re-ingest
Expand Down
17 changes: 12 additions & 5 deletions skills/uipath-process-mining/references/lifecycle-and-rbac.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ query commands (default `dev`):
of the data: ingest a sample, get the mapping + `Cases.sql` + your custom models
and data-model tables right, verify with `query`, then move on. Short feedback
loops matter — a full re-transform on a large dataset is slow.
- **`published`** — the stage consumers use, carrying the **full dataset**.
Dashboards and shared analysis read published data.
- **`published`** — the stage consumers use through the **dashboards / shared UI**,
carrying the **full dataset**. `apps publish` promotes the definition here.

Typical loop: develop and validate on `--stage dev` with a subset → publish →
run the real analysis / share on `--stage published` with everything.
Typical loop: develop and validate on `--stage dev` with a subset → publish → let
consumers read the **dashboards** on the published data.

> **CLI caveat:** `uip pm query --stage published` is currently **not reachable** —
> `/query/{id}/published` needs a completed ingestion on that stage and no `uip pm`
> path produces one (`apps publish` answers `IngestionNeeded: true`, but a following
> `ingestions create --wait` still leaves published querying at
> `UserError_InvalidOrNoIngestion`; verified against a live tenant). So publishing
> promotes the app to the **dashboards**, but **CLI-driven `query` stays on `dev`**.

## Publishing

Expand All @@ -29,7 +36,7 @@ clobbering a newer model. The result envelope carries:
- **`Changes`** — what the publish moved.
- **`IngestionNeeded`** — when `true`, the published stage still needs a
re-ingestion before the change reaches the **data**. Dev transformation *or*
data-model changes (including `apps model add-table`) only become queryable
data-model changes (including `apps data-model add-table`) only become queryable
after a re-ingest; publishing alone promotes the definition, not the rows.

So the full promote loop is: validate on `dev` → `uip pm apps publish <app>` →
Expand Down
112 changes: 112 additions & 0 deletions skills/uipath-process-mining/references/model-editing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Editing the Process Mining app model

There are **two** models behind a process app. Editing the wrong one is the most common source
of confusion, so be precise about which you mean.

| | `apps model` (semantic) | `apps data-model` (structural) |
| --- | --- | --- |
| Endpoint | `/apps/{id}/{stage}/model` | `/apps/{id}/{stage}/dataModel` |
| Contains | `data` → tables → **fields with their `kind`** (data kind), **calculated fields**, **metrics**; plus `view` → dashboards, charts, `metricFilters` | Tables with `primaryKey`/`foreignKeys` and the process-mining **role columns** (`activityColumn`, `endColumn`, …) |
| Think of it as | "the app definition" the user sees and edits | "the table plumbing" — which tables exist and how they join to `Cases` |
| Edited by | `fields set/remove`, `update`; the data manager & dashboard editor | `add-table`; the data-model editor |

`query info` shows the resolved *query* model (field ids, physical `ColumnDataType`, metrics) — useful
to discover the exact field ids to pass to `fields set` and `query`.

## Field editing surface

```bash
uip pm apps model fields list <app-id> [--stage dev|published]
uip pm apps model fields set <app-id> <field-id> [--kind <k>] [--display-name <t>] [--expression <json|@file>] [--table <table-id>]
uip pm apps model fields remove <app-id> <field-id>
```

- **Upsert semantics.** If `<field-id>` exists, its `--kind` / `--display-name` are updated, and
passing `--expression` turns it into (or updates) a **calculated field**. If it does not exist, a
new **calculated field** is created in `--table` — so `--table` + `--expression` (+ `--kind`) are
required to create. Mapped *column* fields can't be created through the model; they come from the
ingested data.
- **Data kinds you can set (`--kind`):** `ordinal, nominal, numeric, datetime, boolean, percentage, currency, duration`
— the union of the data manager's field-type options (FE `ColumnDataTypeFieldCompatibilityMap`).
`duration`, `currency`, `percentage` are **user choices stored in the model `kind`** (a number column
defaults to `numeric` — the user upgrades it). `id` and `ref` are **structural** (system-assigned to
key/reference columns) and not settable, though `fields list` may report a field that already has them.
- **Expressions** are JSON expression-node trees, the same shape the app model stores. A comparison:
```json
{"type":"operator","operation":"lt",
"left": {"type":"reference","referenceType":"field","reference":"<field-id>"},
"right": {"type":"constant","dataType":"duration","value":86400000}}
```
Operators: `lt le gt ge eq ne and or add subtract multiply divide percentage`. Constant
`dataType` **must match** the data kind of what it's compared to (see below). Reference a field
with `{type:"reference","referenceType":"field","reference":"<field-id>"}`.

Every edit is `If-Match`-guarded and applies on `dev`, returning the new edit `Versions` — but the two
routes differ in who supplies the ETag:

- **`fields set` / `fields remove` take no `--etag`.** They read the model and merge your change into
exactly that version, so the read's own ETag is a real compare-and-swap. On a lost race, **just
re-run** — they re-read and re-apply. (They refuse to write at all if the read came back without an
ETag, rather than writing unguarded.)
- **`apps model update` REQUIRES `--etag`** — it replaces a document you edited locally, so it must
carry the `Data.ETag` that `apps model get` returned. On 409/412, re-`get` for the latest model
**and its new ETag**, re-apply your change on top, then update again with the new `--etag`.

```bash
uip pm apps model get <app> --destination model.json # prints Data.ETag
uip pm apps model update <app> --file model.json --etag 'W/"3"'
```

Prefer `fields set` for a targeted change: no ETag to thread, and it can't clobber unrelated parts of
the model. After editing, `publish` to reach the dashboards, and re-ingest if a data kind changed.

## The data-kind rule

Relational/arithmetic operators require their operands to share a data kind (backend
`OperatorRelationalOrdering` / `CheckFunctionArguments`). So a comparison like `field < constant` is
only valid when the constant's `dataType` equals the field's `kind`. If they differ the model fails
validation with:

```
UserError_UnsupportedOperatorArgumentDataKind
{ argument:"right", operation:"lt", actual:"numeric", expected:"duration" }
→ "Must be duration, not numeric, for the 'lt' input."
```

The `fields set` / `update` commands **run this validation synchronously** and refuse an edit that
would create the mismatch, surfacing a hint that names the conflicting comparison. So via the CLI you
cannot flip a field to `duration` while a calculated field / metric compares it to a numeric constant —
update or remove that comparison first, or make the constant a `duration`. This synchronous check is
exactly what the **data-manager UI does not do** (it defers the kind change to the next re-ingestion —
the footgun below), so `fields set` is the *safe* way to change a kind. Caveat: the check covers
comparisons in the typed model `data` (calculated fields, metrics); a kind change that conflicts only
with an opaque dashboard **view** filter/chart is not caught, so publish and re-open to confirm.

## The data-kind footgun (DNA-46960)

A customer's app failed to open with exactly the error above. Root cause, from their exported app:
a metric **`% Tijdigheid`** was `PERCENTAGE( DOORLOOPTIJD[duration] lt 864000000[numeric] )` — a
throughput-time field (kind **duration**) compared to a **numeric** constant (10 days in ms). That
`lt(duration, numeric)` is evaluated when the query model is built at open, so it blocks every
dashboard (the data-upload module stays reachable — hence "I can only reach the data upload module").

How an app reaches this state via the **data-manager UI** (not the CLI, which validates synchronously):

1. Field is **numeric**; a metric/calculated field compares it to a numeric constant → valid.
2. The field's type is changed to **duration** in the *data manager*. This is applied in a
**deferred** way — it is not written to the app model synchronously; it is baked in when the app
model is regenerated at the **next re-ingestion**.
3. On re-ingest the field becomes `duration`, so the pre-existing comparison is now
`lt(duration, numeric)` — and the ingestion-time regeneration does **not** re-run the edit
validation, so the now-invalid model is persisted → the app won't open.

Takeaways when working with an app in this state:
- To reproduce/inspect: `apps model get` / `fields list` shows the field `kind` and the offending
calculated field/metric; the mismatch is a comparison whose constant `dataType` ≠ the field `kind`.
- **Range filters do not trigger it** — filters on a field go through a coercing path, so a numeric
range filter on a now-duration field still opens. Only real expressions (calculated fields, metrics)
hit the operator data-kind check.
- The fix for a broken app is to make the comparison consistent: either revert the field to the kind
the constant expects, or re-type the constant to match the field (e.g. a `duration` constant).
- Import (`.pmapp`) does not re-run this expression validation (exports are trusted), so importing a
broken app reproduces the broken state; that is expected and not the bug.
22 changes: 13 additions & 9 deletions skills/uipath-process-mining/references/pre-flight.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ A mapping mistake is **not** a reason to delete the app and start over. `apps
data-mapping` reads and replaces the mapping of an existing app:

```bash
uip pm apps data-mapping get <app> --destination ./mapping.json # download the current mapping
uip pm apps data-mapping get <app> --destination ./mapping.json # download the mapping + note Data.ETag
# ...edit: fix the DateTimeFormatString, move a column to the right TargetName, map one more column...
uip pm apps data-mapping update <app> --file ./mapping.json # ETag-guarded replace
uip pm apps data-mapping update <app> --file ./mapping.json --etag 'W/"639…"' # --etag REQUIRED
uip pm files upload <app> ./data.csv --input-table Event_log # ONLY if the source columns changed
uip pm ingestions create <app> --wait # the mapping applies to the NEXT ingestion
```
Expand All @@ -77,13 +77,17 @@ Facts worth not re-learning:
mapping and then running `apply` looks successful and changes nothing.
- **`dev` only.** The backend allows `PUT` on the dev stage; `published` is
read-only, and the CLI restricts `update --stage` to `dev` up front.
- **Concurrency is ETag-guarded.** `update` reads the current ETag and sends it as
`If-Match`, so you never pass one — but a concurrent edit (someone in the UI's
mapping editor) between read and write is rejected `409
UserError_ETagFileConflict`. Just re-run. `update` reports `Tables` (the mapped
table names) and `IngestionNeeded: true`, not an ETag; to confirm a write landed,
`get` again and diff — the `get` ETag is a **content checksum**, so re-pushing an
identical mapping leaves it unchanged.
- **`--etag` is REQUIRED on `update`, and it must be the one *your* `get` returned.**
You edited the file locally, so only that ETag proves the edit was based on the
version you read; the CLI deliberately does not fetch a fresh one before the `PUT`
(which would make the `If-Match` pass no matter who wrote in between). A concurrent
edit (someone in the UI's mapping editor) is therefore rejected `409
UserError_ETagFileConflict` — recover by re-running `get` for the latest version
**and its new ETag**, re-applying your change on top of that, then updating with the
new `--etag`. Re-running the same `update` unchanged just fails again.
- `update` reports `Tables` (the mapped table names) and `IngestionNeeded: true`, not
an ETag; to confirm a write landed, `get` again and diff — the `get` ETag is a
**content checksum**, so re-pushing an identical mapping leaves it unchanged.
- **A table-less mapping is refused locally.** `{ "Tables": [] }` (or any file with
no usable table) fails `No tables found in …` before any API call, so a bad file
cannot overwrite and wipe the stored mapping.
Expand Down
5 changes: 4 additions & 1 deletion skills/uipath-process-mining/references/querying.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
Pull numbers out of a built app. Subcommands: `info` (metadata), `run`
(aggregate group-by + metrics), `details` (raw rows), `percentile`, `rca`
(root-cause), `insights` (process insights), `layout`. All take `--stage
dev|published` (default `dev`).
dev|published` (default `dev`) — but query on **`dev`**: `--stage published` is
currently unreachable via the CLI (`UserError_InvalidOrNoIngestion`; no `uip pm`
path completes a published-stage ingestion — see
[`lifecycle-and-rbac.md`](lifecycle-and-rbac.md)).

## Start with `query info`

Expand Down
25 changes: 19 additions & 6 deletions skills/uipath-process-mining/references/transformations.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
The transformation layer is a dbt project that runs on **Snowflake**. Loaded
source tables (one per input table) feed the models that produce the process
model. `transformations list/get/create/update/apply/run/status/logs` is the ELT
editor surface.
editor surface:

| Operation | Command |
|-----------|---------|
| List the model tree | `transformations list <app>` |
| Read a file (or save locally) + its ETag | `transformations get <app> <path> [--destination <file>]` |
| Edit an existing file (ETag-guarded) | `transformations update <app> <path> --file <local> --etag '<etag>'` |
| Create a new model file | `transformations create <app> <path> --file <local>` |
| Re-run the **full** transform on loaded data | `transformations apply <app> --wait` |
| Rebuild **one** dev model + dependents | `transformations run <app> --model models/X.sql` |
| Status / logs of the last build | `transformations status <app>` · `transformations logs <app>` |

## Model set for `uipath.custom`

Expand All @@ -13,7 +23,7 @@ Four template models: **`Event_log`** (built from the source; the events table),
`Tags` and `Due_dates` are pre-registered **Case-child** tables — fill their stubs
with real rows keyed on `Case_ID` to get per-case labels (`Tag`/`Tag_type`) or
per-case SLAs (`Expected_date`/`Actual_date`/`On_time`/`Cost`) with **no add-table
needed**. See [`references/data-model.md`](references/data-model.md) for when to use
needed**. See [`data-model.md`](data-model.md) for when to use
them vs a custom table. `Event_log` builds first and independently. `models/schema/sources.yml` is
generated from the data mapping and lists every input table with **all** its
columns (mapped → TargetName, unmapped → raw source name), so multi-table custom
Expand Down Expand Up @@ -46,10 +56,13 @@ A successful run then reports `SUCCESS_WITH_WARNINGS` with repeated
- **`apply`** re-runs the **full** transform on already-loaded data — the fix-loop
verb after a transform-only failure. **Do not re-ingest** for a SQL-only change.
- **`run --model models/X.sql`** rebuilds one dev model and its dependents.
- **`create <path> --file`** adds a **new** model file (PUT without ETag);
**`update <path> --file`** edits an **existing** file (ETag-safe). `update` on a
missing path 404s — use `create`. You can also inline intermediate logic as CTEs
inside one model instead of many files.
- **`create <path> --file`** adds a **new** model file (PUT without ETag — there is no
prior version to guard); **`update <path> --file`** edits an **existing** file and
**requires `--etag`**: the `Data.ETag` the `get` you edited from returned. `update` on
a missing path 404s — use `create`. A 409/412 means someone replaced the file after
your `get`: re-`get` for the new content **and** ETag, re-apply your edit on top, then
`update` with the new `--etag`. You can also inline intermediate logic as CTEs inside
one model instead of many files.
- Use **`apply --wait`** to block to a terminal state and auto-print the dbt error.

## Snowflake / dbt notes
Expand Down
Loading
Loading