Skip to content

Commit c0017d2

Browse files
committed
Refactored and cleaned up AIInfoExtractor
1 parent 9cf1624 commit c0017d2

4 files changed

Lines changed: 324 additions & 430 deletions

File tree

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
24+
namespace App\Services\InfoProviderSystem;
25+
26+
use App\Entity\Parts\ManufacturingStatus;
27+
use App\Services\InfoProviderSystem\DTOs\FileDTO;
28+
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
29+
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
30+
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
31+
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
32+
33+
/**
34+
* This class allows to convert the JSON data returned by an LLM into the DTOs used by the info provider system later.
35+
*/
36+
final class DTOJsonSchemaConverter
37+
{
38+
/**
39+
* Returns the JSON schema, that defines the expected structure of the JSON data returned by the LLM.
40+
* @return array
41+
*/
42+
public function getJSONSchema(): array
43+
{
44+
return [
45+
'name' => 'clock',
46+
'strict' => true,
47+
'schema' => [
48+
'type' => 'object',
49+
'properties' => [
50+
'name' => ['type' => 'string', 'description' => 'Product name'],
51+
'description' => ['type' => 'string', 'description' => 'Product description'],
52+
'manufacturer' => ['type' => ['string', 'null'], 'description' => 'Manufacturer name'],
53+
'mpn' => ['type' => ['string', 'null'], 'description' => 'Manufacturer Part Number'],
54+
'category' => ['type' => ['string', 'null'], 'description' => 'Product category'],
55+
'manufacturing_status' => ['type' => ['string', 'null'], 'enum' => ['active', 'obsolete', 'nrfnd', 'discontinued', null], 'description' => 'Manufacturing status'],
56+
'footprint' => ['type' => ['string', 'null'], 'description' => 'Package/footprint type'],
57+
'mass' => ['type' => ['number', 'null'], 'description' => 'Mass in grams'],
58+
'parameters' => [
59+
'type' => 'array',
60+
'items' => [
61+
'type' => 'object',
62+
'properties' => [
63+
'name' => ['type' => 'string'],
64+
'value' => ['type' => 'string'],
65+
'unit' => ['type' => ['string', 'null']],
66+
],
67+
'required' => ['name', 'value'],
68+
],
69+
],
70+
'datasheets' => [
71+
'type' => 'array',
72+
'items' => [
73+
'type' => 'object',
74+
'properties' => [
75+
'url' => ['type' => 'string'],
76+
'description' => ['type' => 'string'],
77+
],
78+
'required' => ['url'],
79+
],
80+
],
81+
'images' => [
82+
'type' => 'array',
83+
'items' => [
84+
'type' => 'object',
85+
'properties' => [
86+
'url' => ['type' => 'string'],
87+
'description' => ['type' => 'string'],
88+
],
89+
'required' => ['url'],
90+
],
91+
],
92+
'vendor_infos' => [
93+
'type' => 'array',
94+
'items' => [
95+
'type' => 'object',
96+
'properties' => [
97+
'distributor_name' => ['type' => 'string'],
98+
'order_number' => ['type' => ['string', 'null']],
99+
'product_url' => ['type' => 'string'],
100+
'prices' => [
101+
'type' => 'array',
102+
'items' => [
103+
'type' => 'object',
104+
'properties' => [
105+
'minimum_quantity' => ['type' => 'integer'],
106+
'price' => ['type' => 'number'],
107+
'currency' => ['type' => 'string'],
108+
],
109+
'required' => ['minimum_quantity', 'price', 'currency'],
110+
],
111+
],
112+
],
113+
'required' => ['distributor_name', 'product_url'],
114+
],
115+
],
116+
'manufacturer_product_url' => ['type' => ['string', 'null'], 'description' => 'Manufacturer product page URL'],
117+
],
118+
'required' => ['name', 'description'],
119+
]
120+
];
121+
}
122+
123+
public function jsonToDTO(array $data, string $providerKey, string $providerId, ?string $productUrl = null, string $distributorNameFallback = '???'): PartDetailDTO
124+
{
125+
// Map manufacturing status
126+
$manufacturingStatus = null;
127+
if (!empty($data['manufacturing_status'])) {
128+
$status = strtolower((string) $data['manufacturing_status']);
129+
$manufacturingStatus = match ($status) {
130+
'active' => ManufacturingStatus::ACTIVE,
131+
'obsolete', 'discontinued' => ManufacturingStatus::DISCONTINUED,
132+
'nrfnd', 'not recommended for new designs' => ManufacturingStatus::NRFND,
133+
'eol' => ManufacturingStatus::EOL,
134+
'announced' => ManufacturingStatus::ANNOUNCED,
135+
default => null,
136+
};
137+
}
138+
139+
// Build parameters
140+
$parameters = null;
141+
if (!empty($data['parameters']) && is_array($data['parameters'])) {
142+
$parameters = [];
143+
foreach ($data['parameters'] as $p) {
144+
if (!empty($p['name'])) {
145+
$value = $p['value'] ?? '';
146+
$unit = $p['unit'] ?? null;
147+
// Combine value and unit for parsing
148+
$valueWithUnit = $unit ? $value . ' ' . $unit : $value;
149+
$parameters[] = ParameterDTO::parseValueField(
150+
name: $p['name'],
151+
value: $valueWithUnit
152+
);
153+
}
154+
}
155+
}
156+
157+
// Build datasheets
158+
$datasheets = null;
159+
if (!empty($data['datasheets']) && is_array($data['datasheets'])) {
160+
$datasheets = [];
161+
foreach ($data['datasheets'] as $d) {
162+
if (!empty($d['url'])) {
163+
$datasheets[] = new FileDTO(
164+
url: $d['url'],
165+
name: $d['description'] ?? 'Datasheet'
166+
);
167+
}
168+
}
169+
}
170+
171+
// Build images
172+
$images = null;
173+
if (!empty($data['images']) && is_array($data['images'])) {
174+
$images = [];
175+
foreach ($data['images'] as $i) {
176+
if (!empty($i['url'])) {
177+
$images[] = new FileDTO(
178+
url: $i['url'],
179+
name: $i['description'] ?? 'Image'
180+
);
181+
}
182+
}
183+
}
184+
185+
// Build vendor infos
186+
$vendorInfos = null;
187+
if (!empty($data['vendor_infos']) && is_array($data['vendor_infos'])) {
188+
$vendorInfos = [];
189+
foreach ($data['vendor_infos'] as $v) {
190+
$prices = [];
191+
if (!empty($v['prices']) && is_array($v['prices'])) {
192+
foreach ($v['prices'] as $p) {
193+
$prices[] = new PriceDTO(
194+
minimum_discount_amount: (int) ($p['minimum_quantity'] ?? 1),
195+
price: (string) ($p['price'] ?? 0),
196+
currency_iso_code: $p['currency'] ?? 'USD',
197+
price_related_quantity: (int) ($p['minimum_quantity'] ?? 1),
198+
);
199+
}
200+
}
201+
202+
$vendorInfos[] = new PurchaseInfoDTO(
203+
distributor_name: $v['distributor_name'] ?? $distributorNameFallback,
204+
order_number: $v['order_number'] ?? 'Unknown',
205+
prices: $prices,
206+
product_url: $v['product_url'] ?? $productUrl,
207+
);
208+
}
209+
}
210+
211+
// Get preview image URL
212+
$previewImageUrl = null;
213+
if (!empty($data['images']) && is_array($data['images']) && !empty($data['images'][0]['url'])) {
214+
$previewImageUrl = $data['images'][0]['url'];
215+
}
216+
217+
return new PartDetailDTO(
218+
provider_key: $providerKey,
219+
provider_id: $providerId,
220+
name: $data['name'] ?? 'Unknown',
221+
description: $data['description'] ?? '',
222+
category: $data['category'] ?? null,
223+
manufacturer: $data['manufacturer'] ?? null,
224+
mpn: $data['mpn'] ?? null,
225+
preview_image_url: $previewImageUrl,
226+
manufacturing_status: $manufacturingStatus,
227+
provider_url: $productUrl,
228+
footprint: $data['footprint'] ?? null,
229+
notes: null,
230+
datasheets: $datasheets,
231+
images: $images,
232+
parameters: $parameters,
233+
vendor_infos: $vendorInfos,
234+
mass: isset($data['mass']) && is_numeric($data['mass']) ? (float) $data['mass'] : null,
235+
manufacturer_product_url: $data['manufacturer_product_url'] ?? null,
236+
);
237+
}
238+
239+
}

0 commit comments

Comments
 (0)