adapted concho's dell html parser to dell's 2021 web pages format
note : dell's 2020 web pages are still supported
This commit is contained in:
		
							parent
							
								
									82359a549c
								
							
						
					
					
						commit
						048de6fed9
					
				
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										268
									
								
								concho/dell.py
								
								
								
								
							
							
						
						
									
										268
									
								
								concho/dell.py
								
								
								
								
							|  | @ -381,23 +381,22 @@ class DellConfiguratorParser(): | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
|     @classmethod |     def _get_module(self, root_element, section_label): | ||||||
|     def _get_module(cls, root_element, section_label): |         modules_element = root_element.xpath(self.get_xpath_filter('root_to_modules_element'))[0] | ||||||
|         modules_element = root_element.xpath(".//div[@class='col-md-10']")[0] |  | ||||||
|         # print(modules_element) |         # print(modules_element) | ||||||
|         for module_root in modules_element.xpath(".//div[@class='col-md-12 module']"): |         for module_root in modules_element.xpath(self.get_xpath_filter('modules_element_to_modules')): | ||||||
|             # print(module_root) |             # print(module_root) | ||||||
|             # blue modules such as "Processeurs (Passage)" |             # blue modules such as "Processeurs (Passage)" | ||||||
|             module_titles = module_root.xpath(".//div[@class='col-md-4 module-title color-017EB8']") |             module_titles = module_root.xpath(self.get_xpath_filter('module_to_blue_title')) | ||||||
|             if len(module_titles) > 0: |             if len(module_titles) > 0: | ||||||
|                 # print(module_title.text) |  | ||||||
|                 # print(len(module_title.text)) |                 # print(len(module_title.text)) | ||||||
|                 module_title = module_titles[0] |                 module_title = module_titles[0] | ||||||
|  |                 # print('module_title.text = %s ' % module_title.text) | ||||||
|                 # print(module_title.text_content()) |                 # print(module_title.text_content()) | ||||||
|                 if module_title.text == section_label: |                 if module_title.text == section_label: | ||||||
|                     return module_root |                     return module_root | ||||||
|             # grey modules such as 'Base' |             # grey modules such as 'Base' | ||||||
|             module_titles = module_root.xpath(".//div[@class='col-md-4 module-title color-808080']") |             module_titles = module_root.xpath(self.get_xpath_filter('module_to_grey_title')) | ||||||
|             if len(module_titles) > 0: |             if len(module_titles) > 0: | ||||||
|                 # print(module_title.text) |                 # print(module_title.text) | ||||||
|                 # print(len(module_title.text)) |                 # print(len(module_title.text)) | ||||||
|  | @ -405,25 +404,33 @@ class DellConfiguratorParser(): | ||||||
|                 # print(module_title.text_content()) |                 # print(module_title.text_content()) | ||||||
|                 if module_title.text == section_label: |                 if module_title.text == section_label: | ||||||
|                     return module_root |                     return module_root | ||||||
|  |         assert False, 'failed to find module "%s"' % section_label | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @abstractmethod | ||||||
|     def price_str_as_float(cls, price_as_str): |     def price_str_as_float(self, price_as_str): | ||||||
|         match = re.match(r'^\s*(?P<sign>[-+]?)\s*(?P<numbers>[0-9.]*)\s*€\s*$', price_as_str.replace(',','')) |         assert False | ||||||
|         assert match, 'unexpected price string (%s)' % price_as_str |  | ||||||
|         # print(match['sign'], match['numbers']) |  | ||||||
|         price_as_float = float("%s%s" % (match['sign'], match['numbers'])) |  | ||||||
|         return price_as_float |  | ||||||
|      |      | ||||||
|  |     @abstractmethod | ||||||
|  |     def get_module_label(self, module_id): | ||||||
|  |         assert False | ||||||
|  | 
 | ||||||
|  |     @abstractmethod | ||||||
|  |     def get_xpath_filter(self, filter_id): | ||||||
|  |         assert False | ||||||
|  | 
 | ||||||
|  |     @abstractmethod | ||||||
|  |     def get_base_price(self, html_root): | ||||||
|  |         assert False | ||||||
| 
 | 
 | ||||||
