Skip to content

Preparation

Every upload is prepared after it lands. The prepare run sorts the takes into tasks (task discovery) and gives each task a Task: a published one it matched, or a draft. It also splits takes into pieces and reads the task names the dataset’s own metadata gives. Everything it does is a proposal or a flag: nothing is removed and no Task is published. client.preparation reads and changes what the dataset page’s Tasks tab shows.

Every method takes a dataset id, its full name (accounts/{account}/datasets/{dataset}) or the preparation’s name (.../preparation). A merged dataset or a mixture has no run of its own: its uploads are prepared one by one, so get answers state NONE. Nothing here starts a run. client.preparation is in qualia-sdk 0.8.0rc12 and later; on 0.8.0rc11 it raises AttributeError.

from qualia import Qualia
client = Qualia()
prep = client.preparation.get(dataset_id)
print(prep.state, prep.run_id, prep.error)
for warning in prep.warnings:
print("warning:", warning)
found = prep.discovery # None on runs from before discovery
for task in found.tasks if found else []:
# task_id: the matched Task or the draft ("" when no draft was made);
# linked_tasks, below, says which
print(task.name, task.episode_count, task.health_flag, task.task_id)
print(" references:", task.reference_episode_ids)
names = prep.task_strings # the dataset's own task names
for c in names.candidates if names else []:
print(c.key, c.text, c.episode_count, c.blocked_reason, c.confirmation)

state is NONE, QUEUED, RUNNING, SUCCEEDED, FAILED or SKIPPED. discovery.status is RUNNING, DONE, FAILED or FALLBACK (one task for the whole dataset). A run discovery did not split has its one Task in task_match instead.

A found task with a task_id is a Task you can read, edit and publish with client.tasks. One without a Task can become one:

task = client.tasks.create(
found.tasks[0].name[:32],
references=[
{"role": "SUCCESS", "episode": f"{dataset_name}/episodes/{episode}"}
for episode in found.tasks[0].reference_episode_ids[:3]
],
)
client.datasets.update(dataset_name, linked_task=task.name)

Preparation links each found task’s episodes to a Task: one that already existed in the account, when it matches, else a draft it writes. The preparation names the Task (task_id) without saying which it is. Only the one Task of a run that discovery did not split carries task_match.matched. linked_tasks reads each named Task’s published revisions once and tells them apart where a publication predates queueing or the server explicitly matched a published revision. Otherwise a published Task’s origin is UNKNOWN:

prep = client.preparation.get(dataset_id)
for linked in client.preparation.linked_tasks(dataset_id, preparation=prep):
print(linked.display_name, linked.origin, linked.revision, linked.publish_time)
if linked.origin == "EXISTING":
print(" episodes linked to it now:", linked.linked_episode_count)
print(" revisions checks use:", linked.linked_revisions)
print(" their task labels:", linked.labels)
originThe Task
EXISTINGHad a published revision before the prepare run was queued, or the server explicitly matched a published revision. Checks use the stored link revisions
NEW_DRAFTHas no published revision: a draft a prepare run wrote for this upload (this run, or an earlier run of the upload whose draft was kept; the two read the same)
MISSINGCannot be read any more: deleted, or out of the key’s reach
UNKNOWNThe available data cannot establish the origin: no known publication predates queueing, or timestamps are missing

The API has no stable completion timestamp. update_time is the last change and can advance after success when calibration finishes. It cannot distinguish a draft published after preparation from a Task published during preparation. DRAFT_PUBLISHED and PUBLISHED_DURING_PREPARATION remain accepted origin values for compatibility, but are no longer inferred from that mutable timestamp.

revision is the Task’s newest published revision (0 for none) and publish_time when that was published; episode_count is how many takes the run sorted into the task. For an EXISTING Task, linked_tasks reads every page of its current episode links: linked_episode_count is how many are linked to it now (they may have been linked elsewhere since the run), and labels the task labels they carry with their counts, including the empty label. linked_revisions gives (revision, episode_count) pairs from the stored links, including inherited links; 0 means a draft link. Checks use those revisions even when revision names a newer publication. To judge those episodes against a Task of your own while keeping every label, including an empty one, use link_task:

