EnrichmentTask

EnrichmentTask is the durable work-item record for Well's asynchronous enrichment pipeline. Each row represents one unit of background AI or data-processing work โ€” logo resolution, AI company/people/invoice extraction, OCR, bank-sync reconciliation, provider scoring, custom column compute, field rule evaluation, or monthly invoice closure โ€” targeted at an arbitrary entity identified by the polymorphic (entity_type, entity_id) pair. Tasks are scoped to a Workspace, can be grouped into a batch_id (one Magic-button press), and support parent/subtask nesting via a self-referencing parent_task relationship. The enrichment pipeline writes and advances all lifecycle states; users and operators observe tasks read-only.

NamingValue
ObjectEnrichmentTask
Resource type (JSON:API type)enrichment_task
Collection / records rootโ€” (not a records root)
REST base/v1/enrichment-tasks
Entity classEnrichmentTask

Internal object. Not currently exposed on the public REST API. The operations below describe the intended contract.

API operations

OperationMethod & pathStatus
ListGET /v1/enrichment-tasks๐ŸŸก Planned
RetrieveGET /v1/enrichment-tasks/{id}๐ŸŸก Planned
CreatePOST /v1/enrichment-tasks๐ŸŸก Planned
UpdatePATCH /v1/enrichment-tasks/{id}๐ŸŸก Planned
DeleteDELETE /v1/enrichment-tasks/{id}๐ŸŸก Planned

Data model

Attributes

FieldTypeRequiredConstraintsAllowed valuesDescription
enrichment_task_idUUID (๐Ÿ”’ system)โœ… YesUNIQUEโ€”Public stable identifier. Auto-generated by gen_random_uuid() at insert. This is the id exposed in JSON:API responses; the internal pk is never surfaced.
entity_typestring (varchar 100)โœ… Yesmax 100 chars; NOT NULL; composite index with entity_id (enrichment_tasks_entity_idx)โ€”Polymorphic discriminator identifying the kind of entity this task targets (e.g. company, person, invoice, document, transaction). Pairs with entity_id to form the generic entity reference introduced in Migration20260323100000 to replace per-type FK columns.
entity_idstring (varchar 255)โœ… Yesmax 255 chars; NOT NULL; composite index with entity_type; partial unique index on (entity_type, entity_id, enrichment_type) WHERE status = 'pending' AND deleted_at IS NULL AND enrichment_type = 'monthly_close' enforces one active monthly_close task per target monthโ€”Public UUID of the target entity (the *_id column of the referenced row, e.g. company_id, person_id, invoice_id). Stored as a string to support multiple entity types without per-type FK constraints. Composite index with entity_type.
statusenum (๐Ÿ”’ system) โ€” native Postgres type enrichment_task_status_enumโœ… YesDEFAULT 'pending'; NOT NULLpending | processing | completed | awaiting_approval | failed | rejectedLifecycle state of the enrichment task. Default pending. Transitions are driven exclusively by the enrichment pipeline workers; users cannot write this field.
enrichment_typestring (text) โ€” backed by Postgres type enrichment_type_enum historically but stored as text on the columnโœ… YesNOT NULLlogo | ai_company | ai_people | ai_invoice | ocr_extract | reconcile | provider_score | document_reconciliation_backfill | monthly_close | monthly_close_backfill | custom_column | field_ruleIdentifies the enrichment worker that should process this task. Determines which Cloud Tasks handler is dispatched. See EnrichmentTypeEnum for the full controlled vocabulary.
inputjsonbโšช Nonullableโ€”Worker input payload. Shape is worker-specific. For monthly_close the dedup key is input->>'record_id' (month label); a JSONB expression partial index (idx_enrichment_tasks_input_record_id) covers lookups on that path within a workspace + status window.
outputjsonbโšช Nonullableโ€”Worker output payload written on task completion. Shape is worker-specific. For monthly_close_backfill, holds aggregate fan-out counts and request parameters.
target_fieldsjsonb (string[])โšช Nonullableโ€”Optional list of field names the enrichment worker should populate or re-evaluate. Used by custom_column and field_rule workers to scope work to a subset of columns.
errortextโšช Nonullableโ€”Error message or stack trace written by the worker when the task transitions to failed. Null on success.
source_channelenum โ€” native Postgres type source_channel_enumโšช Nonullablecompany_create | person_create | document_upload | ocr_completion | bank_sync | email_import | mcp_hub | cell_edit | manualRecords which product surface triggered the task. Used for attribution and observability. Added in Migration20260319120000.
input_hashtextโšช Nonullable; index enrichment_tasks_input_hash_idxโ€”Deduplication fingerprint of the input payload. Allows workers to detect duplicate enqueues with identical inputs and skip redundant work. Added in Migration20260319120000.
descriptiontextโšช Nonullableโ€”Human-readable markdown summary of the enrichment task, populated by the worker or the pipeline orchestrator for display in the UI. Added in Migration20260327100000.
batch_idUUIDโšช Nonullable; index enrichment_tasks_batch_id_idx; composite index idx_enrichment_tasks_batch_status on (batch_id, status)โ€”Groups tasks triggered by a single user action (Magic button press). All tasks sharing a batch_id were enqueued together and can be tracked collectively. Index idx_enrichment_tasks_batch_status covers hot-path batch completion detection. Added in Migration20260327100000.
created_attimestamptz (๐Ÿ”’ system)โœ… YesNOT NULL; set once on insertโ€”Row creation timestamp set by MikroORM onCreate hook. Not writable after insert.
updated_attimestamptz (๐Ÿ”’ system)โšช Nonullable; updated on every writeโ€”Last modification timestamp, maintained automatically by MikroORM onUpdate hook on every flush.
deleted_attimestamptzโšช Nonullableโ€”Soft-delete timestamp. When set the row is logically deleted and filtered out of standard queries. The partial unique index on monthly_close tasks is scoped to deleted_at IS NULL.
completed_attimestamptzโšช Nonullableโ€”Timestamp written by the worker when the task reaches completed, failed, or rejected. Distinct from updated_at to allow precise pipeline latency measurement.