|     def _parse_proc_change_options(self, html_root): |     def _parse_proc_change_options(self, html_root): | ||||||
|         proc_options = Module('processor-change') |         proc_options = Module('processor-change') | ||||||
|         #module_root_element = DellConfiguratorParser._get_module(html_root, 'Processeurs (Passage)') |         #module_root_element = self._get_module(html_root, 'Processeurs (Passage)') | ||||||
|         module_root_element = DellConfiguratorParser._get_module(html_root, 'Processeurs (Passage)') |         module_root_element = self._get_module(html_root, self.get_module_label('cpu_change')) | ||||||
|         for option_root_element in module_root_element.xpath(".//div[@class='row']"): |         for option_root_element in module_root_element.xpath(self.get_xpath_filter('module_to_options')): | ||||||
|             label_elements = option_root_element.xpath(".//div[@class='option-not-selected ']") |             label_elements = option_root_element.xpath(self.get_xpath_filter('option_to_label')) | ||||||
|             if len(label_elements) > 0: |             if len(label_elements) > 0: | ||||||
|                 label = label_elements[0].text_content().replace('\n', '') |                 label = label_elements[0].text_content().replace('\n', '') | ||||||
|                 price = DellConfiguratorParser.price_str_as_float(option_root_element.xpath(".//div[@class='col-md-3 text-right option-price ']")[0].text_content()) |                 price = self.price_str_as_float(option_root_element.xpath(self.get_xpath_filter('option_to_price'))[0].text_content()) | ||||||
|                 # print(label, price) |                 # print(label, price) | ||||||
|                 num_cpus = 1 |                 num_cpus = 1 | ||||||
|                 # Passage à processeur Intel Xeon Gold 6240L 2.6GHz, 24.75M Cache,10.40GT/s, 2UPI, Turbo, HT,18C/36T (150W) - DDR4-2933 |                 # Passage à processeur Intel Xeon Gold 6240L 2.6GHz, 24.75M Cache,10.40GT/s, 2UPI, Turbo, HT,18C/36T (150W) - DDR4-2933 | ||||||
|  | @ -444,13 +451,13 @@ class DellConfiguratorParser(): | ||||||
| 
 | 
 | ||||||
|     def _parse_proc_options(self, html_root): |     def _parse_proc_options(self, html_root): | ||||||
|         proc_options = Module('processor') |         proc_options = Module('processor') | ||||||
|         #module_root_element = DellConfiguratorParser._get_module(html_root, 'Processeurs (Passage)') |         #module_root_element = self._get_module(html_root, 'Processeurs (Passage)') | ||||||
|         module_root_element = DellConfiguratorParser._get_module(html_root, 'Processeurs  additionnels') |         module_root_element = self._get_module(html_root, self.get_module_label('additional_cpus')) | ||||||
|         for option_root_element in module_root_element.xpath(".//div[@class='row']"): |         for option_root_element in module_root_element.xpath(self.get_xpath_filter('module_to_options')): | ||||||
|             label_elements = option_root_element.xpath(".//div[@class='option-not-selected ']") |             label_elements = option_root_element.xpath(self.get_xpath_filter('option_to_label')) | ||||||
|             if len(label_elements) > 0: |             if len(label_elements) > 0: | ||||||
|                 label = label_elements[0].text_content() |                 label = label_elements[0].text_content() | ||||||
|                 price = DellConfiguratorParser.price_str_as_float(option_root_element.xpath(".//div[@class='col-md-3 text-right option-price ']")[0].text_content()) |                 price = self.price_str_as_float(option_root_element.xpath(self.get_xpath_filter('option_to_price'))[0].text_content()) | ||||||
|                 # print(label, price) |                 # print(label, price) | ||||||
|                 num_additional_cpus = 1 |                 num_additional_cpus = 1 | ||||||
|                 match = re.match(r'^Processeur additionnel Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLY]?).*', label) |                 match = re.match(r'^Processeur additionnel Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLY]?).*', label) | ||||||
|  | @ -471,16 +478,16 @@ class DellConfiguratorParser(): | ||||||
| 
 | 
 | ||||||