cursor = None
while True:
page = client.episodes.list(
dataset_id, filter=f'linked_task = "{linked.task}"', cursor=cursor
)
for episode in page.items:
client.episodes.link_task(episode.name, my_task.name)
if not page.page_info.has_next:
break
cursor = page.page_info.end_cursor

linked_tasks is in qualia-sdk 0.8.0rc14 and later; on 0.8.0rc13 it raises AttributeError.

Preparation starts by itself after an upload and takes minutes. Automatic checks depend on policy and upload eligibility. Free quality checks default on; paid video (VLM) checks default off and require opt-in. An eligible paid check may run after preparation if the current policy still enables it and admission succeeds.

wait covers preparation only. It reads the preparation every interval seconds until it has ended, and returns that reading:

dataset_id = client.data.upload("./takes", "box-pick")["dataset_id"]
prep = client.preparation.wait(
dataset_id,
timeout=1800, # seconds; the default is 3600
on_progress=lambda p: print(p.state, p.discovery.status if p.discovery else ""),
)
if prep.state == "FAILED":
raise SystemExit(prep.error)
for warning in prep.warnings: # for example, task discovery fell back
print("warning:", warning)
What get readsWhat wait does
NONE: no prepare runReads again for up to 30 seconds, then returns it. An upload’s run is queued as the upload settles; a merged dataset, a mixture and a deployment that does not prepare uploads never get one
QUEUED or RUNNINGReads again
QUEUED or RUNNING, discovery.status already FAILED or FALLBACKReads again: the run goes on without task discovery. on_progress gets this reading at once, and a timeout’s message carries discovery.reason
SUCCEEDEDReturns it. With discovery.status FAILED or FALLBACK it ended with a fallback: warnings and discovery.reason say why, so check the tasks before publishing one
FAILEDReturns it, never raises: error says why
SKIPPEDReturns it: there was nothing to prepare
An ended state, discovery.status RUNNINGReads again: task discovery is running again, after confirm_task_strings

on_progress is called with each reading whose state, discovery.status or discovery.reason differs from the one before, the first reading included. After timeout seconds wait raises TimeoutError, whose message says what the last reading showed: the state, task discovery’s status and reason, the run and when it last changed. Timing out stops nothing: the run goes on, and get or another wait reads it later. A failed read that is transient (a 5xx, a timeout) is retried, as in checks.wait.

wait is in qualia-sdk 0.8.0rc14 and later; on 0.8.0rc13 it raises AttributeError.

When task_strings.confirmable is true, a person decides which of the dataset’s own task names to use as tasks. The set you send replaces the decisions on the run’s names: a name not listed is cleared, and [] clears them all. Send one choice per usable name, as the Tasks tab does: the ones to use confirmed=True with a Task name of at most 32 characters, the rest confirmed=False.

prep = client.preparation.get(dataset_id)
usable = [
c for c in prep.task_strings.candidates
if not c.blocked and not c.looks_like_code
]
client.preparation.confirm_task_strings(
dataset_id,
prep.run_id,
[{"key": c.key, "display_name": "Fold the towel", "confirmed": True}
for c in usable],
)

Confirmed names start task discovery again, which drafts a Task for each. Read the preparation again to see them, or wait for the re-run to end.

RefusalWhy
409 ABORTEDA newer run replaced the names you read: read again and decide again
400 FAILED_PRECONDITIONA blocked name (outcome words, a code, already a task label), or no names
400 INVALID_ARGUMENTA key the run does not have, a key twice, or a name over 32 characters
403 PERMISSION_DENIEDA workspace’s service key: a person decides on task names

A personal API key decides as the person who made it, and the decision records the key.

The CLI has the same: qualia dataset preparation (--wait waits; its table tells each Task apart) and qualia dataset confirm-task-names.