jobRepository->findById(Uuid::fromString($message->jobId)); if (null === $job) { return; } $missing = $this->findMissingFields($message); if ([] === $missing) { $this->bus->dispatch(new DraftArticleMessage( jobId: $message->jobId, articleTypeId: $message->articleTypeId, attributes: $message->attributes, condition: 'good', inventoryNumber: null, serialNumber: null, )); return; } if ($job->getAttemptCount() >= self::MAX_ATTEMPTS) { $job->markNeedsReview('Validation failed after '.self::MAX_ATTEMPTS.' attempts. Missing: '.implode(', ', $missing)); $this->jobRepository->save($job); return; } $job->incrementAttempt($missing); $this->jobRepository->save($job); $this->bus->dispatch(new JsonCodingMessage( jobId: $message->jobId, articleTypeId: $message->articleTypeId, specsText: $message->specsText, missingFields: $missing, )); } /** @return list attribute names that are required but not present */ private function findMissingFields(ValidationMessage $message): array { // Empty attributes always means retry if ([] === $message->attributes) { return ['(no attributes returned by LLM)']; } if (null === $this->articleTypeRepository) { return []; } $articleType = $this->articleTypeRepository->findById(Uuid::fromString($message->articleTypeId)); if (null === $articleType) { return []; } $missing = []; foreach ($articleType->getRequiredAttributeDefinitions() as $def) { if (!\array_key_exists($def->getId()->toRfc4122(), $message->attributes)) { $missing[] = $def->getName(); } } return $missing; } }