|     def _parse_ram_options(self, html_root): |     def _parse_ram_options(self, html_root): | ||||||
|         ram_options = Module('ram') |         ram_options = Module('ram') | ||||||
|         #module_root_element = DellConfiguratorParser._get_module(html_root, 'Processeurs (Passage)') |         #module_root_element = self._get_module(html_root, 'Processeurs (Passage)') | ||||||
|         module_root_element = DellConfiguratorParser._get_module(html_root, 'Mémoire: Ajout de barettes additionnelles') |         module_root_element = self._get_module(html_root, self.get_module_label('ram_additions')) | ||||||
|         for option_root_element in module_root_element.xpath(".//div[@class='row']"): |         for option_root_element in module_root_element.xpath(self.get_xpath_filter('module_to_options')): | ||||||
|             label_elements = option_root_element.xpath(".//div[@class='option-not-selected ']") |             label_elements = option_root_element.xpath(self.get_xpath_filter('option_to_label')) | ||||||
|             if len(label_elements) > 0: |             if len(label_elements) > 0: | ||||||
|                 label = label_elements[0].text_content() |                 label = label_elements[0].text_content() | ||||||
|                 price = DellConfiguratorParser.price_str_as_float(option_root_element.xpath(".//div[@class='col-md-3 text-right option-price ']")[0].text_content()) |                 price = self.price_str_as_float(option_root_element.xpath(self.get_xpath_filter('option_to_price'))[0].text_content()) | ||||||
|                 # print(label, price) |                 # print(label, price) | ||||||
|                 # Ajout d'une barette de 128Go 2667 Mhz LRDIMM |                 # Ajout d'une barette de 128Go 2667 Mhz LRDIMM | ||||||
|                 match = re.match(r'^Ajout d\'une barette de (?P<num_gb>[0-9]+)Go (?P<num_mhz>[0-9][0-9][0-9][0-9]) Mhz (?P<mem_type>LRDIMM|RDIMM)$', label) |                 match = re.match(r'^Ajout d\'une barette de (?P<num_gb>[0-9]+)Go (?P<num_mhz>[0-9][0-9][0-9][0-9]) *M[Hh]z (?P<mem_type>LRDIMM|RDIMM)$', label) | ||||||
|                 if match: |                 if match: | ||||||
| 
 | 
 | ||||||
|                     # print(match['num_gb'], match['num_mhz']) |                     # print(match['num_gb'], match['num_mhz']) | ||||||
|  | @ -505,19 +512,9 @@ class DellConfiguratorParser(): | ||||||
|         assert len(ram_options.options) > 0 |         assert len(ram_options.options) > 0 | ||||||
|         return ram_options |         return ram_options | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @abstractmethod | ||||||
|     def _get_module_default_item(cls, module_label, html_root): |     def _get_module_default_item(self, module_label, html_root): | ||||||
|         module_root_element = DellConfiguratorParser._get_module(html_root, module_label) |         assert False | ||||||
|         assert module_root_element is not None |  | ||||||
|         for option_root_element in module_root_element.xpath(".//div[@class='row']"): |  | ||||||
|             label_elements = option_root_element.xpath(".//div[@class='option-selected ']") |  | ||||||
|             assert label_elements is not None |  | ||||||
|             if len(label_elements) > 0: |  | ||||||
|                 label = label_elements[0].text_content().replace('\n', '') |  | ||||||
|                 price = DellConfiguratorParser.price_str_as_float(option_root_element.xpath(".//div[@class='col-md-3 text-right option-price option-price-selected']")[0].text_content()) |  | ||||||
|                 assert price == 0.0, 'default items are expected to have a price of 0.0 € (%s s price is %f)' % (label, price) |  | ||||||
|                 return label |  | ||||||
|         assert False, 'failed to find the default item of module %s' % module_label |  | ||||||
| 
 | 
 | ||||||
|     def _parse_base_config(self, html_root, configurator): |     def _parse_base_config(self, html_root, configurator): | ||||||
|         base_config = Config(configurator) |         base_config = Config(configurator) | ||||||
|  | @ -525,7 +522,7 @@ class DellConfiguratorParser(): | ||||||
|         base_config.num_cpu_per_server = 1 |         base_config.num_cpu_per_server = 1 | ||||||
| 
 | 
 | ||||||
|         # initialize cpu |         # initialize cpu | ||||||
|         item_label = DellConfiguratorParser._get_module_default_item('Processeurs (Passage)', html_root) |         item_label = self._get_module_default_item('Processeurs (Passage)', html_root) | ||||||
|         # Processeur Intel Xeon Silver 4208 2.1GHz,11M Cache,9.60GT/s, 2UPI,No Turbo, HT,8C/16T (85W) - DDR4-2400 |         # Processeur Intel Xeon Silver 4208 2.1GHz,11M Cache,9.60GT/s, 2UPI,No Turbo, HT,8C/16T (85W) - DDR4-2400 | ||||||
|         match = re.match(r'^Processeur Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLYU]?).*', item_label) |         match = re.match(r'^Processeur Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLYU]?).*', item_label) | ||||||
|         if match is None: |         if match is None: | ||||||
|  | @ -537,7 +534,7 @@ class DellConfiguratorParser(): | ||||||
|         base_config.set_cpu(Cpu(cpu_id)) |         base_config.set_cpu(Cpu(cpu_id)) | ||||||
| 
 | 
 | ||||||
