fixed bugs in DellConfiguratorParser2025 that caused the computed price to not match (at all) the price displayed by the dell web page
- the price of the cpu upgrade was misinterpreted as the price for 1 cpu, while it's actually the price for 2 cpus - now the estimated price exactly matches what we would get on the web site work related to [https://bugzilla.ipr.univ-rennes.fr/show_bug.cgi?id=4171]
This commit is contained in:
parent
3cd99587ca
commit
716d2d3262
|
|
@ -404,6 +404,9 @@ class Config():
|
||||||
self.cpu = None
|
self.cpu = None
|
||||||
self.cpu_slots_mem = []
|
self.cpu_slots_mem = []
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'Config(cpu={self.cpu.uid if self.cpu else None}, num_servers={self.num_servers}, num_cpu_per_server={self.num_cpu_per_server}, ram_size={self.ram_size} GiB)'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def chassis(self) -> ItemUid:
|
def chassis(self) -> ItemUid:
|
||||||
return self.configurator.chassis.item
|
return self.configurator.chassis.item
|
||||||
|
|
@ -513,7 +516,7 @@ class Config():
|
||||||
|
|
||||||
def get_price(self) -> Price:
|
def get_price(self) -> Price:
|
||||||
price = self.configurator.chassis.price
|
price = self.configurator.chassis.price
|
||||||
print(self.cpu.uid, self.configurator.chassis.price, self.configurator.get_item_price(self.cpu.uid), self.ram_price)
|
logging.debug(f'cpu: {self.cpu.uid}, chassis price: {self.configurator.chassis.price}, 1 cpu price: {self.configurator.get_item_price(self.cpu.uid)}, ram price: {self.ram_price}')
|
||||||
price += self.num_servers * self.num_cpu_per_server * self.configurator.get_item_price(self.cpu.uid) + self.ram_price
|
price += self.num_servers * self.num_cpu_per_server * self.configurator.get_item_price(self.cpu.uid) + self.ram_price
|
||||||
assert price > 0.0
|
assert price > 0.0
|
||||||
return price
|
return price
|
||||||
|
|
@ -617,7 +620,7 @@ class Configurator():
|
||||||
|
|
||||||
def get_item_price(self, item_uid: ItemUid) -> Optional[Price]:
|
def get_item_price(self, item_uid: ItemUid) -> Optional[Price]:
|
||||||
for module in self.modules.values():
|
for module in self.modules.values():
|
||||||
logging.debug(f'items in module {module.name}: {list(module.options.keys())}')
|
# logging.debug(f'items in module {module.name}: {list(module.options.keys())}')
|
||||||
if item_uid in module.options:
|
if item_uid in module.options:
|
||||||
return module.options[item_uid].price
|
return module.options[item_uid].price
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -428,6 +428,11 @@ class DellConfiguratorParser(IHtmlConfiguratorParser):
|
||||||
def get_xpath_filter(self, filter_id) -> str:
|
def get_xpath_filter(self, filter_id) -> str:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def additional_cpu_is_automatic(self) -> bool:
|
||||||
|
'''on some dell configurators (web pages), the user doesn't have the possibility to decide if he wants 1 or 2 CPUs, it is automatic. For example, on r670 configurator on 10/2025, there is no way to get a 1 cpu only configuration (the 'Processeur supplémentaires' module indicates 'Inclus dans le prix') '''
|
||||||
|
assert False
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_base_price(self, html_root: HtmlElement) -> Price:
|
def get_base_price(self, html_root: HtmlElement) -> Price:
|
||||||
assert False
|
assert False
|
||||||
|
|
@ -484,6 +489,8 @@ class DellConfiguratorParser(IHtmlConfiguratorParser):
|
||||||
if cpu_class == 'platinium':
|
if cpu_class == 'platinium':
|
||||||
cpu_class = 'platinum'
|
cpu_class = 'platinum'
|
||||||
cpu_id = "intel-xeon-%s-%s" % (cpu_class, match['cpu_number'].lower())
|
cpu_id = "intel-xeon-%s-%s" % (cpu_class, match['cpu_number'].lower())
|
||||||
|
if self.additional_cpu_is_automatic():
|
||||||
|
num_cpus = 2
|
||||||
assert match, 'unhandled label : %s' % label
|
assert match, 'unhandled label : %s' % label
|
||||||
# print(match['cpu_class'], match['cpu_number'])
|
# print(match['cpu_class'], match['cpu_number'])
|
||||||
option = Option(Cpu(cpu_id), price / num_cpus)
|
option = Option(Cpu(cpu_id), price / num_cpus)
|
||||||
|
|
@ -810,7 +817,7 @@ class DellConfiguratorParser(IHtmlConfiguratorParser):
|
||||||
break
|
break
|
||||||
if not cpu_already_exists:
|
if not cpu_already_exists:
|
||||||
cpu_price = proc_change_option.price + configurator.modules['processor'].options[base_cpu.uid].price
|
cpu_price = proc_change_option.price + configurator.modules['processor'].options[base_cpu.uid].price
|
||||||
print('estimated price of cpu %s : %f' % (proc_change_option.item.uid, cpu_price))
|
logging.info('estimated price of cpu %s : %f' % (proc_change_option.item.uid, cpu_price))
|
||||||
configurator.modules['processor'].add_option(Option(Cpu(proc_change_option.item.uid), cpu_price))
|
configurator.modules['processor'].add_option(Option(Cpu(proc_change_option.item.uid), cpu_price))
|
||||||
# delete the 'processor-change' module as its items ids are the same as the ones in the 'processor' modules but their prices are 'wrong' (upgrade prices rather than item price).
|
# delete the 'processor-change' module as its items ids are the same as the ones in the 'processor' modules but their prices are 'wrong' (upgrade prices rather than item price).
|
||||||
# in a configuration, no item should be found more than once
|
# in a configuration, no item should be found more than once
|
||||||
|
|
@ -818,6 +825,7 @@ class DellConfiguratorParser(IHtmlConfiguratorParser):
|
||||||
|
|
||||||
one_cpu_price = configurator.get_item_price(configurator.base_config.cpu.uid)
|
one_cpu_price = configurator.get_item_price(configurator.base_config.cpu.uid)
|
||||||
ram_price = configurator.base_config.ram_price
|
ram_price = configurator.base_config.ram_price
|
||||||
|
logging.debug('configurator.base_config.num_cpus = %d' % configurator.base_config.num_cpus)
|
||||||
configurator.chassis.price = base_price - configurator.base_config.num_cpus * one_cpu_price - ram_price
|
configurator.chassis.price = base_price - configurator.base_config.num_cpus * one_cpu_price - ram_price
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -846,6 +854,9 @@ class DellConfiguratorParser2020(DellConfiguratorParser):
|
||||||
'base_module_to_label': ".//div[@class='option-selected ']",
|
'base_module_to_label': ".//div[@class='option-selected ']",
|
||||||
}[filter_id]
|
}[filter_id]
|
||||||
|
|
||||||
|
def additional_cpu_is_automatic(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
def price_str_as_float(self, price_as_str):
|
def price_str_as_float(self, price_as_str):
|
||||||
# eg '+ 2,255.00 €'
|
# eg '+ 2,255.00 €'
|
||||||
match = re.match(r'^\s*(?P<sign>[-+]?)\s*(?P<numbers>[0-9.]*)\s*€\s*$', price_as_str.replace(',', ''))
|
match = re.match(r'^\s*(?P<sign>[-+]?)\s*(?P<numbers>[0-9.]*)\s*€\s*$', price_as_str.replace(',', ''))
|
||||||
|
|
@ -918,6 +929,9 @@ class DellConfiguratorParser2021(DellConfiguratorParser):
|
||||||
'base_module_to_label': ".//div[@class='product-options-configuration-block option-selected']",
|
'base_module_to_label': ".//div[@class='product-options-configuration-block option-selected']",
|
||||||
}[filter_id]
|
}[filter_id]
|
||||||
|
|
||||||
|
def additional_cpu_is_automatic(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
def price_str_as_float(self, price_as_str):
|
def price_str_as_float(self, price_as_str):
|
||||||
# eg '+ 2 255,00 €' # contains a Narrow No-Break Space (NNBSP) https://www.compart.com/en/unicode/U+202F
|
# eg '+ 2 255,00 €' # contains a Narrow No-Break Space (NNBSP) https://www.compart.com/en/unicode/U+202F
|
||||||
nnbsp = ' '
|
nnbsp = ' '
|
||||||
|
|
@ -1022,6 +1036,13 @@ class DellConfiguratorParser2025(DellConfiguratorParser):
|
||||||
'base_module_to_label': ".//div[@class='product-options-configuration-block option-selected']",
|
'base_module_to_label': ".//div[@class='product-options-configuration-block option-selected']",
|
||||||
}[filter_id]
|
}[filter_id]
|
||||||
|
|
||||||
|
def additional_cpu_is_automatic(self) -> bool:
|
||||||
|
# TODO: parse actual number of cpus per server from html
|
||||||
|
# in Processeur supplémentaires module, there's:
|
||||||
|
# Processeur Intel® Xeon® 6 Performance 6505P 2,2 GHz, 12C/24T, 24 GT/s, 48 Mo de cache, Turbo (150 W), mémoire DDR5-6400
|
||||||
|
# Inclus dans le prix
|
||||||
|
return True
|
||||||
|
|
||||||
def price_str_as_float(self, price_as_str):
|
def price_str_as_float(self, price_as_str):
|
||||||
# eg '+ 2 255,00 €' # contains a Narrow No-Break Space (NNBSP) https://www.compart.com/en/unicode/U+202F
|
# eg '+ 2 255,00 €' # contains a Narrow No-Break Space (NNBSP) https://www.compart.com/en/unicode/U+202F
|
||||||
nnbsp = ' '
|
nnbsp = ' '
|
||||||
|
|
@ -1181,6 +1202,9 @@ class DellConfiguratorParser2025(DellConfiguratorParser):
|
||||||
def _parse_base_config(self, html_root: HtmlElement, configurator: Configurator) -> Config:
|
def _parse_base_config(self, html_root: HtmlElement, configurator: Configurator) -> Config:
|
||||||
base_config = Config(configurator)
|
base_config = Config(configurator)
|
||||||
base_config.num_servers = configurator.chassis.item.max_num_servers
|
base_config.num_servers = configurator.chassis.item.max_num_servers
|
||||||
|
if self.additional_cpu_is_automatic(self):
|
||||||
|
base_config.num_cpu_per_server = 2
|
||||||
|
else:
|
||||||
base_config.num_cpu_per_server = 1
|
base_config.num_cpu_per_server = 1
|
||||||
|
|
||||||
# initialize cpu
|
# initialize cpu
|
||||||
|
|
@ -1191,21 +1215,8 @@ class DellConfiguratorParser2025(DellConfiguratorParser):
|
||||||
assert match, 'unhandled label : %s' % item_label
|
assert match, 'unhandled label : %s' % item_label
|
||||||
if match:
|
if match:
|
||||||
cpu_id = "intel-xeon-%s-%s" % (match['cpu_class'].lower(), match['cpu_number'].lower())
|
cpu_id = "intel-xeon-%s-%s" % (match['cpu_class'].lower(), match['cpu_number'].lower())
|
||||||
if match is None:
|
|
||||||
match = re.match(r'^2 processeurs Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLYU]?).*', item_label)
|
|
||||||
if match:
|
|
||||||
base_config.num_cpu_per_server = 2
|
|
||||||
cpu_id = "intel-xeon-%s-%s" % (match['cpu_class'].lower(), match['cpu_number'].lower())
|
|
||||||
if match is None:
|
|
||||||
print('item_label=%s' % item_label)
|
|
||||||
|
|
||||||
# match = re.match(r'^2 Processeurs AMD EPYC (?P<cpu_number>[0-9][0-9][0-9][0-9]).*', item_label)
|
|
||||||
match = re.match(r'^2 Processeurs AMD EPYC (?P<cpu_number>[0-9][0-9][0-9][0-9]).*', clean_string(item_label))
|
|
||||||
|
|
||||||
if match:
|
|
||||||
base_config.num_cpu_per_server = 2
|
|
||||||
cpu_id = "amd-epyc-%s" % (match['cpu_number'].lower())
|
|
||||||
assert match, 'unhandled label : %s' % item_label
|
assert match, 'unhandled label : %s' % item_label
|
||||||
|
|
||||||
# print(match['cpu_class'], match['cpu_number'])
|
# print(match['cpu_class'], match['cpu_number'])
|
||||||
base_config.set_cpu(Cpu(cpu_id))
|
base_config.set_cpu(Cpu(cpu_id))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue