Init
This commit is contained in:
198
app/Temporal/DataEnrichment/DataEnrichmentWorkflow.php
Normal file
198
app/Temporal/DataEnrichment/DataEnrichmentWorkflow.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace App\Temporal\DataEnrichment;
|
||||
|
||||
use Carbon\CarbonInterval;
|
||||
use Temporal\Activity\ActivityOptions;
|
||||
use Temporal\Common\RetryOptions;
|
||||
use Temporal\Workflow;
|
||||
|
||||
class DataEnrichmentWorkflow implements DataEnrichmentWorkflowInterface
|
||||
{
|
||||
private string $status = 'pending';
|
||||
private int $totalRecords = 0;
|
||||
private int $enrichedRecords = 0;
|
||||
private int $failedRecords = 0;
|
||||
private array $apiStats = [
|
||||
'geocode' => ['calls' => 0, 'retries' => 0, 'rateLimits' => 0],
|
||||
'email' => ['calls' => 0, 'retries' => 0, 'rateLimits' => 0],
|
||||
'credit' => ['calls' => 0, 'retries' => 0, 'rateLimits' => 0],
|
||||
];
|
||||
|
||||
/** @var DataEnrichmentActivityInterface - geocoding: patient retries */
|
||||
private $geocodeStub;
|
||||
|
||||
/** @var DataEnrichmentActivityInterface - email: fast fail */
|
||||
private $emailStub;
|
||||
|
||||
/** @var DataEnrichmentActivityInterface - credit: medium retries */
|
||||
private $creditStub;
|
||||
|
||||
/** @var DataEnrichmentActivityInterface - save: minimal retries */
|
||||
private $saveStub;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Geocoding: 5 attempts, 3s initial, 1.5 backoff (patient)
|
||||
$this->geocodeStub = Workflow::newActivityStub(
|
||||
DataEnrichmentActivityInterface::class,
|
||||
ActivityOptions::new()
|
||||
->withStartToCloseTimeout(CarbonInterval::minutes(2))
|
||||
->withRetryOptions(
|
||||
RetryOptions::new()
|
||||
->withMaximumAttempts(5)
|
||||
->withInitialInterval(CarbonInterval::seconds(3))
|
||||
->withBackoffCoefficient(1.5)
|
||||
)
|
||||
);
|
||||
|
||||
// Email validation: 2 attempts, 500ms initial, 2.0 backoff (fast fail)
|
||||
$this->emailStub = Workflow::newActivityStub(
|
||||
DataEnrichmentActivityInterface::class,
|
||||
ActivityOptions::new()
|
||||
->withStartToCloseTimeout(CarbonInterval::seconds(30))
|
||||
->withRetryOptions(
|
||||
RetryOptions::new()
|
||||
->withMaximumAttempts(2)
|
||||
->withInitialInterval(CarbonInterval::milliseconds(500))
|
||||
->withBackoffCoefficient(2.0)
|
||||
)
|
||||
);
|
||||
|
||||
// Credit scoring: 3 attempts, 2s initial, 2.0 backoff (medium)
|
||||
$this->creditStub = Workflow::newActivityStub(
|
||||
DataEnrichmentActivityInterface::class,
|
||||
ActivityOptions::new()
|
||||
->withStartToCloseTimeout(CarbonInterval::minutes(1))
|
||||
->withRetryOptions(
|
||||
RetryOptions::new()
|
||||
->withMaximumAttempts(3)
|
||||
->withInitialInterval(CarbonInterval::seconds(2))
|
||||
->withBackoffCoefficient(2.0)
|
||||
)
|
||||
);
|
||||
|
||||
// Save: just 1 attempt
|
||||
$this->saveStub = Workflow::newActivityStub(
|
||||
DataEnrichmentActivityInterface::class,
|
||||
ActivityOptions::new()
|
||||
->withStartToCloseTimeout(CarbonInterval::seconds(15))
|
||||
->withRetryOptions(
|
||||
RetryOptions::new()->withMaximumAttempts(2)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function enrich(array $recordIds, array $simulationConfig = []): \Generator
|
||||
{
|
||||
$this->totalRecords = count($recordIds);
|
||||
$this->status = 'enriching';
|
||||
|
||||
try {
|
||||
$batchSize = 5;
|
||||
$batches = array_chunk($recordIds, $batchSize);
|
||||
|
||||
foreach ($batches as $batch) {
|
||||
foreach ($batch as $recordId) {
|
||||
// Run 3 enrichment APIs in parallel per record
|
||||
$geocodePromise = Workflow::async(function () use ($recordId, $simulationConfig) {
|
||||
try {
|
||||
$result = yield $this->geocodeStub->geocodeAddress(
|
||||
['id' => $recordId],
|
||||
$simulationConfig
|
||||
);
|
||||
return ['success' => true, 'data' => (array) $result];
|
||||
} catch (\Throwable $e) {
|
||||
return ['success' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
});
|
||||
|
||||
$emailPromise = Workflow::async(function () use ($recordId, $simulationConfig) {
|
||||
try {
|
||||
$result = yield $this->emailStub->validateEmail(
|
||||
['id' => $recordId, 'customer_email' => "customer{$recordId}@example.com"],
|
||||
$simulationConfig
|
||||
);
|
||||
return ['success' => true, 'data' => (array) $result];
|
||||
} catch (\Throwable $e) {
|
||||
return ['success' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
});
|
||||
|
||||
$creditPromise = Workflow::async(function () use ($recordId, $simulationConfig) {
|
||||
try {
|
||||
$result = yield $this->creditStub->calculateCreditScore(
|
||||
['id' => $recordId],
|
||||
$simulationConfig
|
||||
);
|
||||
return ['success' => true, 'data' => (array) $result];
|
||||
} catch (\Throwable $e) {
|
||||
return ['success' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for all 3 to complete
|
||||
$geocodeResult = (array) (yield $geocodePromise);
|
||||
$emailResult = (array) (yield $emailPromise);
|
||||
$creditResult = (array) (yield $creditPromise);
|
||||
|
||||
// Track per-API stats
|
||||
$this->trackApiStat('geocode', $geocodeResult);
|
||||
$this->trackApiStat('email', $emailResult);
|
||||
$this->trackApiStat('credit', $creditResult);
|
||||
|
||||
// Save enriched data
|
||||
$enrichments = [
|
||||
'geocode' => $geocodeResult['success'] ? $geocodeResult['data'] : null,
|
||||
'email' => $emailResult['success'] ? $emailResult['data'] : null,
|
||||
'credit' => $creditResult['success'] ? $creditResult['data'] : null,
|
||||
];
|
||||
|
||||
$anySuccess = $geocodeResult['success'] || $emailResult['success'] || $creditResult['success'];
|
||||
|
||||
if ($anySuccess) {
|
||||
yield $this->saveStub->saveEnrichedRecord($recordId, $enrichments);
|
||||
$this->enrichedRecords++;
|
||||
} else {
|
||||
$this->failedRecords++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->status = 'failed';
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->status = 'completed';
|
||||
|
||||
return [
|
||||
'status' => 'completed',
|
||||
'totalRecords' => $this->totalRecords,
|
||||
'enrichedRecords' => $this->enrichedRecords,
|
||||
'failedRecords' => $this->failedRecords,
|
||||
'apiStats' => $this->apiStats,
|
||||
];
|
||||
}
|
||||
|
||||
private function trackApiStat(string $api, array $result): void
|
||||
{
|
||||
$this->apiStats[$api]['calls']++;
|
||||
if ($result['success'] && isset($result['data']['attempt'])) {
|
||||
$attempt = $result['data']['attempt'];
|
||||
if ($attempt > 1) {
|
||||
$this->apiStats[$api]['retries'] += ($attempt - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getProgress(): array
|
||||
{
|
||||
return [
|
||||
'status' => $this->status,
|
||||
'totalRecords' => $this->totalRecords,
|
||||
'enrichedRecords' => $this->enrichedRecords,
|
||||
'failedRecords' => $this->failedRecords,
|
||||
'apiStats' => $this->apiStats,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user