2026-04-25 22:21:06 +02:00
< ? php
/*
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
*
* Copyright ( C ) 2019 - 2026 Jan Böhmer ( https :// github . com / jbtronics )
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*/
declare ( strict_types = 1 );
namespace App\Services\InfoProviderSystem ;
use App\Entity\Parts\ManufacturingStatus ;
use App\Services\InfoProviderSystem\DTOs\FileDTO ;
use App\Services\InfoProviderSystem\DTOs\ParameterDTO ;
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO ;
use App\Services\InfoProviderSystem\DTOs\PriceDTO ;
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO ;
/**
* This class allows to convert the JSON data returned by an LLM into the DTOs used by the info provider system later .
*/
final class DTOJsonSchemaConverter
{
/**
* Returns the JSON schema , that defines the expected structure of the JSON data returned by the LLM .
* @ return array
*/
public function getJSONSchema () : array
{
return [
'name' => 'clock' ,
'strict' => true ,
'schema' => [
'type' => 'object' ,
'properties' => [
'name' => [ 'type' => 'string' , 'description' => 'Product name' ],
2026-04-26 21:31:07 +02:00
'description' => [ 'type' => 'string' , 'description' => 'A short description of the product, maybe containing the most important things. Onnly One line.' ],
2026-04-25 22:21:06 +02:00
'manufacturer' => [ 'type' => [ 'string' , 'null' ], 'description' => 'Manufacturer name' ],
'mpn' => [ 'type' => [ 'string' , 'null' ], 'description' => 'Manufacturer Part Number' ],
2026-04-26 21:31:07 +02:00
'category' => [ 'type' => [ 'string' , 'null' ], 'description' => 'Product category, e.g. "Passive components -> Resistors"' ],
2026-04-25 22:21:06 +02:00
'manufacturing_status' => [ 'type' => [ 'string' , 'null' ], 'enum' => [ 'active' , 'obsolete' , 'nrfnd' , 'discontinued' , null ], 'description' => 'Manufacturing status' ],
2026-04-26 21:31:07 +02:00
'footprint' => [ 'type' => [ 'string' , 'null' ], 'description' => 'Package/footprint type, like "SOT-23", "DIP-8", "QFN-32" etc.' ],
2026-04-26 19:36:03 +02:00
'mass' => [ 'type' => [ 'number' , 'null' ], 'description' => 'Mass of the product in grams' ],
2026-04-26 21:31:07 +02:00
'gtin' => [ 'type' => [ 'string' , 'null' ], 'description' => 'Global Trade Item Number (GTIN) / EAN / UPC code for barcodes' ],
'notes' => [ 'type' => [ 'string' , 'null' ], 'description' => 'Optional long description of the part with more details than description. Can be markdown formatted.' ],
2026-04-25 22:21:06 +02:00
'parameters' => [
'type' => 'array' ,
'items' => [
'type' => 'object' ,
'properties' => [
'name' => [ 'type' => 'string' ],
'value' => [ 'type' => 'string' ],
'unit' => [ 'type' => [ 'string' , 'null' ]],
],
'required' => [ 'name' , 'value' ],
],
],
'datasheets' => [
'type' => 'array' ,
'items' => [
'type' => 'object' ,
'properties' => [
'url' => [ 'type' => 'string' ],
'description' => [ 'type' => 'string' ],
],
'required' => [ 'url' ],
],
],
'images' => [
'type' => 'array' ,
'items' => [
'type' => 'object' ,
'properties' => [
'url' => [ 'type' => 'string' ],
'description' => [ 'type' => 'string' ],
],
'required' => [ 'url' ],
],
],
'vendor_infos' => [
'type' => 'array' ,
'items' => [
'type' => 'object' ,
'properties' => [
2026-04-26 19:36:03 +02:00
'distributor_name' => [ 'type' => 'string' , 'description' => 'Name of the distributor or vendor. Typically the shop name' ],
'order_number' => [ 'type' => [ 'string' , 'null' ], 'description' => 'The order number or SKU used by the distributor. Optional, but can help to find the product on the distributor website.' ],
2026-04-25 22:21:06 +02:00
'product_url' => [ 'type' => 'string' ],
2026-04-26 21:31:07 +02:00
'prices_include_vat' => [ 'type' => [ 'boolean' , 'null' ], 'description' => 'Whether the prices include VAT or not. Null if unknown.' ],
2026-04-25 22:21:06 +02:00
'prices' => [
'type' => 'array' ,
'items' => [
'type' => 'object' ,
'properties' => [
2026-04-26 19:36:03 +02:00
'minimum_quantity' => [ 'type' => 'integer' , 'description' => 'Minimum quantity for this price tier. 1 when no tiered pricing is available.' ],
'price' => [ 'type' => 'number' , 'description' => 'Price for the given minimum quantity.' ],
'currency' => [ 'type' => 'string' , 'description' => 'Currency ISO code, e.g. USD' ],
2026-04-25 22:21:06 +02:00
],
'required' => [ 'minimum_quantity' , 'price' , 'currency' ],
],
],
],
'required' => [ 'distributor_name' , 'product_url' ],
],
],
'manufacturer_product_url' => [ 'type' => [ 'string' , 'null' ], 'description' => 'Manufacturer product page URL' ],
],
'required' => [ 'name' , 'description' ],
]
];
}
public function jsonToDTO ( array $data , string $providerKey , string $providerId , ? string $productUrl = null , string $distributorNameFallback = '???' ) : PartDetailDTO
{
// Map manufacturing status
$manufacturingStatus = null ;
if ( ! empty ( $data [ 'manufacturing_status' ])) {
$status = strtolower (( string ) $data [ 'manufacturing_status' ]);
$manufacturingStatus = match ( $status ) {
'active' => ManufacturingStatus :: ACTIVE ,
'obsolete' , 'discontinued' => ManufacturingStatus :: DISCONTINUED ,
'nrfnd' , 'not recommended for new designs' => ManufacturingStatus :: NRFND ,
'eol' => ManufacturingStatus :: EOL ,
'announced' => ManufacturingStatus :: ANNOUNCED ,
default => null ,
};
}
// Build parameters
$parameters = null ;
if ( ! empty ( $data [ 'parameters' ]) && is_array ( $data [ 'parameters' ])) {
$parameters = [];
foreach ( $data [ 'parameters' ] as $p ) {
if ( ! empty ( $p [ 'name' ])) {
$value = $p [ 'value' ] ? ? '' ;
$unit = $p [ 'unit' ] ? ? null ;
// Combine value and unit for parsing
$valueWithUnit = $unit ? $value . ' ' . $unit : $value ;
$parameters [] = ParameterDTO :: parseValueField (
name : $p [ 'name' ],
value : $valueWithUnit
);
}
}
}
// Build datasheets
$datasheets = null ;
if ( ! empty ( $data [ 'datasheets' ]) && is_array ( $data [ 'datasheets' ])) {
$datasheets = [];
foreach ( $data [ 'datasheets' ] as $d ) {
if ( ! empty ( $d [ 'url' ])) {
$datasheets [] = new FileDTO (
url : $d [ 'url' ],
name : $d [ 'description' ] ? ? 'Datasheet'
);
}
}
}
// Build images
$images = null ;
if ( ! empty ( $data [ 'images' ]) && is_array ( $data [ 'images' ])) {
$images = [];
foreach ( $data [ 'images' ] as $i ) {
if ( ! empty ( $i [ 'url' ])) {
$images [] = new FileDTO (
url : $i [ 'url' ],
name : $i [ 'description' ] ? ? 'Image'
);
}
}
}
// Build vendor infos
$vendorInfos = null ;
if ( ! empty ( $data [ 'vendor_infos' ]) && is_array ( $data [ 'vendor_infos' ])) {
$vendorInfos = [];
foreach ( $data [ 'vendor_infos' ] as $v ) {
$prices = [];
if ( ! empty ( $v [ 'prices' ]) && is_array ( $v [ 'prices' ])) {
foreach ( $v [ 'prices' ] as $p ) {
$prices [] = new PriceDTO (
minimum_discount_amount : ( int ) ( $p [ 'minimum_quantity' ] ? ? 1 ),
price : ( string ) ( $p [ 'price' ] ? ? 0 ),
2026-04-26 21:31:07 +02:00
currency_iso_code : $p [ 'currency' ] ? ? null ,
price_related_quantity : 1 ,
2026-04-25 22:21:06 +02:00
);
}
}
$vendorInfos [] = new PurchaseInfoDTO (
distributor_name : $v [ 'distributor_name' ] ? ? $distributorNameFallback ,
order_number : $v [ 'order_number' ] ? ? 'Unknown' ,
prices : $prices ,
product_url : $v [ 'product_url' ] ? ? $productUrl ,
2026-04-26 21:31:07 +02:00
prices_include_vat : $v [ 'prices_include_vat' ] ? ? null ,
2026-04-25 22:21:06 +02:00
);
}
}
// Get preview image URL
$previewImageUrl = null ;
if ( ! empty ( $data [ 'images' ]) && is_array ( $data [ 'images' ]) && ! empty ( $data [ 'images' ][ 0 ][ 'url' ])) {
$previewImageUrl = $data [ 'images' ][ 0 ][ 'url' ];
}
return new PartDetailDTO (
provider_key : $providerKey ,
provider_id : $providerId ,
name : $data [ 'name' ] ? ? 'Unknown' ,
description : $data [ 'description' ] ? ? '' ,
category : $data [ 'category' ] ? ? null ,
manufacturer : $data [ 'manufacturer' ] ? ? null ,
mpn : $data [ 'mpn' ] ? ? null ,
preview_image_url : $previewImageUrl ,
manufacturing_status : $manufacturingStatus ,
provider_url : $productUrl ,
footprint : $data [ 'footprint' ] ? ? null ,
2026-04-26 19:36:03 +02:00
gtin : $data [ 'gtin' ] ? ? null ,
2026-04-26 21:32:19 +02:00
notes : $data [ 'notes' ] ? ? null ,
2026-04-25 22:21:06 +02:00
datasheets : $datasheets ,
images : $images ,
parameters : $parameters ,
vendor_infos : $vendorInfos ,
mass : isset ( $data [ 'mass' ]) && is_numeric ( $data [ 'mass' ]) ? ( float ) $data [ 'mass' ] : null ,
manufacturer_product_url : $data [ 'manufacturer_product_url' ] ? ? null ,
);
}
}