From 726588bbeda8cc43954cfa8e25a53f3d0dcb4a20 Mon Sep 17 00:00:00 2001 From: Wesley Vitor Date: Tue, 21 Mar 2023 09:57:07 -0300 Subject: [PATCH 01/14] =?UTF-8?q?elabora=C3=A7=C3=A3o=20da=20base=20do=20s?= =?UTF-8?q?ugestor.=20#52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ComponentSugestor/sugestor.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/framework/application/ComponentSugestor/sugestor.py diff --git a/src/framework/application/ComponentSugestor/sugestor.py b/src/framework/application/ComponentSugestor/sugestor.py new file mode 100644 index 0000000..1ef5431 --- /dev/null +++ b/src/framework/application/ComponentSugestor/sugestor.py @@ -0,0 +1,58 @@ +from enum import Enum + +from framework.domain.components import EComponentType, Component + +class EComputerPurposes(Enum): + GAMING = 0 + STUDYING = 1 + PROGRAMMING = 2 + WEB_BROWSING = 3 + + +_component_priorities = { + EComputerPurposes.GAMING: { + EComponentType.GPU: 1, + EComponentType.CPU: 0.8, + EComponentType.RAM: 0.7, + EComponentType.PERSISTENCE: 0.5, + }, + EComputerPurposes.STUDYING: { + EComponentType.GPU: 0.2, + EComponentType.CPU: 1, + EComponentType.RAM: 0.7, + EComponentType.PERSISTENCE: 0.6 + }, + #TODO completar o dicionário com as prioridades +} + +_component_specs_priorities = { + EComponentType.GPU: { + "vram": 1, + "consumption": 0.8, + }, + EComponentType.CPU: { + "base_clock_spd": 1, + "n_cores": 0.7, + "ram_clock_max": 0.7, + "consumption": 0.5, + } + #TODO completar o dicionário com as prioridade de especificações +} + + +class ComponentSugestor: + def __init__(self, budget: float, purpose: EComputerPurposes): + self.budget = budget + self.purpose = purpose + + def generate_computer() -> dict[EComponentType, Component]: + # TODO restringe o custo por componente. + # TODO define custo estimado para PS + # TODO Fitra componentes abaixo de seus custos limite. + # TODO if prioridade GPU == 0, filtrar CPUS com GPU integrada + # TODO calcula sua 'pontuação' com base na prioridade de suas especificações. + # TODO Executa problema da mochila, restringindo com base na compatibilidade. + # TODO somar consumo total e definir fonte com o orçamento estabelecido. + # TODO caso o orçamento não seja totalmente preenchido, aumentar o orçamento do item prioritário, mantendo as compatibilidades anteriores + + pass From f99435f02678ea4214d8c18846e49fe858940d16 Mon Sep 17 00:00:00 2001 From: Wesley Vitor Date: Tue, 21 Mar 2023 10:04:04 -0300 Subject: [PATCH 02/14] conformidade com lint. #52 --- .../application/ComponentSugestor/sugestor.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/framework/application/ComponentSugestor/sugestor.py b/src/framework/application/ComponentSugestor/sugestor.py index 1ef5431..d4b5d7c 100644 --- a/src/framework/application/ComponentSugestor/sugestor.py +++ b/src/framework/application/ComponentSugestor/sugestor.py @@ -2,6 +2,7 @@ from framework.domain.components import EComponentType, Component + class EComputerPurposes(Enum): GAMING = 0 STUDYING = 1 @@ -20,9 +21,9 @@ class EComputerPurposes(Enum): EComponentType.GPU: 0.2, EComponentType.CPU: 1, EComponentType.RAM: 0.7, - EComponentType.PERSISTENCE: 0.6 + EComponentType.PERSISTENCE: 0.6, }, - #TODO completar o dicionário com as prioridades + # TODO completar o dicionário com as prioridades } _component_specs_priorities = { @@ -36,7 +37,7 @@ class EComputerPurposes(Enum): "ram_clock_max": 0.7, "consumption": 0.5, } - #TODO completar o dicionário com as prioridade de especificações + # TODO completar o dicionário com as prioridade de especificações } @@ -46,11 +47,11 @@ def __init__(self, budget: float, purpose: EComputerPurposes): self.purpose = purpose def generate_computer() -> dict[EComponentType, Component]: - # TODO restringe o custo por componente. + # TODO restringe o custo por componente. # TODO define custo estimado para PS # TODO Fitra componentes abaixo de seus custos limite. # TODO if prioridade GPU == 0, filtrar CPUS com GPU integrada - # TODO calcula sua 'pontuação' com base na prioridade de suas especificações. + # TODO calcula sua 'pontuação' com base na prioridade de suas especificações. # TODO Executa problema da mochila, restringindo com base na compatibilidade. # TODO somar consumo total e definir fonte com o orçamento estabelecido. # TODO caso o orçamento não seja totalmente preenchido, aumentar o orçamento do item prioritário, mantendo as compatibilidades anteriores From 70430d846fcf6062bc0a1a236079f2d2b7bd5475 Mon Sep 17 00:00:00 2001 From: M3L4O Date: Thu, 23 Mar 2023 09:29:55 -0300 Subject: [PATCH 03/14] criacao da inicial da mochila #52 --- src/framework/domain/components.py | 15 +++++++++++++++ src/framework/domain/exception.py | 10 ++++++++++ src/framework/domain/value_object.py | 6 ++++++ 3 files changed, 31 insertions(+) diff --git a/src/framework/domain/components.py b/src/framework/domain/components.py index 345d839..a819673 100644 --- a/src/framework/domain/components.py +++ b/src/framework/domain/components.py @@ -3,6 +3,7 @@ from typing import List from .entity import Entity +from .exception import KnapsackBurst __all__ = [ "Component", @@ -220,6 +221,20 @@ class PSUComponent(Component): modularity: EPSUModularity +@dataclass +class Knapsack(Entity): + components: list[Component] + max_price: Money + current_price: Money + + def push(self, component: Component, price: Money): + if self.current_price + price > self.max_price: + raise KnapsackBurst() + + # TODO checar restrições + self.components.append(component) + + component_cls_idx = [ Component, MotherboardComponent, diff --git a/src/framework/domain/exception.py b/src/framework/domain/exception.py index 0f45d56..6d501e0 100644 --- a/src/framework/domain/exception.py +++ b/src/framework/domain/exception.py @@ -9,3 +9,13 @@ class DomainException(Exception): def __repr__(self): return f"{self.__class__.__name__}: {self._message}" + + +@dataclass +class KnapsackBurst(DomainException): + _message: str = "A bolsa atingiu o limite de preço." + + +@dataclass +class CurrencyNotEqual(DomainException): + _message: str = "As moedas são diferentes" diff --git a/src/framework/domain/value_object.py b/src/framework/domain/value_object.py index 5813968..86466da 100644 --- a/src/framework/domain/value_object.py +++ b/src/framework/domain/value_object.py @@ -6,6 +6,7 @@ from typing import Tuple from .rule import Rule, BussinessAssertionExtension +from .exception import CurrencyNotEqual __all__ = ["UUID", "UUIDv4", "UUIDv5", "ValueObject", "Money", "URL"] @@ -32,6 +33,11 @@ def __eq__(self, oMoney: "Money") -> bool: def __lt__(self, oMoney: "Money") -> bool: return self.currency == oMoney.currency and self.amount < oMoney.amount + def __add__(self, oMoney: "Money") -> "Money": + if self.currency != oMoney.currency: + raise CurrencyNotEqual() + return Money(self.amount + oMoney.amount) + def __repr__(self): return f"{self.currency} {self.amount:.2f}" From 68bd3b29b64be16fa74c25d67ce9704d089bb857 Mon Sep 17 00:00:00 2001 From: Wesley Vitor Date: Thu, 23 Mar 2023 09:36:02 -0300 Subject: [PATCH 04/14] =?UTF-8?q?inser=C3=A7=C3=A3o=20de=20comandos=20rela?= =?UTF-8?q?cionados=20=C3=A0=20dados=20vol=C3=A1teis.=20#52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Scraper/application/handlers.py | 24 ++++++++++--- src/Scraper/domain/commands.py | 17 +++++++++ .../SQL_alchemy_volatile_data.py | 35 ++++++++++++++----- src/framework/domain/commands.py | 2 +- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/Scraper/application/handlers.py b/src/Scraper/application/handlers.py index d91540a..95f0983 100644 --- a/src/Scraper/application/handlers.py +++ b/src/Scraper/application/handlers.py @@ -63,16 +63,30 @@ def __call__(self, cmd: GetAllDomains): return self.uow.repository.get_all_domains() +class AddVolatileDataHandler(MessageHandler): + def __call__(self, cmd: AddVolatileData): + with self.uow: + self.uow.repository.add(cmd.volatile_data) + + class GetVolatileDataByDomainHandler(MessageHandler): def __call__(self, cmd: GetCategoryURLByDomain): with self.uow: return self.uow.repository.get(filters_eq={"domain": cmd.domain}) -class AddVolatileDataHandler(MessageHandler): - def __call__(self, cmd: AddVolatileData): +class GetVolatileDataByMaxCostHandler(MessageHandler): + def __call__(self, cmd: GetVolatileDataByMaxCost): with self.uow: - self.uow.repository.add(cmd.volatile_data) + return self.uow.repository.get(filters_lt={"cost": cmd.cost}) + + +class GetVolatileDataByComponentUIDHandler(MessageHandler): + def __call__(self, cmd: GetVolatileDataByComponentUID): + with self.uow: + return self.uow.repository.get( + filters_eq={"component_uid": cmd.component_uid} + ) CURL_COMMAND_HANDLER_MAPPER: Dict[Type[Command], Type[MessageHandler]] = { @@ -84,7 +98,9 @@ def __call__(self, cmd: AddVolatileData): CURL_EVENT_HANDLER_MAPPER: Dict[Type[DomainEvent], List[Type[MessageHandler]]] = {} VD_COMMAND_HANDLER_MAPPER: Dict[Type[Command], Type[MessageHandler]] = { - AddVolatileData: AddVolatileDataHandler + AddVolatileData: AddVolatileDataHandler, + GetVolatileDataByMaxCost: GetVolatileDataByMaxCostHandler, + GetVolatileDataByComponentUID: GetVolatileDataByComponentUIDHandler, } VD_EVENT_HANDLER_MAPPER: Dict[Type[DomainEvent], List[Type[MessageHandler]]] = { diff --git a/src/Scraper/domain/commands.py b/src/Scraper/domain/commands.py index eeb4660..c0c45f5 100644 --- a/src/Scraper/domain/commands.py +++ b/src/Scraper/domain/commands.py @@ -3,12 +3,15 @@ from Scraper.domain.entity import CategoryURL from Scraper.domain.aggragate import VolatileData +from framework.domain.value_object import UUID, Money __all__ = [ "AddCategoryURL", "GetAllDomains", "GetCategoryURLByDomain", "AddVolatileData", + "GetVolatileDataByMaxCost", + "GetVolatileDataByComponentUID", ] @@ -30,3 +33,17 @@ class GetCategoryURLByDomain(Command): @dataclass class AddVolatileData(Command): volatile_data: VolatileData + + +@dataclass +class GetVolatileDataByUID(Command): + uid: UUID + + +@dataclass +class GetVolatileDataByMaxCost(Command): + cost: float + + +class GetVolatileDataByComponentUID(Command): + component_uid: UUID diff --git a/src/Scraper/infrastructure/SQL_alchemy_volatile_data.py b/src/Scraper/infrastructure/SQL_alchemy_volatile_data.py index a2c7ba6..e82578c 100644 --- a/src/Scraper/infrastructure/SQL_alchemy_volatile_data.py +++ b/src/Scraper/infrastructure/SQL_alchemy_volatile_data.py @@ -2,8 +2,12 @@ from sqlalchemy.exc import NoResultFound from sqlalchemy import update -from framework.domain.value_object import UUID -from framework.infrastructure.db_management.db_mapping import map_from_to +from framework.domain.value_object import UUID, Money +from framework.infrastructure.db_management.db_mapping import ( + map_from_to, + parse_filters, + filter_instance_from_db, +) from framework.infrastructure.db_management.db_structure import ( VolatileDataInstance, AttrsVolatileData, @@ -20,7 +24,7 @@ class SQLAlchemyVolatileData(IVolatileDataRepository): def __init__(self, session): self._session: Session = session - def volatile_data_to_db_object( + def _volatile_data_to_db_object( self, volatile_data: VolatileData ) -> VolatileDataInstance: mapped_vol_data = map_from_to( @@ -29,14 +33,17 @@ def volatile_data_to_db_object( return VolatileDataInstance(**mapped_vol_data) - def db_object_to_volatile_data( + def _db_object_to_volatile_data( self, volatile_data_instance: VolatileDataInstance ) -> VolatileData: mapped_vol_data = map_from_to( volatile_data_instance, AttrsVolatileData, VolatileData.get_attrs() ) - return VolatileData(**mapped_vol_data) + volatile_data = VolatileData(**mapped_vol_data) + volatile_data.cost = Money(volatile_data_instance.cost) + + return volatile_data def _get_instance_by_uid(self, ref: UUID) -> VolatileDataInstance: query_filter = [VolatileDataInstance.url_id == ref] @@ -52,7 +59,7 @@ def _get_instance_by_uid(self, ref: UUID) -> VolatileDataInstance: return vol_data_inst def _add(self, volatile_data: VolatileData): - db_volatile_data: VolatileDataInstance = self.volatile_data_to_db_object( + db_volatile_data: VolatileDataInstance = self._volatile_data_to_db_object( volatile_data ) @@ -83,10 +90,22 @@ def _add(self, volatile_data: VolatileData): self._session.commit() def _get(self, **kwargs): - return super()._get(**kwargs) + cost = kwargs.get("cost", None) + + filters = parse_filters(VolatileDataInstance, **kwargs) + + volatile_datas_instances = filter_instance_from_db( + self._session, VolatileDataInstance, filters + ) + volatile_datas = [ + self._db_object_to_volatile_data(instance) + for instance in volatile_datas_instances + ] + + return volatile_datas def _get_by_uid(self, ref: UUID): volatile_data_instance = self._get_instance_by_uid(ref) - volatile_data = self.db_object_to_volatile_data(volatile_data_instance) + volatile_data = self._db_object_to_volatile_data(volatile_data_instance) return volatile_data diff --git a/src/framework/domain/commands.py b/src/framework/domain/commands.py index b5a2582..9626415 100644 --- a/src/framework/domain/commands.py +++ b/src/framework/domain/commands.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from ..domain.users import User -from framework.domain.value_object import UUID, UUIDv4 +from framework.domain.value_object import UUID __all__ = [ "AddUser", From fab0eafaef96b46680800c3484a3662e5a74a38f Mon Sep 17 00:00:00 2001 From: M3L4O Date: Fri, 24 Mar 2023 09:31:23 -0300 Subject: [PATCH 05/14] reestruturando arquivos #52 --- src/Suggester/__init__.py | 0 src/Suggester/application/sugestor.py | 59 +++++++++++++++++++++++++++ src/Suggester/domain/exception.py | 8 ++++ src/Suggester/domain/knapsack.py | 22 ++++++++++ 4 files changed, 89 insertions(+) create mode 100644 src/Suggester/__init__.py create mode 100644 src/Suggester/application/sugestor.py create mode 100644 src/Suggester/domain/exception.py create mode 100644 src/Suggester/domain/knapsack.py diff --git a/src/Suggester/__init__.py b/src/Suggester/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/Suggester/application/sugestor.py b/src/Suggester/application/sugestor.py new file mode 100644 index 0000000..a6a5a92 --- /dev/null +++ b/src/Suggester/application/sugestor.py @@ -0,0 +1,59 @@ +from enum import Enum + +from framework.domain.components import Component, EComponentType + + +class EComputerPurposes(Enum): + GAMING = 0 + STUDYING = 1 + PROGRAMMING = 2 + WEB_BROWSING = 3 + + +_component_priorities = { + EComputerPurposes.GAMING: { + EComponentType.GPU: 1, + EComponentType.CPU: 0.8, + EComponentType.RAM: 0.7, + EComponentType.PERSISTENCE: 0.5, + }, + EComputerPurposes.STUDYING: { + EComponentType.GPU: 0.2, + EComponentType.CPU: 1, + EComponentType.RAM: 0.7, + EComponentType.PERSISTENCE: 0.6, + }, + # TODO completar o dicionário com as prioridades +} + +_component_specs_priorities = { + EComponentType.GPU: { + "vram": 1, + "consumption": 0.8, + }, + EComponentType.CPU: { + "base_clock_spd": 1, + "n_cores": 0.7, + "ram_clock_max": 0.7, + "consumption": 0.5, + } + # TODO completar o dicionário com as prioridade de especificações +} + + +class ComponentSugestor: + def __init__(self, budget: float, purpose: EComputerPurposes): + self.budget = budget + self.purpose = purpose + + def generate_computer() -> dict[EComponentType, Component]: + # TODO restringe o custo por componente. + # TODO define custo estimado para PS + # TODO Fitra componentes abaixo de seus custos limite. + # TODO if prioridade GPU == 0, filtrar CPUS com GPU integrada + # TODO calcula sua 'pontuação' com base na prioridade de suas especificações. + # TODO Executa problema da mochila, restringindo com base na compatibilidade. + # TODO somar consumo total e definir fonte com o orçamento estabelecido. + # TODO caso o orçamento não seja totalmente preenchido, aumentar o orçamento do item prioritário, mantendo as compatibilidades anteriores + + pass diff --git a/src/Suggester/domain/exception.py b/src/Suggester/domain/exception.py new file mode 100644 index 0000000..58ea15d --- /dev/null +++ b/src/Suggester/domain/exception.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + +from ...framework.domain.exception import DomainException + + +@dataclass +class KnapsackBurst(DomainException): + _message: str = "A bolsa atingiu o limite de preço." diff --git a/src/Suggester/domain/knapsack.py b/src/Suggester/domain/knapsack.py new file mode 100644 index 0000000..f5ba6e2 --- /dev/null +++ b/src/Suggester/domain/knapsack.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass + +from framework.domain.components import Component +from framework.domain.value_object import Money + +from .exception import KnapsackBurst + +__all__ = ["Knapsack"] + + +@dataclass +class Knapsack: + components: list[Component] + max_price: Money + current_price: Money + + def push(self, component: Component, price: Money): + if self.current_price + price > self.max_price: + raise KnapsackBurst() + + # TODO checar restrições + self.components.append(component) From ab60f1f4203a9e1207c061ea03d47c7525b9d48c Mon Sep 17 00:00:00 2001 From: M3L4O Date: Fri, 24 Mar 2023 09:33:57 -0300 Subject: [PATCH 06/14] reestruturando arquivos novamente #52 --- .../application/ComponentSugestor/sugestor.py | 59 ------------------- src/framework/domain/components.py | 15 ----- src/framework/domain/exception.py | 5 -- 3 files changed, 79 deletions(-) delete mode 100644 src/framework/application/ComponentSugestor/sugestor.py diff --git a/src/framework/application/ComponentSugestor/sugestor.py b/src/framework/application/ComponentSugestor/sugestor.py deleted file mode 100644 index d4b5d7c..0000000 --- a/src/framework/application/ComponentSugestor/sugestor.py +++ /dev/null @@ -1,59 +0,0 @@ -from enum import Enum - -from framework.domain.components import EComponentType, Component - - -class EComputerPurposes(Enum): - GAMING = 0 - STUDYING = 1 - PROGRAMMING = 2 - WEB_BROWSING = 3 - - -_component_priorities = { - EComputerPurposes.GAMING: { - EComponentType.GPU: 1, - EComponentType.CPU: 0.8, - EComponentType.RAM: 0.7, - EComponentType.PERSISTENCE: 0.5, - }, - EComputerPurposes.STUDYING: { - EComponentType.GPU: 0.2, - EComponentType.CPU: 1, - EComponentType.RAM: 0.7, - EComponentType.PERSISTENCE: 0.6, - }, - # TODO completar o dicionário com as prioridades -} - -_component_specs_priorities = { - EComponentType.GPU: { - "vram": 1, - "consumption": 0.8, - }, - EComponentType.CPU: { - "base_clock_spd": 1, - "n_cores": 0.7, - "ram_clock_max": 0.7, - "consumption": 0.5, - } - # TODO completar o dicionário com as prioridade de especificações -} - - -class ComponentSugestor: - def __init__(self, budget: float, purpose: EComputerPurposes): - self.budget = budget - self.purpose = purpose - - def generate_computer() -> dict[EComponentType, Component]: - # TODO restringe o custo por componente. - # TODO define custo estimado para PS - # TODO Fitra componentes abaixo de seus custos limite. - # TODO if prioridade GPU == 0, filtrar CPUS com GPU integrada - # TODO calcula sua 'pontuação' com base na prioridade de suas especificações. - # TODO Executa problema da mochila, restringindo com base na compatibilidade. - # TODO somar consumo total e definir fonte com o orçamento estabelecido. - # TODO caso o orçamento não seja totalmente preenchido, aumentar o orçamento do item prioritário, mantendo as compatibilidades anteriores - - pass diff --git a/src/framework/domain/components.py b/src/framework/domain/components.py index a819673..345d839 100644 --- a/src/framework/domain/components.py +++ b/src/framework/domain/components.py @@ -3,7 +3,6 @@ from typing import List from .entity import Entity -from .exception import KnapsackBurst __all__ = [ "Component", @@ -221,20 +220,6 @@ class PSUComponent(Component): modularity: EPSUModularity -@dataclass -class Knapsack(Entity): - components: list[Component] - max_price: Money - current_price: Money - - def push(self, component: Component, price: Money): - if self.current_price + price > self.max_price: - raise KnapsackBurst() - - # TODO checar restrições - self.components.append(component) - - component_cls_idx = [ Component, MotherboardComponent, diff --git a/src/framework/domain/exception.py b/src/framework/domain/exception.py index 6d501e0..964a20a 100644 --- a/src/framework/domain/exception.py +++ b/src/framework/domain/exception.py @@ -11,11 +11,6 @@ def __repr__(self): return f"{self.__class__.__name__}: {self._message}" -@dataclass -class KnapsackBurst(DomainException): - _message: str = "A bolsa atingiu o limite de preço." - - @dataclass class CurrencyNotEqual(DomainException): _message: str = "As moedas são diferentes" From 925200ebb9359a9b06e6cc9a7849ca51fb6dd6cc Mon Sep 17 00:00:00 2001 From: Wesley Vitor Date: Mon, 27 Mar 2023 20:53:09 -0300 Subject: [PATCH 07/14] =?UTF-8?q?filtragem=20de=20componenetes=20e=20dados?= =?UTF-8?q?=20vol=C3=A1teis=20com=20base=20em=20menores=20pre=C3=A7os=20de?= =?UTF-8?q?ntro=20de=20um=20intervalo.=20#52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScraperOrchestration/wrapper.py | 8 +- src/Scraper/application/handlers.py | 56 ++++++++- src/Scraper/domain/aggragate.py | 15 ++- src/Scraper/domain/commands.py | 24 ++++ src/Scraper/domain/repositories.py | 7 +- .../SQL_alchemy_volatile_data.py | 82 +++++++++++--- src/SearchEngine/domain/commands.py | 6 +- .../SQL_alchemy_repository.py | 1 + .../ComponentManagment/component_mapper.py | 1 + src/Suggester/application/sugestor.py | 59 ---------- src/Suggester/application/suggester.py | 107 ++++++++++++++++++ src/framework/domain/components.py | 88 ++------------ src/framework/domain/components_enums.py | 62 ++++++++++ src/framework/domain/repository.py | 10 ++ .../db_filling/store_components.py | 39 ++++++- .../db_management/db_mapping.py | 14 ++- .../db_management/db_structure.py | 54 +++++---- 17 files changed, 437 insertions(+), 196 deletions(-) delete mode 100644 src/Suggester/application/sugestor.py create mode 100644 src/Suggester/application/suggester.py create mode 100644 src/framework/domain/components_enums.py diff --git a/src/Scraper/application/ScraperOrchestration/wrapper.py b/src/Scraper/application/ScraperOrchestration/wrapper.py index 0c5c20d..da627c0 100644 --- a/src/Scraper/application/ScraperOrchestration/wrapper.py +++ b/src/Scraper/application/ScraperOrchestration/wrapper.py @@ -25,6 +25,7 @@ from framework.infrastructure.connection_util import _get_engine from framework.infrastructure.db_management.db_connection import create_session from Scraper.domain.commands import * +from framework.domain.components import Component from framework.application.handler import MessageBus @@ -76,12 +77,15 @@ async def run_scraping(self): component_manager = SQLAlchemyRepository( self.session ) # placeholder - component = component_manager.get(filters_gt={"consumption": -1})[ - 0 + component: Component = component_manager.get( + filters_gt={"consumption": -1} + )[ + int(uniform(0, 10)) ] # placeholder volatile_data = VolatileData( _id=UUIDv5(url.url), component_id=component.uid, + component_type=component.type, url=url, cost=cost, availability=availability, diff --git a/src/Scraper/application/handlers.py b/src/Scraper/application/handlers.py index 95f0983..bd80f9a 100644 --- a/src/Scraper/application/handlers.py +++ b/src/Scraper/application/handlers.py @@ -1,4 +1,4 @@ -from typing import Dict, Type, List +from typing import Type, Dict, List from smtplib import SMTP from ssl import create_default_context from email.mime.text import MIMEText @@ -12,10 +12,11 @@ ) from SearchEngine.application.unit_of_work import SQLAlchemyUnitOfWork from framework.domain.components import Component -from framework.application.handler import MessageHandler, Command -from ..domain.repositories import ICategoryURLRepository +from framework.domain.events import Command, DomainEvent +from framework.application.handler import MessageHandler +from Scraper.domain.aggragate import VolatileData +from ..domain.repositories import ICategoryURLRepository, IVolatileDataRepository from ..domain.events import LowerPriceRegisteredEvent -from framework.domain.events import DomainEvent from ..domain.commands import * @@ -81,6 +82,14 @@ def __call__(self, cmd: GetVolatileDataByMaxCost): return self.uow.repository.get(filters_lt={"cost": cmd.cost}) +class GetLowerCostVolatileDatasHandler(MessageHandler): + def __call__(self, cmd: GetLowerCostVolatileDatas): + with self.uow: + cost_filter = {} if cmd.cost >= 0 else {"filters_lt": {"cost": cmd.cost}} + if isinstance(self.uow.repository, IVolatileDataRepository): + return self.uow.repository.get_lower_costs(**cost_filter) + + class GetVolatileDataByComponentUIDHandler(MessageHandler): def __call__(self, cmd: GetVolatileDataByComponentUID): with self.uow: @@ -89,6 +98,42 @@ def __call__(self, cmd: GetVolatileDataByComponentUID): ) +def _get_components_from_volatile_data(volatile_data: list[VolatileData]): + message_bus = get_message_bus( + SE_EVENT_HANDLER_MAPPER, SE_COMMAND_HANDLER_MAPPER, SQLAlchemyUnitOfWork + ) + + components = [ + message_bus.handle(GetComponentByUID(vd.component_id)) for vd in volatile_data + ] + + return components + + +class GetComponentsFromVolatileDataHandler(MessageHandler): + def __call__(self, cmd: GetComponentsFromVolatileData): + with self.uow: + return _get_components_from_volatile_data(cmd.volatile_data) + + +class GetVolatileDataByCostIntervalHandler(MessageHandler): + def __call__(self, cmd: GetVolatileDataByCostInterval): + with self.uow: + filters_lt = {"cost": cmd.max_cost} + filters_gt = {"cost": cmd.min_cost} + filters_eq = {"component_type": cmd.component_type} + + volatile_data = self.uow.repository.get_lower_costs( + filters_eq=filters_eq, + filters_lt=filters_lt, + filters_gt=filters_gt, + ) + + components = _get_components_from_volatile_data(volatile_data) + + return components, volatile_data + + CURL_COMMAND_HANDLER_MAPPER: Dict[Type[Command], Type[MessageHandler]] = { AddCategoryURL: AddCategoryURLHandler, GetCategoryURLByDomain: GetVolatileDataByDomainHandler, @@ -101,6 +146,9 @@ def __call__(self, cmd: GetVolatileDataByComponentUID): AddVolatileData: AddVolatileDataHandler, GetVolatileDataByMaxCost: GetVolatileDataByMaxCostHandler, GetVolatileDataByComponentUID: GetVolatileDataByComponentUIDHandler, + GetLowerCostVolatileDatas: GetLowerCostVolatileDatasHandler, + GetComponentsFromVolatileData: GetComponentsFromVolatileDataHandler, + GetVolatileDataByCostInterval: GetVolatileDataByCostIntervalHandler, } VD_EVENT_HANDLER_MAPPER: Dict[Type[DomainEvent], List[Type[MessageHandler]]] = { diff --git a/src/Scraper/domain/aggragate.py b/src/Scraper/domain/aggragate.py index 9571c2b..c85d834 100644 --- a/src/Scraper/domain/aggragate.py +++ b/src/Scraper/domain/aggragate.py @@ -2,17 +2,26 @@ from dataclasses import dataclass, field from typing import List +from framework.domain.components_enums import EComponentType from framework.domain.entity import AggregateRoot from framework.domain.value_object import UUID, Money, URL -_AttrsVolatileData = ["_id", "url", "component_id", "cost", "availability", "timestamp"] +_AttrsVolatileData = [ + "_id", + "url", + "component_id", + "component_type", + "cost", + "availability", + "timestamp", +] @dataclass(kw_only=True) class VolatileData(AggregateRoot): - # url_id: UUID url: URL component_id: UUID + component_type: EComponentType cost: Money availability: bool @@ -29,6 +38,7 @@ def generateVolatileDataPoint( self, _id: UUID, component_id: UUID, + component_type: EComponentType, url: URL, cost: Money, availability: bool, @@ -36,6 +46,7 @@ def generateVolatileDataPoint( return VolatileData( _id=_id, component_id=component_id, + component_type=component_type, url=url, cost=cost, availability=availability, diff --git a/src/Scraper/domain/commands.py b/src/Scraper/domain/commands.py index c0c45f5..f215aef 100644 --- a/src/Scraper/domain/commands.py +++ b/src/Scraper/domain/commands.py @@ -4,6 +4,8 @@ from Scraper.domain.entity import CategoryURL from Scraper.domain.aggragate import VolatileData from framework.domain.value_object import UUID, Money +from framework.domain.components import Component +from framework.domain.components_enums import * __all__ = [ "AddCategoryURL", @@ -12,6 +14,9 @@ "AddVolatileData", "GetVolatileDataByMaxCost", "GetVolatileDataByComponentUID", + "GetLowerCostVolatileDatas", + "GetComponentsFromVolatileData", + "GetVolatileDataByCostInterval", ] @@ -45,5 +50,24 @@ class GetVolatileDataByMaxCost(Command): cost: float +@dataclass +class GetLowerCostVolatileDatas(Command): + cost: float = -1 + pass + + +@dataclass class GetVolatileDataByComponentUID(Command): component_uid: UUID + + +@dataclass +class GetComponentsFromVolatileData(Command): + volatile_data: list[VolatileData] + + +@dataclass +class GetVolatileDataByCostInterval(Command): + component_type: EComponentType + min_cost: float + max_cost: float diff --git a/src/Scraper/domain/repositories.py b/src/Scraper/domain/repositories.py index 8bb042a..42bf098 100644 --- a/src/Scraper/domain/repositories.py +++ b/src/Scraper/domain/repositories.py @@ -6,6 +6,7 @@ from framework.domain.repository import ( AbstractRepository, AbstractCategoryURLRepository, + AbstractVolatileDataRepository, ) from framework.domain.exception import DomainException from Scraper.domain.aggragate import VolatileData @@ -62,7 +63,7 @@ def __post_init__(self): f"URL de categoria com UID {self.entity_id} já existe." -class IVolatileDataRepository(AbstractRepository, metaclass=ABCMeta): +class IVolatileDataRepository(AbstractVolatileDataRepository, metaclass=ABCMeta): @abstractmethod def __init__(self, session): raise NotImplemented @@ -79,6 +80,10 @@ def _get_by_uid(self, ref: UUID): def _get(self, **kwargs): raise NotImplemented + @abstractmethod + def _get_lower_costs(self, **kwargs): + raise NotImplemented + def __repr__(self): raise NotImplemented diff --git a/src/Scraper/infrastructure/SQL_alchemy_volatile_data.py b/src/Scraper/infrastructure/SQL_alchemy_volatile_data.py index e82578c..cd0b690 100644 --- a/src/Scraper/infrastructure/SQL_alchemy_volatile_data.py +++ b/src/Scraper/infrastructure/SQL_alchemy_volatile_data.py @@ -1,6 +1,8 @@ from sqlalchemy.orm.session import Session from sqlalchemy.exc import NoResultFound -from sqlalchemy import update +from sqlalchemy import func +from sqlalchemy import Column +from datetime import datetime, timedelta from framework.domain.value_object import UUID, Money from framework.infrastructure.db_management.db_mapping import ( @@ -10,6 +12,7 @@ ) from framework.infrastructure.db_management.db_structure import ( VolatileDataInstance, + LowerCostsInstance, AttrsVolatileData, ) from Scraper.domain.events import LowerPriceRegisteredEvent @@ -45,6 +48,18 @@ def _db_object_to_volatile_data( return volatile_data + def _db_volatile_data_to_lower_cost( + self, volatile_data: VolatileData + ) -> LowerCostsInstance: + lower = LowerCostsInstance() + lower.component_uid = volatile_data.component_id + lower.component_type = volatile_data.component_type + lower.volatile_data_uid = volatile_data.uid + lower.cost = volatile_data.cost.amount + lower.timestamp = volatile_data.timestamp + + return lower + def _get_instance_by_uid(self, ref: UUID) -> VolatileDataInstance: query_filter = [VolatileDataInstance.url_id == ref] @@ -58,40 +73,65 @@ def _get_instance_by_uid(self, ref: UUID) -> VolatileDataInstance: return vol_data_inst - def _add(self, volatile_data: VolatileData): - db_volatile_data: VolatileDataInstance = self._volatile_data_to_db_object( - volatile_data - ) - - db_volatile_data.url = volatile_data.url.url # TODO modificar dicionário - db_volatile_data.cost = volatile_data.cost.amount + def _get_lower_cost_by_uid(self, ref: UUID | Column) -> LowerCostsInstance | None: + query_filter = [LowerCostsInstance.component_uid == ref] try: - current_volatile_data = self._get_instance_by_uid(volatile_data.uid) + vol_data_inst: LowerCostsInstance = ( + self._session.query(LowerCostsInstance).filter(*query_filter).one() + ) + + except Exception: + return None + + return vol_data_inst + + def _update_lower_cost(self, volatile_data: VolatileData) -> None: + current_lower = self._get_lower_cost_by_uid(volatile_data.component_id) + min_timestamp = datetime.now() - timedelta(days=1) + + if current_lower is None: + lower = self._db_volatile_data_to_lower_cost(volatile_data) + self._session.add(lower) + return - if ( - db_volatile_data.cost + 0.1 < current_volatile_data.cost - and db_volatile_data.availability - ): + price_reduced = volatile_data.cost.amount + 0.1 < current_lower.cost + current_outdate = current_lower.timestamp < min_timestamp.date() + + if ( + (price_reduced or current_outdate) and volatile_data.availability + ) or current_lower.volatile_data_uid == volatile_data: + current_lower.cost = volatile_data.cost.amount + current_lower.volatile_data_uid = volatile_data.uid + current_lower.timestamp = volatile_data.timestamp + + if price_reduced: volatile_data.events.append( LowerPriceRegisteredEvent( volatile_data.component_id, volatile_data.cost ) ) - print("redução do preço") + def _add(self, volatile_data: VolatileData): + db_volatile_data: VolatileDataInstance = self._volatile_data_to_db_object( + volatile_data + ) + + db_volatile_data.url = volatile_data.url.url # TODO modificar dicionário + db_volatile_data.cost = volatile_data.cost.amount + try: + current_volatile_data = self._get_instance_by_uid(volatile_data.uid) current_volatile_data.cost = db_volatile_data.cost current_volatile_data.timestamp = db_volatile_data.timestamp except EntityUIDNotFoundException: self._session.add(db_volatile_data) + self._update_lower_cost(volatile_data) self._session.commit() def _get(self, **kwargs): - cost = kwargs.get("cost", None) - filters = parse_filters(VolatileDataInstance, **kwargs) volatile_datas_instances = filter_instance_from_db( @@ -109,3 +149,13 @@ def _get_by_uid(self, ref: UUID): volatile_data = self._db_object_to_volatile_data(volatile_data_instance) return volatile_data + + def _get_lower_costs(self, **kwargs): + filters = parse_filters(LowerCostsInstance, **kwargs) + + lower_costs = filter_instance_from_db( + self._session, LowerCostsInstance.volatile_data_uid, filters + ) + volatile_datas = [self._get_by_uid(vd_uid[0]) for vd_uid in lower_costs] + + return volatile_datas diff --git a/src/SearchEngine/domain/commands.py b/src/SearchEngine/domain/commands.py index a71e036..1f59443 100644 --- a/src/SearchEngine/domain/commands.py +++ b/src/SearchEngine/domain/commands.py @@ -5,7 +5,11 @@ from framework.domain.value_object import UUID, UUIDv4 from framework.domain.events import Command -__all__ = ["GetComponentByUID", "ListComponentsByType", "AddComponent"] +__all__ = [ + "GetComponentByUID", + "ListComponentsByType", + "AddComponent", +] @dataclass diff --git a/src/SearchEngine/infrastructure/ComponentManagment/SQL_alchemy_repository.py b/src/SearchEngine/infrastructure/ComponentManagment/SQL_alchemy_repository.py index 559d272..2f40be7 100644 --- a/src/SearchEngine/infrastructure/ComponentManagment/SQL_alchemy_repository.py +++ b/src/SearchEngine/infrastructure/ComponentManagment/SQL_alchemy_repository.py @@ -4,6 +4,7 @@ from typing import List from framework.domain.components import * +from framework.domain.components_enums import * from framework.domain.value_object import UUID from SearchEngine.domain.repositories import ( ISQLAlchemyRepository, diff --git a/src/SearchEngine/infrastructure/ComponentManagment/component_mapper.py b/src/SearchEngine/infrastructure/ComponentManagment/component_mapper.py index 40c288c..5e1ca14 100644 --- a/src/SearchEngine/infrastructure/ComponentManagment/component_mapper.py +++ b/src/SearchEngine/infrastructure/ComponentManagment/component_mapper.py @@ -1,5 +1,6 @@ from typing import List from framework.domain.components import * +from framework.domain.components_enums import * from framework.infrastructure.db_management.db_mapping import map_from_to from framework.infrastructure.db_management.db_structure import ( ComponentInstance, diff --git a/src/Suggester/application/sugestor.py b/src/Suggester/application/sugestor.py deleted file mode 100644 index a6a5a92..0000000 --- a/src/Suggester/application/sugestor.py +++ /dev/null @@ -1,59 +0,0 @@ -from enum import Enum - -from framework.domain.components import Component, EComponentType - - -class EComputerPurposes(Enum): - GAMING = 0 - STUDYING = 1 - PROGRAMMING = 2 - WEB_BROWSING = 3 - - -_component_priorities = { - EComputerPurposes.GAMING: { - EComponentType.GPU: 1, - EComponentType.CPU: 0.8, - EComponentType.RAM: 0.7, - EComponentType.PERSISTENCE: 0.5, - }, - EComputerPurposes.STUDYING: { - EComponentType.GPU: 0.2, - EComponentType.CPU: 1, - EComponentType.RAM: 0.7, - EComponentType.PERSISTENCE: 0.6, - }, - # TODO completar o dicionário com as prioridades -} - -_component_specs_priorities = { - EComponentType.GPU: { - "vram": 1, - "consumption": 0.8, - }, - EComponentType.CPU: { - "base_clock_spd": 1, - "n_cores": 0.7, - "ram_clock_max": 0.7, - "consumption": 0.5, - } - # TODO completar o dicionário com as prioridade de especificações -} - - -class ComponentSugestor: - def __init__(self, budget: float, purpose: EComputerPurposes): - self.budget = budget - self.purpose = purpose - - def generate_computer() -> dict[EComponentType, Component]: - # TODO restringe o custo por componente. - # TODO define custo estimado para PS - # TODO Fitra componentes abaixo de seus custos limite. - # TODO if prioridade GPU == 0, filtrar CPUS com GPU integrada - # TODO calcula sua 'pontuação' com base na prioridade de suas especificações. - # TODO Executa problema da mochila, restringindo com base na compatibilidade. - # TODO somar consumo total e definir fonte com o orçamento estabelecido. - # TODO caso o orçamento não seja totalmente preenchido, aumentar o orçamento do item prioritário, mantendo as compatibilidades anteriores - - pass diff --git a/src/Suggester/application/suggester.py b/src/Suggester/application/suggester.py new file mode 100644 index 0000000..08ca4ae --- /dev/null +++ b/src/Suggester/application/suggester.py @@ -0,0 +1,107 @@ +from enum import Enum + +from framework.domain.components import Component, EComponentType, CPUComponent +from Scraper.domain.aggragate import VolatileData +from framework.infrastructure.connection_util import get_message_bus +from Scraper.domain.commands import * +from SearchEngine.domain.commands import * +from Scraper.application.handlers import ( + VD_COMMAND_HANDLER_MAPPER, + VD_EVENT_HANDLER_MAPPER, +) +from SearchEngine.application.handlers import ( + SE_COMMAND_HANDLER_MAPPER, + SE_EVENT_HANDLER_MAPPER, +) +from Scraper.application.unit_of_work import SQLAlchemyVolatileDataUnitOfWork +from SearchEngine.application.unit_of_work import SQLAlchemyUnitOfWork + + +class EComputerPurposes(Enum): + GAMING = 0 + STUDYING = 1 + PROGRAMMING = 2 + WEB_BROWSING = 3 + + +_component_priorities = { + EComputerPurposes.GAMING: { + EComponentType.GPU: (0.2, 0.5), + EComponentType.CPU: (0.1, 0.3), + EComponentType.RAM: (0.05, 0.2), + EComponentType.PERSISTENCE: (0.1, 0.25), + EComponentType.MOTHERBOARD: (0.05, 0.25), + EComponentType.PSU: (0.1, 0.4), + }, + # TODO completar o dicionário com as prioridades +} + +_gpu_price_thresh = 3500 + + +class ComponentSuggester: + def __init__(self, budget: float, purpose: EComputerPurposes): + self.budget = budget + self.purpose = purpose + + self.vd_message_bus = get_message_bus( + VD_EVENT_HANDLER_MAPPER, + VD_COMMAND_HANDLER_MAPPER, + SQLAlchemyVolatileDataUnitOfWork, + ) + + self.se_message_bus = get_message_bus( + SE_EVENT_HANDLER_MAPPER, SE_COMMAND_HANDLER_MAPPER, SQLAlchemyUnitOfWork + ) + + def _get_components_by_cost_interval( + self, priorities: dict[EComponentType, tuple], component_type: EComponentType + ) -> tuple[list[Component], list[VolatileData]]: + priority = priorities[component_type] + + component, volatile_data = self.vd_message_bus.handle( + GetVolatileDataByCostInterval( + component_type, + self.budget * priority[0], + self.budget * priority[1], + ) + ) + + return component, volatile_data + + def generate_computer(self) -> list[Component]: + component_priorities = _component_priorities[self.purpose] + + cpus, cpus_vd = self._get_components_by_cost_interval( + component_priorities, EComponentType.CPU + ) + gpus, gpus_vd = self._get_components_by_cost_interval( + component_priorities, EComponentType.GPU + ) + rams, rams_vd = self._get_components_by_cost_interval( + component_priorities, EComponentType.RAM + ) + motherboards, motherboards_vd = self._get_components_by_cost_interval( + component_priorities, EComponentType.MOTHERBOARD + ) + persistences, persistences_vd = self._get_components_by_cost_interval( + component_priorities, EComponentType.PERSISTENCE + ) + psus, psus_vd = self._get_components_by_cost_interval( + component_priorities, EComponentType.PSU + ) + + for cpu, cpu_vd in zip(cpus, cpus_vd): + print(cpu) + print(cpu_vd, "\n") + + # if self.budget < _gpu_price_thresh: + # a = [cpu for cpu in cpus if cpu.integrated_gpu] + # input(a) + + # TODO calcula sua 'pontuação' com base na prioridade de suas especificações. + # TODO Executa problema da mochila, restringindo com base na compatibilidade. + # TODO somar consumo total e definir fonte com o orçamento estabelecido. + # TODO caso o orçamento não seja totalmente preenchido, aumentar o orçamento do item prioritário, mantendo as compatibilidades anteriores + + return [] diff --git a/src/framework/domain/components.py b/src/framework/domain/components.py index 345d839..8e8adc7 100644 --- a/src/framework/domain/components.py +++ b/src/framework/domain/components.py @@ -3,6 +3,7 @@ from typing import List from .entity import Entity +from .components_enums import * __all__ = [ "Component", @@ -12,80 +13,12 @@ "RAMComponent", "PersistenceComponent", "PSUComponent", - "EComponentType", - "EChipsetType", - "EBoardSize", - "EPersistenceIOType", - "EPSURate", - "ERAMGeneration", - "ESocketType", - "EPCIeGeneration", - "EPSUModularity", "component_cls_idx", "_component_attrs_idx", ] -class EComponentType(IntEnum): - _BASE = 0 - MOTHERBOARD = 1 - CPU = 2 - GPU = 3 - RAM = 4 - PERSISTENCE = 5 - PSU = 6 - - -class EPCIeGeneration(IntEnum): - GEN3 = 0 - GEN4 = 1 - GEN5 = 2 - - -class EPSURate(IntEnum): - WHITE = 0 - BRONZE = 1 - SILVER = 2 - GOLD = 3 - PLATINUM = 4 - TITANIUM = 5 - - -class EPersistenceIOType(IntEnum): - SATA = 0 - M2 = 1 - NVME = 2 - - -class ERAMGeneration(IntEnum): - DDR3 = 0 - DDR4 = 1 - DDR5 = 2 - - -class EBoardSize(IntEnum): - PICO_ITX = 0 - NANO_ITX = 1 - MINI_ITX = 2 - MICRO_ATX = 3 - STANDARD = 4 - - -class EChipsetType(IntEnum): - TIPO = 0 - - -class ESocketType(IntEnum): - TIPO = 0 - - -class EPSUModularity(IntEnum): - NON = 0 - SEMI = 1 - FULL = 2 - - -_AttrsComponent = ["type", "manufacturer", "model"] +_AttrsComponent = ["type", "manufacturer", "model", "rank"] _AttrsCommon = ["_id"] @@ -95,6 +28,8 @@ class Component(Entity): manufacturer: str model: str + rank: int = 0 + def __hash__(self): return hash(self.uid) @@ -119,7 +54,6 @@ def get_attrs(cls, ctype: EComponentType) -> List[str]: "n_vga", "n_hdmi", "n_display_port", - "pcie_gen", "n_pcie_x1", "n_pcie_x4", "n_pcie_x8", @@ -130,7 +64,7 @@ def get_attrs(cls, ctype: EComponentType) -> List[str]: @dataclass(kw_only=True, eq=False) class MotherboardComponent(Component): type: EComponentType = EComponentType.MOTHERBOARD - chipset: EChipsetType + chipset: str board_size: EBoardSize n_ram_slots: int consumption: int @@ -142,7 +76,6 @@ class MotherboardComponent(Component): n_hdmi: int n_display_port: int - pcie_gen: EPCIeGeneration n_pcie_x1: int n_pcie_x4: int n_pcie_x8: int @@ -154,7 +87,6 @@ class MotherboardComponent(Component): "n_cores", "base_clock_spd", "boost_clock_spd", - "ram_clock_max", "consumption", "integrated_gpu", "overclock", @@ -164,12 +96,11 @@ class MotherboardComponent(Component): @dataclass(kw_only=True, eq=False) class CPUComponent(Component): type: EComponentType = EComponentType.CPU - socket: ESocketType + socket: str n_cores: int base_clock_spd: float boost_clock_spd: float - ram_clock_max: int consumption: int integrated_gpu: str @@ -187,26 +118,27 @@ class GPUComponent(Component): vram_spd: int -_AttrsRAM = ["generation", "frequency"] +_AttrsRAM = ["msize", "generation", "frequency"] @dataclass(kw_only=True, eq=False) class RAMComponent(Component): type: EComponentType = EComponentType.RAM + msize: int generation: ERAMGeneration frequency: int -_AttrsPersistence = ["storage", "spd", "io", "is_HDD"] +_AttrsPersistence = ["storage", "rpm", "io", "is_HDD"] @dataclass(kw_only=True, eq=False) class PersistenceComponent(Component): type: EComponentType = EComponentType.PERSISTENCE storage: int - spd: int io: EPersistenceIOType is_HDD: bool + rpm: int = 0 _AttrsPSU = ["power", "rate", "modularity"] diff --git a/src/framework/domain/components_enums.py b/src/framework/domain/components_enums.py new file mode 100644 index 0000000..d26005f --- /dev/null +++ b/src/framework/domain/components_enums.py @@ -0,0 +1,62 @@ +from enum import IntEnum, auto + +__all__ = [ + "EComponentType", + "EBoardSize", + "EPersistenceIOType", + "EPSURate", + "ERAMGeneration", + "EPCIeGeneration", + "EPSUModularity", +] + + +class EComponentType(IntEnum): + _BASE = 0 + MOTHERBOARD = 1 + CPU = 2 + GPU = 3 + RAM = 4 + PERSISTENCE = 5 + PSU = 6 + + +class EPCIeGeneration(IntEnum): + GEN3 = auto() + GEN4 = auto() + GEN5 = auto() + + +class EPSURate(IntEnum): + Plus_Gold = auto() + Plus_Platinum = auto() + Plus_Bronze = auto() + Plus_Titanium = auto() + Plus = auto() + Plus_Silver = auto() + + +class EPersistenceIOType(IntEnum): + SATA = auto() + NVM = auto() + + +class ERAMGeneration(IntEnum): + DDR3 = auto() + DDR3L = auto() + DDR4 = auto() + DDR5 = auto() + + +class EBoardSize(IntEnum): + PICO_ITX = auto() + NANO_ITX = auto() + MINI_ITX = auto() + MICRO_ATX = auto() + STANDARD = auto() + + +class EPSUModularity(IntEnum): + NON = auto() + SEMI = auto() + FULL = auto() diff --git a/src/framework/domain/repository.py b/src/framework/domain/repository.py index c4a5cd2..ba27aee 100644 --- a/src/framework/domain/repository.py +++ b/src/framework/domain/repository.py @@ -49,6 +49,16 @@ def _get_all_domains(self): raise NotImplemented +class AbstractVolatileDataRepository(AbstractRepository): + def get_lower_costs(self, **kwargs): + _res = self._get_lower_costs(**kwargs) + return _res + + @abstractmethod + def _get_lower_costs(self, **kwargs): + raise NotImplemented + + class AbstractUserRepository(AbstractRepository): def add(self, item: UniqueObject, password: str): self._add(item, password) diff --git a/src/framework/infrastructure/db_filling/store_components.py b/src/framework/infrastructure/db_filling/store_components.py index 0a8bf00..cb09f71 100644 --- a/src/framework/infrastructure/db_filling/store_components.py +++ b/src/framework/infrastructure/db_filling/store_components.py @@ -5,7 +5,9 @@ from typing import Type import json +from framework.domain.repositories import EntityCollisionException from framework.domain.components import * +from framework.domain.components_enums import * from framework.infrastructure.connection_util import get_message_bus from SearchEngine.application.handlers import ( SE_COMMAND_HANDLER_MAPPER, @@ -14,8 +16,17 @@ from SearchEngine.application.unit_of_work import SQLAlchemyUnitOfWork from SearchEngine.domain.commands import AddComponent +_enumerators = { + "board_size": EBoardSize, + "pcie_gen": EPCIeGeneration, + "generation": ERAMGeneration, + "io": EPersistenceIOType, + "rate": EPSURate, + "modularity": EPSUModularity, +} -def store_components_from_json(json_dir: str, component_cls: Type[Component]): + +def store_components_from_json(json_dir: str, component_cls: Type[Component], **kwargs): component_message_bus = get_message_bus( SE_EVENT_HANDLER_MAPPER, SE_COMMAND_HANDLER_MAPPER, SQLAlchemyUnitOfWork ) @@ -24,13 +35,31 @@ def store_components_from_json(json_dir: str, component_cls: Type[Component]): json_objects = json.load(json_file) for json_object in json_objects: - component = component_cls(_id=Component.next_id(), **json_object) - - component_message_bus.handle(AddComponent(component)) + json_filtered = { + k: v for k, v in json_object.items() if k not in _enumerators + } + json_modified = { + k: _enumerators[k][v] + for k, v in json_object.items() + if k in _enumerators + } + component = component_cls( + _id=Component.next_id(), **json_filtered, **json_modified, **kwargs + ) + print(component) + try: + component_message_bus.handle(AddComponent(component)) + except EntityCollisionException: + pass def main(): - json_dirs = {GPUComponent: r"..\res\data\raw\gpu.json"} + json_dirs = { + GPUComponent: r"..\res\data\raw\gpu.json", + CPUComponent: r"..\res\data\raw\cpu.json", + RAMComponent: r"..\res\data\raw\ram.json", + # PSUComponent: r"..\res\data\raw\psu.json", + } [ store_components_from_json(json_dir, component_cls) diff --git a/src/framework/infrastructure/db_management/db_mapping.py b/src/framework/infrastructure/db_management/db_mapping.py index 77238d9..18112f7 100644 --- a/src/framework/infrastructure/db_management/db_mapping.py +++ b/src/framework/infrastructure/db_management/db_mapping.py @@ -1,6 +1,8 @@ from operator import eq, lt, gt from typing import List +from sqlalchemy import Column from sqlalchemy.orm.session import Session +from sqlalchemy.orm.query import Query _filters_ops: dict = {"filters_eq": eq, "filters_lt": lt, "filters_gt": gt} @@ -34,10 +36,16 @@ def map_from_to( def filter_instance_from_db( - session: Session, instance_cls, filters: List, qsize: int | None = None + session: Session, + instance_cls, + filters: List, + qsize: int | None = None, + group_by: Column | None = None, ) -> List: - db_instances: List[instance_cls] = ( - session.query(instance_cls).filter(*filters).limit(qsize).all() + query: Query = ( + session.query(instance_cls).filter(*filters).group_by(group_by).limit(qsize) ) + db_instances: List[instance_cls] = query.all() + return db_instances diff --git a/src/framework/infrastructure/db_management/db_structure.py b/src/framework/infrastructure/db_management/db_structure.py index 9dea239..64c9255 100644 --- a/src/framework/infrastructure/db_management/db_structure.py +++ b/src/framework/infrastructure/db_management/db_structure.py @@ -12,8 +12,10 @@ FLOAT, ) +from framework.domain.components_enums import * from .binaryUUID import BinaryUUID from framework.domain.components import * +from framework.domain.components_enums import * def get_attrs(ctype: EComponentType) -> List[str]: @@ -40,7 +42,7 @@ class UserInstance(base): is_admin = Column(BOOLEAN()) -_AttrsComponent = ["uid", "type", "manufacturer", "model"] +_AttrsComponent = ["uid", "type", "manufacturer", "model", "rank"] class ComponentInstance(base): @@ -50,34 +52,39 @@ class ComponentInstance(base): type = Column(ENUM(EComponentType)) manufacturer = Column(VARCHAR(20)) model = Column(VARCHAR(100)) - - -class VolatileDataInstance(base): - __tablename__ = "volatile_data" - url_id = Column(BinaryUUID, primary_key=True) - url = Column(VARCHAR(255)) - component_uid = Column(BinaryUUID, ForeignKey(ComponentInstance.uid)) - cost = Column(FLOAT(7, 2, False)) - availability = Column(BOOLEAN()) - timestamp = Column(DATETIME(timezone=False, fsp=0)) + rank = Column(INTEGER(4)) AttrsVolatileData = [ "url_id", "url", "component_uid", + "component_type", "cost", "availability", "timestamp", ] -class PriceHistoryInstance(base): - __tablename__ = "prices_history" - uid = Column(BinaryUUID, primary_key=True) +class VolatileDataInstance(base): + __tablename__ = "volatile_data" + url_id = Column(BinaryUUID, primary_key=True) + url = Column(VARCHAR(255)) component_uid = Column(BinaryUUID, ForeignKey(ComponentInstance.uid)) - price = Column(FLOAT(7, 2, False)) - price_mean = Column(FLOAT(7, 2, False)) + component_type = Column(ENUM(EComponentType)) + cost = Column(FLOAT(7, 2, False)) + availability = Column(BOOLEAN()) + timestamp = Column(DATETIME(timezone=False, fsp=0)) + + +class LowerCostsInstance(base): + __tablename__ = "lower_costs" + component_uid = Column( + BinaryUUID, ForeignKey(ComponentInstance.uid), primary_key=True + ) + volatile_data_uid = Column(BinaryUUID, ForeignKey(VolatileDataInstance.url_id)) + component_type = Column(ENUM(EComponentType)) + cost = Column(FLOAT(7, 2, False)) timestamp = Column(DATE) @@ -91,7 +98,6 @@ class PriceHistoryInstance(base): "n_vga", "n_hdmi", "n_display_port", - "pcie_gen", "n_pcie_x1", "n_pcie_x4", "n_pcie_x8", @@ -105,7 +111,7 @@ class MotherboardInstance(ComponentInstance): BinaryUUID, ForeignKey(ComponentInstance.uid), primary_key=True ) consumption = Column(INTEGER(5)) - chipset = Column(ENUM(EChipsetType)) + chipset = Column(VARCHAR(10)) board_size = Column(ENUM(EBoardSize)) n_ram_slots = Column(INTEGER(1)) @@ -116,7 +122,6 @@ class MotherboardInstance(ComponentInstance): n_hdmi = Column(INTEGER(1)) n_display_port = Column(INTEGER(1)) - pcie_gen = Column(ENUM(EPCIeGeneration)) n_pcie_x1 = Column(INTEGER(1)) n_pcie_x4 = Column(INTEGER(1)) n_pcie_x8 = Column(INTEGER(1)) @@ -128,7 +133,6 @@ class MotherboardInstance(ComponentInstance): "n_cores", "base_clock_spd", "boost_clock_spd", - "ram_clock_max", "consumption", "integrated_gpu", "overclock", @@ -141,11 +145,10 @@ class CPUInstance(ComponentInstance): BinaryUUID, ForeignKey(ComponentInstance.uid), primary_key=True ) consumption = Column(INTEGER(5)) - socket = Column(ENUM(ESocketType)) + socket = Column(VARCHAR(10)) n_cores = Column(INTEGER(1)) base_clock_spd = Column(FLOAT(4, 2, True)) boost_clock_spd = Column(FLOAT(4, 2, False)) - ram_clock_max = Column(INTEGER(5)) integrated_gpu = Column(VARCHAR(30)) overclock = Column(BOOLEAN()) @@ -163,7 +166,7 @@ class GPUInstance(ComponentInstance): vram_spd = Column(INTEGER(5)) -_AttrsRAM = ["generation", "frequency"] +_AttrsRAM = ["msize", "generation", "frequency"] class RAMInstance(ComponentInstance): @@ -171,11 +174,12 @@ class RAMInstance(ComponentInstance): component_uid = Column( BinaryUUID, ForeignKey(ComponentInstance.uid), primary_key=True ) + msize = Column(INTEGER(3)) generation = Column(ENUM(ERAMGeneration)) frequency = Column(INTEGER(5)) -_AttrsPersistence = ["storage", "spd", "io", "is_HDD"] +_AttrsPersistence = ["storage", "rpm", "io", "is_HDD"] class PersistenceInstance(ComponentInstance): @@ -185,7 +189,7 @@ class PersistenceInstance(ComponentInstance): ) is_HDD = Column(BOOLEAN()) storage = Column(INTEGER(5)) - spd = Column(INTEGER(5)) + rpm = Column(INTEGER(5)) io = Column(ENUM(EPersistenceIOType)) From 6675b5b4a4809c001d897b1a5ef85cfb106d0916 Mon Sep 17 00:00:00 2001 From: Wesley Vitor Date: Tue, 28 Mar 2023 01:52:20 -0300 Subject: [PATCH 08/14] =?UTF-8?q?filtragem=20de=20cpus=20com=20gpu=20integ?= =?UTF-8?q?rada=20quando=20or=C3=A7amento=20for=20baixo.=20#52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Scraper/application/handlers.py | 7 +++- src/Scraper/domain/commands.py | 1 + src/SearchEngine/domain/repositories.py | 21 ++++++++++ src/Suggester/application/suggester.py | 20 ++++++---- src/framework/domain/components.py | 16 ++++---- src/framework/domain/components_enums.py | 18 ++++++--- .../db_filling/store_components.py | 38 ++++++++++++++++--- .../db_management/db_structure.py | 16 ++++---- 8 files changed, 104 insertions(+), 33 deletions(-) diff --git a/src/Scraper/application/handlers.py b/src/Scraper/application/handlers.py index bd80f9a..b52bdc9 100644 --- a/src/Scraper/application/handlers.py +++ b/src/Scraper/application/handlers.py @@ -123,7 +123,7 @@ def __call__(self, cmd: GetVolatileDataByCostInterval): filters_gt = {"cost": cmd.min_cost} filters_eq = {"component_type": cmd.component_type} - volatile_data = self.uow.repository.get_lower_costs( + volatile_data: list = self.uow.repository.get_lower_costs( filters_eq=filters_eq, filters_lt=filters_lt, filters_gt=filters_gt, @@ -131,6 +131,11 @@ def __call__(self, cmd: GetVolatileDataByCostInterval): components = _get_components_from_volatile_data(volatile_data) + for i, component in enumerate(components): + if component.rank is None: + del volatile_data[i] + del components[i] + return components, volatile_data diff --git a/src/Scraper/domain/commands.py b/src/Scraper/domain/commands.py index f215aef..c787c67 100644 --- a/src/Scraper/domain/commands.py +++ b/src/Scraper/domain/commands.py @@ -71,3 +71,4 @@ class GetVolatileDataByCostInterval(Command): component_type: EComponentType min_cost: float max_cost: float + need_rank: bool = False diff --git a/src/SearchEngine/domain/repositories.py b/src/SearchEngine/domain/repositories.py index 96b185c..f71c291 100644 --- a/src/SearchEngine/domain/repositories.py +++ b/src/SearchEngine/domain/repositories.py @@ -81,3 +81,24 @@ def _get(self, **kwargs): def __repr__(self): raise NotImplemented + + +class ISQLAlchemyRepository(AbstractRepository, metaclass=ABCMeta): + @abstractmethod + def __init__(self, session): + raise NotImplemented + + @abstractmethod + def _add(self, component: Component): + raise NotImplemented + + @abstractmethod + def _get_by_uid(self, ref: UUID): + raise NotImplemented + + @abstractmethod + def _get(self, **kwargs): + raise NotImplemented + + def __repr__(self): + raise NotImplemented diff --git a/src/Suggester/application/suggester.py b/src/Suggester/application/suggester.py index 08ca4ae..33c399d 100644 --- a/src/Suggester/application/suggester.py +++ b/src/Suggester/application/suggester.py @@ -1,5 +1,6 @@ from enum import Enum +import pygad from framework.domain.components import Component, EComponentType, CPUComponent from Scraper.domain.aggragate import VolatileData from framework.infrastructure.connection_util import get_message_bus @@ -54,6 +55,11 @@ def __init__(self, budget: float, purpose: EComputerPurposes): SE_EVENT_HANDLER_MAPPER, SE_COMMAND_HANDLER_MAPPER, SQLAlchemyUnitOfWork ) + # def fitness_func(solution, solution_idx): + # output = numpy.sum(solution*function_inputs) + # fitness = 1.0 / numpy.abs(output - desired_output) + # return fitness + def _get_components_by_cost_interval( self, priorities: dict[EComponentType, tuple], component_type: EComponentType ) -> tuple[list[Component], list[VolatileData]]: @@ -64,6 +70,7 @@ def _get_components_by_cost_interval( component_type, self.budget * priority[0], self.budget * priority[1], + True, ) ) @@ -91,15 +98,12 @@ def generate_computer(self) -> list[Component]: component_priorities, EComponentType.PSU ) - for cpu, cpu_vd in zip(cpus, cpus_vd): - print(cpu) - print(cpu_vd, "\n") - - # if self.budget < _gpu_price_thresh: - # a = [cpu for cpu in cpus if cpu.integrated_gpu] - # input(a) + for i, cpu in enumerate(cpus): + if isinstance(cpu, CPUComponent): + if cpu.integrated_gpu is None or len(cpu.integrated_gpu) == 0: + del cpus[i] + del cpus_vd[i] - # TODO calcula sua 'pontuação' com base na prioridade de suas especificações. # TODO Executa problema da mochila, restringindo com base na compatibilidade. # TODO somar consumo total e definir fonte com o orçamento estabelecido. # TODO caso o orçamento não seja totalmente preenchido, aumentar o orçamento do item prioritário, mantendo as compatibilidades anteriores diff --git a/src/framework/domain/components.py b/src/framework/domain/components.py index 8e8adc7..2b17b79 100644 --- a/src/framework/domain/components.py +++ b/src/framework/domain/components.py @@ -48,9 +48,9 @@ def get_attrs(cls, ctype: EComponentType) -> List[str]: "chipset", "board_size", "n_ram_slots", - "consumption", - "n_usb2", - "n_usb3x", + "memory_type", + "sata", + "n_usb", "n_vga", "n_hdmi", "n_display_port", @@ -67,10 +67,12 @@ class MotherboardComponent(Component): chipset: str board_size: EBoardSize n_ram_slots: int - consumption: int - n_usb2: int - n_usb3x: int + memory_type: ERAMGeneration + + sata: int + + n_usb: int n_vga: int n_hdmi: int @@ -137,7 +139,7 @@ class PersistenceComponent(Component): type: EComponentType = EComponentType.PERSISTENCE storage: int io: EPersistenceIOType - is_HDD: bool + is_HDD: bool = False rpm: int = 0 diff --git a/src/framework/domain/components_enums.py b/src/framework/domain/components_enums.py index d26005f..9785ddd 100644 --- a/src/framework/domain/components_enums.py +++ b/src/framework/domain/components_enums.py @@ -46,14 +46,22 @@ class ERAMGeneration(IntEnum): DDR3L = auto() DDR4 = auto() DDR5 = auto() + SODIMM = auto() class EBoardSize(IntEnum): - PICO_ITX = auto() - NANO_ITX = auto() - MINI_ITX = auto() - MICRO_ATX = auto() - STANDARD = auto() + ATX = auto() + Micro_ATX = auto() + Mini_ITX = auto() + ITX = auto() + E_ATX = auto() + Mini_STX = auto() + Thin_Mini_ITX = auto() + CEB = auto() + Mini_DTX = auto() + SSI = auto() + XL_ATX = auto() + EEB = auto() class EPSUModularity(IntEnum): diff --git a/src/framework/infrastructure/db_filling/store_components.py b/src/framework/infrastructure/db_filling/store_components.py index cb09f71..19a28d6 100644 --- a/src/framework/infrastructure/db_filling/store_components.py +++ b/src/framework/infrastructure/db_filling/store_components.py @@ -23,18 +23,26 @@ "io": EPersistenceIOType, "rate": EPSURate, "modularity": EPSUModularity, + "memory_type": ERAMGeneration, } -def store_components_from_json(json_dir: str, component_cls: Type[Component], **kwargs): +def store_components_from_json( + json_dir: str, component_cls: Type[Component], save_dataframe=False, **kwargs +): component_message_bus = get_message_bus( SE_EVENT_HANDLER_MAPPER, SE_COMMAND_HANDLER_MAPPER, SQLAlchemyUnitOfWork ) + save_path = json_dir.replace("raw", "run") + data_frame = [] + with open(json_dir, "r") as json_file: json_objects = json.load(json_file) for json_object in json_objects: + print(json_object) + json_filtered = { k: v for k, v in json_object.items() if k not in _enumerators } @@ -46,23 +54,43 @@ def store_components_from_json(json_dir: str, component_cls: Type[Component], ** component = component_cls( _id=Component.next_id(), **json_filtered, **json_modified, **kwargs ) - print(component) + try: component_message_bus.handle(AddComponent(component)) + + if save_dataframe: + json_object["uid"] = component.uid.hex + data_frame.append(json_object) + except EntityCollisionException: pass + if save_dataframe: + with open(save_path, "w") as f: + json.dump(data_frame, f, indent=4) + def main(): + hdd_dir = r"..\res\data\raw\hdd.json" + store_components_from_json( + hdd_dir, + PersistenceComponent, + save_dataframe=True, + is_HDD=True, + io=EPersistenceIOType.SATA, + ) + json_dirs = { - GPUComponent: r"..\res\data\raw\gpu.json", + MotherboardComponent: r"..\res\data\raw\motherboard.json", CPUComponent: r"..\res\data\raw\cpu.json", + GPUComponent: r"..\res\data\raw\gpu.json", RAMComponent: r"..\res\data\raw\ram.json", - # PSUComponent: r"..\res\data\raw\psu.json", + PSUComponent: r"..\res\data\raw\psu.json", + PersistenceComponent: r"..\res\data\raw\ssd.json", } [ - store_components_from_json(json_dir, component_cls) + store_components_from_json(json_dir, component_cls, save_dataframe=True) for component_cls, json_dir in json_dirs.items() ] diff --git a/src/framework/infrastructure/db_management/db_structure.py b/src/framework/infrastructure/db_management/db_structure.py index 64c9255..e272b03 100644 --- a/src/framework/infrastructure/db_management/db_structure.py +++ b/src/framework/infrastructure/db_management/db_structure.py @@ -50,7 +50,7 @@ class ComponentInstance(base): uid = Column(BinaryUUID, primary_key=True) component_uid = None type = Column(ENUM(EComponentType)) - manufacturer = Column(VARCHAR(20)) + manufacturer = Column(VARCHAR(50)) model = Column(VARCHAR(100)) rank = Column(INTEGER(4)) @@ -92,9 +92,9 @@ class LowerCostsInstance(base): "chipset", "board_size", "n_ram_slots", - "consumption", - "n_usb2", - "n_usb3x", + "memory_type", + "sata", + "n_usb", "n_vga", "n_hdmi", "n_display_port", @@ -110,13 +110,15 @@ class MotherboardInstance(ComponentInstance): component_uid = Column( BinaryUUID, ForeignKey(ComponentInstance.uid), primary_key=True ) - consumption = Column(INTEGER(5)) chipset = Column(VARCHAR(10)) board_size = Column(ENUM(EBoardSize)) n_ram_slots = Column(INTEGER(1)) - n_usb2 = Column(INTEGER(1)) - n_usb3x = Column(INTEGER(1)) + n_usb = Column(INTEGER(1)) + + memory_type = Column(ENUM(ERAMGeneration)) + + sata = Column(INTEGER(2)) n_vga = Column(INTEGER(1)) n_hdmi = Column(INTEGER(1)) From 39d4c4335b3e21e8a7ac0392b4cf8c40bb140732 Mon Sep 17 00:00:00 2001 From: vitin-m Date: Tue, 28 Mar 2023 03:12:15 -0300 Subject: [PATCH 09/14] =?UTF-8?q?cria=20comandos=20relaiconados=20=C3=A0?= =?UTF-8?q?=20busca=20por=20string=20#30?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SearchEngine/domain/commands.py | 62 ++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/SearchEngine/domain/commands.py b/src/SearchEngine/domain/commands.py index a71e036..83a2a56 100644 --- a/src/SearchEngine/domain/commands.py +++ b/src/SearchEngine/domain/commands.py @@ -5,7 +5,7 @@ from framework.domain.value_object import UUID, UUIDv4 from framework.domain.events import Command -__all__ = ["GetComponentByUID", "ListComponentsByType", "AddComponent"] +__all__ = ["GetComponentByUID", "ListComponentsByType", "AddComponent", "SearchByName", "MatchName"] @dataclass @@ -106,3 +106,63 @@ def buildPersistence(cls, **kwargs): @classmethod def buildPSU(cls, **kwargs): return cls._from_kwargs(EComponentType.PSU, kwargs) + + +@dataclass +class SearchByName(Command): + name: str + ctype: EComponentType + + @classmethod + def Motherboard(cls, query) -> "SearchByName": + return SearchByName(query, EComponentType.MOTHERBOARD) + + @classmethod + def CPU(cls, query) -> "SearchByName": + return SearchByName(query, EComponentType.CPU) + + @classmethod + def GPU(cls, query) -> "SearchByName": + return SearchByName(query, EComponentType.GPU) + + @classmethod + def RAM(cls, query) -> "SearchByName": + return SearchByName(query, EComponentType.RAM) + + @classmethod + def Persistence(cls, query) -> "SearchByName": + return SearchByName(query, EComponentType.PERSISTENCE) + + @classmethod + def PSU(cls, query) -> "SearchByName": + return SearchByName(query, EComponentType.PSU) + + +@dataclass +class MatchName(Command): + name: str + ctype: EComponentType + + @classmethod + def Motherboard(cls, query) -> "MatchName": + return MatchName(query, EComponentType.MOTHERBOARD) + + @classmethod + def CPU(cls, query) -> "MatchName": + return MatchName(query, EComponentType.CPU) + + @classmethod + def GPU(cls, query) -> "MatchName": + return MatchName(query, EComponentType.GPU) + + @classmethod + def RAM(cls, query) -> "MatchName": + return MatchName(query, EComponentType.RAM) + + @classmethod + def Persistence(cls, query) -> "MatchName": + return MatchName(query, EComponentType.PERSISTENCE) + + @classmethod + def PSU(cls, query) -> "MatchName": + return MatchName(query, EComponentType.PSU) \ No newline at end of file From 7b92468c4a596c133c2a9922a5ef517d3c6cb2b9 Mon Sep 17 00:00:00 2001 From: vitin-m Date: Tue, 28 Mar 2023 03:12:58 -0300 Subject: [PATCH 10/14] =?UTF-8?q?cria=20arquitetura=20referente=20=C3=A0?= =?UTF-8?q?=20busca=20por=20string=20#30?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SearchEngine/application/unit_of_work.py | 13 ++++++++++++ src/SearchEngine/domain/repositories.py | 21 ++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/SearchEngine/application/unit_of_work.py b/src/SearchEngine/application/unit_of_work.py index 1e629fc..317f39f 100644 --- a/src/SearchEngine/application/unit_of_work.py +++ b/src/SearchEngine/application/unit_of_work.py @@ -4,6 +4,7 @@ from ..infrastructure.ComponentManagment.SQL_alchemy_repository import ( SQLAlchemyRepository, ) +from ..infrastructure.dataframe_repository import DataFrameRepository class MockUnitOfWork(AbstractUnitOfWork): @@ -28,3 +29,15 @@ def commit(self): def rollback(self): pass + + +class DataFrameUnitOfWork(AbstractDBUnitOfWork): + def __init__(self, path): + self.repository = DataFrameRepository(path) + self.commited = False + + def commit(self): + self.commited = True + + def rollback(self): + pass \ No newline at end of file diff --git a/src/SearchEngine/domain/repositories.py b/src/SearchEngine/domain/repositories.py index 96b185c..3bb7378 100644 --- a/src/SearchEngine/domain/repositories.py +++ b/src/SearchEngine/domain/repositories.py @@ -81,3 +81,24 @@ def _get(self, **kwargs): def __repr__(self): raise NotImplemented + + +class IDataFrameRepository(AbstractRepository, metaclass=ABCMeta): + @abstractmethod + def __init__(self) -> None: + raise NotImplemented + + @abstractmethod + def _add(self, component: Component): + raise NotImplemented + + @abstractmethod + def _get_by_uid(self, ref: UUID): + raise NotImplemented + + @abstractmethod + def _get(self, **kwargs): + raise NotImplemented + + def __repr__(self) -> str: + return NotImplemented From 031be5da4bdb065882bf59904bfe76a978035144 Mon Sep 17 00:00:00 2001 From: vitin-m Date: Tue, 28 Mar 2023 03:13:35 -0300 Subject: [PATCH 11/14] cria handlers e conformidade com lint #30 --- src/SearchEngine/application/handlers.py | 50 +++++++++++++++++++ src/SearchEngine/application/unit_of_work.py | 6 +-- src/SearchEngine/domain/commands.py | 10 +++- src/SearchEngine/domain/repositories.py | 8 +-- .../infrastructure/dataframe_repository.py | 47 +++++++++++++++++ 5 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 src/SearchEngine/infrastructure/dataframe_repository.py diff --git a/src/SearchEngine/application/handlers.py b/src/SearchEngine/application/handlers.py index 0a5411b..adb4ca4 100644 --- a/src/SearchEngine/application/handlers.py +++ b/src/SearchEngine/application/handlers.py @@ -4,6 +4,9 @@ from framework.domain.events import Command, DomainEvent from framework.application.handler import MessageHandler +from SearchEngine.application.unit_of_work import DataFrameUnitOfWork +from SearchEngine.infrastructure.dataframe_repository import DataFrameRepository +from framework.domain.components import EComponentType class GetComponentByUIDHandler(MessageHandler): @@ -31,6 +34,53 @@ def __call__(self, cmd: AddComponent): return True +class SearchByNameHandler(MessageHandler): + def __call__(self, cmd: SearchByName): + with self.uow: + if isinstance(self.uow.repository, DataFrameRepository): + match cmd.ctype: + case EComponentType.CPU: + df = self.uow.repository.cpus + case EComponentType.GPU: + df = self.uow.repository.gpus + case EComponentType.PSU: + df = self.uow.repository.psus + case EComponentType.RAM: + df = self.uow.repository.rams + case EComponentType.MOTHERBOARD: + df = self.uow.repository.motherboards + case EComponentType.PERSISTENCE: + df = self.uow.repository.ssds + case _: + df = self.uow.repository.all + + return self._get_best_matches(df, cmd.name) + + def _get_best_matches(self, df, query): + from thefuzz import fuzz + from thefuzz.utils import full_process + + query = full_process(query) + + w_ratio = df.model.apply(lambda x: fuzz.WRatio(x, query)) + if "rank" in df.columns: + df.sort_values(by="rank", inplace=True) + + return df[w_ratio > w_ratio.max()].uid + + +class MatchNameHandler(MessageHandler): + def __call__(self, cmd: MatchName): + with self.uow: + if isinstance(self.uow.repository, DataFrameRepository): + from thefuzz import process + + matched = process.extractOne(cmd.name, self.uow.repository.all.model) + return self.uow.repository.all[ + self.uow.repository.all.model == matched + ].uid + + SE_COMMAND_HANDLER_MAPPER: Dict[Type[Command], Type[MessageHandler]] = { GetComponentByUID: GetComponentByUIDHandler, ListComponentsByType: ListComponentsByTypeHandler, diff --git a/src/SearchEngine/application/unit_of_work.py b/src/SearchEngine/application/unit_of_work.py index 317f39f..9765415 100644 --- a/src/SearchEngine/application/unit_of_work.py +++ b/src/SearchEngine/application/unit_of_work.py @@ -35,9 +35,9 @@ class DataFrameUnitOfWork(AbstractDBUnitOfWork): def __init__(self, path): self.repository = DataFrameRepository(path) self.commited = False - + def commit(self): self.commited = True - + def rollback(self): - pass \ No newline at end of file + pass diff --git a/src/SearchEngine/domain/commands.py b/src/SearchEngine/domain/commands.py index 83a2a56..97d98f0 100644 --- a/src/SearchEngine/domain/commands.py +++ b/src/SearchEngine/domain/commands.py @@ -5,7 +5,13 @@ from framework.domain.value_object import UUID, UUIDv4 from framework.domain.events import Command -__all__ = ["GetComponentByUID", "ListComponentsByType", "AddComponent", "SearchByName", "MatchName"] +__all__ = [ + "GetComponentByUID", + "ListComponentsByType", + "AddComponent", + "SearchByName", + "MatchName", +] @dataclass @@ -165,4 +171,4 @@ def Persistence(cls, query) -> "MatchName": @classmethod def PSU(cls, query) -> "MatchName": - return MatchName(query, EComponentType.PSU) \ No newline at end of file + return MatchName(query, EComponentType.PSU) diff --git a/src/SearchEngine/domain/repositories.py b/src/SearchEngine/domain/repositories.py index 3bb7378..f9f27fb 100644 --- a/src/SearchEngine/domain/repositories.py +++ b/src/SearchEngine/domain/repositories.py @@ -87,18 +87,18 @@ class IDataFrameRepository(AbstractRepository, metaclass=ABCMeta): @abstractmethod def __init__(self) -> None: raise NotImplemented - + @abstractmethod def _add(self, component: Component): raise NotImplemented - + @abstractmethod def _get_by_uid(self, ref: UUID): raise NotImplemented - + @abstractmethod def _get(self, **kwargs): raise NotImplemented - + def __repr__(self) -> str: return NotImplemented diff --git a/src/SearchEngine/infrastructure/dataframe_repository.py b/src/SearchEngine/infrastructure/dataframe_repository.py new file mode 100644 index 0000000..f842c57 --- /dev/null +++ b/src/SearchEngine/infrastructure/dataframe_repository.py @@ -0,0 +1,47 @@ +import pandas as pd + +from framework.domain.value_object import UUID + +from ..domain.repositories import IDataFrameRepository + + +class DataFrameRepository(IDataFrameRepository): + def __init__(self, path): + self.cpus = self._import_df(f"{path}/cpu.json") + self.gpus = self._import_df(f"{path}/gpu.json") + self.psus = self._import_df(f"{path}/psu.json") + self.rams = self._import_df(f"{path}/ram.json") + self.hdds = self._import_df(f"{path}/hdd.json") + self.ssds = self._import_df(f"{path}/ssd.json") + self.motherboards = self._import_df(f"{path}/motherboard.json") + + idx = ["uid", "model"] + self.all = pd.concat( + [ + self.cpus[idx], + self.gpus[idx], + self.psus[idx], + self.rams[idx], + self.hdds[idx], + self.ssds[idx], + self.motherboards[idx], + ] + ) + + def _import_df(self, path): + from thefuzz.utils import full_process + + df = pd.read_json(path) + df.set_index("uid") + df.model = df.model.apply(lambda x: full_process(x)) + + return df + + def _add(self, component): + pass + + def _get_by_uid(self, ref: UUID): + pass + + def _get(self, **kwargs): + pass From a5535edf1157dd36827e9bdef018901c299d32ef Mon Sep 17 00:00:00 2001 From: vitin-m Date: Tue, 28 Mar 2023 04:42:53 -0300 Subject: [PATCH 12/14] =?UTF-8?q?integra=C3=A7=C3=A3o=20do=20servi=C3=A7o?= =?UTF-8?q?=20de=20orquestra=C3=A7=C3=A3o=20com=20string=20matching=20#30?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScraperOrchestration/wrapper.py | 26 +++++++++++++------ src/SearchEngine/application/handlers.py | 22 +++++++++++----- src/SearchEngine/domain/commands.py | 4 +-- .../infrastructure/dataframe_repository.py | 10 +++++++ .../infrastructure/connection_util.py | 5 +++- .../db_filling/store_category_urls.py | 2 +- 6 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/Scraper/application/ScraperOrchestration/wrapper.py b/src/Scraper/application/ScraperOrchestration/wrapper.py index da627c0..053528e 100644 --- a/src/Scraper/application/ScraperOrchestration/wrapper.py +++ b/src/Scraper/application/ScraperOrchestration/wrapper.py @@ -27,6 +27,12 @@ from Scraper.domain.commands import * from framework.domain.components import Component from framework.application.handler import MessageBus +from SearchEngine.application.handlers import SE_COMMAND_HANDLER_MAPPER +from SearchEngine.application.unit_of_work import ( + DataFrameUnitOfWork, + SQLAlchemyUnitOfWork, +) +from SearchEngine.domain.commands import GetComponentByUID, MatchName class Wrapper: @@ -55,6 +61,14 @@ def __init__(self, scheme: str, domain: str): SQLAlchemyCategoryURLUnitOfWork, ) + self._sse_message_bus = get_message_bus( + {}, SE_COMMAND_HANDLER_MAPPER, DataFrameUnitOfWork, "../res/data/run" + ) + + self._search_message_bus = get_message_bus( + {}, SE_COMMAND_HANDLER_MAPPER, SQLAlchemyUnitOfWork + ) + self.domain_urls = self._category_url_message_bus.handle( GetCategoryURLByDomain(domain) ) @@ -74,14 +88,10 @@ async def run_scraping(self): for url, name, cost, availability in components_volatile_data: # TODO: fazer chamada da engine de busca para classificar o componente # component = SearchEngine.classifie(name) - component_manager = SQLAlchemyRepository( - self.session - ) # placeholder - component: Component = component_manager.get( - filters_gt={"consumption": -1} - )[ - int(uniform(0, 10)) - ] # placeholder + component_id = self._sse_message_bus.handle(MatchName(name)) + component = self._search_message_bus.handle( + GetComponentByUID(component_id) + ) volatile_data = VolatileData( _id=UUIDv5(url.url), component_id=component.uid, diff --git a/src/SearchEngine/application/handlers.py b/src/SearchEngine/application/handlers.py index adb4ca4..3b97e5d 100644 --- a/src/SearchEngine/application/handlers.py +++ b/src/SearchEngine/application/handlers.py @@ -7,6 +7,7 @@ from SearchEngine.application.unit_of_work import DataFrameUnitOfWork from SearchEngine.infrastructure.dataframe_repository import DataFrameRepository from framework.domain.components import EComponentType +from framework.domain.value_object import UUID class GetComponentByUIDHandler(MessageHandler): @@ -73,18 +74,27 @@ class MatchNameHandler(MessageHandler): def __call__(self, cmd: MatchName): with self.uow: if isinstance(self.uow.repository, DataFrameRepository): - from thefuzz import process - - matched = process.extractOne(cmd.name, self.uow.repository.all.model) - return self.uow.repository.all[ - self.uow.repository.all.model == matched - ].uid + self.uow.repository.tfidf.match( + [cmd.name], self.uow.repository.all.model.to_list() + ) + print(cmd.name) + matched = self.uow.repository.tfidf.get_matches().To.values[0] + print(matched) + return UUID( + str( + self.uow.repository.all[ + self.uow.repository.all.model == matched + ].uid.values[0] + ) + ) SE_COMMAND_HANDLER_MAPPER: Dict[Type[Command], Type[MessageHandler]] = { GetComponentByUID: GetComponentByUIDHandler, ListComponentsByType: ListComponentsByTypeHandler, AddComponent: AddComponentHandler, + SearchByName: SearchByNameHandler, + MatchName: MatchNameHandler, } diff --git a/src/SearchEngine/domain/commands.py b/src/SearchEngine/domain/commands.py index 97d98f0..374c9de 100644 --- a/src/SearchEngine/domain/commands.py +++ b/src/SearchEngine/domain/commands.py @@ -117,7 +117,7 @@ def buildPSU(cls, **kwargs): @dataclass class SearchByName(Command): name: str - ctype: EComponentType + ctype: EComponentType | None = None @classmethod def Motherboard(cls, query) -> "SearchByName": @@ -147,7 +147,7 @@ def PSU(cls, query) -> "SearchByName": @dataclass class MatchName(Command): name: str - ctype: EComponentType + ctype: EComponentType | None = None @classmethod def Motherboard(cls, query) -> "MatchName": diff --git a/src/SearchEngine/infrastructure/dataframe_repository.py b/src/SearchEngine/infrastructure/dataframe_repository.py index f842c57..4444786 100644 --- a/src/SearchEngine/infrastructure/dataframe_repository.py +++ b/src/SearchEngine/infrastructure/dataframe_repository.py @@ -1,9 +1,12 @@ import pandas as pd +import sys, os from framework.domain.value_object import UUID from ..domain.repositories import IDataFrameRepository +sys.path.insert(0, os.getcwd()) + class DataFrameRepository(IDataFrameRepository): def __init__(self, path): @@ -15,6 +18,12 @@ def __init__(self, path): self.ssds = self._import_df(f"{path}/ssd.json") self.motherboards = self._import_df(f"{path}/motherboard.json") + from polyfuzz import PolyFuzz + from polyfuzz.models import TFIDF + + tfidf_vec = TFIDF(n_gram_range=(3, 3), clean_string=True) + self.tfidf = PolyFuzz(tfidf_vec) + idx = ["uid", "model"] self.all = pd.concat( [ @@ -31,6 +40,7 @@ def __init__(self, path): def _import_df(self, path): from thefuzz.utils import full_process + print(path) df = pd.read_json(path) df.set_index("uid") df.model = df.model.apply(lambda x: full_process(x)) diff --git a/src/framework/infrastructure/connection_util.py b/src/framework/infrastructure/connection_util.py index b4c0e4b..9019fbf 100644 --- a/src/framework/infrastructure/connection_util.py +++ b/src/framework/infrastructure/connection_util.py @@ -24,7 +24,10 @@ def get_message_bus( uow_cls: Type[AbstractDBUnitOfWork], engine=_get_engine(), ) -> MessageBus: - uow = uow_cls(create_session(engine)) + if isinstance(engine, Engine): + uow = uow_cls(create_session(engine)) + else: + uow = uow_cls(engine) event_handler_callables = { c: list(map(lambda han: han(uow), h)) for c, h in event_mapper.items() diff --git a/src/framework/infrastructure/db_filling/store_category_urls.py b/src/framework/infrastructure/db_filling/store_category_urls.py index 0d6f5d3..b3f188a 100644 --- a/src/framework/infrastructure/db_filling/store_category_urls.py +++ b/src/framework/infrastructure/db_filling/store_category_urls.py @@ -17,7 +17,7 @@ from Scraper.domain.commands import AddCategoryURL from Scraper.domain.repositories import EntityUIDCollisionException -_csv_file_dir = r"C:\Users\wesle\OneDrive\Documentos\UFPI\ESII\WiseBuilder\res\data\raw\category_urls.csv" +_csv_file_dir = r"..\res\data\raw\category_urls.csv" def store_components_from_csv(csv_dir: str): From 74bcc562306e1a7c365879ecbf596c7b57917683 Mon Sep 17 00:00:00 2001 From: vitin-m Date: Tue, 28 Mar 2023 05:56:33 -0300 Subject: [PATCH 13/14] =?UTF-8?q?suporte=20=C3=A0=20API=20para=20servi?= =?UTF-8?q?=C3=A7o=20de=20interpreta=C3=A7=C3=A3o=20de=20strings=20#30?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SearchEngine/application/handlers.py | 4 +-- .../SQL_alchemy_repository.py | 1 + src/entrypoints/api/endpoints/cpus.py | 2 +- src/entrypoints/api/endpoints/motherboards.py | 2 +- src/entrypoints/api/endpoints/search.py | 34 ++++++++++++++++--- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/SearchEngine/application/handlers.py b/src/SearchEngine/application/handlers.py index 3b97e5d..33ce8e4 100644 --- a/src/SearchEngine/application/handlers.py +++ b/src/SearchEngine/application/handlers.py @@ -20,7 +20,7 @@ class ListComponentsByTypeHandler(MessageHandler): def __call__(self, cmd: ListComponentsByType): with self.uow: return self.uow.repository.get( - ctype=cmd.ctype, + ctypes=[cmd.ctype], qsize=cmd.qsize, filters_eq=cmd._filters_eq, filters_gt=cmd._filters_gt, @@ -67,7 +67,7 @@ def _get_best_matches(self, df, query): if "rank" in df.columns: df.sort_values(by="rank", inplace=True) - return df[w_ratio > w_ratio.max()].uid + return df[w_ratio > w_ratio.max()].uid.values class MatchNameHandler(MessageHandler): diff --git a/src/SearchEngine/infrastructure/ComponentManagment/SQL_alchemy_repository.py b/src/SearchEngine/infrastructure/ComponentManagment/SQL_alchemy_repository.py index 2f40be7..994bfdd 100644 --- a/src/SearchEngine/infrastructure/ComponentManagment/SQL_alchemy_repository.py +++ b/src/SearchEngine/infrastructure/ComponentManagment/SQL_alchemy_repository.py @@ -57,6 +57,7 @@ def _get_by_uid(self, ref: UUID) -> Component: return component def _get(self, **kwargs) -> List[Component]: + print(kwargs) qsize: int = kwargs.get("qsize", None) ctypes: List = kwargs.get("ctypes", []) ret = [] diff --git a/src/entrypoints/api/endpoints/cpus.py b/src/entrypoints/api/endpoints/cpus.py index 4446a69..fb85408 100644 --- a/src/entrypoints/api/endpoints/cpus.py +++ b/src/entrypoints/api/endpoints/cpus.py @@ -24,7 +24,7 @@ "type": fields.String(description="Tipo do componente."), "manufacturer": fields.String(required=True, description="Fabricante da CPU."), "model": fields.String(required=True, description="Modelo da CPU."), - "socket": fields.Integer(required=True, description="Socket da CPU."), + "socket": fields.String(required=True, description="Socket da CPU."), "n_cores": fields.Integer(required=True, description="Número de nucleos."), "base_clock_spd": fields.Float( required=True, description="Velocidade de clock base da CPU." diff --git a/src/entrypoints/api/endpoints/motherboards.py b/src/entrypoints/api/endpoints/motherboards.py index 71764e9..01fcb91 100644 --- a/src/entrypoints/api/endpoints/motherboards.py +++ b/src/entrypoints/api/endpoints/motherboards.py @@ -26,7 +26,7 @@ required=True, description="Fabricante da Placa-Mãe." ), "model": fields.String(required=True, description="Modelo da Placa-Mãe."), - "chipset": fields.Integer(required=True), + "chipset": fields.String(required=True), "board_size": fields.Integer(required=True), "n_ram_slots": fields.Integer(required=True), "consumption": fields.Integer( diff --git a/src/entrypoints/api/endpoints/search.py b/src/entrypoints/api/endpoints/search.py index dd9388b..7a85586 100644 --- a/src/entrypoints/api/endpoints/search.py +++ b/src/entrypoints/api/endpoints/search.py @@ -1,7 +1,17 @@ from dataclasses import dataclass +from functools import reduce, partial from flask_restx import Namespace, Resource, reqparse, fields +from SearchEngine.domain.commands import SearchByName +from Scraper.application.handlers import CURL_COMMAND_HANDLER_MAPPER, CURL_EVENT_HANDLER_MAPPER, VD_COMMAND_HANDLER_MAPPER, VD_EVENT_HANDLER_MAPPER +from Scraper.application.unit_of_work import SQLAlchemyCategoryURLUnitOfWork, SQLAlchemyVolatileDataUnitOfWork +from SearchEngine.application.handlers import SE_COMMAND_HANDLER_MAPPER +from SearchEngine.application.unit_of_work import DataFrameUnitOfWork, SQLAlchemyUnitOfWork +from framework.infrastructure.connection_util import get_message_bus +from Scraper.domain.commands import GetVolatileDataByComponentUID +from SearchEngine.domain.commands import GetComponentByUID + search_namespace = Namespace("Search") result_model = search_namespace.model( @@ -49,6 +59,20 @@ }, ] +_volatile_data_message_bus = get_message_bus( + VD_EVENT_HANDLER_MAPPER, + VD_COMMAND_HANDLER_MAPPER, + SQLAlchemyVolatileDataUnitOfWork, +) + +_sse_message_bus = get_message_bus( + {}, SE_COMMAND_HANDLER_MAPPER, DataFrameUnitOfWork, "../res/data/run" +) + +_search_message_bus = get_message_bus( + {}, SE_COMMAND_HANDLER_MAPPER, SQLAlchemyUnitOfWork +) + @dataclass class AbstractSearchEngine: @@ -67,8 +91,8 @@ class SearchList(Resource): def get(self): args = parser.parse_args() name = args.get("name") - sla = AbstractSearchEngine.search( - name, - ) - print(sla) - return sla + matches = _sse_message_bus.handle(SearchByName(name)) + components = [_search_message_bus.handle(GetComponentByUID(match_)) for match_ in matches] + vol_data = [_volatile_data_message_bus.handle(GetVolatileDataByComponentUID(component.uid)) for component in components] + vol_data = [reduce(lambda x, y: x < y, l) for l in vol_data] + return [{'_id': component.uid, 'model': component.model, 'price': vol.price, 'available': vol.aval} for component, vol in zip(components, vol_data)] From 28f37bc1d851c43abfff7164696fbaa2f64f9c17 Mon Sep 17 00:00:00 2001 From: vitin-m Date: Tue, 28 Mar 2023 05:59:38 -0300 Subject: [PATCH 14/14] conformidade com lint #30 --- src/entrypoints/api/endpoints/search.py | 38 +++++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/entrypoints/api/endpoints/search.py b/src/entrypoints/api/endpoints/search.py index 7a85586..f4dc9fa 100644 --- a/src/entrypoints/api/endpoints/search.py +++ b/src/entrypoints/api/endpoints/search.py @@ -4,10 +4,21 @@ from flask_restx import Namespace, Resource, reqparse, fields from SearchEngine.domain.commands import SearchByName -from Scraper.application.handlers import CURL_COMMAND_HANDLER_MAPPER, CURL_EVENT_HANDLER_MAPPER, VD_COMMAND_HANDLER_MAPPER, VD_EVENT_HANDLER_MAPPER -from Scraper.application.unit_of_work import SQLAlchemyCategoryURLUnitOfWork, SQLAlchemyVolatileDataUnitOfWork +from Scraper.application.handlers import ( + CURL_COMMAND_HANDLER_MAPPER, + CURL_EVENT_HANDLER_MAPPER, + VD_COMMAND_HANDLER_MAPPER, + VD_EVENT_HANDLER_MAPPER, +) +from Scraper.application.unit_of_work import ( + SQLAlchemyCategoryURLUnitOfWork, + SQLAlchemyVolatileDataUnitOfWork, +) from SearchEngine.application.handlers import SE_COMMAND_HANDLER_MAPPER -from SearchEngine.application.unit_of_work import DataFrameUnitOfWork, SQLAlchemyUnitOfWork +from SearchEngine.application.unit_of_work import ( + DataFrameUnitOfWork, + SQLAlchemyUnitOfWork, +) from framework.infrastructure.connection_util import get_message_bus from Scraper.domain.commands import GetVolatileDataByComponentUID from SearchEngine.domain.commands import GetComponentByUID @@ -92,7 +103,22 @@ def get(self): args = parser.parse_args() name = args.get("name") matches = _sse_message_bus.handle(SearchByName(name)) - components = [_search_message_bus.handle(GetComponentByUID(match_)) for match_ in matches] - vol_data = [_volatile_data_message_bus.handle(GetVolatileDataByComponentUID(component.uid)) for component in components] + components = [ + _search_message_bus.handle(GetComponentByUID(match_)) for match_ in matches + ] + vol_data = [ + _volatile_data_message_bus.handle( + GetVolatileDataByComponentUID(component.uid) + ) + for component in components + ] vol_data = [reduce(lambda x, y: x < y, l) for l in vol_data] - return [{'_id': component.uid, 'model': component.model, 'price': vol.price, 'available': vol.aval} for component, vol in zip(components, vol_data)] + return [ + { + "_id": component.uid, + "model": component.model, + "price": vol.price, + "available": vol.aval, + } + for component, vol in zip(components, vol_data) + ]