|         # initialize the default ram dimms |         # initialize the default ram dimms | ||||||
|         item_label = DellConfiguratorParser._get_module_default_item('Mémoires (Passage)', html_root) |         item_label = self._get_module_default_item(self.get_module_label('ram_change'), html_root) | ||||||
|         # Mémoire 16 Go DDR4 à 2933MHz (1x16Go) |         # Mémoire 16 Go DDR4 à 2933MHz (1x16Go) | ||||||
|         match = re.match(r'^Mémoire (?P<num_gb>[0-9]+) Go DDR4 à (?P<num_mhz>[0-9]+)MHz \((?P<num_dimms>[0-9]+)x(?P<num_gb_per_dimm>[0-9]+)Go\)', item_label) |         match = re.match(r'^Mémoire (?P<num_gb>[0-9]+) Go DDR4 à (?P<num_mhz>[0-9]+)MHz \((?P<num_dimms>[0-9]+)x(?P<num_gb_per_dimm>[0-9]+)Go\)', item_label) | ||||||
|         assert match, 'unhandled label : %s' % item_label |         assert match, 'unhandled label : %s' % item_label | ||||||
|  | @ -610,18 +607,21 @@ class DellConfiguratorParser(): | ||||||
|         # modules_element = body.xpath("//div[@class='col-md-10']") |         # modules_element = body.xpath("//div[@class='col-md-10']") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         module_root_element = DellConfiguratorParser._get_module(html_root, 'Base') |         module_root_element = self._get_module(html_root, 'Base') | ||||||
|         assert module_root_element is not None |         assert module_root_element is not None | ||||||
|         for option_root_element in module_root_element.xpath(".//div[@class='row']"): |         #option_root_elements = module_root_element.xpath(".//div[@class='row']") | ||||||
|             label_elements = option_root_element.xpath(".//div[@class='option-selected ']") |         #assert len(option_root_elements) > 0 | ||||||
|             assert len(label_elements) > 0 | 
 | ||||||
|             label = label_elements[0].text_content().replace('\n', '') |         # option_root_element = option_root_elements[0] | ||||||
|             # PowerEdge R640 |         label_elements = module_root_element.xpath(self.get_xpath_filter('base_module_to_label')) | ||||||
|             match = re.match(r'^PowerEdge (?P<chassis_type>[CR][0-9][0-9][0-9][0-9]?).*', label) |         assert len(label_elements) > 0 | ||||||
|             assert match, 'unhandled label : %s' % label |         label = label_elements[0].text_content().replace('\n', '') | ||||||
|             # print(match['cpu_class'], match['cpu_number']) |         # PowerEdge R640 | ||||||
|             chassis_id = "dell-poweredge-%s" % (match['chassis_type'].lower(), ) |         match = re.match(r'^PowerEdge (?P<chassis_type>[CR][0-9][0-9][0-9][0-9]?).*', label) | ||||||
|             configurator.chassis = Option(Chassis(chassis_id), 0.0) |         assert match, 'unhandled label : %s' % label | ||||||
|  |         # print(match['cpu_class'], match['cpu_number']) | ||||||
|  |         chassis_id = "dell-poweredge-%s" % (match['chassis_type'].lower(), ) | ||||||
|  |         configurator.chassis = Option(Chassis(chassis_id), 0.0) | ||||||
| 
 | 
 | ||||||
|         configurator.base_config = self._parse_base_config(html_root, configurator) |         configurator.base_config = self._parse_base_config(html_root, configurator) | ||||||
| 
 | 
 | ||||||
|  | @ -637,6 +637,60 @@ class DellConfiguratorParser(): | ||||||
|             assert configurator.get_item_price(base_cpu.uid) is not None |             assert configurator.get_item_price(base_cpu.uid) is not None | ||||||
| 
 | 
 | ||||||
|         # compute the price of the chassis |         # compute the price of the chassis | ||||||
|  |         base_price = self.get_base_price(html_root) | ||||||
|  | 
 | ||||||
|  |         one_cpu_price = configurator.get_item_price(configurator.base_config.cpu.uid) | ||||||
|  |         ram_price = configurator.base_config.ram_price | ||||||
|  |         configurator.chassis.price = base_price - configurator.base_config.num_cpus * one_cpu_price - ram_price | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DellConfiguratorParser2020(DellConfiguratorParser): | ||||||
|  | 
 | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  | 
 | ||||||
|  |     def get_module_label(self, module_id): | ||||||
|  |         return { | ||||||
|  |             'cpu_change': 'Processeurs (Passage)', | ||||||
|  |             'additional_cpus': 'Processeurs  additionnels', | ||||||
|  |             'ram_change': 'Mémoires (Passage)', | ||||||
|  |             'ram_additions': 'Mémoire: Ajout de barettes additionnelles', | ||||||
|  |             }[module_id] | ||||||
|  | 
 | ||||||
