# Cheat sheet

Everything starts from a `nominal.Client`, and each object hands you the next.

## Client

`c = nominal.Client.fromProfile()`

| Action | MATLAB |
| --- | --- |
| Connect from a stored profile | `nominal.Client.fromProfile()` |
| Connect with an API key | `nominal.Client.fromToken("<api-key>")` |
| Either, whichever is available | `nominal.Client.connect()` |
| Who am I (also a credential check) | `c.whoAmI()` |
| Search assets by name | `c.assets("<search text>")` |
| Search datasets by name | `c.datasets("<search text>")` |
| Search runs | `c.runs("<search text>", Labels=…, Properties=…, StartAfter=t0)` |
| List *every* asset (slow, unpaginated) | `c.assets()` |
| List *every* dataset (slow, unpaginated) | `c.datasets()` |
| Get an asset by RID | `c.assetByRid(rid)` |
| Get an asset by name, creating if absent | `c.getOrCreateAsset(name)` |
| Create an asset, even if the name exists | `c.createAsset(name)` |
| Get a dataset by RID | `c.datasetByRid(rid)` |
| Create a dataset attached to no asset | `c.createDataset(name)` |
| Get a run by RID | `c.runByRid(rid)` |
| Create a run from tagged data, no asset | `c.createRun(name, ds, Tags=…, StartTime=…, EndTime=…)` |
| Create an event | `c.createEvent(assetRids, name)` |
| …with properties | `c.createEvent(rids, name, Properties=struct(status="pass"))` |
| Upload and ingest a CSV or Parquet file | `c.ingest(path, TimestampColumn=…)` |
| …tagging every point in it | `c.ingest(path, …, Tags=struct(UUT="A"))` |
| Run a SQL query | `c.query(sql)` |
| SQL result too big for memory | `c.queryExportUrl(sql)` |
| Workspace / base URL | `c.WorkspaceRid`, `c.BaseUrl` |

## Asset

`a = c.getOrCreateAsset("<asset name>")`

| Action | MATLAB |
| --- | --- |
| Identity and metadata | `a.Rid`, `a.Name`, `a.Description`, `a.Url`, `a.Labels` |
| Read one property | `a.property("<key>")` |
| All properties, as a `containers.Map` | `a.Properties` |
| Set or remove one, keeping the rest | `a = a.setProperty("<key>", "<value>")`, `a.removeProperty("<key>")` |
| Everything attached to it, with tag filters | `a.datasources()` |
| …re-read from Nominal | `a.datasources(Refresh=true)` |
| Read a channel as the asset sees it | `a.fetch("<channel>", t0, t1)`, `a.fetch(…, RefName="<ref name>")` |
| A dataset already on it, by name | `a.getAttachedDataset(name)` |
| Attach a dataset you already have | `a.addDataset(ds)`, `a.addDataset(ds, "<ref name>")` |
| …only the series with these tags | `a.addDataset(ds, Tags=struct(UUT="A"))` |
| …a tag key that isn’t a field name | `a.addDataset(ds, Tags=dictionary("test-stand", "3"))` |
| Rename a reference name | `a.renameRefName("<old>", "<new>")` |
| Detach a data source | `a.removeDataset("<ref name>")` |
| Ensure it has a dataset of that name | `a.getOrCreateDataset(name)` |
| …without adopting an existing one | `a.getOrCreateDataset(name, AttachExisting=false)` |
| Start a run on it | `a.run(name)`, `a.run(name, startTime)` |
| Change metadata | `a.update(Name=…, Description=…, Labels=…, Properties=…)` |

An asset object is a **snapshot** taken when it was fetched. `update()` returns a new object
instead of changing the one you have. `datasources()` asks Nominal once and reuses the
answer until you pass `Refresh=true`.

Dataset names are not unique in Nominal, so `getOrCreateDataset` resolves in three steps and
prints which one it took:

1. A dataset of that name already on the asset is returned.
2. Otherwise a dataset of that exact name elsewhere in the workspace is **attached** to the
   asset and returned.
3. Otherwise one is created.

Step 2 is how “the dataset for serial 12345678” finds the one a colleague already made. Because
it changes your asset based on a name match, you can turn it off with `AttachExisting=false`.
If several datasets share the name, it errors and lists their RIDs. Attach the one you meant
with `a.addDataset(c.datasetByRid(rid))`.

