From 525424a6a10f8380b3700c210787d03e538bbb7f Mon Sep 17 00:00:00 2001 From: Simon Kuehn Date: Mon, 18 May 2026 10:09:26 +0000 Subject: [PATCH] feat: extract modelName and modelNumber separately in vision pipeline Vision prompt now distinguishes MODEL_NAME (human-readable product name, e.g. "ThinkPad T490s") from MODEL_NUMBER (part/product code, e.g. "20NXS0BA00"). Both fields flow through the pipeline and are written to the article. Specs research uses model number as search subject when available, falling back to model name. Co-Authored-By: Claude Sonnet 4.6 --- src/Infrastructure/AI/Agent/OllamaVisionAgent.php | 5 +++-- src/Infrastructure/AI/PromptTemplateService.php | 9 ++++++--- .../Messenger/Handler/DraftArticleHandler.php | 7 +++++-- .../Messenger/Handler/PhotoUploadHandler.php | 10 ++++++---- .../Messenger/Handler/SpecsResearchHandler.php | 4 +++- .../Messenger/Message/SpecsResearchMessage.php | 1 + 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Infrastructure/AI/Agent/OllamaVisionAgent.php b/src/Infrastructure/AI/Agent/OllamaVisionAgent.php index 2f483d0..3d6dd5d 100644 --- a/src/Infrastructure/AI/Agent/OllamaVisionAgent.php +++ b/src/Infrastructure/AI/Agent/OllamaVisionAgent.php @@ -16,7 +16,7 @@ final class OllamaVisionAgent ) { } - /** @return array{manufacturer: string, model: string, serial: string} */ + /** @return array{manufacturer: string, modelName: string, modelNumber: string, serial: string} */ public function analyze(string $imagePath): array { $prompt = $this->prompts->render('vision_analyze'); @@ -25,7 +25,8 @@ final class OllamaVisionAgent return [ 'manufacturer' => $this->extractField($response, 'MANUFACTURER'), - 'model' => $this->extractField($response, 'MODEL'), + 'modelName' => $this->extractField($response, 'MODEL_NAME'), + 'modelNumber' => $this->extractField($response, 'MODEL_NUMBER'), 'serial' => $this->extractField($response, 'SERIAL'), ]; } diff --git a/src/Infrastructure/AI/PromptTemplateService.php b/src/Infrastructure/AI/PromptTemplateService.php index 5fadb06..a4dfb7e 100644 --- a/src/Infrastructure/AI/PromptTemplateService.php +++ b/src/Infrastructure/AI/PromptTemplateService.php @@ -43,11 +43,14 @@ PROMPT, 'vision_analyze' => <<<'PROMPT' Look at this nameplate/label photo of IT hardware. -Extract the manufacturer (brand), model number/designation, and serial number visible on the label. -Do not guess or add information not on the label. +Extract the manufacturer, model name, model number, and serial number visible on the label. +- MODEL_NAME is the human-readable product name (e.g. "ThinkPad T490s", "EliteBook 840 G6", "Galaxy S24 Ultra"). +- MODEL_NUMBER is the part/product code or designation (e.g. "20NXS0BA00", "5SS67UC", "SM-S928B"). Often printed as "Model No.", "Part No.", "Type", or "P/N". +Do not guess or add information not visible on the label. Respond in exactly this format (use empty string if not visible): MANUFACTURER: -MODEL: +MODEL_NAME: +MODEL_NUMBER: SERIAL: PROMPT, diff --git a/src/Infrastructure/Messenger/Handler/DraftArticleHandler.php b/src/Infrastructure/Messenger/Handler/DraftArticleHandler.php index b4840c6..fa0b87e 100644 --- a/src/Infrastructure/Messenger/Handler/DraftArticleHandler.php +++ b/src/Infrastructure/Messenger/Handler/DraftArticleHandler.php @@ -67,8 +67,11 @@ final class DraftArticleHandler if (isset($vision['manufacturer']) && '' !== $vision['manufacturer']) { $article->setManufacturer((string) $vision['manufacturer']); } - if (isset($vision['model']) && '' !== $vision['model']) { - $article->setModelNumber((string) $vision['model']); + if (isset($vision['modelNumber']) && '' !== $vision['modelNumber']) { + $article->setModelNumber((string) $vision['modelNumber']); + } + if (isset($vision['modelName']) && '' !== $vision['modelName']) { + $article->setModelName((string) $vision['modelName']); } if ([] !== $message->attributes) { diff --git a/src/Infrastructure/Messenger/Handler/PhotoUploadHandler.php b/src/Infrastructure/Messenger/Handler/PhotoUploadHandler.php index 1565db8..9eaacd7 100644 --- a/src/Infrastructure/Messenger/Handler/PhotoUploadHandler.php +++ b/src/Infrastructure/Messenger/Handler/PhotoUploadHandler.php @@ -37,13 +37,14 @@ final class PhotoUploadHandler $job->recordStep('vision', [ 'manufacturer' => $result['manufacturer'], - 'model' => $result['model'], + 'modelName' => $result['modelName'], + 'modelNumber' => $result['modelNumber'], 'serial' => $result['serial'], ]); $this->jobRepository->save($job); - if ('' === $result['model']) { - $job->markNeedsReview('OllamaVisionAgent: no model name detected on nameplate'); + if ('' === $result['modelNumber'] && '' === $result['modelName']) { + $job->markNeedsReview('OllamaVisionAgent: no model detected on nameplate'); $this->jobRepository->save($job); return; @@ -52,7 +53,8 @@ final class PhotoUploadHandler $this->bus->dispatch(new SpecsResearchMessage( jobId: $message->jobId, articleTypeId: $message->articleTypeId, - modelName: $result['model'], + modelNumber: $result['modelNumber'], + modelName: $result['modelName'], serialNumber: $result['serial'], manufacturer: $result['manufacturer'], )); diff --git a/src/Infrastructure/Messenger/Handler/SpecsResearchHandler.php b/src/Infrastructure/Messenger/Handler/SpecsResearchHandler.php index e9de043..c719cc5 100644 --- a/src/Infrastructure/Messenger/Handler/SpecsResearchHandler.php +++ b/src/Infrastructure/Messenger/Handler/SpecsResearchHandler.php @@ -39,9 +39,11 @@ final class SpecsResearchHandler return; } + $searchSubject = $message->modelNumber !== '' ? $message->modelNumber : $message->modelName; + try { $specsText = $this->specsAgent->research( - $message->modelName, + $searchSubject, $articleType->getName(), $message->manufacturer, ); diff --git a/src/Infrastructure/Messenger/Message/SpecsResearchMessage.php b/src/Infrastructure/Messenger/Message/SpecsResearchMessage.php index 40e402b..e452856 100644 --- a/src/Infrastructure/Messenger/Message/SpecsResearchMessage.php +++ b/src/Infrastructure/Messenger/Message/SpecsResearchMessage.php @@ -9,6 +9,7 @@ final readonly class SpecsResearchMessage public function __construct( public string $jobId, public string $articleTypeId, + public string $modelNumber, public string $modelName, public string $serialNumber, public string $manufacturer = '',