|  |     def get_xpath_filter(self, filter_id): | ||||||
|  |         return { | ||||||
|  |             'root_to_modules_element': ".//div[@class='col-md-10']", | ||||||
|  |             'modules_element_to_modules': ".//div[@class='col-md-12 module']", | ||||||
|  |             'module_to_blue_title': ".//div[@class='col-md-4 module-title color-017EB8']", | ||||||
|  |             'module_to_grey_title': ".//div[@class='col-md-4 module-title color-808080']", | ||||||
|  |             'module_to_options': ".//div[@class='row']", | ||||||
|  |             'option_to_label': ".//div[@class='option-not-selected ']", | ||||||
|  |             'option_to_price': ".//div[@class='col-md-3 text-right option-price ']", | ||||||
|  |             'base_module_to_label': ".//div[@class='option-selected ']", | ||||||
|  |             }[filter_id] | ||||||
|  | 
 | ||||||
|  |     def price_str_as_float(self, price_as_str): | ||||||
|  |         # eg '+ 2,255.00 €' | ||||||
|  |         match = re.match(r'^\s*(?P<sign>[-+]?)\s*(?P<numbers>[0-9.]*)\s*€\s*$', price_as_str.replace(',','')) | ||||||
|  |         assert match, 'unexpected price string (%s)' % price_as_str | ||||||
|  |         # print(match['sign'], match['numbers']) | ||||||
|  |         price_as_float = float("%s%s" % (match['sign'], match['numbers'])) | ||||||
|  |         return price_as_float | ||||||
|  | 
 | ||||||
|  |     def _get_module_default_item(self, module_label, html_root): | ||||||
|  |         module_root_element = self._get_module(html_root, module_label) | ||||||
|  |         assert module_root_element is not None | ||||||
|  |         for option_root_element in module_root_element.xpath(".//div[@class='row']"): | ||||||
|  |             label_elements = option_root_element.xpath(".//div[@class='option-selected ']") | ||||||
|  |             assert label_elements is not None | ||||||
|  |             if len(label_elements) > 0: | ||||||
|  |                 label = label_elements[0].text_content().replace('\n', '') | ||||||
|  |                 price = self.price_str_as_float(option_root_element.xpath(".//div[@class='col-md-3 text-right option-price option-price-selected']")[0].text_content()) | ||||||
|  |                 assert price == 0.0, 'default items are expected to have a price of 0.0 € (%s s price is %f)' % (label, price) | ||||||
|  |                 return label | ||||||
|  |         assert False, 'failed to find the default item of module %s' % module_label | ||||||
|  | 
 | ||||||
|  |     def get_base_price(self, html_root): | ||||||
|         base_price = None |         base_price = None | ||||||
|         price_preview_element = html_root.xpath(".//div[@class='price-preview']")[0] |         price_preview_element = html_root.xpath(".//div[@class='price-preview']")[0] | ||||||
|         assert price_preview_element is not None |         assert price_preview_element is not None | ||||||
|  | @ -657,24 +711,108 @@ class DellConfiguratorParser(): | ||||||
|             if label == 'Prix': |             if label == 'Prix': | ||||||
|                 price_value_element = price_element.xpath(".//span[@class='col-md-8']")[0] |                 price_value_element = price_element.xpath(".//span[@class='col-md-8']")[0] | ||||||
|                 assert price_value_element is not None |                 assert price_value_element is not None | ||||||
|                 base_price = DellConfiguratorParser.price_str_as_float(price_value_element.text_content()) |                 base_price = self.price_str_as_float(price_value_element.text_content()) | ||||||
|         assert base_price is not None |         assert base_price is not None | ||||||
|  |         return base_price | ||||||
|  | 
 | ||||||
|  | class DellConfiguratorParser2021(DellConfiguratorParser): | ||||||
|  | 
 | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  | 
 | ||||||
|  |     def get_module_label(self, module_id): | ||||||
|  |         return { | ||||||
|  |             'cpu_change': 'Processeurs (Passage)', | ||||||
|  |             'additional_cpus': 'Processeurs  additionnels', | ||||||
|  |             'ram_change': 'Mémoires (Passage)', | ||||||
|  |             'ram_additions': 'Mémoire: Ajout de barettes additionnelles', | ||||||
|  |             }[module_id] | ||||||
|  | 
 | ||||||
|  |     def get_xpath_filter(self, filter_id): | ||||||
|  |         return { | ||||||
|  |             'root_to_modules_element': ".//div[@class='modules']", | ||||||
|  |             'modules_element_to_modules': ".//div[@class='product-module-configuration']", | ||||||
|  |             'module_to_blue_title': ".//header", | ||||||
|  |             'module_to_grey_title': ".//div[@class='col-md-4 module-title color-808080']", | ||||||
|  |             'module_to_options': ".//div[@class='product-options-configuration-line']", | ||||||
|  |             'option_to_label': ".//div[@class='option-info']", | ||||||
|  |             'option_to_price': ".//div[@class='option-price']", | ||||||
|  |             'base_module_to_label': ".//div[@class='product-options-configuration-block option-selected']", | ||||||
|  |             }[filter_id] | ||||||
|  | 
 | ||||||
