Portal Community

State Transitions as Machine Learning Labels

Every entity that flows through a workflow produces a sequence of status transitions. That sequence is a behavioral label — a description of how the entity was processed.

Transition Sequence Interpretation ML Label
None → Processing → Processed Clean single-pass processing label: "clean"
None → Processing → Failed → Processing → Processed One retry before success label: "retry_1"
None → Processing → Failed → Failed → Failed Three failures, no recovery label: "persistent_failure"
None → Processing → [timeout] → Failed → Processing → Processed Lock timeout then recovery label: "timeout_recovery"

These labels emerge naturally from the state record history. No separate labeling pipeline required. Query the Process_StateLogs table for an entity type, walk the status chains, and you have a labeled dataset ready for model training.

-- Generate labeled training data for failure prediction
SELECT
    sl.ItemEntityKey    AS entity_type,
    sl.ItemKey          AS entity_id,
    COUNT(*)            AS total_attempts,
    SUM(CASE WHEN sl.Status = 3 THEN 1 ELSE 0 END) AS failure_count,
    SUM(CASE WHEN sl.Status = 1 THEN 1 ELSE 0 END) AS success_count,
    MAX(sl.Created)     AS last_attempt_at,
    DATEDIFF(second,
        MIN(sl.Created),
        MAX(CASE WHEN sl.Status = 1 THEN sl.Created END)) AS time_to_success_seconds,
    CASE
        WHEN MAX(sl.Status) = 1 AND COUNT(*) = 1 THEN 'clean'
        WHEN MAX(sl.Status) = 1 AND COUNT(*) > 1 THEN 'retry_success'
        WHEN MAX(sl.Status) IN (2,3) THEN 'unresolved_failure'
    END AS processing_label
FROM Process_StateLogs sl
WHERE sl.ItemEntityKey = 'invoice'
  AND sl.Deleted = 0
GROUP BY sl.ItemEntityKey, sl.ItemKey
ORDER BY last_attempt_at DESC;

Failure Prediction

With labeled training data from state records, you can train a model to predict which entities are likely to fail before processing begins. The model watches incoming entity characteristics (size, type, source, time of day) and predicts the failure probability.

If the predicted failure probability exceeds a threshold, the node can:

// Model score injected via upstream AI node
var failureProbability = ctx.Input.GetDouble("inferenceScore");

if (failureProbability > 0.85)
{
    // High risk — acquire lock with extra caution, log to monitoring
    _logger.Warning("High failure probability {Score:P0} for {Entity}", failureProbability, entityId);
    // Use extended lock window for high-risk entities (passed via options)
}

bool locked = await DataStateMachine.TryAcquireLockAsync(entityId, entityKey, ct: ct);

Data Enrichment Pipeline

DataStateMachine provides native state management for enrichment workflows. Each enrichment operation is an independent tracked step. The SubItemKey distinguishes enrichment types. The Data field stores the enrichment result. The pipeline can be partially replayed — only failed enrichments need to re-run.

Customer Entity (customerId = ItemEntityKey)
    │
    ├── SubItemKey: "address"     → AddressValidationNode → Processed (normalized address in Data)
    ├── SubItemKey: "credit"      → CreditScoreNode       → Processed (score + bureau in Data)
    ├── SubItemKey: "social"      → SocialGraphNode       → Failed    (API timeout — retry pending)
    ├── SubItemKey: "compliance"  → SanctionScreenNode    → Processed (clear — no matches)
    └── SubItemKey: "industry"    → IndustryClassifier    → Processed (SIC code + confidence)

Query: which enrichments succeeded?
→ SELECT SubItemKey FROM Process_StateLogs
  WHERE ItemEntityKey = 'CUST-1001' AND Status = 1  -- Processed

Data Enhancement Workflows

Data enhancement differs from enrichment — enrichment adds new data (credit score, address validation), enhancement improves existing data (normalise a messy address, standardise a phone number, fix encoding). DataStateMachine handles both identically.

Enhancement nodes use UpsertAsync: each enhancement run creates a new record for the same entity. The SupersededByID chain shows the enhancement history — original value → first pass → second pass → current. You can reconstruct the pre-enhancement data at any point.

// Before: raw customer name "SMITH, JOHN J."
// Enhancement: normalize to "John J. Smith"
await DataStateMachine.UpsertAsync(
    itemKey:       customerId,
    itemEntityKey: "customer-name",
    subItemKey:    "name-normalisation",
    data: JsonSerializer.Serialize(new {
        original:   rawName,
        normalized: normalizedName,
        algorithm:  "Name.NET v2.1",
        confidence: 0.97
    }),
    ct: ct);
// → Creates new row, marks previous row Superseded
// → Full enhancement history preserved in append-only chain

Data Tracking Across Runs

Tracking asks: "What happened to entity X across ALL processing runs, ever?" The append-only state records answer this question directly.

Tracking Question Query Approach
Has this entity been processed before? ExistsAsync — O(1) check
How many times was it processed? COUNT(*) WHERE ItemKey = ? AND Status = 1
When was it first and last processed? MIN/MAX(Created) WHERE Status = 1
What data was produced each time? SELECT Data ORDER BY Created where SupersededByID IS NULL per run
Which processing runs failed? WHERE Status = 3 — full failure history
What is the complete processing timeline? Walk SupersededByID chain from latest row

Feature Store Integration

State records can feed a feature store for ML model serving. The processing history of an entity (how many times processed, average processing time, failure rate, last enrichment date) becomes a feature vector that is continuously updated as new records are written.

// Feature extraction service — runs asynchronously on state record writes
public class ProcessingHistoryFeatureExtractor
{
    public async Task<ProcessingFeatureVector> ExtractAsync(
        string entityId, string entityType, CancellationToken ct)
    {
        var records = await _stateLogRepo.QueryAsync(
            itemKey: entityId, itemEntityKey: entityType, ct: ct);

        return new ProcessingFeatureVector
        {
            TotalAttempts    = records.Count,
            SuccessCount     = records.Count(r => r.Status == IdempotencyStatus.Processed),
            FailureCount     = records.Count(r => r.Status == IdempotencyStatus.Failed),
            FailureRate      = records.Count > 0
                ? (double)records.Count(r => r.Status == IdempotencyStatus.Failed) / records.Count
                : 0,
            DaysSinceFirst   = (DateTime.UtcNow - records.Min(r => r.Created)).TotalDays,
            DaysSinceLast    = (DateTime.UtcNow - records.Max(r => r.Created)).TotalDays,
            EnrichmentCount  = records.Count(r => !string.IsNullOrEmpty(r.SubItemKey))
        };
    }
}

Summary — DataStateMachine as a Data Platform

Capability How DataStateMachine Enables It
Idempotency TryAcquireLock + ExistsAsync — exactly-once processing
Deduplication ExistsAsync at node level — skip before any work is done
Data Lineage Every state row = lineage record with entity, scope, execution, timestamp
ML Training Labels Status transition chains = behavioral labels per entity
Failure Prediction Historical failure patterns → predictive features
Data Enrichment SubItemKey per enrichment type; partial replay for failed enrichments
Data Enhancement UpsertAsync = versioned enhancement history with full rollback
Data Tracking Append-only records answer any "what happened to X?" question
Feature Engineering State record aggregates = live feature vectors per entity
Event Sourcing SupersededByID chain = ordered event log per entity
The DataStateMachine was designed as an idempotency engine. The data science and enrichment capabilities emerged from the structural properties of the state records — append-only, keyed, scoped, timestamped, and queryable. No additional infrastructure is required to unlock these patterns.