`refName` is the dataset’s name within the asset. It is only used when attaching, and you can
leave it out: a free one is chosen (`"default"` on an asset with none, otherwise the dataset’s
own name). Supply one when an asset has two sources measuring the same thing, such as a CAN
log and a test rig both reporting `engine temp`, and you want to tell them apart.

An asset holds a dataset once per tag filter. Attaching the same dataset again with the same
`Tags` moves it to the new `refName`. Attaching it with different `Tags` adds it alongside.

## Dataset

`ds = a.getOrCreateDataset("<dataset name>")`

| Action | MATLAB |
| --- | --- |
| Identity and metadata | `ds.Rid`, `ds.Name`, `ds.Description`, `ds.Labels` |
| Read one property | `ds.property("<key>")` |
| All properties, as a `containers.Map` | `ds.Properties` |
| Set or remove one, keeping the rest | `ds = ds.setProperty("<key>", "<value>")`, `ds.removeProperty("<key>")` |
| What channels are in it | `ds.channels()` |
| Which assets hold it | `ds.assets()` |
| Open a live stream | `ds.stream()` |
| Write a block already in memory | `ds.write(channels, t, V)` |
| …tagging every point | `ds.write(channels, t, V, Tags=struct(UUT="A"))` |
| Read one channel back | `ds.fetch("<channel>", t0, t1)` |
| Read it decimated, for plotting | `ds.fetch("<channel>", t0, t1, Buckets=2000)` |
| …one tag set, when it has several | `ds.fetch("<channel>", t0, t1, Tags=struct(UUT="A"))` |
| Export channels to a file | `ds.export("out.mat", channels, t0, t1)` |
| Export to a download link instead | `ds.exportUrl(channels, t0, t1)` |
| Read one channel’s units | `ds.channelMetadata("<channel>")` |
| Declare a channel’s units | `ds.setChannelMetadata("rpm", "double", Unit="1/min")` |
| Change metadata | `ds.update(Name=…, Labels=…, Properties=…)` |

## Run

`r = a.run("burn-12")`

| Action | MATLAB |
| --- | --- |
| Identity and metadata | `r.Rid`, `r.Name`, `r.Description`, `r.Url`, `r.Number`, `r.Labels` |
| Timing | `r.StartTime`, `r.EndTime` |
| Assets it belongs to | `r.AssetRids` |
| Put it on another asset too | `r = r.addAsset(dut)` |
| Take it off one | `r = r.removeAsset(dut)` |
| Read one property | `r.property("<key>")` |
| All properties, as a `containers.Map` | `r.Properties` |
| Set or remove one, keeping the rest | `r = r.setProperty("<key>", "<value>")`, `r.removeProperty("<key>")` |
| Close it | `r.finish()`, `r.finish(endTime)` |
| Read a channel over the run’s window | `r.fetch("<channel>")`, `r.fetch("<channel>", RefName="<ref name>")` |
| Change metadata | `r.update(Name=…, Labels=…, Properties=…)` |

## Stream and Channel

`s = ds.stream()`

| Action | MATLAB |
| --- | --- |
| Get a write address for a channel | `s.channel("rpm")` |
| Push an N-by-C block across channels | `s.push(channels, t, V)` |
| Push one channel | `ch.push(t, v)` |
| Tag every later point on an address | `ch.tag("bank", "1")` |
| Flush and close | `delete(s)` |

## Event

`e = c.createEvent(rids, "overspeed")`

| Action | MATLAB |
| --- | --- |
| Read it back | `e.Rid`, `e.Name`, `e.Type`, `e.Timestamp`, `e.Duration`, `e.AssetRids` |
| Read one property | `e.property("<key>")` |
| All properties, as a `containers.Map` | `e.Properties` |

## IngestJob

`job = c.ingest("<file>.csv", …)`

| Action | MATLAB |
| --- | --- |
| Where the data is landing | `job.DatasetRid` |
| Check progress | `job.status()` |
| Block until it finishes | `job.wait()` |

## Free functions

| Action | MATLAB |
| --- | --- |
| Current time, full nanosecond precision | `nominal.now()` |
| datetime → int64 nanoseconds | `nominal.toNanos(dt)`, `nominal.toNanosVector(dt)` |
| int64 nanoseconds → UTC datetime | `nominal.fromNanos(nanos)` |
| Shut the toolbox down, closing every connection | `nominal.shutdown()` |