|  |     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 | ||||||
|  |         nnbsp = ' ' | ||||||
|  |         match = re.match(r'^\s*(?P<sign>[-+]?)\s*(?P<numbers>[0-9.]*)\s*€\s*$', price_as_str.replace(',','.').replace(nnbsp, '')) | ||||||
|  |         assert match, 'unexpected price string (%s)' % price_as_str | ||||||
|  |         # print(match['sign'], match['numbers']) | ||||||
|  |         price_as_float = float("%s%s" % (match['sign'], match['numbers'])) | ||||||
|  |         return price_as_float | ||||||
|  | 
 | ||||||
|  |     def _get_module_default_item(self, module_label, html_root): | ||||||
|  |         module_root_element = self._get_module(html_root, module_label) | ||||||
|  |         assert module_root_element is not None | ||||||
|  | 
 | ||||||
|  |         if module_label == self.get_module_label('ram_change'): | ||||||
|  |             # <div | ||||||
|  |             #     class="product-options-configuration-block option-selected"> | ||||||
|  |             #     <header>Mémoire 16 Go DDR4 à 3200MHz (1x16Go)<div | ||||||
|  |             #             class="option-selector"><i | ||||||
|  |             #                 class="fas fa-check "></i></div> | ||||||
|  |             #     </header> | ||||||
|  |             #     <div class="mt-2 option-price">+ 0,00 €</div> | ||||||
|  |             # </div> | ||||||
|  |             selected_option_filter = ".//div[@class='product-options-configuration-block option-selected']" | ||||||
|  |             label_filter = ".//header" | ||||||
|  |             price_filter = ".//div[@class='mt-2 option-price']" | ||||||
|  |         else: | ||||||
|  |             selected_option_filter = ".//div[@class='product-options-configuration-line option-selected']" | ||||||
|  |             label_filter = ".//div[@class='option-info']" | ||||||
|  |             price_filter = ".//div[@class='option-price']" | ||||||
|  | 
 | ||||||
|  |         for selected_option_root_element in module_root_element.xpath(selected_option_filter): | ||||||
|  |             label_elements = selected_option_root_element.xpath(label_filter) | ||||||
|  |             assert label_elements is not None | ||||||
|  |             if len(label_elements) > 0: | ||||||
|  |                 label = label_elements[0].text_content().replace('\n', '') | ||||||
|  |                 price = self.price_str_as_float(selected_option_root_element.xpath(price_filter)[0].text_content()) | ||||||
|  |                 assert price == 0.0, 'default items are expected to have a price of 0.0 € (%s s price is %f)' % (label, price) | ||||||
|  |                 return label | ||||||
|  |         assert False, 'failed to find the default item of module %s' % module_label | ||||||
|  | 
 | ||||||
|  |     def get_base_price(self, html_root): | ||||||
|  |         base_price = None | ||||||
|  |         price_preview_element = html_root.xpath(".//div[@class='product-info']")[0] | ||||||
|  |         assert price_preview_element is not None | ||||||
|  |         for price_element in price_preview_element.xpath(".//div[@class='info']"): | ||||||
|  |             price_label_element = price_element.xpath(".//span[@class='info-label']")[0] | ||||||
|  |             # <div class="info"><span class="info-label">Prix de base</span><span | ||||||
|  |             #         class="info-value strong">1175 € HT</span></div> | ||||||
|  |             # <hr> | ||||||
|  |             # <div class="info"><span class="info-label">Avec options</span><span | ||||||
|  |             #         class="info-value strong">1175 € HT</span></div> | ||||||
|  |             # <hr> | ||||||
|  | 
 | ||||||
|  |             assert price_label_element is not None | ||||||
|  |             label = price_label_element.text_content().replace('\n', '') | ||||||
|  |             if label == 'Prix de base': | ||||||
|  |                 price_value_element = price_element.xpath(".//span[@class='info-value strong']")[0] | ||||||
|  |                 assert price_value_element is not None | ||||||
|  |                 base_price = self.price_str_as_float(price_value_element.text_content().replace(' HT','')) | ||||||
|  |         assert base_price is not None | ||||||
|  |         return base_price | ||||||
| 
 | 
 | ||||||