Relationships

NameTypeRequiredDescription
workspaceto-one (ManyToOne)โœ… YesThe workspace that owns this enrichment task. All task queries are tenant-scoped via this relationship. deleteRule: cascade โ€” deleting a workspace removes all its tasks. FK column workspace_pk.
parent_taskto-one (ManyToOne, self-referencing)โšช NoOptional reference to a parent EnrichmentTask. Used by the Monthly Invoice Closure pipeline to link per-counterparty sub-tasks to their macro parent task. FK column parent_task_pk. deleteRule: set null โ€” deleting a parent task nullifies the FK on children without cascading deletion. Added in Migration20260327100000.
subtasksto-many (OneToMany, self-referencing)โ€”Inverse collection of child EnrichmentTask rows that reference this task as their parent_task. Populated for macro tasks in the Monthly Invoice Closure pipeline. Mapped by parent_task on the child side.

System-computed

  • enrichment_task_id โ€” auto-generated by gen_random_uuid() Postgres default at INSERT; never set by application code
  • created_at โ€” set by MikroORM onCreate hook; never writable after insert
  • updated_at โ€” set and maintained by MikroORM onUpdate hook on every flush
  • deleted_at โ€” soft-delete; set by the pipeline or admin tooling, never by user PATCH
  • status โ€” default 'pending' at creation; all state transitions (pending โ†’ processing โ†’ completed / failed / rejected / awaiting_approval) are driven exclusively by enrichment pipeline workers via Cloud Tasks handlers
  • input_hash โ€” computed by the enqueuing service as a fingerprint of the input payload for dedup detection; not computed by the database
  • batch_id โ€” assigned at enqueue time by the orchestrator when multiple tasks are triggered together (Magic button); not user-assignable
  • completed_at โ€” written by the worker on task terminal state; not set by application business logic outside the worker
  • entity_type / entity_id โ€” set at enqueue time from the triggering entity's public UUID; the generic polymorphic reference replaced per-type FK columns (company_pk, person_pk, document_pk, invoice_pk, transaction_pk) in Migration20260323100000
  • subtasks collection โ€” populated by ORM from the OneToMany inverse of parent_task; not a stored column

Example

{
  "data": {
    "type": "enrichment_task",
    "id": "c3a1f9e2-4b7d-4e2a-8f1c-9a0b3d5e7f21",
    "attributes": {
      "enrichment_task_id": "c3a1f9e2-4b7d-4e2a-8f1c-9a0b3d5e7f21",
      "entity_type": "company",
      "entity_id": "a1b2c3d4-0000-0000-0000-000000000001",
      "status": "completed",
      "enrichment_type": "ai_company",
      "input": { "company_id": "a1b2c3d4-0000-0000-0000-000000000001" },
      "output": { "domain": "acme.com", "registered_name": "Acme Corp" },
      "target_fields": ["domain", "registered_name"],
      "error": null,
      "source_channel": "company_create",
      "input_hash": "sha256:abc123",
      "description": null,
      "batch_id": "f7e6d5c4-3b2a-1908-7654-321098fedcba",
      "created_at": "2026-05-15T10:30:00.000Z",
      "updated_at": "2026-05-15T10:31:05.000Z",
      "deleted_at": null,
      "completed_at": "2026-05-15T10:31:05.000Z"
    },
    "relationships": {
      "workspace": {
        "data": { "type": "workspace", "id": "ws-uuid-0001" }
      },
      "parent_task": {
        "data": null
      }
    }
  }
}
Source: apps/api/src/database/entities/EnrichmentTask.ts ยท domain: ingestion ยท tier: Infrastructure