Add states and quick-access properties on Device; merge in ereuse-rate

This commit is contained in:
Xavier Bustamante Talavera 2018-10-13 14:53:46 +02:00
parent 319e041ebc
commit 3cc510831c
29 changed files with 1330 additions and 210 deletions

View File

@ -12,6 +12,7 @@ state Attributes {
state Usufructuarees state Usufructuarees
state Reservees state Reservees
state "Physical\nPossessor" state "Physical\nPossessor"
state "Waste\n\Product"
} }
state Physical { state Physical {
@ -35,8 +36,17 @@ state Trading {
Reserved --> Cancelled : Cancel Reserved --> Cancelled : Cancel
Sold --> Cancelled : Cancel Sold --> Cancelled : Cancel
Sold --> Payed : Pay Sold --> Payed : Pay
Registered --> ToBeDisposed Registered --> ToBeDisposed : ToDisposeProduct
ToBeDisposed --> Disposed : DisposeProduct ToBeDisposed --> ProductDisposed : DisposeProduct
Registered --> Donated: Donate
Registered --> Renting: Rent
Donated --> Cancelled : Cancel
Renting --> Cancelled : Cancel
}
state DataStoragePrivacyCompliance {
state Erased
state Destroyed
} }

View File

@ -11,7 +11,7 @@ device:
resolutionHeight: 1080 resolutionHeight: 1080
size: 21.5 size: 21.5
events: events:
- type: AppRate - type: ManualRate
appearanceRange: A appearanceRange: A
functionalityRange: C functionalityRange: C
labelling: False labelling: False

View File

@ -8,7 +8,7 @@ device:
serialNumber: ABCDEF serialNumber: ABCDEF
imei: 35686800-004141-20 imei: 35686800-004141-20
events: events:
- type: AppRate - type: ManualRate
appearanceRange: A appearanceRange: A
functionalityRange: B functionalityRange: B
labelling: False labelling: False

View File

@ -26,6 +26,7 @@ device:
functionalityRange: B functionalityRange: B
- type: BenchmarkRamSysbench - type: BenchmarkRamSysbench
rate: 2444 rate: 2444
elapsed: 1
components: components:
- type: GraphicCard - type: GraphicCard
serialNumber: gc1-1s serialNumber: gc1-1s
@ -35,22 +36,27 @@ components:
serialNumber: rm1-1s serialNumber: rm1-1s
model: rm1-1ml model: rm1-1ml
manufacturer: rm1-1mr manufacturer: rm1-1mr
size: 1024
- type: RamModule - type: RamModule
serialNumber: rm2-1s serialNumber: rm2-1s
model: rm2-1ml model: rm2-1ml
manufacturer: rm2-1mr manufacturer: rm2-1mr
size: 1024
- type: Processor - type: Processor
model: p1-1s model: p1-1ml
manufacturer: p1-1mr manufacturer: p1-1mr
events: events:
- type: BenchmarkProcessor - type: BenchmarkProcessor
rate: 2410 rate: 2410
elapsed: 44
- type: BenchmarkProcessorSysbench - type: BenchmarkProcessorSysbench
rate: 4400 rate: 4400
elapsed: 44
- type: SolidStateDrive - type: SolidStateDrive
serialNumber: ssd1-1s serialNumber: ssd1-1s
model: ssd1-1ml model: ssd1-1ml
manufacturer: ssd1-1mr manufacturer: ssd1-1mr
size: 1100
events: events:
- type: BenchmarkDataStorage - type: BenchmarkDataStorage
readSpeed: 20 readSpeed: 20
@ -78,7 +84,24 @@ components:
- type: BenchmarkDataStorage - type: BenchmarkDataStorage
readSpeed: 10 readSpeed: 10
writeSpeed: 5 writeSpeed: 5
elapsed: 20
- type: Motherboard - type: Motherboard
serialNumber: mb1-1s serialNumber: mb1-1s
model: mb1-1ml model: mb1-1ml
manufacturer: mb1-1mr manufacturer: mb1-1mr
- type: NetworkAdapter
serialNumber: na1-s
model: na1-1ml
manufacturer: na1-1mr
speed: 1000
wireless: False
- type: NetworkAdapter
serialNumber: na2-s
model: na2-1ml
manufacturer: na2-1mr
wireless: True
speed: 58
- type: RamModule
serialNumber: rm3-1s
model: rm3-1ml
manufacturer: rm3-1mr

View File

@ -21,8 +21,8 @@ from teal.marshmallow import ValidationError
from teal.resource import url_for_resource from teal.resource import url_for_resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \ from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, \
RamFormat, RamInterface DataStoragePrivacyCompliance, DisplayTech, RamFormat, RamInterface
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
@ -44,19 +44,22 @@ class Device(Thing):
model = Column(Unicode(), check_lower('model')) model = Column(Unicode(), check_lower('model'))
manufacturer = Column(Unicode(), check_lower('manufacturer')) manufacturer = Column(Unicode(), check_lower('manufacturer'))
serial_number = Column(Unicode(), check_lower('serial_number')) serial_number = Column(Unicode(), check_lower('serial_number'))
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 3)) weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
weight.comment = """ weight.comment = """
The weight of the device in Kgm. The weight of the device in Kgm.
""" """
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 3)) width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
width.comment = """ width.comment = """
The width of the device in meters. The width of the device in meters.
""" """
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 3)) height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
height.comment = """ height.comment = """
The height of the device in meters. The height of the device in meters.
""" """
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 3)) depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
depth.comment = """
The depth of the device in meters.
"""
color = Column(ColorType) color = Column(ColorType)
color.comment = """The predominant color of the device.""" color.comment = """The predominant color of the device."""
@ -98,6 +101,54 @@ class Device(Thing):
"""The URL where to GET this device.""" """The URL where to GET this device."""
return urlutils.URL(url_for_resource(Device, item_id=self.id)) return urlutils.URL(url_for_resource(Device, item_id=self.id))
@property
def rate(self):
"""Gets the last aggregate rate."""
with suppress(LookupError, ValueError):
from ereuse_devicehub.resources.event.models import AggregateRate
return self.last_event_of(AggregateRate)
@property
def price(self):
"""Gets the actual Price of the device or None
if no price has ever been set."""
with suppress(LookupError, ValueError):
from ereuse_devicehub.resources.event.models import Price
return self.last_event_of(Price)
@property
def trading(self):
"""The actual trading state or None if there is no trading info."""
from ereuse_devicehub.resources.device import states
with suppress(LookupError, ValueError):
event = self.last_event_of(*states.Trading.events())
return states.Trading(event.__class__)
@property
def physical(self):
"""The actual physical state, None if there is no state."""
from ereuse_devicehub.resources.device import states
with suppress(LookupError, ValueError):
event = self.last_event_of(*states.Physical.events())
return states.Physical(event.__class__)
@property
def physical_possessor(self):
"""The actual physical possessor or None.
The physical possessor is the Agent that has physically
the device. It differs from legal owners, usufructuarees
or reserves in that the physical possessor does not have
a legal relation per se with the device, but it is the one
that has it physically. As an example, a transporter could
be a physical possessor of a device although it does not
own it legally.
"""
from ereuse_devicehub.resources.event.models import Receive
with suppress(LookupError):
event = self.last_event_of(Receive)
return event.agent
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """
@ -112,6 +163,16 @@ class Device(Thing):
args[POLYMORPHIC_ON] = cls.type args[POLYMORPHIC_ON] = cls.type
return args return args
def last_event_of(self, *types):
"""Gets the last event of the given types.
:raise LookupError: Device has not an event of the given type.
"""
try:
return next(e for e in reversed(self.events) if isinstance(e, types))
except StopIteration:
raise LookupError('{!r} does not contain events of types {}.'.format(self, types))
def __lt__(self, other): def __lt__(self, other):
return self.id < other.id return self.id < other.id
@ -169,31 +230,38 @@ class Computer(Device):
@property @property
def ram_size(self) -> int: def ram_size(self) -> int:
"""The total of RAM memory the computer has.""" """The total of RAM memory the computer has."""
return sum(ram.size for ram in self.components if isinstance(ram, RamModule)) return sum(ram.size or 0 for ram in self.components if isinstance(ram, RamModule))
@property @property
def data_storage_size(self) -> int: def data_storage_size(self) -> int:
"""The total of data storage the computer has.""" """The total of data storage the computer has."""
return sum(ds.size for ds in self.components if isinstance(ds, DataStorage)) return sum(ds.size or 0 for ds in self.components if isinstance(ds, DataStorage))
@property @property
def processor_model(self) -> str: def processor_model(self) -> str:
"""The model of one of the processors of the computer.""" """The model of one of the processors of the computer."""
return next(p.model for p in self.components if isinstance(p, Processor)) return next((p.model for p in self.components if isinstance(p, Processor)), None)
@property @property
def graphic_card_model(self) -> str: def graphic_card_model(self) -> str:
"""The model of one of the graphic cards of the computer.""" """The model of one of the graphic cards of the computer."""
return next(p.model for p in self.components if isinstance(p, GraphicCard)) return next((p.model for p in self.components if isinstance(p, GraphicCard)), None)
@property @property
def network_speeds(self) -> List[int]: def network_speeds(self) -> List[int]:
"""Returns two speeds: the first for the eth and the """Returns two values representing the speeds of the network
second for the wifi networks, or 0 respectively if not found. adapters of the device.
1. The max Ethernet speed of the computer, 0 if ethernet
adaptor exists but its speed is unknown, None if no eth
adaptor exists.
2. The max WiFi speed of the computer, 0 if computer has
WiFi but its speed is unknown, None if no WiFi adaptor
exists.
""" """
speeds = [0, 0] speeds = [None, None]
for net in (c for c in self.components if isinstance(c, NetworkAdapter)): for net in (c for c in self.components if isinstance(c, NetworkAdapter)):
speeds[net.wireless] = max(net.speed or 0, speeds[net.wireless]) speeds[net.wireless] = max(net.speed or 0, speeds[net.wireless] or 0)
return speeds return speeds
def __format__(self, format_spec): def __format__(self, format_spec):
@ -322,6 +390,15 @@ class DataStorage(JoinedComponentTableMixin, Component):
""" """
interface = Column(DBEnum(DataStorageInterface)) interface = Column(DBEnum(DataStorageInterface))
@property
def privacy(self):
"""Returns the privacy compliance state of the data storage."""
# todo add physical destruction event
from ereuse_devicehub.resources.event.models import EraseBasic
with suppress(LookupError):
erase = self.last_event_of(EraseBasic)
return DataStoragePrivacyCompliance.from_erase(erase)
def __format__(self, format_spec): def __format__(self, format_spec):
v = super().__format__(format_spec) v = super().__format__(format_spec)
if 's' in format_spec: if 's' in format_spec:
@ -353,7 +430,7 @@ class NetworkMixin:
speed.comment = """ speed.comment = """
The maximum speed this network adapter can handle, in mbps. The maximum speed this network adapter can handle, in mbps.
""" """
wireless = Column(Boolean) wireless = Column(Boolean, nullable=False, default=False)
wireless.comment = """ wireless.comment = """
Whether it is a wireless interface. Whether it is a wireless interface.
""" """

View File

@ -1,4 +1,4 @@
from typing import Dict, List, Set from typing import Dict, List, Set, Type, Union
from boltons import urlutils from boltons import urlutils
from boltons.urlutils import URL from boltons.urlutils import URL
@ -7,10 +7,11 @@ from sqlalchemy import Column, Integer
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from teal.db import Model from teal.db import Model
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \ from ereuse_devicehub.resources.agent.models import Agent
RamFormat, RamInterface from ereuse_devicehub.resources.device import states
from ereuse_devicehub.resources.event.models import Event, EventWithMultipleDevices, \ from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, \
EventWithOneDevice DataStoragePrivacyCompliance, DisplayTech, RamFormat, RamInterface
from ereuse_devicehub.resources.event import models as e
from ereuse_devicehub.resources.image.models import ImageList from ereuse_devicehub.resources.image.models import ImageList
from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.lot.models import Lot
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
@ -44,10 +45,10 @@ class Device(Thing):
self.height = ... # type: float self.height = ... # type: float
self.depth = ... # type: float self.depth = ... # type: float
self.color = ... # type: Color self.color = ... # type: Color
self.events = ... # type: List[Event] self.events = ... # type: List[e.Event]
self.physical_properties = ... # type: Dict[str, object or None] self.physical_properties = ... # type: Dict[str, object or None]
self.events_multiple = ... # type: Set[EventWithMultipleDevices] self.events_multiple = ... # type: Set[e.EventWithMultipleDevices]
self.events_one = ... # type: Set[EventWithOneDevice] self.events_one = ... # type: Set[e.EventWithOneDevice]
self.images = ... # type: ImageList self.images = ... # type: ImageList
self.tags = ... # type: Set[Tag] self.tags = ... # type: Set[Tag]
self.lots = ... # type: Set[Lot] self.lots = ... # type: Set[Lot]
@ -56,6 +57,30 @@ class Device(Thing):
def url(self) -> urlutils.URL: def url(self) -> urlutils.URL:
pass pass
@property
def rate(self) -> Union[e.AggregateRate, None]:
pass
@property
def price(self) -> Union[e.Price, None]:
pass
@property
def trading(self) -> Union[states.Trading, None]:
pass
@property
def physical(self) -> Union[states.Physical, None]:
pass
@property
def physical_possessor(self) -> Union[Agent, None]:
pass
def last_event_of(self, *types: Type[e.Event]) -> e.Event:
pass
class DisplayMixin: class DisplayMixin:
technology = ... # type: Column technology = ... # type: Column
size = ... # type: Column size = ... # type: Column
@ -77,7 +102,7 @@ class Computer(DisplayMixin, Device):
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self.components = ... # type: Set[Component] self.components = ... # type: Set[Component]
self.events_parent = ... # type: Set[Event] self.events_parent = ... # type: Set[e.Event]
self.chassis = ... # type: ComputerChassis self.chassis = ... # type: ComputerChassis
@property @property
@ -104,6 +129,7 @@ class Computer(DisplayMixin, Device):
def network_speeds(self) -> List[int]: def network_speeds(self) -> List[int]:
pass pass
class Desktop(Computer): class Desktop(Computer):
pass pass
@ -155,7 +181,7 @@ class Component(Device):
super().__init__(**kwargs) super().__init__(**kwargs)
self.parent_id = ... # type: int self.parent_id = ... # type: int
self.parent = ... # type: Computer self.parent = ... # type: Computer
self.events_components = ... # type: Set[Event] self.events_components = ... # type: Set[e.Event]
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component': def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
pass pass
@ -178,6 +204,10 @@ class DataStorage(Component):
self.size = ... # type: int self.size = ... # type: int
self.interface = ... # type: DataStorageInterface self.interface = ... # type: DataStorageInterface
@property
def privacy(self) -> DataStoragePrivacyCompliance:
pass
class HardDrive(DataStorage): class HardDrive(DataStorage):
pass pass

View File

@ -1,5 +1,5 @@
from marshmallow import post_load, pre_load from marshmallow import post_load, pre_load
from marshmallow.fields import Boolean, Float, Integer, Str, String from marshmallow.fields import Boolean, Float, Integer, List, Str, String
from marshmallow.validate import Length, OneOf, Range from marshmallow.validate import Length, OneOf, Range
from sqlalchemy.util import OrderedSet from sqlalchemy.util import OrderedSet
from stdnum import imei, meid from stdnum import imei, meid
@ -7,9 +7,9 @@ from teal.marshmallow import EnumField, SanitizedStr, URL, ValidationError
from teal.resource import Schema from teal.resource import Schema
from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.device import models as m from ereuse_devicehub.resources.device import models as m, states
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \ from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, \
RamFormat, RamInterface DataStoragePrivacyCompliance, DisplayTech, RamFormat, RamInterface
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing, UnitCodes from ereuse_devicehub.resources.schemas import Thing, UnitCodes
@ -24,13 +24,19 @@ class Device(Thing):
model = SanitizedStr(lower=True, validate=Length(max=STR_BIG_SIZE)) model = SanitizedStr(lower=True, validate=Length(max=STR_BIG_SIZE))
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE)) manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
serial_number = SanitizedStr(lower=True, data_key='serialNumber') serial_number = SanitizedStr(lower=True, data_key='serialNumber')
weight = Float(validate=Range(0.1, 3), unit=UnitCodes.kgm, description=m.Device.weight.comment) weight = Float(validate=Range(0.1, 5), unit=UnitCodes.kgm, description=m.Device.weight.comment)
width = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.width.comment) width = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.width.comment)
height = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.height.comment) height = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.height.comment)
depth = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.depth.comment)
events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__) events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__)
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet) events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
url = URL(dump_only=True, description=m.Device.url.__doc__) url = URL(dump_only=True, description=m.Device.url.__doc__)
lots = NestedOn('Lot', many=True, dump_only=True) lots = NestedOn('Lot', many=True, dump_only=True)
rate = NestedOn('AggregateRate', dump_only=True)
price = NestedOn('Price', dump_only=True)
trading = EnumField(states.Trading, dump_only=True)
physical = EnumField(states.Physical, dump_only=True)
physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor')
@pre_load @pre_load
def from_events_to_events_one(self, data: dict): def from_events_to_events_one(self, data: dict):
@ -60,6 +66,11 @@ class Device(Thing):
class Computer(Device): class Computer(Device):
components = NestedOn('Component', many=True, dump_only=True, collection_class=OrderedSet) components = NestedOn('Component', many=True, dump_only=True, collection_class=OrderedSet)
chassis = EnumField(ComputerChassis, required=True) chassis = EnumField(ComputerChassis, required=True)
ram_size = Integer(dump_only=True, data_key='ramSize')
data_storage_size = Integer(dump_only=True, data_key='dataStorageSize')
processor_model = Str(dump_only=True, data_key='processorModel')
graphic_card_model = Str(dump_only=True, data_key='graphicCardModel')
network_speeds = List(Integer(dump_only=True), dump_only=True, data_key='networkSpeeds')
class Desktop(Computer): class Desktop(Computer):
@ -148,6 +159,7 @@ class DataStorage(Component):
unit=UnitCodes.mbyte, unit=UnitCodes.mbyte,
description=m.DataStorage.size.comment) description=m.DataStorage.size.comment)
interface = EnumField(DataStorageInterface) interface = EnumField(DataStorageInterface)
privacy = EnumField(DataStoragePrivacyCompliance, dump_only=True)
class HardDrive(DataStorage): class HardDrive(DataStorage):

View File

@ -0,0 +1,30 @@
from enum import Enum
from ereuse_devicehub.resources.event import models as e
class State(Enum):
@classmethod
def events(cls):
"""Events participating in this state."""
return (s.value for s in cls)
class Trading(State):
Reserved = e.Reserve
Cancelled = e.CancelTrade
Sold = e.Sell
Donated = e.Donate
Renting = e.Rent
# todo add Pay = e.Pay
ToBeDisposed = e.ToDisposeProduct
ProductDisposed = e.DisposeProduct
class Physical(State):
ToBeRepaired = e.ToRepair
Repaired = e.Repair
Preparing = e.ToPrepare
Prepared = e.Prepare
ReadyToBeUsed = e.ReadyToUse
InUse = e.Live

View File

@ -235,3 +235,21 @@ class ReceiverRole(Enum):
CollectionPoint = 'A collection point.' CollectionPoint = 'A collection point.'
RecyclingPoint = 'A recycling point.' RecyclingPoint = 'A recycling point.'
Transporter = 'An user that ships the devices to another one.' Transporter = 'An user that ships the devices to another one.'
class DataStoragePrivacyCompliance(Enum):
EraseBasic = 'EraseBasic'
EraseBasicError = 'EraseBasicError'
EraseSectors = 'EraseSectors'
EraseSectorsError = 'EraseSectorsError'
Destruction = 'Destruction'
DestructionError = 'DestructionError'
@classmethod
def from_erase(cls, erasure) -> 'DataStoragePrivacyCompliance':
"""Returns the correct enum depending of the passed-in erasure."""
from ereuse_devicehub.resources.event.models import EraseSectors
if isinstance(erasure, EraseSectors):
return cls.EraseSectors if not erasure.error else cls.EraseSectorsError
else:
return cls.EraseBasic if not erasure.error else cls.EraseBasicError

View File

@ -74,9 +74,9 @@ class PhotoboxSystemRateDef(RateDef):
SCHEMA = schemas.PhotoboxSystemRate SCHEMA = schemas.PhotoboxSystemRate
class AppRateDef(RateDef): class ManualRateDef(RateDef):
VIEW = None VIEW = None
SCHEMA = schemas.AppRate SCHEMA = schemas.ManualRate
class PriceDef(EventDef): class PriceDef(EventDef):
@ -98,7 +98,8 @@ class SnapshotDef(EventDef):
VIEW = SnapshotView VIEW = SnapshotView
SCHEMA = schemas.Snapshot SCHEMA = schemas.Snapshot
def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None, static_url_path=None, def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None,
static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder, super().__init__(app, import_name, static_folder, static_url_path, template_folder,

View File

@ -1,5 +1,6 @@
from collections import Iterable from collections import Iterable
from datetime import datetime, timedelta from datetime import datetime, timedelta
from distutils.version import StrictVersion
from typing import Set, Union from typing import Set, Union
from uuid import uuid4 from uuid import uuid4
@ -24,17 +25,12 @@ from ereuse_devicehub.db import db
from ereuse_devicehub.resources.agent.models import Agent from ereuse_devicehub.resources.agent.models import Agent
from ereuse_devicehub.resources.device.models import Component, Computer, DataStorage, Desktop, \ from ereuse_devicehub.resources.device.models import Component, Computer, DataStorage, Desktop, \
Device, Laptop, Server Device, Laptop, Server
from ereuse_devicehub.resources.enums import AppearanceRange, BOX_RATE_3, BOX_RATE_5, Bios, \ from ereuse_devicehub.resources.enums import AppearanceRange, Bios, \
FunctionalityRange, PriceSoftware, RATE_NEGATIVE, RATE_POSITIVE, RatingRange, RatingSoftware, \ FunctionalityRange, PriceSoftware, RATE_NEGATIVE, RATE_POSITIVE, RatingRange, RatingSoftware, \
ReceiverRole, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength ReceiverRole, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
from ereuse_devicehub.resources.image.models import Image
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import User
"""
A quantity of money with a currency.
"""
class JoinedTableMixin: class JoinedTableMixin:
# noinspection PyMethodParameters # noinspection PyMethodParameters
@ -54,7 +50,7 @@ class Event(Thing):
incidence.comment = """ incidence.comment = """
Should this event be reviewed due some anomaly? Should this event be reviewed due some anomaly?
""" """
closed = Column(Boolean, default=False, nullable=False) closed = Column(Boolean, default=True, nullable=False)
closed.comment = """ closed.comment = """
Whether the author has finished the event. Whether the author has finished the event.
After this is set to True, no modifications are allowed. After this is set to True, no modifications are allowed.
@ -389,30 +385,6 @@ class IndividualRate(Rate):
pass pass
class AggregateRate(Rate):
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
ratings = relationship(IndividualRate,
backref=backref('aggregated_ratings',
lazy=True,
order_by=lambda: IndividualRate.created,
collection_class=OrderedSet),
secondary=lambda: RateAggregateRate.__table__,
order_by=lambda: IndividualRate.created,
collection_class=OrderedSet)
"""The ratings this aggregateRate aggregates."""
class RateAggregateRate(db.Model):
"""
Represents the ``many to many`` relationship between
``Rate`` and ``AggregateRate``.
"""
rate_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
aggregate_rate_id = Column(UUID(as_uuid=True),
ForeignKey(AggregateRate.id),
primary_key=True)
class ManualRate(IndividualRate): class ManualRate(IndividualRate):
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True) id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
labelling = Column(Boolean) labelling = Column(Boolean)
@ -432,51 +404,67 @@ class WorkbenchRate(ManualRate):
# todo ensure for WorkbenchRate version and software are not None when inserting them # todo ensure for WorkbenchRate version and software are not None when inserting them
def ratings(self) -> Set['WorkbenchRate']: def ratings(self):
""" """
Computes all the possible rates taking this rating as a model. Computes all the possible rates taking this rating as a model.
Returns a set of ratings, including this one, which is mutated. Returns a set of ratings, including this one, which is mutated.
""" """
from ereuse_rate.main import main from ereuse_devicehub.resources.event.rate.main import main
return main(self, **app.config.get_namespace('WORKBENCH_RATE_')) return main(self, **app.config.get_namespace('WORKBENCH_RATE_'))
class AppRate(ManualRate): class AggregateRate(Rate):
pass
class PhotoboxRate(IndividualRate):
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True) id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
image_id = Column(UUID(as_uuid=True), ForeignKey(Image.id), nullable=False) manual_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
image = relationship(Image, manual = relationship(ManualRate,
uselist=False, backref=backref('aggregate_rate_manual',
cascade=CASCADE_OWN, lazy=True,
single_parent=True, order_by=lambda: AggregateRate.created,
primaryjoin=Image.id == image_id) collection_class=OrderedSet),
primaryjoin=manual_id == ManualRate.id)
workbench_id = Column(UUID(as_uuid=True), ForeignKey(WorkbenchRate.id))
workbench = relationship(WorkbenchRate,
backref=backref('aggregate_rate_workbench',
lazy=True,
order_by=lambda: AggregateRate.created,
collection_class=OrderedSet),
primaryjoin=workbench_id == WorkbenchRate.id)
# todo how to ensure phtoboxrate.device == image.image_list.device? def __init__(self, *args, **kwargs) -> None:
kwargs.setdefault('version', StrictVersion('1.0'))
super().__init__(*args, **kwargs)
@property
def processor(self):
return self.workbench.processor
class PhotoboxUserRate(PhotoboxRate): @property
id = Column(UUID(as_uuid=True), ForeignKey(PhotoboxRate.id), primary_key=True) def ram(self):
assembling = Column(SmallInteger, check_range('assembling', *BOX_RATE_5), nullable=False) return self.workbench.ram
parts = Column(SmallInteger, check_range('parts', *BOX_RATE_5), nullable=False)
buttons = Column(SmallInteger, check_range('buttons', *BOX_RATE_5), nullable=False)
dents = Column(SmallInteger, check_range('dents', *BOX_RATE_5), nullable=False)
decolorization = Column(SmallInteger,
check_range('decolorization', *BOX_RATE_5),
nullable=False)
scratches = Column(SmallInteger, check_range('scratches', *BOX_RATE_5), nullable=False)
tag_alignment = Column(SmallInteger,
check_range('tag_alignment', *BOX_RATE_3),
nullable=False)
tag_adhesive = Column(SmallInteger, check_range('tag_adhesive', *BOX_RATE_3), nullable=False)
dirt = Column(SmallInteger, check_range('dirt', *BOX_RATE_3), nullable=False)
@property
def data_storage(self):
return self.workbench.data_storage
class PhotoboxSystemRate(PhotoboxRate): @property
id = Column(UUID(as_uuid=True), ForeignKey(PhotoboxRate.id), primary_key=True) def graphic_card(self):
return self.workbench.graphic_card
@property
def bios(self):
return self.workbench.bios
@classmethod
def from_workbench_rate(cls, rate: WorkbenchRate):
aggregate = cls()
aggregate.rating = rate.rating
aggregate.software = rate.software
aggregate.appearance = rate.appearance
aggregate.functionality = rate.functionality
aggregate.device = rate.device
aggregate.workbench = rate
return aggregate
class Price(JoinedWithOneDeviceMixin, EventWithOneDevice): class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):

View File

@ -19,7 +19,6 @@ from ereuse_devicehub.resources.device.models import Component, Computer, Device
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \ from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
PriceSoftware, RatingSoftware, ReceiverRole, SnapshotExpectedEvents, SnapshotSoftware, \ PriceSoftware, RatingSoftware, ReceiverRole, SnapshotExpectedEvents, SnapshotSoftware, \
TestDataStorageLength TestDataStorageLength
from ereuse_devicehub.resources.image.models import Image
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import User
@ -165,11 +164,43 @@ class IndividualRate(Rate):
class AggregateRate(Rate): class AggregateRate(Rate):
manual_id = ... # type: Column
manual = ... # type: relationship
workbench = ... # type: relationship
workbench_id = ... # type: Column
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self.ratings = ... # type: Set[IndividualRate] self.manual_id = ... # type: UUID
self.manual = ... # type: ManualRate
self.workbench = ... # type: WorkbenchRate
self.workbench_id = ... # type: UUID
self.price = ... # type: Price self.price = ... # type: Price
@property
def processor(self):
return self.workbench.processor
@property
def ram(self):
return self.workbench.ram
@property
def data_storage(self):
return self.workbench.data_storage
@property
def graphic_card(self):
return self.workbench.graphic_card
@property
def bios(self):
return self.workbench.bios
@classmethod
def from_workbench_rate(cls, rate: WorkbenchRate) -> AggregateRate:
pass
class ManualRate(IndividualRate): class ManualRate(IndividualRate):
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
@ -177,6 +208,7 @@ class ManualRate(IndividualRate):
self.labelling = ... # type: bool self.labelling = ... # type: bool
self.appearance_range = ... # type: AppearanceRange self.appearance_range = ... # type: AppearanceRange
self.functionality_range = ... # type: FunctionalityRange self.functionality_range = ... # type: FunctionalityRange
self.aggregate_rate_manual = ... #type: AggregateRate
class WorkbenchRate(ManualRate): class WorkbenchRate(ManualRate):
@ -187,33 +219,9 @@ class WorkbenchRate(ManualRate):
self.data_storage = ... # type: float self.data_storage = ... # type: float
self.graphic_card = ... # type: float self.graphic_card = ... # type: float
self.bios = ... # type: Bios self.bios = ... # type: Bios
self.aggregate_rate_workbench = ... #type: AggregateRate
def ratings(self) -> Set[Rate]:
class AppRate(ManualRate):
pass
class PhotoboxRate(IndividualRate):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.num = ... # type: int
self.image = ... # type: Image
class PhotoboxUserRate(PhotoboxRate):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.assembling = ... # type: int
self.parts = ... # type: int
self.buttons = ... # type: int
self.dents = ... # type: int
self.decolorization = ... # type: int
self.scratches = ... # type: int
self.tag_adhesive = ... # type: int
self.dirt = ... # type: int
class PhotoboxSystemRate(PhotoboxRate):
pass pass
@ -237,8 +245,24 @@ class Price(EventWithOneDevice):
class EreusePrice(Price): class EreusePrice(Price):
MULTIPLIER = ... # type: Dict MULTIPLIER = ... # type: Dict
class Type:
def __init__(self) -> None:
super().__init__()
self.amount = ... # type: float
self.percentage = ... # type: float
class Service:
def __init__(self) -> None:
super().__init__()
self.standard = ... # type: EreusePrice.Type
self.warranty2 = ... # type: EreusePrice.Type
def __init__(self, rating: AggregateRate, **kwargs) -> None: def __init__(self, rating: AggregateRate, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self.retailer = ... # type: EreusePrice.Service
self.platform = ... # type: EreusePrice.Service
self.refurbisher = ... # type: EreusePrice.Service
self.warranty2 = ... # type: float
class Test(EventWithOneDevice): class Test(EventWithOneDevice):

View File

@ -0,0 +1,78 @@
from contextlib import suppress
from distutils.version import StrictVersion
from typing import Set, Union
from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.enums import RatingSoftware
from ereuse_devicehub.resources.event.models import AggregateRate, EreusePrice, Rate, \
WorkbenchRate
from ereuse_devicehub.resources.event.rate.workbench import v1_0
RATE_TYPES = {
WorkbenchRate: {
RatingSoftware.ECost: {
'1.0': v1_0.Rate()
},
RatingSoftware.EMarket: {
}
}
}
def rate(device: Device, rate: Rate):
"""
Rates the passed-in ``rate`` using values from the rate itself
and the ``device``.
This method mutates ``rate``.
:param device: The device to use as a model.
:param rate: A half-filled rate.
"""
cls = rate.__class__
assert cls in RATE_TYPES, 'Rate type {} not supported.'.format(cls)
assert rate.software in RATE_TYPES[cls], 'Rate soft {} not supported.'.format(rate.software)
assert str(rate.version) in RATE_TYPES[cls][rate.software], \
'Rate version {} not supported.'.format(rate.version)
RATE_TYPES[cls][rate.software][str(rate.version)].compute(device, rate)
def main(rating_model: WorkbenchRate,
software: RatingSoftware,
version: StrictVersion) -> Set[Union[WorkbenchRate, AggregateRate, EreusePrice]]:
"""
Generates all the rates (per software and version) for a given
half-filled rate acting as a model, and finally it generates
an ``AggregateRating`` with the rate that matches the
``software`` and ``version``.
This method mutates ``rating_model`` by fulfilling it and
``rating_model.device`` by adding the new rates.
:return: A set of rates with the ``rate`` value computed, where
the first rate is the ``rating_model``.
"""
assert rating_model.device
events = set()
for soft, value in RATE_TYPES[rating_model.__class__].items():
for vers, func in value.items():
if not rating_model.rating: # Fill the rating before creating another rate
rating = rating_model
else: # original rating was filled already; use a new one
rating = WorkbenchRate(
labelling=rating_model.labelling,
appearance_range=rating_model.appearance_range,
functionality_range=rating_model.functionality_range,
device=rating_model.device,
)
rating.software = soft
rating.version = vers
rate(rating_model.device, rating)
events.add(rating)
if soft == software and vers == version:
aggregation = AggregateRate.from_workbench_rate(rating)
events.add(aggregation)
with suppress(ValueError):
# We will have exception if range == VERY_LOW
events.add(EreusePrice(aggregation))
return events

View File

@ -0,0 +1,54 @@
import math
from typing import Iterable
from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.event.models import WorkbenchRate
class BaseRate:
"""growing exponential from this value"""
CEXP = 0
"""growing lineal starting on this value"""
CLIN = 242
"""growing logarithmic starting on this value"""
CLOG = 0.5
"""Processor has 50% of weight over total score, used in harmonic mean"""
PROCESSOR_WEIGHT = 0.5
"""Storage has 20% of weight over total score, used in harmonic mean"""
DATA_STORAGE_WEIGHT = 0.2
"""Ram has 30% of weight over total score, used in harmonic mean"""
RAM_WEIGHT = 0.3
def compute(self, device: Device, rate: WorkbenchRate):
raise NotImplementedError()
@staticmethod
def norm(x, x_min, x_max):
return (x - x_min) / (x_max - x_min)
@staticmethod
def rate_log(x):
return math.log10(2 * x) + 3.57 # todo magic number!
@staticmethod
def rate_lin(x):
return 7 * x + 0.06 # todo magic number!
@staticmethod
def rate_exp(x):
return math.exp(x) / (2 - math.exp(x))
@staticmethod
def harmonic_mean(weights: Iterable[float], rates: Iterable[float]):
return sum(weights) / sum(char / rate for char, rate in zip(weights, rates))
def harmonic_mean_rates(self, rate_processor, rate_storage, rate_ram):
"""
Merging components
"""
total_weights = self.PROCESSOR_WEIGHT + self.DATA_STORAGE_WEIGHT + self.RAM_WEIGHT
total_rate = self.PROCESSOR_WEIGHT / rate_processor \
+ self.DATA_STORAGE_WEIGHT / rate_storage \
+ self.RAM_WEIGHT / rate_ram
return total_weights / total_rate

View File

@ -0,0 +1,253 @@
from enum import Enum
from itertools import groupby
from typing import Iterable
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Desktop, Laptop, \
Processor, RamModule, Server
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, BenchmarkProcessor, \
WorkbenchRate
# todo if no return assign then rate_c = 1 is assigned
# todo fix corner cases, like components characteristics == None
from ereuse_devicehub.resources.event.rate.rate import BaseRate
class Rate(BaseRate):
"""
Rate all components in Computer
"""
class Range(Enum):
@classmethod
def from_devicehub(cls, r: Enum):
return getattr(cls, r.name) if r else cls.NONE
class Appearance(Range):
Z = 0.5
A = 0.3
B = 0
C = -0.2
D = -0.5
E = -1.0
NONE = -0.3
class Functionality(Range):
A = 0.4
B = -0.5
C = -0.75
D = -1
NONE = -0.3
def __init__(self) -> None:
super().__init__()
self.RATES = {
# composition: type: (field, compute class)
Processor.t: ('processor', ProcessorRate()),
RamModule.t: ('ram', RamRate()),
DataStorage.t: ('data_storage', DataStorageRate())
}
def compute(self, device: Computer, rate: WorkbenchRate):
"""
Compute 'Workbench'Rate computer is a rate (score) ranging from 0 to 4.7
that represents estimating value of use of desktop and laptop computer components.
This mutates "rate".
"""
assert isinstance(device, (Desktop, Laptop, Server))
assert isinstance(rate, WorkbenchRate)
rate.processor = rate.data_storage = rate.ram = 1 # Init
# Group cpus, rams, storages and compute their rate
# Treat the same way with HardDrive and SolidStateDrive like (DataStorage)
clause = lambda x: DataStorage.t if isinstance(x, DataStorage) else x.t
c = (c for c in device.components if clause(c) in set(self.RATES.keys()))
for type, components in groupby(sorted(c, key=clause), key=clause):
if type == Processor.t: # ProcessorRate.compute expects only 1 processor
components = next(components)
field, rate_cls = self.RATES[type] # type: str, BaseRate
result = rate_cls.compute(components, rate)
if result:
setattr(rate, field, result)
rate_components = self.harmonic_mean_rates(rate.processor, rate.data_storage, rate.ram)
rate.appearance = self.Appearance.from_devicehub(rate.appearance_range).value
rate.functionality = self.Functionality.from_devicehub(rate.functionality_range).value
rate.rating = round(max(rate_components + rate.functionality + rate.appearance, 0), 2)
rate.appearance = round(rate.appearance, 2)
rate.functionality = round(rate.functionality, 2)
rate.processor = round(rate.processor, 2)
rate.ram = round(rate.ram, 2)
rate.data_storage = round(rate.data_storage, 2)
class ProcessorRate(BaseRate):
"""
Calculate a ProcessorRate of all Processor devices
"""
# processor.xMin, processor.xMax
PROCESSOR_NORM = 3196.17, 17503.81
DEFAULT_CORES = 1
DEFAULT_SPEED = 1.6
# In case of i2, i3,.. result penalized.
# Intel(R) Core(TM) i3 CPU 530 @ 2.93GHz, score = 23406.92 but results inan score of 17503.
DEFAULT_SCORE = 4000
def compute(self, processor: Processor, rate: WorkbenchRate):
""" Compute processor rate
Obs: cores and speed are possible NULL value
:return: result is a rate (score) of Processor characteristics
"""
# todo for processor_device in processors; more than one processor
cores = processor.cores or self.DEFAULT_CORES
speed = processor.speed or self.DEFAULT_SPEED
# todo fix StopIteration if don't exists BenchmarkProcessor
benchmark_cpu = next(e for e in processor.events if isinstance(e, BenchmarkProcessor))
benchmark_cpu = benchmark_cpu.rate or self.DEFAULT_SCORE
# STEP: Fusion components
processor_rate = (benchmark_cpu + speed * 2000 * cores) / 2 # todo magic number!
# STEP: Normalize values
processor_norm = max(self.norm(processor_rate, *self.PROCESSOR_NORM), 0)
# STEP: Compute rate/score from every component
# Calculate processor_rate
if processor_norm >= self.CEXP:
processor_rate = self.rate_exp(processor_norm)
if self.CLIN <= processor_norm < self.CLOG:
processor_rate = self.rate_lin(processor_norm)
if processor_norm >= self.CLOG:
processor_rate = self.rate_log(processor_norm)
assert processor_rate, 'Could not rate processor.'
return processor_rate
class RamRate(BaseRate):
"""
Calculate a RamRate of all RamModule devices
"""
# ram.size.xMin; ram.size.xMax
SIZE_NORM = 256, 8192
RAM_SPEED_NORM = 133, 1333
# ram.speed.factor
RAM_SPEED_FACTOR = 3.7
# ram.size.weight; ram.speed.weight;
RAM_WEIGHTS = 0.7, 0.3
def compute(self, ram_devices: Iterable[RamModule], rate: WorkbenchRate):
"""
Obs: RamModule.speed is possible NULL value & size != NULL or NOT??
:return: result is a rate (score) of all RamModule components
"""
size = 0.0
speed = 0.0
# STEP: Filtering, data cleaning and merging of component parts
for ram in ram_devices:
_size = ram.size or 0
size += _size
if ram.speed:
speed += (ram.speed or 0) * _size
else:
speed += (_size / self.RAM_SPEED_FACTOR) * _size
# STEP: Fusion components
# To guarantee that there will be no 0/0
if size:
speed /= size
# STEP: Normalize values
size_norm = max(self.norm(size, *self.SIZE_NORM), 0)
ram_speed_norm = max(self.norm(speed, *self.RAM_SPEED_NORM), 0)
# STEP: Compute rate/score from every component
# Calculate size_rate
if self.CEXP <= size_norm < self.CLIN:
size_rate = self.rate_exp(size_norm)
if self.CLIN <= size_norm < self.CLOG:
size_rate = self.rate_lin(size_norm)
if size_norm >= self.CLOG:
size_rate = self.rate_log(size_norm)
# Calculate ram_speed_rate
if self.CEXP <= ram_speed_norm < self.CLIN:
ram_speed_rate = self.rate_exp(ram_speed_norm)
if self.CLIN <= ram_speed_norm < self.CLOG:
ram_speed_rate = self.rate_lin(ram_speed_norm)
if ram_speed_norm >= self.CLOG:
ram_speed_rate = self.rate_log(ram_speed_norm)
# STEP: Fusion Characteristics
return self.harmonic_mean(self.RAM_WEIGHTS, rates=(size_rate, ram_speed_rate))
class DataStorageRate(BaseRate):
"""
Calculate the rate of all DataStorage devices
"""
# drive.size.xMin; drive.size.xMax
SIZE_NORM = 4, 265000
READ_SPEED_NORM = 2.7, 109.5
WRITE_SPEED_NORM = 2, 27.35
# drive.size.weight; drive.readingSpeed.weight; drive.writingSpeed.weight;
DATA_STORAGE_WEIGHTS = 0.5, 0.25, 0.25
def compute(self, data_storage_devices: Iterable[DataStorage], rate: WorkbenchRate):
"""
Obs: size != NULL and 0 value & read_speed and write_speed != NULL
:return: result is a rate (score) of all DataStorage devices
"""
size = 0
read_speed = 0
write_speed = 0
# STEP: Filtering, data cleaning and merging of component parts
for storage in data_storage_devices:
# todo fix StopIteration if don't exists BenchmarkDataStorage
benchmark = next(e for e in storage.events if isinstance(e, BenchmarkDataStorage))
# prevent NULL values
_size = storage.size or 0
size += _size
read_speed += benchmark.read_speed * _size
write_speed += benchmark.write_speed * _size
# STEP: Fusion components
# Check almost one storage have size, try catch exception 0/0
if size:
read_speed /= size
write_speed /= size
# STEP: Normalize values
size_norm = max(self.norm(size, *self.SIZE_NORM), 0)
read_speed_norm = max(self.norm(read_speed, *self.READ_SPEED_NORM), 0)
write_speed_norm = max(self.norm(write_speed, *self.WRITE_SPEED_NORM), 0)
# STEP: Compute rate/score from every component
# Calculate size_rate
if size_norm >= self.CLOG:
size_rate = self.rate_log(size_norm)
elif self.CLIN <= size_norm < self.CLOG:
size_rate = self.rate_lin(size_norm)
elif self.CEXP <= size_norm < self.CLIN:
size_rate = self.rate_exp(size_norm)
# Calculate read_speed_rate
if read_speed_norm >= self.CLOG:
read_speed_rate = self.rate_log(read_speed_norm)
elif self.CLIN <= read_speed_norm < self.CLOG:
read_speed_rate = self.rate_lin(read_speed_norm)
elif self.CEXP <= read_speed_norm < self.CLIN:
read_speed_rate = self.rate_exp(read_speed_norm)
# write_speed_rate
if write_speed_norm >= self.CLOG:
write_speed_rate = self.rate_log(write_speed_norm)
elif self.CLIN <= write_speed_norm < self.CLOG:
write_speed_rate = self.rate_lin(write_speed_norm)
elif self.CEXP <= write_speed_norm < self.CLIN:
write_speed_rate = self.rate_exp(write_speed_norm)
# STEP: Fusion Characteristics
return self.harmonic_mean(self.DATA_STORAGE_WEIGHTS,
rates=(size_rate, read_speed_rate, write_speed_rate))

View File

@ -101,7 +101,7 @@ class StepRandom(Step):
class Rate(EventWithOneDevice): class Rate(EventWithOneDevice):
rating = Integer(validate=Range(*RATE_POSITIVE), rating = Integer(validate=Range(*RATE_POSITIVE),
dump_only=True, dump_only=True,
data_key='ratingValue', data_key='rating',
description='The rating for the content.') description='The rating for the content.')
software = EnumField(RatingSoftware, software = EnumField(RatingSoftware,
dump_only=True, dump_only=True,
@ -118,10 +118,6 @@ class IndividualRate(Rate):
pass pass
class AggregateRate(Rate):
ratings = NestedOn(IndividualRate, many=True)
class PhotoboxRate(IndividualRate): class PhotoboxRate(IndividualRate):
num = Integer(dump_only=True) num = Integer(dump_only=True)
# todo Image # todo Image
@ -155,10 +151,6 @@ class ManualRate(IndividualRate):
labelling = Boolean(description='Sets if there are labels stuck that should be removed.') labelling = Boolean(description='Sets if there are labels stuck that should be removed.')
class AppRate(ManualRate):
pass
class WorkbenchRate(ManualRate): class WorkbenchRate(ManualRate):
processor = Float() processor = Float()
ram = Float() ram = Float()
@ -168,6 +160,16 @@ class WorkbenchRate(ManualRate):
'boot from the network.') 'boot from the network.')
class AggregateRate(Rate):
workbench = NestedOn(WorkbenchRate, dump_only=True)
manual = NestedOn(ManualRate, dump_only=True)
processor = Float(dump_only=True)
ram = Float(dump_only=True)
data_storage = Float(dump_only=True)
graphic_card = Float(dump_only=True)
bios = EnumField(Bios, dump_only=True)
class Price(EventWithOneDevice): class Price(EventWithOneDevice):
currency = EnumField(Currency, required=True) currency = EnumField(Currency, required=True)
price = Decimal(places=4, rounding=decimal.ROUND_HALF_EVEN, required=True) price = Decimal(places=4, rounding=decimal.ROUND_HALF_EVEN, required=True)
@ -285,7 +287,7 @@ class StressTest(Test):
class Benchmark(EventWithOneDevice): class Benchmark(EventWithOneDevice):
elapsed = TimeDelta(precision=TimeDelta.SECONDS) elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
class BenchmarkDataStorage(Benchmark): class BenchmarkDataStorage(Benchmark):

View File

@ -1,4 +1,3 @@
from contextlib import suppress
from distutils.version import StrictVersion from distutils.version import StrictVersion
from typing import List from typing import List
from uuid import UUID from uuid import UUID
@ -77,11 +76,9 @@ class SnapshotView(View):
snapshot.events |= events snapshot.events |= events
# Compute ratings # Compute ratings
with suppress(StopIteration): for rate in (e for e in events_device if isinstance(e, WorkbenchRate)):
# todo are we sure we want to have snapshots without rates? rates = rate.ratings()
snapshot.events |= next( snapshot.events |= rates
e.ratings() for e in events_device if isinstance(e, WorkbenchRate)
)
db.session.add(snapshot) db.session.add(snapshot)
db.session.commit() db.session.commit()

View File

@ -15,7 +15,8 @@ class LotDef(Resource):
AUTH = True AUTH = True
ID_CONVERTER = Converters.uuid ID_CONVERTER = Converters.uuid
def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None, static_url_path=None, def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None,
static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder, super().__init__(app, import_name, static_folder, static_url_path, template_folder,

View File

@ -5,7 +5,6 @@ click==6.7
click-spinner==0.1.8 click-spinner==0.1.8
colorama==0.3.9 colorama==0.3.9
colour==0.1.5 colour==0.1.5
ereuse-rate==0.0.2
ereuse-utils==0.4.0b9 ereuse-utils==0.4.0b9
Flask==1.0.2 Flask==1.0.2
Flask-Cors==3.0.6 Flask-Cors==3.0.6

View File

@ -37,7 +37,6 @@ setup(
'teal>=0.2.0a24', # teal always first 'teal>=0.2.0a24', # teal always first
'click', 'click',
'click-spinner', 'click-spinner',
'ereuse-rate==0.0.2',
'ereuse-utils[Naming]>=0.4b9', 'ereuse-utils[Naming]>=0.4b9',
'hashids', 'hashids',
'marshmallow_enum', 'marshmallow_enum',

View File

@ -33,3 +33,4 @@ components:
events: events:
- type: BenchmarkProcessor - type: BenchmarkProcessor
rate: 2410 rate: 2410
elapsed: 11

View File

@ -91,7 +91,14 @@ def test_physical_properties():
manufacturer='mr', manufacturer='mr',
width=2.0, width=2.0,
color=Color()) color=Color())
pc = Desktop(chassis=ComputerChassis.Tower) pc = Desktop(chassis=ComputerChassis.Tower,
model='foo',
manufacturer='bar',
serial_number='foo-bar',
weight=2.8,
width=1.4,
height=2.1,
color=Color('LightSeaGreen'))
pc.components.add(c) pc.components.add(c)
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()
@ -110,6 +117,17 @@ def test_physical_properties():
'color': Color(), 'color': Color(),
'depth': None 'depth': None
} }
assert pc.physical_properties == {
'model': 'foo',
'manufacturer': 'bar',
'serial_number': 'foo-bar',
'weight': 2.8,
'width': 1.4,
'height': 2.1,
'depth': None,
'color': Color('LightSeaGreen'),
'chassis': ComputerChassis.Tower
}
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)

View File

@ -1,5 +1,6 @@
import ipaddress import ipaddress
from datetime import timedelta from datetime import timedelta
from typing import Tuple
import pytest import pytest
from flask import current_app as app, g from flask import current_app as app, g
@ -8,6 +9,7 @@ from teal.enums import Currency, Subdivision
from ereuse_devicehub.client import UserClient from ereuse_devicehub.client import UserClient
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device import states
from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \ from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \
RamModule, SolidStateDrive RamModule, SolidStateDrive
from ereuse_devicehub.resources.enums import ComputerChassis, TestDataStorageLength from ereuse_devicehub.resources.enums import ComputerChassis, TestDataStorageLength
@ -175,22 +177,24 @@ def test_update_parent():
assert not benchmark.parent assert not benchmark.parent
@pytest.mark.parametrize('event_model', [ @pytest.mark.parametrize('event_model_state', [
models.ToRepair, (models.ToRepair, states.Physical.ToBeRepaired),
models.Repair, (models.Repair, states.Physical.Repaired),
models.ToPrepare, (models.ToPrepare, states.Physical.Preparing),
models.ReadyToUse, (models.ReadyToUse, states.Physical.ReadyToBeUsed),
models.ToPrepare, (models.ToPrepare, states.Physical.Preparing),
models.Prepare, (models.Prepare, states.Physical.Prepared)
]) ])
def test_generic_event(event_model: models.Event, user: UserClient): def test_generic_event(event_model_state: Tuple[models.Event, states.Trading], user: UserClient):
"""Tests POSTing all generic events.""" """Tests POSTing all generic events."""
event_model, state = event_model_state
snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
event = {'type': event_model.t, 'devices': [snapshot['device']['id']]} event = {'type': event_model.t, 'devices': [snapshot['device']['id']]}
event, _ = user.post(event, res=models.Event) event, _ = user.post(event, res=models.Event)
assert event['devices'][0]['id'] == snapshot['device']['id'] assert event['devices'][0]['id'] == snapshot['device']['id']
device, _ = user.get(res=Device, item=snapshot['device']['id']) device, _ = user.get(res=Device, item=snapshot['device']['id'])
assert device['events'][-1]['id'] == event['id'] assert device['events'][-1]['id'] == event['id']
assert device['physical'] == state.name
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
@ -214,6 +218,8 @@ def test_live():
assert live['ip'] == '79.147.10.10' assert live['ip'] == '79.147.10.10'
assert live['subdivision'] == 'ES-CA' assert live['subdivision'] == 'ES-CA'
assert live['country'] == 'ES' assert live['country'] == 'ES'
device, _ = client.get(res=Device, item=live['device']['id'])
assert device['physical'] == states.Physical.InUse.name
@pytest.mark.xfail(reson='Functionality not developed.') @pytest.mark.xfail(reson='Functionality not developed.')
@ -226,14 +232,15 @@ def test_reserve(user: UserClient):
"""Performs a reservation and then cancels it.""" """Performs a reservation and then cancels it."""
@pytest.mark.parametrize('event_model', [ @pytest.mark.parametrize('event_model_state', [
models.Sell, (models.Sell, states.Trading.Sold),
models.Donate, (models.Donate, states.Trading.Donated),
models.Rent, (models.Rent, states.Trading.Renting),
models.DisposeProduct (models.DisposeProduct, states.Trading.ProductDisposed)
]) ])
def test_trade(event_model: models.Event, user: UserClient): def test_trade(event_model_state: Tuple[models.Event, states.Trading], user: UserClient):
"""Tests POSTing all generic events.""" """Tests POSTing all Trade events."""
event_model, state = event_model_state
snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
event = { event = {
'type': event_model.t, 'type': event_model.t,
@ -246,6 +253,7 @@ def test_trade(event_model: models.Event, user: UserClient):
assert event['devices'][0]['id'] == snapshot['device']['id'] assert event['devices'][0]['id'] == snapshot['device']['id']
device, _ = user.get(res=Device, item=snapshot['device']['id']) device, _ = user.get(res=Device, item=snapshot['device']['id'])
assert device['events'][-1]['id'] == event['id'] assert device['events'][-1]['id'] == event['id']
assert device['trading'] == state.name
@pytest.mark.xfail(reson='Develop migrate') @pytest.mark.xfail(reson='Develop migrate')
@ -259,6 +267,7 @@ def test_price_custom():
chassis=ComputerChassis.Docking) chassis=ComputerChassis.Docking)
price = models.Price(price=25.25, currency=Currency.EUR) price = models.Price(price=25.25, currency=Currency.EUR)
price.device = computer price.device = computer
assert computer.price == price
db.session.add(computer) db.session.add(computer)
db.session.commit() db.session.commit()
@ -268,3 +277,6 @@ def test_price_custom():
assert p['device']['id'] == price.device.id == computer.id assert p['device']['id'] == price.device.id == computer.id
assert p['price'] == 25.25 assert p['price'] == 25.25
assert p['currency'] == Currency.EUR.name == 'EUR' assert p['currency'] == Currency.EUR.name == 'EUR'
c, _ = client.get(res=Device, item=computer.id)
assert c['price']['id'] == p['id']

View File

@ -3,11 +3,13 @@ from distutils.version import StrictVersion
import pytest import pytest
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Computer, Desktop from ereuse_devicehub.resources.device.models import Computer, Desktop, HardDrive, Processor, \
from ereuse_devicehub.resources.enums import Bios, ComputerChassis, ImageMimeTypes, Orientation, \ RamModule
RatingSoftware from ereuse_devicehub.resources.enums import AppearanceRange, Bios, ComputerChassis, \
from ereuse_devicehub.resources.event.models import PhotoboxRate, WorkbenchRate FunctionalityRange, RatingSoftware
from ereuse_devicehub.resources.image.models import Image, ImageList from ereuse_devicehub.resources.event.models import AggregateRate, BenchmarkDataStorage, \
BenchmarkProcessor, EreusePrice, WorkbenchRate
from ereuse_devicehub.resources.event.rate import main
from tests import conftest from tests import conftest
@ -26,17 +28,63 @@ def test_workbench_rate_db():
db.session.commit() db.session.commit()
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.xfail(reason='AggreagteRate only takes data from WorkbenchRate as for now')
def test_photobox_rate_db(): def test_rate_workbench_then_manual():
pc = Desktop(serial_number='24', chassis=ComputerChassis.Tower) """Checks that a new AggregateRate is generated with a new rate
image = Image(name='foo', value when a ManualRate is performed after performing a
content=b'123', WorkbenchRate.
file_format=ImageMimeTypes.jpg,
orientation=Orientation.Horizontal, The new AggregateRate needs to be computed by the values of
image_list=ImageList(device=pc)) the WorkbenchRate + new values from ManualRate.
rate = PhotoboxRate(image=image, """
software=RatingSoftware.ECost, pass
version=StrictVersion('1.0'),
device=pc)
db.session.add(rate) @pytest.mark.usefixtures(conftest.app_context.__name__)
db.session.commit() def test_rate():
"""Test generating an AggregateRate for a given PC / components /
WorkbenchRate ensuring results and relationships between
pc - rate - workbenchRate - price.
"""
rate = WorkbenchRate(
appearance_range=AppearanceRange.A,
functionality_range=FunctionalityRange.A
)
pc = Desktop()
hdd = HardDrive(size=476940)
hdd.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
cpu = Processor(cores=2, speed=3.4)
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
pc.components |= {
hdd,
RamModule(size=4096, speed=1600),
RamModule(size=2048, speed=1067),
cpu
}
rate.device = pc
events = main.main(rate, RatingSoftware.ECost, StrictVersion('1.0'))
price = next(e for e in events if isinstance(e, EreusePrice))
assert price.price == 92.2
assert price.retailer.standard.amount == 40.97
assert price.platform.standard.amount == 18.84
assert price.refurbisher.standard.amount == 32.38
assert price.price >= price.retailer.standard.amount \
+ price.platform.standard.amount \
+ price.refurbisher.standard.amount
assert price.retailer.warranty2.amount == 55.30
assert price.platform.warranty2.amount == 25.43
assert price.refurbisher.warranty2.amount == 43.72
assert price.warranty2 == 124.45
# Checks relationships
workbench_rate = next(e for e in events if isinstance(e, WorkbenchRate))
aggregate_rate = next(e for e in events if isinstance(e, AggregateRate))
assert price.rating == aggregate_rate
assert aggregate_rate.workbench == workbench_rate
assert aggregate_rate.rating == workbench_rate.rating == 4.61
assert aggregate_rate.software == workbench_rate.software == RatingSoftware.ECost
assert aggregate_rate.version == StrictVersion('1.0')
assert aggregate_rate.appearance == workbench_rate.appearance
assert aggregate_rate.functionality == workbench_rate.functionality
assert aggregate_rate.rating_range == workbench_rate.rating_range
assert cpu.rate == pc.rate == hdd.rate == aggregate_rate
assert cpu.price == pc.price == aggregate_rate.price == hdd.price == price

View File

@ -0,0 +1,417 @@
"""
Tests of compute rating for every component in a Device
Rates test done:
-DataStorage
-RamModule
-Processor
Excluded cases in tests
- No Processor
-
"""
import pytest
from ereuse_devicehub.resources.device.models import Desktop, HardDrive, Processor, RamModule
from ereuse_devicehub.resources.enums import AppearanceRange, FunctionalityRange
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, BenchmarkProcessor, \
WorkbenchRate
from ereuse_devicehub.resources.event.rate.workbench.v1_0 import DataStorageRate, ProcessorRate, \
RamRate, Rate
def test_rate_data_storage_rate():
"""
Test to check if compute data storage rate have same value than previous score version;
id = pc_1193, pc_1201, pc_79, pc_798
"""
hdd_1969 = HardDrive(size=476940)
hdd_1969.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
data_storage_rate = DataStorageRate().compute([hdd_1969], WorkbenchRate())
assert round(data_storage_rate, 2) == 4.02, 'DataStorageRate returns incorrect value(rate)'
hdd_3054 = HardDrive(size=476940)
hdd_3054.events_one.add(BenchmarkDataStorage(read_speed=158, write_speed=34.7))
# calculate DataStorage Rate
data_storage_rate = DataStorageRate().compute([hdd_3054], WorkbenchRate())
assert round(data_storage_rate, 2) == 4.07, 'DataStorageRate returns incorrect value(rate)'
hdd_81 = HardDrive(size=76319)
hdd_81.events_one.add(BenchmarkDataStorage(read_speed=72.2, write_speed=24.3))
data_storage_rate = DataStorageRate().compute([hdd_81], WorkbenchRate())
assert round(data_storage_rate, 2) == 2.61, 'DataStorageRate returns incorrect value(rate)'
hdd_1556 = HardDrive(size=152587)
hdd_1556.events_one.add(BenchmarkDataStorage(read_speed=78.1, write_speed=24.4))
data_storage_rate = DataStorageRate().compute([hdd_1556], WorkbenchRate())
assert round(data_storage_rate, 2) == 3.70, 'DataStorageRate returns incorrect value(rate)'
def test_rate_data_storage_size_is_null():
"""
Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0,
BenchmarkDataStorage.write_speed = 0 is like no DataStorage has been detected;
id = pc_2992
"""
hdd_null = HardDrive(size=None)
hdd_null.events_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0))
data_storage_rate = DataStorageRate().compute([hdd_null], WorkbenchRate())
assert data_storage_rate is None
def test_rate_no_data_storage():
"""
Test without data storage devices
"""
hdd_null = HardDrive()
hdd_null.events_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0))
data_storage_rate = DataStorageRate().compute([hdd_null], WorkbenchRate())
assert data_storage_rate is None
# RAM MODULE DEVICE TEST
def test_rate_ram_rate():
"""
Test to check if compute ram rate have same value than previous score version
only with 1 RamModule; id = pc_1201
"""
ram1 = RamModule(size=2048, speed=1333)
ram_rate = RamRate().compute([ram1], WorkbenchRate())
assert round(ram_rate, 2) == 2.02, 'RamRate returns incorrect value(rate)'
def test_rate_ram_rate_2modules():
"""
Test to check if compute ram rate have same value than previous score version
with 2 RamModule; id = pc_1193
"""
ram1 = RamModule(size=4096, speed=1600)
ram2 = RamModule(size=2048, speed=1067)
ram_rate = RamRate().compute([ram1, ram2], WorkbenchRate())
assert round(ram_rate, 2) == 3.79, 'RamRate returns incorrect value(rate)'
def test_rate_ram_rate_4modules():
"""
Test to check if compute ram rate have same value than previous score version
with 2 RamModule; id = pc_79
"""
ram1 = RamModule(size=512, speed=667)
ram2 = RamModule(size=512, speed=800)
ram3 = RamModule(size=512, speed=667)
ram4 = RamModule(size=512, speed=533)
ram_rate = RamRate().compute([ram1, ram2, ram3, ram4], WorkbenchRate())
assert round(ram_rate, 2) == 1.99, 'RamRate returns incorrect value(rate)'
def test_rate_ram_module_size_is_0():
"""
Test where input data RamModule.size = 0; is like no RamModule has been detected; id = pc_798
"""
ram0 = RamModule(size=0, speed=888)
ram_rate = RamRate().compute([ram0], WorkbenchRate())
assert ram_rate is None
def test_rate_ram_speed_is_null():
"""
Test where RamModule.speed is NULL (not detected) but has size.
Pc ID = 795(1542), 745(1535), 804(1549)
"""
ram0 = RamModule(size=2048, speed=None)
ram_rate = RamRate().compute([ram0], WorkbenchRate())
assert round(ram_rate, 2) == 1.85, 'RamRate returns incorrect value(rate)'
ram0 = RamModule(size=1024, speed=None)
ram_rate = RamRate().compute([ram0], WorkbenchRate())
assert round(ram_rate, 2) == 1.25, 'RamRate returns incorrect value(rate)'
def test_rate_no_ram_module():
"""
Test without RamModule
"""
ram0 = RamModule()
ram_rate = RamRate().compute([ram0], WorkbenchRate())
assert ram_rate is None
# PROCESSOR DEVICE TEST
def test_rate_processor_rate():
"""
Test to check if compute processor rate have same value than previous score version
only with 1 core; id = 79
"""
cpu = Processor(cores=1, speed=1.6)
# add score processor benchmark
cpu.events_one.add(BenchmarkProcessor(rate=3192.34))
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
assert processor_rate == 1, 'ProcessorRate returns incorrect value(rate)'
def test_rate_processor_rate_2cores():
"""
Test to check if compute processor rate have same value than previous score version
with 2 cores; id = pc_1193, pc_1201
"""
cpu = Processor(cores=2, speed=3.4)
# add score processor benchmark
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
assert round(processor_rate, 2) == 3.95, 'ProcessorRate returns incorrect value(rate)'
cpu = Processor(cores=2, speed=3.3)
cpu.events_one.add(BenchmarkProcessor(rate=26339.48))
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
assert round(processor_rate, 2) == 3.93, 'ProcessorRate returns incorrect value(rate)'
@pytest.mark.xfail(reason='Debug test')
def test_rate_processor_with_null_cores():
"""
Test with processor device have null number of cores
"""
cpu = Processor(cores=None, speed=3.3)
cpu.events_one.add(BenchmarkProcessor(rate=0))
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
assert processor_rate == 1, 'ProcessorRate returns incorrect value(rate)'
@pytest.mark.xfail(reason='Debug test')
def test_rate_processor_with_null_speed():
"""
Test with processor device have null speed value
"""
cpu = Processor(cores=1, speed=None)
cpu.events_one.add(BenchmarkProcessor(rate=0))
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
assert processor_rate == 1.06, 'ProcessorRate returns incorrect value(rate)'
def test_rate_computer_rate():
""" Test rate v1
pc_1193 = Computer()
price = 92.2
# add components characteristics of pc with id = 1193
hdd_1969 = HardDrive(size=476940)
hdd_1969.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
ram1 = RamModule(size=4096, speed=1600)
ram2 = RamModule(size=2048, speed=1067)
cpu = Processor(cores=2, speed=3.4)
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
pc_1193.components.add(hdd_1969, ram1, ram2, cpu)
# add functionality and appearance range
rate_pc_1193 = WorkbenchRate(appearance_range=AppearanceRange.A, functionality_range=FunctionalityRange.A)
# add component rate
HDD_rate = 4.02
RAM_rate = 3.79
Processor_rate = 3.95
Rating = 4.61
pc_1201 = Computer()
price = 69.6
hdd_3054 = HardDrive(size=476940)
hdd_3054.events_one.add(BenchmarkDataStorage(read_speed=158, write_speed=34.7))
ram1 = RamModule(size=2048, speed=1333)
cpu = Processor(cores=2, speed=3.3)
cpu.events_one.add(BenchmarkProcessor(rate=26339.48))
pc_1201.components.add(hdd_3054, ram1, cpu)
# add functionality and appearance range
rate_pc_1201 = WorkbenchRate(appearance_range=AppearanceRange.B, functionality_range=FunctionalityRange.A)
# add component rate
HDD_rate = 4.07
RAM_rate = 2.02
Processor_rate = 3.93
Rating = 3.48
pc_79 = Computer()
price = VeryLow
hdd_81 = HardDrive(size=76319)
hdd_81.events_one.add(BenchmarkDataStorage(read_speed=72.2, write_speed=24.3))
ram1 = RamModule(size=512, speed=667)
ram2 = RamModule(size=512, speed=800)
ram3 = RamModule(size=512, speed=667)
ram4 = RamModule(size=512, speed=533)
cpu = Processor(cores=1, speed=1.6)
cpu.events_one.add(BenchmarkProcessor(rate=3192.34))
pc_79.components.add(hdd_81, ram1, ram2, ram3, ram4, cpu)
# add functionality and appearance range
rate_pc_79 = WorkbenchRate(appearance_range=AppearanceRange.C, functionality_range=FunctionalityRange.A)
# add component rate
HDD_rate = 2.61
RAM_rate = 1.99
Processor_rate = 1
Rating = 1.58
pc_798 = Computer()
price = 50
hdd_1556 = HardDrive(size=152587)
hdd_1556.events_one.add(BenchmarkDataStorage(read_speed=78.1, write_speed=24.4))
ram0 = RamModule(size=0, speed=None)
cpu = Processor(cores=2, speed=2.5)
cpu.events_one.add(BenchmarkProcessor(rate=9974.3))
pc_798.components.add(hdd_1556, ram0, cpu)
# add functionality and appearance range
rate_pc_798 = WorkbenchRate(appearance_range=AppearanceRange.B, functionality_range=FunctionalityRange.A)
# add component rate
HDD_rate = 3.7
RAM_rate = 1
Processor_rate = 4.09
Rating = 2.5
"""
# Create a new Computer with components characteristics of pc with id = 1193
pc_test = Desktop()
data_storage = HardDrive(size=476940)
data_storage.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
cpu = Processor(cores=2, speed=3.4)
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
pc_test.components |= {
data_storage,
RamModule(size=4096, speed=1600),
RamModule(size=2048, speed=1067),
cpu
}
# add functionality and appearance range
rate_pc = WorkbenchRate(appearance_range=AppearanceRange.A,
functionality_range=FunctionalityRange.A)
# Compute all components rates and general rating
Rate().compute(pc_test, rate_pc)
assert round(rate_pc.ram, 2) == 3.79
assert round(rate_pc.data_storage, 2) == 4.02
assert round(rate_pc.processor, 2) == 3.95
assert round(rate_pc.rating, 2) == 4.61
# Create a new Computer with components characteristics of pc with id = 1201
pc_test = Desktop()
data_storage = HardDrive(size=476940)
data_storage.events_one.add(BenchmarkDataStorage(read_speed=158, write_speed=34.7))
cpu = Processor(cores=2, speed=3.3)
cpu.events_one.add(BenchmarkProcessor(rate=26339.48))
pc_test.components |= {
data_storage,
RamModule(size=2048, speed=1333),
cpu
}
# add functionality and appearance range
rate_pc = WorkbenchRate(appearance_range=AppearanceRange.B,
functionality_range=FunctionalityRange.A)
# Compute all components rates and general rating
Rate().compute(pc_test, rate_pc)
assert round(rate_pc.ram, 2) == 2.02
assert round(rate_pc.data_storage, 2) == 4.07
assert round(rate_pc.processor, 2) == 3.93
assert round(rate_pc.rating, 2) == 3.48
# Create a new Computer with components characteristics of pc with id = 79
pc_test = Desktop()
data_storage = HardDrive(size=76319)
data_storage.events_one.add(BenchmarkDataStorage(read_speed=72.2, write_speed=24.3))
cpu = Processor(cores=1, speed=1.6)
cpu.events_one.add(BenchmarkProcessor(rate=3192.34))
pc_test.components |= {
data_storage,
RamModule(size=512, speed=667),
RamModule(size=512, speed=800),
RamModule(size=512, speed=667),
RamModule(size=512, speed=533),
cpu
}
# add functionality and appearance range
rate_pc = WorkbenchRate(appearance_range=AppearanceRange.C,
functionality_range=FunctionalityRange.A)
# Compute all components rates and general rating
Rate().compute(pc_test, rate_pc)
assert round(rate_pc.ram, 2) == 1.99
assert round(rate_pc.data_storage, 2) == 2.61
assert round(rate_pc.processor, 2) == 1
assert round(rate_pc.rating, 2) == 1.58
# Create a new Computer with components characteristics of pc with id = 798
pc_test = Desktop()
data_storage = HardDrive(size=152587)
data_storage.events_one.add(BenchmarkDataStorage(read_speed=78.1, write_speed=24.4))
cpu = Processor(cores=2, speed=2.5)
cpu.events_one.add(BenchmarkProcessor(rate=9974.3))
pc_test.components |= {
data_storage,
RamModule(size=0, speed=None),
cpu
}
# add functionality and appearance range
rate_pc = WorkbenchRate(appearance_range=AppearanceRange.B,
functionality_range=FunctionalityRange.A)
# Compute all components rates and general rating
Rate().compute(pc_test, rate_pc)
assert round(rate_pc.ram, 2) == 1
assert round(rate_pc.data_storage, 2) == 3.7
assert round(rate_pc.processor, 2) == 4.09
assert round(rate_pc.rating, 2) == 2.5
@pytest.mark.xfail(reason='Data Storage rate actually requires a DSSBenchmark')
def test_rate_computer_with_data_storage_without_benchmark():
"""For example if the data storage was introduced manually
or comes from an old version without benchmark."""

View File

@ -1,9 +1,9 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from distutils.version import StrictVersion
from typing import List, Tuple from typing import List, Tuple
from uuid import uuid4 from uuid import uuid4
import pytest import pytest
from boltons import urlutils
from teal.db import UniqueViolation from teal.db import UniqueViolation
from teal.marshmallow import ValidationError from teal.marshmallow import ValidationError
@ -13,8 +13,7 @@ from ereuse_devicehub.devicehub import Devicehub
from ereuse_devicehub.resources.device import models as m from ereuse_devicehub.resources.device import models as m
from ereuse_devicehub.resources.device.exceptions import NeedsId from ereuse_devicehub.resources.device.exceptions import NeedsId
from ereuse_devicehub.resources.device.sync import MismatchBetweenTagsAndHid from ereuse_devicehub.resources.device.sync import MismatchBetweenTagsAndHid
from ereuse_devicehub.resources.enums import Bios, ComputerChassis, RatingSoftware, \ from ereuse_devicehub.resources.enums import ComputerChassis, SnapshotSoftware
SnapshotSoftware
from ereuse_devicehub.resources.event.models import AggregateRate, BenchmarkProcessor, \ from ereuse_devicehub.resources.event.models import AggregateRate, BenchmarkProcessor, \
EraseSectors, Event, Snapshot, SnapshotRequest, WorkbenchRate EraseSectors, Event, Snapshot, SnapshotRequest, WorkbenchRate
from ereuse_devicehub.resources.tag import Tag from ereuse_devicehub.resources.tag import Tag
@ -37,21 +36,11 @@ def test_snapshot_model():
elapsed=timedelta(seconds=25)) elapsed=timedelta(seconds=25))
snapshot.device = device snapshot.device = device
snapshot.request = SnapshotRequest(request={'foo': 'bar'}) snapshot.request = SnapshotRequest(request={'foo': 'bar'})
snapshot.events.add(WorkbenchRate(processor=0.1,
ram=1.0,
bios=Bios.A,
labelling=False,
graphic_card=0.1,
data_storage=4.1,
software=RatingSoftware.ECost,
version=StrictVersion('1.0'),
device=device))
db.session.add(snapshot) db.session.add(snapshot)
db.session.commit() db.session.commit()
device = m.Desktop.query.one() # type: m.Desktop device = m.Desktop.query.one() # type: m.Desktop
e1, e2 = device.events e1 = device.events[0]
assert isinstance(e1, Snapshot), 'Creation order must be preserved: 1. snapshot, 2. WR' assert isinstance(e1, Snapshot), 'Creation order must be preserved: 1. snapshot, 2. WR'
assert isinstance(e2, WorkbenchRate)
db.session.delete(device) db.session.delete(device)
db.session.commit() db.session.commit()
assert Snapshot.query.one_or_none() is None assert Snapshot.query.one_or_none() is None
@ -59,6 +48,8 @@ def test_snapshot_model():
assert User.query.one() is not None assert User.query.one() is not None
assert m.Desktop.query.one_or_none() is None assert m.Desktop.query.one_or_none() is None
assert m.Device.query.one_or_none() is None assert m.Device.query.one_or_none() is None
# Check properties
assert device.url == urlutils.URL('http://localhost/devices/1')
def test_snapshot_schema(app: Devicehub): def test_snapshot_schema(app: Devicehub):
@ -321,27 +312,37 @@ def test_erase(user: UserClient):
assert step['type'] == 'StepZero' assert step['type'] == 'StepZero'
assert step['error'] is False assert step['error'] is False
assert 'num' not in step assert 'num' not in step
assert storage['privacy'] == erasure['device']['privacy'] == 'EraseSectors'
# Let's try a second erasure with an error
s['uuid'] = uuid4()
s['components'][0]['events'][0]['error'] = True
snapshot, _ = user.post(s, res=Snapshot)
assert snapshot['components'][0]['hid'] == 'c1mr-c1s-c1ml'
assert snapshot['components'][0]['privacy'] == 'EraseSectorsError'
def test_snapshot_computer_monitor(user: UserClient): def test_snapshot_computer_monitor(user: UserClient):
s = file('computer-monitor.snapshot') s = file('computer-monitor.snapshot')
snapshot_and_check(user, s, event_types=('AppRate',)) snapshot_and_check(user, s, event_types=('ManualRate',))
# todo check that ManualRate has generated an AggregateRate
def test_snapshot_mobile_smartphone(user: UserClient): def test_snapshot_mobile_smartphone(user: UserClient):
s = file('smartphone.snapshot') s = file('smartphone.snapshot')
snapshot_and_check(user, s, event_types=('AppRate',)) snapshot_and_check(user, s, event_types=('ManualRate',))
# todo check that ManualRate has generated an AggregateRate
@pytest.mark.xfail(reason='Test not developed')
def test_snapshot_components_none(): def test_snapshot_components_none():
""" """
Tests that a snapshot without components does not Tests that a snapshot without components does not
remove them from the computer. remove them from the computer.
""" """
# todo test
pass
@pytest.mark.xfail(reason='Test not developed')
def test_snapshot_components_empty(): def test_snapshot_components_empty():
""" """
Tests that a snapshot whose components are an empty list remove Tests that a snapshot whose components are an empty list remove

View File

@ -27,7 +27,7 @@ def test_workbench_server_condensed(user: UserClient):
file('workbench-server-3.erase'), file('workbench-server-3.erase'),
file('workbench-server-4.install') file('workbench-server-4.install')
)) ))
s['components'][5]['events'] = [file('workbench-server-3.erase')] s['components'][5]['events'].append(file('workbench-server-3.erase'))
# Create tags # Create tags
for t in s['device']['tags']: for t in s['device']['tags']:
user.post({'id': t['id']}, res=Tag) user.post({'id': t['id']}, res=Tag)
@ -35,7 +35,7 @@ def test_workbench_server_condensed(user: UserClient):
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
events = snapshot['events'] events = snapshot['events']
assert {(event['type'], event['device']) for event in events} == { assert {(event['type'], event['device']) for event in events} == {
# todo missing Rate event aggregating the rates ('AggregateRate', 1),
('WorkbenchRate', 1), ('WorkbenchRate', 1),
('BenchmarkProcessorSysbench', 5), ('BenchmarkProcessorSysbench', 5),
('StressTest', 1), ('StressTest', 1),
@ -45,10 +45,24 @@ def test_workbench_server_condensed(user: UserClient):
('Install', 6), ('Install', 6),
('EraseSectors', 7), ('EraseSectors', 7),
('BenchmarkDataStorage', 6), ('BenchmarkDataStorage', 6),
('BenchmarkDataStorage', 7),
('TestDataStorage', 6) ('TestDataStorage', 6)
} }
assert snapshot['closed'] assert snapshot['closed']
assert not snapshot['error'] assert not snapshot['error']
device, _ = user.get(res=Device, item=snapshot['device']['id'])
assert device['dataStorageSize'] == 1100
assert device['chassis'] == 'Tower'
assert device['hid'] == 'd1mr-d1s-d1ml'
assert device['graphicCardModel'] == device['components'][0]['model'] == 'gc1-1ml'
assert device['networkSpeeds'] == [1000, 58]
assert device['processorModel'] == device['components'][3]['model'] == 'p1-1ml'
assert device['ramSize'] == 2048, 'There are 3 RAM: 2 x 1024 and 1 None sizes'
assert device['rate']['closed']
assert not device['rate']['error']
assert device['rate']['rating'] == 0
assert device['rate']['workbench']
assert device['tags'][0]['id'] == 'tag1'
@pytest.mark.xfail(reason='Functionality not yet developed.') @pytest.mark.xfail(reason='Functionality not yet developed.')
@ -122,8 +136,9 @@ def test_workbench_server_phases(user: UserClient):
def test_real_hp_11(user: UserClient): def test_real_hp_11(user: UserClient):
s = file('real-hp.snapshot.11') s = file('real-hp.snapshot.11')
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
assert snapshot['device']['hid'] == 'hewlett-packard-czc0408yjg-hp_compaq_8100_elite_sff' pc = snapshot['device']
assert snapshot['device']['chassis'] == 'Tower' assert pc['hid'] == 'hewlett-packard-czc0408yjg-hp_compaq_8100_elite_sff'
assert pc['chassis'] == 'Tower'
assert set(e['type'] for e in snapshot['events']) == { assert set(e['type'] for e in snapshot['events']) == {
'BenchmarkDataStorage', 'BenchmarkDataStorage',
'BenchmarkProcessor', 'BenchmarkProcessor',
@ -133,6 +148,10 @@ def test_real_hp_11(user: UserClient):
'StressTest' 'StressTest'
} }
assert len(list(e['type'] for e in snapshot['events'])) == 6 assert len(list(e['type'] for e in snapshot['events'])) == 6
assert pc['networkSpeeds'] == [1000, None], 'Device has no WiFi'
assert pc['processorModel'] == 'intel core i3 cpu 530 @ 2.93ghz'
assert pc['ramSize'] == 8192
assert pc['dataStorageSize'] == 305245
def test_real_toshiba_11(user: UserClient): def test_real_toshiba_11(user: UserClient):
@ -140,7 +159,7 @@ def test_real_toshiba_11(user: UserClient):
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
def test_real_eee_1001pxd(user: UserClient): def test_snapshot_real_eee_1001pxd(user: UserClient):
""" """
Checks the values of the device, components, Checks the values of the device, components,
events and their relationships of a real pc. events and their relationships of a real pc.
@ -155,6 +174,7 @@ def test_real_eee_1001pxd(user: UserClient):
assert pc['manufacturer'] == 'asustek computer inc.' assert pc['manufacturer'] == 'asustek computer inc.'
assert pc['hid'] == 'asustek_computer_inc-b8oaas048286-1001pxd' assert pc['hid'] == 'asustek_computer_inc-b8oaas048286-1001pxd'
assert pc['tags'] == [] assert pc['tags'] == []
assert pc['networkSpeeds'] == [100, 0], 'Although it has WiFi we do not know the speed'
components = snapshot['components'] components = snapshot['components']
wifi = components[0] wifi = components[0]
assert wifi['hid'] == 'qualcomm_atheros-74_2f_68_8b_fd_c8-ar9285_wireless_network_adapter' assert wifi['hid'] == 'qualcomm_atheros-74_2f_68_8b_fd_c8-ar9285_wireless_network_adapter'
@ -170,7 +190,7 @@ def test_real_eee_1001pxd(user: UserClient):
assert cpu['threads'] == 1 assert cpu['threads'] == 1
assert cpu['speed'] == 1.667 assert cpu['speed'] == 1.667
assert 'hid' not in cpu assert 'hid' not in cpu
assert cpu['model'] == 'intel atom cpu n455 @ 1.66ghz' assert pc['processorModel'] == cpu['model'] == 'intel atom cpu n455 @ 1.66ghz'
cpu, _ = user.get(res=Device, item=cpu['id']) cpu, _ = user.get(res=Device, item=cpu['id'])
events = cpu['events'] events = cpu['events']
sysbench = next(e for e in events if e['type'] == em.BenchmarkProcessorSysbench.t) sysbench = next(e for e in events if e['type'] == em.BenchmarkProcessorSysbench.t)
@ -204,6 +224,7 @@ def test_real_eee_1001pxd(user: UserClient):
ram = components[6] ram = components[6]
assert ram['interface'] == 'DDR2' assert ram['interface'] == 'DDR2'
assert ram['speed'] == 667 assert ram['speed'] == 667
assert pc['ramSize'] == ram['size'] == 1024
hdd = components[7] hdd = components[7]
assert hdd['type'] == 'HardDrive' assert hdd['type'] == 'HardDrive'
assert hdd['hid'] == 'hitachi-e2024242cv86hj-hts54322' assert hdd['hid'] == 'hitachi-e2024242cv86hj-hts54322'
@ -223,6 +244,7 @@ def test_real_eee_1001pxd(user: UserClient):
assert erase['startTime'] assert erase['startTime']
assert erase['zeros'] is False assert erase['zeros'] is False
assert erase['error'] is False assert erase['error'] is False
assert hdd['privacy'] == 'EraseBasic'
mother = components[8] mother = components[8]
assert mother['hid'] == 'asustek_computer_inc-eee0123456789-1001pxd' assert mother['hid'] == 'asustek_computer_inc-eee0123456789-1001pxd'
@ -243,6 +265,11 @@ def test_real_eee_1000h(user: UserClient):
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
@pytest.mark.xfail(reason='We do not have a snapshot file to use')
def test_real_full_with_workbench_rate(user: UserClient):
pass
SNAPSHOTS_NEED_ID = { SNAPSHOTS_NEED_ID = {
'box-xavier.snapshot.json', 'box-xavier.snapshot.json',
'custom.lshw.snapshot.json', 'custom.lshw.snapshot.json',