|         one_cpu_price = configurator.get_item_price(configurator.base_config.cpu.uid) |  | ||||||
|         ram_price = configurator.base_config.ram_price |  | ||||||
|         configurator.chassis.price = base_price - configurator.base_config.num_cpus * one_cpu_price - ram_price |  | ||||||
| 
 | 
 | ||||||
| class DellMatinfoConfigurator(Configurator): | class DellMatinfoConfigurator(Configurator): | ||||||
|     ''' |     ''' | ||||||
|     a configurator using the web page from dell matinfo |     a configurator using the web page from dell matinfo | ||||||
|     ''' |     ''' | ||||||
| 
 | 
 | ||||||
|     def __init__(self, dell_configurator_html_file_path): |     def __init__(self, dell_configurator_html_file_path, html_parser): | ||||||
|         super().__init__(self) |         super().__init__(self) | ||||||
|         self.base_config = None |         self.base_config = None | ||||||
|         self.chassis = None |         self.chassis = None | ||||||
|         parser = DellConfiguratorParser() |         html_parser.parse(dell_configurator_html_file_path, self) | ||||||
|         parser.parse(dell_configurator_html_file_path, self) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def create_config(self): |     def create_config(self): | ||||||
|  | @ -740,7 +878,7 @@ class DellMatinfoCsvConfigurator(Configurator): | ||||||
| 
 | 
 | ||||||
|                 match = re.match(r'^Passage à 2 Processeurs Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLYUM]?) .*', label) |                 match = re.match(r'^Passage à 2 Processeurs Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLYUM]?) .*', label) | ||||||
|                 if match: |                 if match: | ||||||
|                     price = DellConfiguratorParser.price_str_as_float(line_cells[COLUMN_PRICE]) |                     price = self.price_str_as_float(line_cells[COLUMN_PRICE]) | ||||||
|                     cpu_class = match['cpu_class'].lower() |                     cpu_class = match['cpu_class'].lower() | ||||||
|                     if cpu_class == 'platinium': |                     if cpu_class == 'platinium': | ||||||
|                         cpu_class = 'platinum' |                         cpu_class = 'platinum' | ||||||
|  | @ -752,7 +890,7 @@ class DellMatinfoCsvConfigurator(Configurator): | ||||||
|                 # Ajout d'une barette de 8Go 2667 Mhz DDR-4 - Pour les 4 serveurs |                 # Ajout d'une barette de 8Go 2667 Mhz DDR-4 - Pour les 4 serveurs | ||||||
|                 match = re.match(r'^Ajout d\'une barette de (?P<num_gb>[0-9]+)Go (?P<num_mhz>[0-9][0-9][0-9][0-9]) Mhz (?P<mem_technology>DDR-4) - Pour les 4 serveurs$', label) |                 match = re.match(r'^Ajout d\'une barette de (?P<num_gb>[0-9]+)Go (?P<num_mhz>[0-9][0-9][0-9][0-9]) Mhz (?P<mem_technology>DDR-4) - Pour les 4 serveurs$', label) | ||||||
|                 if match: |                 if match: | ||||||
|                     price_for_four = DellConfiguratorParser.price_str_as_float(line_cells[COLUMN_PRICE]) |                     price_for_four = self.price_str_as_float(line_cells[COLUMN_PRICE]) | ||||||
|                     dimm = Dimm(mem_type='rdimm', num_gb=int(match['num_gb']), num_mhz=int(match['num_mhz'])) |                     dimm = Dimm(mem_type='rdimm', num_gb=int(match['num_gb']), num_mhz=int(match['num_mhz'])) | ||||||
|                     option = Option(dimm, price_for_four/4.0) |                     option = Option(dimm, price_for_four/4.0) | ||||||
|                     ram_options.add_option(option) |                     ram_options.add_option(option) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| from concho.dell import DellMatinfoCsvConfigurator | from concho.dell import DellMatinfoCsvConfigurator | ||||||
| from concho.dell import DellMatinfoConfigurator | from concho.dell import DellMatinfoConfigurator | ||||||
|  | from concho.dell import DellConfiguratorParser2020 | ||||||
|  | from concho.dell import DellConfiguratorParser2021 | ||||||
| from concho.procs_chooser import plot_configurators | from concho.procs_chooser import plot_configurators | ||||||
| from concho.procs_chooser import ConfigPrice | from concho.procs_chooser import ConfigPrice | ||||||
| from concho.procs_chooser import ConfigFlops | from concho.procs_chooser import ConfigFlops | ||||||
|  | @ -10,8 +12,8 @@ def test_all_matinfo_2020_configs(): | ||||||
|     # print(configurator) |     # print(configurator) | ||||||
|     configurators = [ |     configurators = [ | ||||||
|         DellMatinfoCsvConfigurator('c6420-20200716-price.tsv'), |         DellMatinfoCsvConfigurator('c6420-20200716-price.tsv'), | ||||||
|         DellMatinfoConfigurator('rcrc1406676-4834664 - Cat2 Conf4 PowerEdge R640 - Dell.html'), |         DellMatinfoConfigurator('rcrc1406676-4834664 - Cat2 Conf4 PowerEdge R640 - Dell.html', DellConfiguratorParser2020()), | ||||||
|         DellMatinfoConfigurator('rcrc1406676-4824727 - Cat 2 Conf 7 PowerEdge R940 - Dell.html'), |         DellMatinfoConfigurator('rcrc1406676-4824727 - Cat 2 Conf 7 PowerEdge R940 - Dell.html', DellConfiguratorParser2020()), | ||||||
|         # dell.DellPowerEdgeR940(), |         # dell.DellPowerEdgeR940(), | ||||||
|         ] |         ] | ||||||
|      |      | ||||||
|  | @ -22,8 +24,8 @@ def test_credits_2020_configs(): | ||||||
|     # print(configurator) |     # print(configurator) | ||||||
|     configurators = [ |     configurators = [ | ||||||
|         DellMatinfoCsvConfigurator('c6420-20200716-price.tsv'), |         DellMatinfoCsvConfigurator('c6420-20200716-price.tsv'), | ||||||
|         DellMatinfoConfigurator('rcrc1406676-4834664 - Cat2 Conf4 PowerEdge R640 - Dell.html'), |         DellMatinfoConfigurator('rcrc1406676-4834664 - Cat2 Conf4 PowerEdge R640 - Dell.html', DellConfiguratorParser2020()), | ||||||
|         DellMatinfoConfigurator('rcrc1406676-4824727 - Cat 2 Conf 7 PowerEdge R940 - Dell.html'), |         DellMatinfoConfigurator('rcrc1406676-4824727 - Cat 2 Conf 7 PowerEdge R940 - Dell.html', DellConfiguratorParser2020()), | ||||||
|         # dell.DellPowerEdgeR940(), |         # dell.DellPowerEdgeR940(), | ||||||
|         ] |         ] | ||||||
|      |      | ||||||
|  | @ -38,10 +40,30 @@ def test_credits_2020_configs(): | ||||||
|     #     'intel-xeon-gold-6240', |     #     'intel-xeon-gold-6240', | ||||||
|     #     ] |     #     ] | ||||||
| 
 | 
 | ||||||
|     config_filter = lambda config : config.get_price() < 15000.0 |     config_filter = lambda config : config.get_price() < 40000.0 | ||||||
|  | 
 | ||||||
|  |     plot_configurators(configurators=configurators, ram_per_core=4.0e9, xaxis_def=ConfigPrice(), yaxis_def=ConfigFlopsPerEuro(), plot_title='physmol/ts credit 2020 configs', config_filter=config_filter) | ||||||
|  | 
 | ||||||
|  | def test_credits_2021_configs(): | ||||||
|  |     configurators = [ | ||||||
|  |         DellMatinfoConfigurator('20210407 - Cat2 Conf4 PowerEdge R640 - Dell.html', DellConfiguratorParser2021()), | ||||||
|  |         ] | ||||||
|  |      | ||||||
|  |     # config_filter = lambda config : config.cpu.uid in [ | ||||||
|  |     #     'intel-xeon-gold-5222', | ||||||
|  |     #     'intel-xeon-gold-6226r', | ||||||
|  |     #     'intel-xeon-gold-6230r', | ||||||
|  |     #     'intel-xeon-gold-6234r', | ||||||
|  |     #     'intel-xeon-gold-6240r', | ||||||
|  |     #     'intel-xeon-gold-6248r', | ||||||
|  |     #     'intel-xeon-gold-6230', | ||||||
|  |     #     'intel-xeon-gold-6240', | ||||||
|  |     #     ] | ||||||
|  | 
 | ||||||
|  |     config_filter = lambda config : config.get_price() < 40000.0 | ||||||
| 
 | 
 | ||||||
|     plot_configurators(configurators=configurators, ram_per_core=4.0e9, xaxis_def=ConfigPrice(), yaxis_def=ConfigFlopsPerEuro(), plot_title='physmol/ts credit 2020 configs', config_filter=config_filter) |     plot_configurators(configurators=configurators, ram_per_core=4.0e9, xaxis_def=ConfigPrice(), yaxis_def=ConfigFlopsPerEuro(), plot_title='physmol/ts credit 2020 configs', config_filter=config_filter) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     test_credits_2020_configs() |     test_credits_2021_configs() | ||||||
		Loading…
	
		Reference in New Issue