Adding benchmarks and test [Ratev2]
This commit is contained in:
parent
0f508a1d59
commit
147b86f889
|
@ -106,6 +106,24 @@ class AppearanceRange(Enum):
|
|||
return self.name
|
||||
|
||||
|
||||
@unique
|
||||
class AppearanceRangev2(Enum):
|
||||
"""Based on usage condition of a device and its functionality aspects/characteristics."""
|
||||
Z = 'Z. The device is new'
|
||||
A = 'A. Is like new; without visual damage'
|
||||
B = 'B. Is in really good condition; small visual damage in difficult places to spot'
|
||||
C = 'C. Is in good condition; small visual damage in parts that are easy to spot, minor cosmetic blemishes on cabinet)'
|
||||
D = 'D. Is acceptable; visual damage in visible parts, major cosmetic blemishes on cabinet, missing cosmetic parts..'
|
||||
E = 'E. Is unacceptable; considerable visual damage, missing essential parts,.'
|
||||
NONE = 'NA. Grade doesn’t exists'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
APPEARANCE_RANGE = 0.5, -0.3
|
||||
|
||||
|
||||
@unique
|
||||
class FunctionalityRange(Enum):
|
||||
"""Grades the defects of a device that affect its usage."""
|
||||
|
@ -134,6 +152,20 @@ class FunctionalityRangev2(Enum):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@unique
|
||||
class BatteryHealthRange(Enum):
|
||||
"""Grade the battery health status, depending on self report Android system"""
|
||||
A = 'A. The battery health is very good'
|
||||
B = 'B. Battery health is good'
|
||||
C = 'C. Battery health is overheat / over voltage status but can stand the minimum duration'
|
||||
D = 'D. Battery health is bad; can’t stand the minimum duration time'
|
||||
E = 'E. Battery health is very bad; and status is dead; unusable or miss it '
|
||||
NONE = 'NA. Grade doesn’t exists'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@unique
|
||||
class Bios(Enum):
|
||||
"""How difficult it has been to set the bios to boot from the network."""
|
||||
|
|
|
@ -31,7 +31,7 @@ from ereuse_devicehub.resources.device.models import Component, Computer, DataSt
|
|||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, ErasureStandards, \
|
||||
FunctionalityRange, PhysicalErasureMethod, PriceSoftware, RATE_NEGATIVE, RATE_POSITIVE, \
|
||||
RatingRange, RatingSoftware, ReceiverRole, Severity, SnapshotExpectedEvents, SnapshotSoftware, \
|
||||
TestDataStorageLength, FUNCTIONALITY_RANGE, FunctionalityRangev2
|
||||
TestDataStorageLength, FUNCTIONALITY_RANGE, FunctionalityRangev2, AppearanceRangev2, BatteryHealthRange
|
||||
from ereuse_devicehub.resources.event.rate.workbench.v2_0 import QualityRate, FunctionalityRate
|
||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
@ -553,6 +553,220 @@ class SnapshotRequest(db.Model):
|
|||
cascade=CASCADE_OWN))
|
||||
|
||||
|
||||
class Benchmark(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
"""The act of gauging the performance of a device."""
|
||||
elapsed = Column(Interval)
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
#sqlalchemy.ext.declarative.declared_attr>`_
|
||||
"""
|
||||
args = {POLYMORPHIC_ID: cls.t}
|
||||
if cls.t == 'Benchmark':
|
||||
args[POLYMORPHIC_ON] = cls.type
|
||||
return args
|
||||
|
||||
|
||||
class BenchmarkDataStorage(Benchmark):
|
||||
"""Benchmarks the data storage unit reading and writing speeds."""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
|
||||
read_speed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||
write_speed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return 'Read: {} MB/s, write: {} MB/s'.format(self.read_speed, self.write_speed)
|
||||
|
||||
|
||||
class BenchmarkWithRate(Benchmark):
|
||||
"""The act of benchmarking a device with a single rate."""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
|
||||
rate = Column(Float, nullable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{} points'.format(self.rate)
|
||||
|
||||
|
||||
class BenchmarkProcessor(BenchmarkWithRate):
|
||||
"""Benchmarks a processor by executing `BogoMips
|
||||
<https://en.wikipedia.org/wiki/BogoMips>`_. Note that this is not
|
||||
a reliable way of rating processors and we keep it for compatibility
|
||||
purposes.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BenchmarkProcessorSysbench(BenchmarkProcessor):
|
||||
"""Benchmarks a processor by using the processor benchmarking
|
||||
utility of `sysbench <https://github.com/akopytov/sysbench>`_.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BenchmarkRamSysbench(BenchmarkWithRate):
|
||||
pass
|
||||
|
||||
|
||||
class BenchmarkGraphicCard(BenchmarkWithRate):
|
||||
pass
|
||||
|
||||
|
||||
class Test(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
"""The act of testing the physical condition of a device and its
|
||||
components.
|
||||
|
||||
Testing errors and warnings are easily taken in
|
||||
:attr:`ereuse_devicehub.resources.device.models.Device.working`.
|
||||
"""
|
||||
elapsed = Column(Interval, nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
#sqlalchemy.ext.declarative.declared_attr>`_
|
||||
"""
|
||||
args = {POLYMORPHIC_ID: cls.t}
|
||||
if cls.t == 'Test':
|
||||
args[POLYMORPHIC_ON] = cls.type
|
||||
return args
|
||||
|
||||
|
||||
class TestDataStorage(Test):
|
||||
"""
|
||||
The act of testing the data storage.
|
||||
|
||||
Testing is done using the `S.M.A.R.T self test
|
||||
<https://en.wikipedia.org/wiki/S.M.A.R.T.#Self-tests>`_. Note
|
||||
that not all data storage units, specially some new PCIe ones, do not
|
||||
support SMART testing.
|
||||
|
||||
The test takes to other SMART values indicators of the overall health
|
||||
of the data storage.
|
||||
"""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
|
||||
length = Column(DBEnum(TestDataStorageLength), nullable=False) # todo from type
|
||||
status = Column(Unicode(), check_lower('status'), nullable=False)
|
||||
lifetime = Column(Interval)
|
||||
assessment = Column(Boolean)
|
||||
reallocated_sector_count = Column(SmallInteger)
|
||||
power_cycle_count = Column(SmallInteger)
|
||||
reported_uncorrectable_errors = Column(SmallInteger)
|
||||
command_timeout = Column(Integer)
|
||||
current_pending_sector_count = Column(SmallInteger)
|
||||
offline_uncorrectable = Column(SmallInteger)
|
||||
remaining_lifetime_percentage = Column(SmallInteger)
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Define severity
|
||||
# As of https://www.backblaze.com/blog/hard-drive-smart-stats/ and
|
||||
# https://www.backblaze.com/blog-smart-stats-2014-8.html
|
||||
# We can guess some future disk failures by analyzing some SMART data.
|
||||
if self.severity is None:
|
||||
# Test finished successfully
|
||||
if not self.assessment:
|
||||
self.severity = Severity.Error
|
||||
elif self.current_pending_sector_count and self.current_pending_sector_count > 40 \
|
||||
or self.reallocated_sector_count and self.reallocated_sector_count > 10:
|
||||
self.severity = Severity.Warning
|
||||
|
||||
def __str__(self) -> str:
|
||||
t = inflection.humanize(self.status)
|
||||
if self.lifetime:
|
||||
t += ' with a lifetime of {:.1f} years.'.format(self.lifetime.days / 365)
|
||||
t += self.description
|
||||
return t
|
||||
|
||||
|
||||
class StressTest(Test):
|
||||
"""The act of stressing (putting to the maximum capacity)
|
||||
a device for an amount of minutes. If the device is not in great
|
||||
condition won't probably survive such test.
|
||||
"""
|
||||
|
||||
@validates('elapsed')
|
||||
def is_minute_and_bigger_than_1_minute(self, _, value: timedelta):
|
||||
seconds = value.total_seconds()
|
||||
assert not bool(seconds % 60)
|
||||
assert seconds >= 60
|
||||
return value
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{}. Computing for {}'.format(self.severity, self.elapsed)
|
||||
|
||||
|
||||
class TestAudio(Test):
|
||||
"""
|
||||
Test to check all this aspects related with audio functions, Manual Tests??
|
||||
"""
|
||||
loudspeaker = Column(BDEnum(LoudspeakerRange))
|
||||
loudspeaker.comment = 'Range to determine if the speaker is working properly and what sound quality it has.'
|
||||
microphone = Column(Boolean)
|
||||
microphone.comment = 'This evaluate if microphone works correctly'
|
||||
|
||||
|
||||
class TestConnectivity(Test):
|
||||
"""
|
||||
Test to check all this aspects related with functionality connections in devices
|
||||
"""
|
||||
|
||||
SIM = Column(Boolean)
|
||||
SIM.comment = 'Evaluate if SIM works'
|
||||
wifi = Column(Boolean)
|
||||
wifi.comment = 'Evaluate if wifi connection works correctly'
|
||||
bluetooth = Column(Boolean)
|
||||
bluetooth.comment = 'Evaluate if bluetooth works'
|
||||
usb = Column(DBEnum(USBPortRange))
|
||||
usb.comment = 'Evaluate if usb port was detected and charger plug works'
|
||||
|
||||
|
||||
class TestBattery(Test):
|
||||
"""
|
||||
Test battery health, status and length of charge. Minimum X minutes discharging the device
|
||||
"""
|
||||
# TODO how to determinate if test PASS depend on battery stat and/or health
|
||||
battery_stat = Column(Boolean())
|
||||
battery_stat.comment = """
|
||||
Some batteries can report a self-check life status.
|
||||
"""
|
||||
battery_health = Column(DBEnum(BatteryHealthRange))
|
||||
battery_health.comment = BatteryHealthRange.__doc__
|
||||
|
||||
|
||||
class TestBios(Test):
|
||||
"""
|
||||
Test that determinate motherboard no beeps, codes or errors when power on,
|
||||
and a grade to reflect some possibles difficult to access or modify setting in the BIOS, like password protection..
|
||||
"""
|
||||
bios_power_on = Column(Boolean())
|
||||
bios_power_on.comment = """
|
||||
Motherboards do a self check when powering up (R2 p.23), test PASS if no beeps, codes, or errors appears.
|
||||
"""
|
||||
# TODO Eum(BiosAccesRange)
|
||||
bios_access_range = Column(BDEnum(BiosAccessRange))
|
||||
bios_access_range.comment = 'Range of difficult to acces BIOS'
|
||||
|
||||
|
||||
class TestVisual(ManualRate):
|
||||
"""
|
||||
Special test that its aspects are represented with grade and focuses mainly on
|
||||
the aesthetic or cosmetic defects of important parts of a device.
|
||||
Like defects on chassis, display, ..
|
||||
"""
|
||||
# TODO Consider if add some new var in appearance aspect??
|
||||
appearance_range = Column(DBEnum(AppearanceRangev2))
|
||||
appearance_range.comment = AppearanceRangev2.__doc__
|
||||
|
||||
|
||||
class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
"""The act of grading the appearance, performance, and functionality
|
||||
of a device.
|
||||
|
@ -651,50 +865,13 @@ class ManualRate(IndividualRate):
|
|||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ComputerRate(ManualRate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id), primary_key=True)
|
||||
processor = Column(Float(decimal_return_scale=2), check_range('processor', *RATE_POSITIVE))
|
||||
ram = Column(Float(decimal_return_scale=2), check_range('ram', *RATE_POSITIVE))
|
||||
data_storage = Column(Float(decimal_return_scale=2),
|
||||
check_range('data_storage', *RATE_POSITIVE))
|
||||
graphic_card = Column(Float(decimal_return_scale=2),
|
||||
check_range('graphic_card', *RATE_POSITIVE))
|
||||
bios = Column(Float(decimal_return_scale=2),
|
||||
check_range('bios', *RATE_POSITIVE))
|
||||
bios_range = Column(DBEnum(Bios))
|
||||
bios_range.comment = Bios.__doc__
|
||||
# TODO is necessary?
|
||||
class WorkbenchComputer(ManualRate):
|
||||
pass
|
||||
|
||||
# todo ensure for WorkbenchRate version and software are not None when inserting them
|
||||
|
||||
def ratings(self):
|
||||
"""
|
||||
Computes all the possible rates taking this rating as a model.
|
||||
|
||||
Returns a set of ratings, including this one, which is mutated,
|
||||
and the final :class:`.AggregateRate`.
|
||||
"""
|
||||
from ereuse_devicehub.resources.event.rate.main import main
|
||||
return main(self, **app.config.get_namespace('WORKBENCH_RATE_'))
|
||||
|
||||
@property
|
||||
def data_storage_range(self):
|
||||
if self.data_storage:
|
||||
return RatingRange.from_score(self.data_storage)
|
||||
|
||||
@property
|
||||
def ram_range(self):
|
||||
if self.ram:
|
||||
return RatingRange.from_score(self.ram)
|
||||
|
||||
@property
|
||||
def processor_range(self):
|
||||
if self.processor:
|
||||
return RatingRange.from_score(self.processor)
|
||||
|
||||
@property
|
||||
def graphic_card_range(self):
|
||||
if self.graphic_card:
|
||||
return RatingRange.from_score(self.graphic_card)
|
||||
class WorkbenchMobile(ManualRate):
|
||||
pass
|
||||
|
||||
|
||||
class AggregateRate(Rate):
|
||||
|
@ -782,7 +959,7 @@ class AggregateRate(Rate):
|
|||
return self.workbench.labelling
|
||||
|
||||
@classmethod
|
||||
def from_workbench_rate(cls, rate: QualityRateComputer):
|
||||
def from_workbench_rate(cls, rate: QualityRate):
|
||||
aggregate = cls()
|
||||
aggregate.rating = rate.rating
|
||||
aggregate.software = rate.software
|
||||
|
@ -793,189 +970,88 @@ class AggregateRate(Rate):
|
|||
return aggregate
|
||||
|
||||
|
||||
class EreusePrice(Price):
|
||||
"""The act of setting a price by guessing it using the eReuse.org
|
||||
algorithm.
|
||||
|
||||
This algorithm states that the price is the use value of the device
|
||||
(represented by its last :class:`.Rate`) multiplied by a constants
|
||||
value agreed by a circuit or platform.
|
||||
class QualityRate(Rate):
|
||||
"""
|
||||
MULTIPLIER = {
|
||||
Desktop: 20,
|
||||
Laptop: 30
|
||||
}
|
||||
|
||||
class Type:
|
||||
def __init__(self, percentage: float, price: Decimal) -> None:
|
||||
# see https://stackoverflow.com/a/29651462 for the - 0.005
|
||||
self.amount = EreusePrice.to_price(price * Decimal(percentage))
|
||||
self.percentage = EreusePrice.to_price(price * Decimal(percentage))
|
||||
self.percentage = round(percentage - 0.005, 2)
|
||||
|
||||
class Service:
|
||||
REFURBISHER, PLATFORM, RETAILER = 0, 1, 2
|
||||
STANDARD, WARRANTY2 = 'STD', 'WR2'
|
||||
SCHEMA = {
|
||||
Desktop: {
|
||||
RatingRange.HIGH: {
|
||||
STANDARD: (0.35125, 0.204375, 0.444375),
|
||||
WARRANTY2: (0.47425, 0.275875, 0.599875)
|
||||
},
|
||||
RatingRange.MEDIUM: {
|
||||
STANDARD: (0.385, 0.2558333333, 0.3591666667),
|
||||
WARRANTY2: (0.539, 0.3581666667, 0.5028333333)
|
||||
},
|
||||
RatingRange.LOW: {
|
||||
STANDARD: (0.5025, 0.30875, 0.18875),
|
||||
},
|
||||
},
|
||||
Laptop: {
|
||||
RatingRange.HIGH: {
|
||||
STANDARD: (0.3469230769, 0.195, 0.4580769231),
|
||||
WARRANTY2: (0.4522307692, 0.2632307692, 0.6345384615)
|
||||
},
|
||||
RatingRange.MEDIUM: {
|
||||
STANDARD: (0.382, 0.1735, 0.4445),
|
||||
WARRANTY2: (0.5108, 0.2429, 0.6463)
|
||||
},
|
||||
RatingRange.LOW: {
|
||||
STANDARD: (0.4528571429, 0.2264285714, 0.3207142857),
|
||||
}
|
||||
}
|
||||
}
|
||||
SCHEMA[Server] = SCHEMA[Desktop]
|
||||
|
||||
def __init__(self, device, rating_range, role, price: Decimal) -> None:
|
||||
cls = device.__class__ if device.__class__ != Server else Desktop
|
||||
rate = self.SCHEMA[cls][rating_range]
|
||||
self.standard = EreusePrice.Type(rate[self.STANDARD][role], price)
|
||||
if self.WARRANTY2 in rate:
|
||||
self.warranty2 = EreusePrice.Type(rate[self.WARRANTY2][role], price)
|
||||
|
||||
def __init__(self, rating: AggregateRate, **kwargs) -> None:
|
||||
if rating.rating_range == RatingRange.VERY_LOW:
|
||||
raise ValueError('Cannot compute price for Range.VERY_LOW')
|
||||
# We pass ROUND_UP strategy so price is always greater than what refurbisher... amounts
|
||||
price = self.to_price(rating.rating * self.MULTIPLIER[rating.device.__class__], ROUND_UP)
|
||||
super().__init__(rating=rating,
|
||||
device=rating.device,
|
||||
price=price,
|
||||
software=kwargs.pop('software', app.config['PRICE_SOFTWARE']),
|
||||
version=kwargs.pop('version', app.config['PRICE_VERSION']),
|
||||
**kwargs)
|
||||
self._compute()
|
||||
|
||||
@orm.reconstructor
|
||||
def _compute(self):
|
||||
"""
|
||||
Calculates eReuse.org prices when initializing the
|
||||
instance from the price and other properties.
|
||||
"""
|
||||
self.refurbisher = self._service(self.Service.REFURBISHER)
|
||||
self.retailer = self._service(self.Service.RETAILER)
|
||||
self.platform = self._service(self.Service.PLATFORM)
|
||||
if hasattr(self.refurbisher, 'warranty2'):
|
||||
self.warranty2 = round(self.refurbisher.warranty2.amount
|
||||
+ self.retailer.warranty2.amount
|
||||
+ self.platform.warranty2.amount, 2)
|
||||
|
||||
def _service(self, role):
|
||||
return self.Service(self.device, self.rating.rating_range, role, self.price)
|
||||
|
||||
|
||||
class EreusePrice(Price):
|
||||
"""The act of setting a price by guessing it using the eReuse.org
|
||||
algorithm.
|
||||
|
||||
This algorithm states that the price is the use value of the device
|
||||
(represented by its last :class:`.Rate`) multiplied by a constants
|
||||
value agreed by a circuit or platform.
|
||||
The act of compute performance (quality) a device
|
||||
"""
|
||||
MULTIPLIER = {
|
||||
Desktop: 20,
|
||||
Laptop: 30
|
||||
}
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
processor = Column(Float(decimal_return_scale=2), check_range('processor', *RATE_POSITIVE),
|
||||
comment='Is a test explain cpu component.')
|
||||
ram = Column(Float(decimal_return_scale=2), check_range('ram', *RATE_POSITIVE),
|
||||
comment='RAM memory rate.')
|
||||
data_storage = Column(Float(decimal_return_scale=2), check_range('data_storage', *RATE_POSITIVE),
|
||||
comment='Data storage rate, like HHD, SSD.')
|
||||
|
||||
class Type:
|
||||
def __init__(self, percentage: float, price: Decimal) -> None:
|
||||
# see https://stackoverflow.com/a/29651462 for the - 0.005
|
||||
self.amount = EreusePrice.to_price(price * Decimal(percentage))
|
||||
self.percentage = EreusePrice.to_price(price * Decimal(percentage))
|
||||
self.percentage = round(percentage - 0.005, 2)
|
||||
""" MOBILE QUALITY RATE """
|
||||
display = Column(Float(decimal_return_scale=2), check_range('display', *RATE_POSITIVE))
|
||||
display.comment = 'Display rate, screen resolution and size to calculate PPI and convert in score'
|
||||
battery = Column(Float(decimal_return_scale=2), check_range('battery', *RATE_POSITIVE),
|
||||
comment='Battery rate is related with capacity and its health')
|
||||
camera = Column(Float(decimal_return_scale=2), check_range('camera', *RATE_POSITIVE),
|
||||
comment='Camera rate take into account resolution')
|
||||
|
||||
class Service:
|
||||
REFURBISHER, PLATFORM, RETAILER = 0, 1, 2
|
||||
STANDARD, WARRANTY2 = 'STD', 'WR2'
|
||||
SCHEMA = {
|
||||
Desktop: {
|
||||
RatingRange.HIGH: {
|
||||
STANDARD: (0.35125, 0.204375, 0.444375),
|
||||
WARRANTY2: (0.47425, 0.275875, 0.599875)
|
||||
},
|
||||
RatingRange.MEDIUM: {
|
||||
STANDARD: (0.385, 0.2558333333, 0.3591666667),
|
||||
WARRANTY2: (0.539, 0.3581666667, 0.5028333333)
|
||||
},
|
||||
RatingRange.LOW: {
|
||||
STANDARD: (0.5025, 0.30875, 0.18875),
|
||||
},
|
||||
},
|
||||
Laptop: {
|
||||
RatingRange.HIGH: {
|
||||
STANDARD: (0.3469230769, 0.195, 0.4580769231),
|
||||
WARRANTY2: (0.4522307692, 0.2632307692, 0.6345384615)
|
||||
},
|
||||
RatingRange.MEDIUM: {
|
||||
STANDARD: (0.382, 0.1735, 0.4445),
|
||||
WARRANTY2: (0.5108, 0.2429, 0.6463)
|
||||
},
|
||||
RatingRange.LOW: {
|
||||
STANDARD: (0.4528571429, 0.2264285714, 0.3207142857),
|
||||
}
|
||||
}
|
||||
}
|
||||
SCHEMA[Server] = SCHEMA[Desktop]
|
||||
""" COMPUTER QUALITY RATE """
|
||||
graphic_card = Column(Float(decimal_return_scale=2), check_range('graphic_card', *RATE_POSITIVE),
|
||||
comment='Graphic card score in performance, amount of memory and benchmark result')
|
||||
network_adapter = Column(Float(decimal_return_scale=2), check_range('network_adapter', *RATE_POSITIVE),
|
||||
comment='Network adapter rate, take it speed limit')
|
||||
|
||||
def __init__(self, device, rating_range, role, price: Decimal) -> None:
|
||||
cls = device.__class__ if device.__class__ != Server else Desktop
|
||||
rate = self.SCHEMA[cls][rating_range]
|
||||
self.standard = EreusePrice.Type(rate[self.STANDARD][role], price)
|
||||
if self.WARRANTY2 in rate:
|
||||
self.warranty2 = EreusePrice.Type(rate[self.WARRANTY2][role], price)
|
||||
bios = Column(Float(decimal_return_scale=2), check_range('bios', *RATE_POSITIVE))
|
||||
bios_range = Column(DBEnum(Bios))
|
||||
bios_range.comment = Bios.__doc__
|
||||
|
||||
def __init__(self, rating: AggregateRate, **kwargs) -> None:
|
||||
if rating.rating_range == RatingRange.VERY_LOW:
|
||||
raise ValueError('Cannot compute price for Range.VERY_LOW')
|
||||
# We pass ROUND_UP strategy so price is always greater than what refurbisher... amounts
|
||||
price = self.to_price(rating.rating * self.MULTIPLIER[rating.device.__class__], ROUND_UP)
|
||||
super().__init__(rating=rating,
|
||||
device=rating.device,
|
||||
price=price,
|
||||
software=kwargs.pop('software', app.config['PRICE_SOFTWARE']),
|
||||
version=kwargs.pop('version', app.config['PRICE_VERSION']),
|
||||
**kwargs)
|
||||
self._compute()
|
||||
@property
|
||||
def ram_range(self):
|
||||
return self.workbench.ram_range
|
||||
|
||||
@orm.reconstructor
|
||||
def _compute(self):
|
||||
"""
|
||||
Calculates eReuse.org prices when initializing the
|
||||
instance from the price and other properties.
|
||||
"""
|
||||
self.refurbisher = self._service(self.Service.REFURBISHER)
|
||||
self.retailer = self._service(self.Service.RETAILER)
|
||||
self.platform = self._service(self.Service.PLATFORM)
|
||||
if hasattr(self.refurbisher, 'warranty2'):
|
||||
self.warranty2 = round(self.refurbisher.warranty2.amount
|
||||
+ self.retailer.warranty2.amount
|
||||
+ self.platform.warranty2.amount, 2)
|
||||
@property
|
||||
def processor_range(self):
|
||||
return self.workbench.processor_range
|
||||
|
||||
def _service(self, role):
|
||||
return self.Service(self.device, self.rating.rating_range, role, self.price)
|
||||
@property
|
||||
def display_range(self):
|
||||
return self.workbench.data_storage_range
|
||||
|
||||
@property
|
||||
def data_storage_range(self):
|
||||
return self.workbench.data_storage_range
|
||||
|
||||
@property
|
||||
def battery_range(self):
|
||||
return self.workbench.ram_range
|
||||
|
||||
@property
|
||||
def camera_range(self):
|
||||
return self.workbench_mobile.camera_range
|
||||
|
||||
@property
|
||||
def graphic_card_range(self):
|
||||
return self.workbench_mobil.graphic_card_range
|
||||
|
||||
@property
|
||||
def network_adapter_range(self):
|
||||
return self.workbench_mobil.network_adapter_range
|
||||
|
||||
@property
|
||||
def bios_range(self):
|
||||
return self.workbench_mobil.bios_range
|
||||
|
||||
|
||||
class ResultRate(Rate):
|
||||
class FunctionalityRate(Rate):
|
||||
"""
|
||||
The act of compute functionality characteristics of a device.
|
||||
Two functionality variables, functionality rate (float) and functionality range (Enum)
|
||||
|
||||
"""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
functionality = Column(Float(decimal_return_scale=2), check_range('functionality', *FUNCTIONALITY_RANGE))
|
||||
functionality.comment = 'Functionality rate of a device'
|
||||
|
||||
functionality_range = Column(DBEnum(FunctionalityRangev2))
|
||||
functionality_range.comment = FunctionalityRangev2.__doc__
|
||||
|
||||
|
||||
class FinalRate(Rate):
|
||||
"""The act of grading the appearance, quality (performance), and functionality
|
||||
of a device.
|
||||
|
||||
|
@ -987,7 +1063,7 @@ class ResultRate(Rate):
|
|||
5. ``Cost of repair``.
|
||||
|
||||
|
||||
There are types of rating a device:
|
||||
There are different types of rating a device:
|
||||
|
||||
1. Rate Quality
|
||||
2. Rate Functionality
|
||||
|
@ -1003,17 +1079,22 @@ class ResultRate(Rate):
|
|||
"""
|
||||
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
quality_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
|
||||
quality_id = Column(UUID(as_uuid=True), ForeignKey(QualityRate.id))
|
||||
quality_id.comment = """The Quality Rate used to generate this
|
||||
aggregation, or None if none used.
|
||||
"""
|
||||
quality = relationship(QualityRate, )
|
||||
|
||||
func_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
|
||||
func_id.comment = """The Functionality Rate used to generate this
|
||||
functionality_id = Column(UUID(as_uuid=True), ForeignKey(FunctionalityRate.id))
|
||||
functionality_id.comment = """The Functionality Rate used to generate this
|
||||
aggregation, or None if none used.
|
||||
"""
|
||||
functionality = relationship(FunctionalityRate, )
|
||||
|
||||
final_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
|
||||
# TODO is necessary?? create a AppearanceRate..
|
||||
appearance = relationship(TestVisual, )
|
||||
|
||||
final_id = Column(UUID(as_uuid=True), ForeignKey(FinalRate.id))
|
||||
final_id.comment = """The Final Rate used to generate this
|
||||
aggregation, or None if none used.
|
||||
"""
|
||||
|
@ -1058,6 +1139,8 @@ class ResultRate(Rate):
|
|||
collection_class=OrderedSet),
|
||||
primaryjoin=workbench_mobile_id == QualityRateMobile.id)
|
||||
|
||||
# TODO Add more source that global rate can use it
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
kwargs.setdefault('version', StrictVersion('1.0'))
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -1081,11 +1164,11 @@ class ResultRate(Rate):
|
|||
pass
|
||||
|
||||
@classmethod
|
||||
def functionality_category(cls, quality: QualityRate):
|
||||
def functionality_category(cls, functionality: FunctionalityRate):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def appearance_category(cls, quality: QualityRate):
|
||||
def appearance_category(cls, appearance: ManualRate):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
|
@ -1151,103 +1234,6 @@ class ResultRate(Rate):
|
|||
def labelling(self):
|
||||
return self.workbench.labelling
|
||||
|
||||
@classmethod
|
||||
def from_workbench_rate(cls, rate: QualityRateComputer):
|
||||
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 BenchmarkRate:
|
||||
"""
|
||||
Common class to group by benchmark rate classes
|
||||
"""
|
||||
|
||||
|
||||
class BenchmarkQuality(BenchmarkRate):
|
||||
"""
|
||||
Computes quality benchmarks results to aggregate in result rate
|
||||
"""
|
||||
cpu_sysbench = Column(Float(decimal_return_scale=2))
|
||||
cpu_sysbench.comment = 'Benchmark processor component with sysbench tool'
|
||||
ram_sysbench = Column(Float(decimal_return_scale=2))
|
||||
ram_sysbench.comment = 'Benchmark RAM component'
|
||||
# gpu_sysbench = Column(Float(decimal_return_scale=2)) todo how to do?
|
||||
data_storage_sysbench = Column(Float(decimal_return_scale=2))
|
||||
data_storage_sysbench.comment = 'Benchmark data storage component with sysbench tool'
|
||||
data_storage_smart = Column(Float(decimal_return_scale=2))
|
||||
data_storage_smart.comment = 'Benchmark data storage component with SMART tool'
|
||||
|
||||
|
||||
class FunctionalityTest(Test):
|
||||
"""
|
||||
Class where are generic devices functionality aspects
|
||||
"""
|
||||
|
||||
|
||||
class TestAudio(FunctionalityTest):
|
||||
"""
|
||||
Test to check all this aspects related with audio fucntions
|
||||
"""
|
||||
loudspeaker = Column(BDEnum(LoudspeakerRange))
|
||||
loudspeaker.comment = 'Range to determine if the speaker is working properly and what sound quality it has.'
|
||||
microphone = Column(Boolean)
|
||||
microphone.comment = 'This evaluate if microphone works correctly'
|
||||
|
||||
|
||||
class TestConnectivity(FunctionalityTest):
|
||||
"""
|
||||
Test to check all this aspects related with functionality connections in devices
|
||||
"""
|
||||
|
||||
SIM = Column(Boolean)
|
||||
SIM.comment = 'Evaluate if SIM works'
|
||||
wifi = Column(Boolean)
|
||||
wifi.comment = 'Evaluate if wifi connection works correctly'
|
||||
bluetooth = Column(Boolean)
|
||||
bluetooth.comment = 'Evaluate if bluetooth works'
|
||||
usb = Column(DBEnum())
|
||||
usb.comment = 'Evaluate if usb port was detected and charger plug works'
|
||||
|
||||
|
||||
class TestBattery(FunctionalityTest):
|
||||
"""
|
||||
Test of length of charge. Source R2: Minimum X minutes discharging the device
|
||||
"""
|
||||
battery_duration = Column(Boolean())
|
||||
battery_duration.comment = ''
|
||||
|
||||
|
||||
class TestBios(FunctionalityTest):
|
||||
"""
|
||||
Test that determinate if is difficult to access BIOS, like need password, are protected..
|
||||
"""
|
||||
bios_range = Column(DBEnum())
|
||||
bios_range.comment = 'Range of difficult to acces BIOS'
|
||||
|
||||
|
||||
class TestApperance():
|
||||
"""
|
||||
Class with appearance characteristics
|
||||
"""
|
||||
chassis_defects_range = Column(BDEnum(ChassisRange))
|
||||
chassis_defects_range.comment = 'Range to determinate cosmetic defects on chassis like scratches'
|
||||
|
||||
|
||||
class TestVisual(TestAppearance):
|
||||
"""
|
||||
Check aesthetics or cosmetic aspects. Like defects on chassis, display, ..
|
||||
"""
|
||||
camera_defects_range = Column(BDEnum(CameraRange))
|
||||
camera_defects_range = 'Range to determinate cosmetic defects on camera'
|
||||
display_defects_range = Column(BDEnum(DisplayRange))
|
||||
display_defects_range = 'Range to determinate cosmetic defects on display'
|
||||
|
||||
|
||||
class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
"""The act of setting a trading price for the device.
|
||||
|
@ -1316,150 +1302,95 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
return '{0:0.2f} {1}'.format(self.price, self.currency)
|
||||
|
||||
|
||||
class Test(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
"""The act of testing the physical condition of a device and its
|
||||
components.
|
||||
class EreusePrice(Price):
|
||||
"""The act of setting a price by guessing it using the eReuse.org
|
||||
algorithm.
|
||||
|
||||
Testing errors and warnings are easily taken in
|
||||
:attr:`ereuse_devicehub.resources.device.models.Device.working`.
|
||||
This algorithm states that the price is the use value of the device
|
||||
(represented by its last :class:`.Rate`) multiplied by a constants
|
||||
value agreed by a circuit or platform.
|
||||
"""
|
||||
elapsed = Column(Interval, nullable=False)
|
||||
MULTIPLIER = {
|
||||
Desktop: 20,
|
||||
Laptop: 30
|
||||
}
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
class Type:
|
||||
def __init__(self, percentage: float, price: Decimal) -> None:
|
||||
# see https://stackoverflow.com/a/29651462 for the - 0.005
|
||||
self.amount = EreusePrice.to_price(price * Decimal(percentage))
|
||||
self.percentage = EreusePrice.to_price(price * Decimal(percentage))
|
||||
self.percentage = round(percentage - 0.005, 2)
|
||||
|
||||
class Service:
|
||||
REFURBISHER, PLATFORM, RETAILER = 0, 1, 2
|
||||
STANDARD, WARRANTY2 = 'STD', 'WR2'
|
||||
SCHEMA = {
|
||||
Desktop: {
|
||||
RatingRange.HIGH: {
|
||||
STANDARD: (0.35125, 0.204375, 0.444375),
|
||||
WARRANTY2: (0.47425, 0.275875, 0.599875)
|
||||
},
|
||||
RatingRange.MEDIUM: {
|
||||
STANDARD: (0.385, 0.2558333333, 0.3591666667),
|
||||
WARRANTY2: (0.539, 0.3581666667, 0.5028333333)
|
||||
},
|
||||
RatingRange.LOW: {
|
||||
STANDARD: (0.5025, 0.30875, 0.18875),
|
||||
},
|
||||
},
|
||||
Laptop: {
|
||||
RatingRange.HIGH: {
|
||||
STANDARD: (0.3469230769, 0.195, 0.4580769231),
|
||||
WARRANTY2: (0.4522307692, 0.2632307692, 0.6345384615)
|
||||
},
|
||||
RatingRange.MEDIUM: {
|
||||
STANDARD: (0.382, 0.1735, 0.4445),
|
||||
WARRANTY2: (0.5108, 0.2429, 0.6463)
|
||||
},
|
||||
RatingRange.LOW: {
|
||||
STANDARD: (0.4528571429, 0.2264285714, 0.3207142857),
|
||||
}
|
||||
}
|
||||
}
|
||||
SCHEMA[Server] = SCHEMA[Desktop]
|
||||
|
||||
def __init__(self, device, rating_range, role, price: Decimal) -> None:
|
||||
cls = device.__class__ if device.__class__ != Server else Desktop
|
||||
rate = self.SCHEMA[cls][rating_range]
|
||||
self.standard = EreusePrice.Type(rate[self.STANDARD][role], price)
|
||||
if self.WARRANTY2 in rate:
|
||||
self.warranty2 = EreusePrice.Type(rate[self.WARRANTY2][role], price)
|
||||
|
||||
def __init__(self, rating: AggregateRate, **kwargs) -> None:
|
||||
if rating.rating_range == RatingRange.VERY_LOW:
|
||||
raise ValueError('Cannot compute price for Range.VERY_LOW')
|
||||
# We pass ROUND_UP strategy so price is always greater than what refurbisher... amounts
|
||||
price = self.to_price(rating.rating * self.MULTIPLIER[rating.device.__class__], ROUND_UP)
|
||||
super().__init__(rating=rating,
|
||||
device=rating.device,
|
||||
price=price,
|
||||
software=kwargs.pop('software', app.config['PRICE_SOFTWARE']),
|
||||
version=kwargs.pop('version', app.config['PRICE_VERSION']),
|
||||
**kwargs)
|
||||
self._compute()
|
||||
|
||||
@orm.reconstructor
|
||||
def _compute(self):
|
||||
"""
|
||||
Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
#sqlalchemy.ext.declarative.declared_attr>`_
|
||||
Calculates eReuse.org prices when initializing the
|
||||
instance from the price and other properties.
|
||||
"""
|
||||
args = {POLYMORPHIC_ID: cls.t}
|
||||
if cls.t == 'Test':
|
||||
args[POLYMORPHIC_ON] = cls.type
|
||||
return args
|
||||
self.refurbisher = self._service(self.Service.REFURBISHER)
|
||||
self.retailer = self._service(self.Service.RETAILER)
|
||||
self.platform = self._service(self.Service.PLATFORM)
|
||||
if hasattr(self.refurbisher, 'warranty2'):
|
||||
self.warranty2 = round(self.refurbisher.warranty2.amount
|
||||
+ self.retailer.warranty2.amount
|
||||
+ self.platform.warranty2.amount, 2)
|
||||
|
||||
|
||||
class TestDataStorage(Test):
|
||||
"""
|
||||
The act of testing the data storage.
|
||||
|
||||
Testing is done using the `S.M.A.R.T self test
|
||||
<https://en.wikipedia.org/wiki/S.M.A.R.T.#Self-tests>`_. Note
|
||||
that not all data storage units, specially some new PCIe ones, do not
|
||||
support SMART testing.
|
||||
|
||||
The test takes to other SMART values indicators of the overall health
|
||||
of the data storage.
|
||||
"""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
|
||||
length = Column(DBEnum(TestDataStorageLength), nullable=False) # todo from type
|
||||
status = Column(Unicode(), check_lower('status'), nullable=False)
|
||||
lifetime = Column(Interval)
|
||||
assessment = Column(Boolean)
|
||||
reallocated_sector_count = Column(SmallInteger)
|
||||
power_cycle_count = Column(SmallInteger)
|
||||
reported_uncorrectable_errors = Column(SmallInteger)
|
||||
command_timeout = Column(Integer)
|
||||
current_pending_sector_count = Column(SmallInteger)
|
||||
offline_uncorrectable = Column(SmallInteger)
|
||||
remaining_lifetime_percentage = Column(SmallInteger)
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Define severity
|
||||
# As of https://www.backblaze.com/blog/hard-drive-smart-stats/ and
|
||||
# https://www.backblaze.com/blog-smart-stats-2014-8.html
|
||||
# We can guess some future disk failures by analyzing some SMART data.
|
||||
if self.severity is None:
|
||||
# Test finished successfully
|
||||
if not self.assessment:
|
||||
self.severity = Severity.Error
|
||||
elif self.current_pending_sector_count and self.current_pending_sector_count > 40 \
|
||||
or self.reallocated_sector_count and self.reallocated_sector_count > 10:
|
||||
self.severity = Severity.Warning
|
||||
|
||||
def __str__(self) -> str:
|
||||
t = inflection.humanize(self.status)
|
||||
if self.lifetime:
|
||||
t += ' with a lifetime of {:.1f} years.'.format(self.lifetime.days / 365)
|
||||
t += self.description
|
||||
return t
|
||||
|
||||
|
||||
class StressTest(Test):
|
||||
"""The act of stressing (putting to the maximum capacity)
|
||||
a device for an amount of minutes. If the device is not in great
|
||||
condition won't probably survive such test.
|
||||
"""
|
||||
|
||||
@validates('elapsed')
|
||||
def is_minute_and_bigger_than_1_minute(self, _, value: timedelta):
|
||||
seconds = value.total_seconds()
|
||||
assert not bool(seconds % 60)
|
||||
assert seconds >= 60
|
||||
return value
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{}. Computing for {}'.format(self.severity, self.elapsed)
|
||||
|
||||
|
||||
class Benchmark(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
"""The act of gauging the performance of a device."""
|
||||
elapsed = Column(Interval)
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
#sqlalchemy.ext.declarative.declared_attr>`_
|
||||
"""
|
||||
args = {POLYMORPHIC_ID: cls.t}
|
||||
if cls.t == 'Benchmark':
|
||||
args[POLYMORPHIC_ON] = cls.type
|
||||
return args
|
||||
|
||||
|
||||
class BenchmarkDataStorage(Benchmark):
|
||||
"""Benchmarks the data storage unit reading and writing speeds."""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
|
||||
read_speed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||
write_speed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return 'Read: {} MB/s, write: {} MB/s'.format(self.read_speed, self.write_speed)
|
||||
|
||||
|
||||
class BenchmarkWithRate(Benchmark):
|
||||
"""The act of benchmarking a device with a single rate."""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
|
||||
rate = Column(Float, nullable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{} points'.format(self.rate)
|
||||
|
||||
|
||||
class BenchmarkProcessor(BenchmarkWithRate):
|
||||
"""Benchmarks a processor by executing `BogoMips
|
||||
<https://en.wikipedia.org/wiki/BogoMips>`_. Note that this is not
|
||||
a reliable way of rating processors and we keep it for compatibility
|
||||
purposes.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BenchmarkProcessorSysbench(BenchmarkProcessor):
|
||||
"""Benchmarks a processor by using the processor benchmarking
|
||||
utility of `sysbench <https://github.com/akopytov/sysbench>`_.
|
||||
"""
|
||||
|
||||
|
||||
class BenchmarkRamSysbench(BenchmarkWithRate):
|
||||
pass
|
||||
def _service(self, role):
|
||||
return self.Service(self.device, self.rating.rating_range, role, self.price)
|
||||
|
||||
|
||||
class ToRepair(EventWithMultipleDevices):
|
||||
|
|
|
@ -2,7 +2,6 @@ from enum import Enum
|
|||
from typing import Iterable
|
||||
|
||||
from ereuse_devicehub.resources.device.models import DataStorage, Processor, RamModule, Device
|
||||
from ereuse_devicehub.resources.enums import RatingRange
|
||||
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, WorkbenchRate
|
||||
from ereuse_devicehub.resources.event.rate.rate import BaseRate
|
||||
|
||||
|
@ -93,7 +92,7 @@ class FunctionalityRate(BaseRate):
|
|||
LOUDSPEAKER_WEIGHT = 0.15
|
||||
MICROPHONE_WEIGHT = 0.15
|
||||
|
||||
def compute(self, FunctionalityDevice: FunctionalityRate):
|
||||
def compute(self, FunctionalityRate: FunctionalityRate):
|
||||
"""
|
||||
|
||||
:param FunctionalityDevice: List[Boolean]
|
||||
|
@ -116,6 +115,7 @@ class FunctionalityRate(BaseRate):
|
|||
class Appearance(Range):
|
||||
"""
|
||||
APPEARANCE GRADE [0.5,-0.5]
|
||||
Enum(AppearanceRangev2)
|
||||
"""
|
||||
|
||||
Z = 0.5
|
||||
|
@ -127,166 +127,7 @@ class Appearance(Range):
|
|||
NONE = -0.3
|
||||
|
||||
|
||||
class QualityRate(Rate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
processor = Column(Float(decimal_return_scale=2), check_range('processor', *RATE_POSITIVE),
|
||||
comment='Is a test explain cpu component.')
|
||||
ram = Column(Float(decimal_return_scale=2), check_range('ram', *RATE_POSITIVE),
|
||||
comment='RAM memory rate.')
|
||||
data_storage = Column(Float(decimal_return_scale=2), check_range('data_storage', *RATE_POSITIVE),
|
||||
comment='Data storage rate, like HHD, SSD.')
|
||||
|
||||
@property
|
||||
def ram_range(self):
|
||||
return self.workbench.ram_range
|
||||
|
||||
@property
|
||||
def processor_range(self):
|
||||
return self.workbench.processor_range
|
||||
|
||||
@property
|
||||
def display_range(self):
|
||||
return self.workbench.data_storage_range
|
||||
|
||||
@property
|
||||
def data_storage_range(self):
|
||||
return self.workbench.data_storage_range
|
||||
|
||||
@property
|
||||
def battery_range(self):
|
||||
return self.workbench.ram_range
|
||||
|
||||
@property
|
||||
def camera_range(self):
|
||||
return self.workbench_mobile.camera_range
|
||||
|
||||
@property
|
||||
def graphic_card_range(self):
|
||||
return self.workbench_mobil.graphic_card_range
|
||||
|
||||
|
||||
class QualityRateComputer(QualityRate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(QualityRate.id), primary_key=True)
|
||||
processor = Column(Float(decimal_return_scale=2), check_range('processor', *RATE_POSITIVE),
|
||||
comment='Is a test explain cpu component.')
|
||||
ram = Column(Float(decimal_return_scale=2), check_range('ram', *RATE_POSITIVE),
|
||||
comment='RAM memory rate.')
|
||||
data_storage = Column(Float(decimal_return_scale=2), check_range('data_storage', *RATE_POSITIVE),
|
||||
comment='Data storage rate, like HHD, SSD.')
|
||||
|
||||
graphic_card = Column(Float(decimal_return_scale=2), check_range('graphic_card', *RATE_POSITIVE),
|
||||
comment='Graphic card score in performance, amount of memory and benchmark result')
|
||||
network_adapter = Column(Float(decimal_return_scale=2), check_range('network_adapter', *RATE_POSITIVE),
|
||||
comment='Network adapter rate, take it speed limit')
|
||||
|
||||
bios = Column(Float(decimal_return_scale=2), check_range('bios', *RATE_POSITIVE))
|
||||
bios_range = Column(DBEnum(Bios))
|
||||
bios_range.comment = Bios.__doc__
|
||||
|
||||
# todo ensure for WorkbenchRate version and software are not None when inserting them
|
||||
|
||||
def ratings(self):
|
||||
"""
|
||||
#Computes all the possible rates taking this rating as a model.
|
||||
|
||||
#Returns a set of ratings, including this one, which is mutated,
|
||||
#and the final :class:`.AggregateRate`.
|
||||
"""
|
||||
from ereuse_devicehub.resources.event.rate.main import main
|
||||
return main(self, **app.config.get_namespace('WORKBENCH_RATE_'))
|
||||
|
||||
@property
|
||||
def graphic_card_range(self):
|
||||
if self.graphic_card:
|
||||
return RatingRange.from_score(self.graphic_card)
|
||||
|
||||
@property
|
||||
def network_adapter_range(self):
|
||||
return self.workbench_mobil.network_adapter_range
|
||||
|
||||
@property
|
||||
def bios_range(self):
|
||||
return self.workbench_mobil.bios_range
|
||||
|
||||
|
||||
class QualityRateMobile(QualityRate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(QualityRate.id), primary_key=True)
|
||||
display = Column(Float(decimal_return_scale=2), check_range('display', *RATE_POSITIVE))
|
||||
display.comment = 'Display rate, screen resolution and size to calculate PPI and convert in score'
|
||||
battery = Column(Float(decimal_return_scale=2), check_range('battery', *RATE_POSITIVE),
|
||||
comment='Battery rate is related with capacity and its health')
|
||||
camera = Column(Float(decimal_return_scale=2), check_range('camera', *RATE_POSITIVE),
|
||||
comment='Camera rate take into account resolution')
|
||||
|
||||
graphic_card = Column(Float(decimal_return_scale=2), check_range('graphic_card', *RATE_POSITIVE),
|
||||
comment='Graphic card score in performance, amount of memory and benchmark result')
|
||||
network_adapter = Column(Float(decimal_return_scale=2), check_range('network_adapter', *RATE_POSITIVE),
|
||||
comment='Network adapter rate, take it speed limit')
|
||||
|
||||
bios = Column(Float(decimal_return_scale=2), check_range('bios', *RATE_POSITIVE))
|
||||
bios_range = Column(DBEnum(Bios))
|
||||
bios_range.comment = Bios.__doc__
|
||||
|
||||
# todo ensure for WorkbenchRate version and software are not None when inserting them
|
||||
|
||||
def ratings(self):
|
||||
"""
|
||||
#Computes all the possible rates taking this rating as a model.
|
||||
"""
|
||||
from ereuse_devicehub.resources.event.rate.main import main
|
||||
return main(self, **app.config.get_namespace('WORKBENCH_RATE_'))
|
||||
|
||||
@property
|
||||
def display_range(self):
|
||||
if self.data_storage:
|
||||
return RatingRange.from_score(self.data_storage)
|
||||
|
||||
@property
|
||||
def battery_range(self):
|
||||
if self.ram:
|
||||
return RatingRange.from_score(self.ram)
|
||||
|
||||
@property
|
||||
def camera_range(self):
|
||||
if self.processor:
|
||||
return RatingRange.from_score(self.processor)
|
||||
|
||||
@property
|
||||
def graphic_card_range(self):
|
||||
if self.graphic_card:
|
||||
return RatingRange.from_score(self.graphic_card)
|
||||
|
||||
|
||||
class FunctionalityRate(Rate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
functionality = Column(Float(decimal_return_scale=2), check_range('functionality', *FUNCTIONALITY_RANGE))
|
||||
functionality.comment = 'Functionality rate of a device'
|
||||
|
||||
functionality_range = Column(DBEnum(FunctionalityRangev2))
|
||||
functionality_range.comment = FunctionalityRangev2.__doc__
|
||||
|
||||
connectivity = Column(Float(decimal_return_scale=2),
|
||||
comment='This punctuation covers a series of aspects related to connectivity.')
|
||||
audio = Column(Float(decimal_return_scale=2), comment='Take into account loudspeaker and microphone')
|
||||
|
||||
@property
|
||||
def connectivity_rate(self):
|
||||
yield
|
||||
|
||||
@property
|
||||
def audio_rate(self):
|
||||
yield
|
||||
|
||||
@property
|
||||
def test_buttonse(self):
|
||||
yield
|
||||
|
||||
@classmethod
|
||||
def test_camera_defects(self):
|
||||
yield
|
||||
|
||||
|
||||
class ResultRate(Rate):
|
||||
class FinalRate(Rate):
|
||||
"""The act of grading the appearance, quality (performance), and functionality
|
||||
of a device.
|
||||
|
||||
|
@ -297,37 +138,19 @@ class ResultRate(Rate):
|
|||
4. ``Market value``.
|
||||
5. ``Cost of repair``.
|
||||
|
||||
|
||||
There are types of rating a device:
|
||||
|
||||
1. Rate Quality
|
||||
2. Rate Functionality
|
||||
3. Rate Final
|
||||
|
||||
|
||||
List of source where can input information of rating a device:
|
||||
|
||||
1. When processing the device with Workbench Computer/Mobile.
|
||||
2. Using the Android App (through Scan).
|
||||
3.
|
||||
4. Anytime after manually written in a form in the website.
|
||||
"""
|
||||
quality_rate = QualityRate()
|
||||
functionality_rate = FunctionalityRate()
|
||||
appearance_rate = Appearance()
|
||||
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
quality_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
|
||||
quality_id.comment = """The Quality Rate used to generate this
|
||||
aggregation, or None if none used.
|
||||
"""
|
||||
final_rate = None
|
||||
|
||||
func_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
|
||||
func_id.comment = """The Functionality Rate used to generate this
|
||||
aggregation, or None if none used.
|
||||
"""
|
||||
|
||||
final_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
|
||||
final_id.comment = """The Final Rate used to generate this
|
||||
aggregation, or None if none used.
|
||||
"""
|
||||
# TODO Develop merge rate aspects (categories)
|
||||
|
||||
""" MANUAL INPUT """
|
||||
manual_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
|
||||
|
@ -408,7 +231,6 @@ class ResultRate(Rate):
|
|||
pass
|
||||
|
||||
|
||||
|
||||
class DisplayRate(QualityRate):
|
||||
"""
|
||||
Calculate a DisplayRate
|
||||
|
|
|
@ -109,6 +109,77 @@ class StepRandom(Step):
|
|||
__doc__ = m.StepRandom.__doc__
|
||||
|
||||
|
||||
class Benchmark(EventWithOneDevice):
|
||||
__doc__ = m.Benchmark.__doc__
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
|
||||
|
||||
class BenchmarkDataStorage(Benchmark):
|
||||
__doc__ = m.BenchmarkDataStorage.__doc__
|
||||
read_speed = Float(required=True, data_key='readSpeed')
|
||||
write_speed = Float(required=True, data_key='writeSpeed')
|
||||
|
||||
|
||||
class BenchmarkWithRate(Benchmark):
|
||||
__doc__ = m.BenchmarkWithRate.__doc__
|
||||
rate = Float(required=True)
|
||||
|
||||
|
||||
class BenchmarkProcessor(BenchmarkWithRate):
|
||||
__doc__ = m.BenchmarkProcessor.__doc__
|
||||
|
||||
|
||||
class BenchmarkProcessorSysbench(BenchmarkProcessor):
|
||||
__doc__ = m.BenchmarkProcessorSysbench.__doc__
|
||||
|
||||
|
||||
class BenchmarkRamSysbench(BenchmarkWithRate):
|
||||
__doc__ = m.BenchmarkRamSysbench.__doc__
|
||||
|
||||
|
||||
class BenchmarkGraphicCard(BenchmarkWithRate):
|
||||
__doc__ = m.BenchmarkGraphicCard.__doc__
|
||||
|
||||
|
||||
class Test(EventWithOneDevice):
|
||||
__doc__ = m.Test.__doc__
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
|
||||
|
||||
class TestDataStorage(Test):
|
||||
__doc__ = m.TestDataStorage.__doc__
|
||||
length = EnumField(TestDataStorageLength, required=True)
|
||||
status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True)
|
||||
lifetime = TimeDelta(precision=TimeDelta.HOURS)
|
||||
assessment = Boolean()
|
||||
reallocated_sector_count = Integer(data_key='reallocatedSectorCount')
|
||||
power_cycle_count = Integer(data_key='powerCycleCount')
|
||||
reported_uncorrectable_errors = Integer(data_key='reportedUncorrectableErrors')
|
||||
command_timeout = Integer(data_key='commandTimeout')
|
||||
current_pending_sector_count = Integer(data_key='currentPendingSectorCount')
|
||||
offline_uncorrectable = Integer(data_key='offlineUncorrectable')
|
||||
remaining_lifetime_percentage = Integer(data_key='remainingLifetimePercentage')
|
||||
|
||||
|
||||
class StressTest(Test):
|
||||
__doc__ = m.StressTest.__doc__
|
||||
|
||||
|
||||
class TestAudio(Test):
|
||||
__doc__ = m.TestAudio.__doc__
|
||||
|
||||
|
||||
class TestConnectivity(Test):
|
||||
__doc__ = m.TestConnectivity.__doc__
|
||||
|
||||
|
||||
class TestBattery(Test):
|
||||
__doc__ = m.TestBattery.__doc__
|
||||
|
||||
|
||||
class TestVisual(ManualRate):
|
||||
__doc__ = m.TestVisual.__doc__
|
||||
|
||||
|
||||
class Rate(EventWithOneDevice):
|
||||
__doc__ = m.Rate.__doc__
|
||||
|
@ -142,7 +213,7 @@ class ManualRate(IndividualRate):
|
|||
labelling = Boolean(description=m.ManualRate.labelling.comment)
|
||||
|
||||
|
||||
class WorkbenchRate(ManualRate):
|
||||
class WorkbencComputer(ManualRate):
|
||||
__doc__ = m.WorkbenchRate.__doc__
|
||||
processor = Float()
|
||||
ram = Float()
|
||||
|
@ -158,26 +229,8 @@ class WorkbenchRate(ManualRate):
|
|||
graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange')
|
||||
|
||||
|
||||
"""
|
||||
WORKBENCH RATE COMPUTER
|
||||
Adaptation of old class WorkbenchRate
|
||||
"""
|
||||
|
||||
|
||||
class WorkbenchRateComputer(ManualRate):
|
||||
__doc__ = m.WorkbenchRate.__doc__
|
||||
processor = Float()
|
||||
ram = Float()
|
||||
data_storage = Float()
|
||||
graphic_card = Float()
|
||||
bios = Float()
|
||||
bios_range = EnumField(Bios,
|
||||
description=m.WorkbenchComputerRate.bios_range.comment,
|
||||
data_key='biosRange')
|
||||
data_storage_range = EnumField(RatingRange, dump_only=True, data_key='dataStorageRange')
|
||||
ram_range = EnumField(RatingRange, dump_only=True, data_key='ramRange')
|
||||
processor_range = EnumField(RatingRange, dump_only=True, data_key='processorRange')
|
||||
graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange')
|
||||
class WorkbenchMobile(ManualRate):
|
||||
pass
|
||||
|
||||
|
||||
class AggregateRate(Rate):
|
||||
|
@ -210,6 +263,9 @@ class AggregateRate(Rate):
|
|||
graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange')
|
||||
|
||||
|
||||
""" RATE v2 CODE"""
|
||||
|
||||
|
||||
class QualityRate(Rate):
|
||||
__doc__ = m.QualityRate.__doc__
|
||||
|
||||
|
@ -217,37 +273,17 @@ class QualityRate(Rate):
|
|||
processor = Float(dump_only=True, description=m.QualityRate.processor.comment)
|
||||
data_storage = Float(dump_only=True, description=m.QualityRate.data_storage.comment)
|
||||
|
||||
graphic_card = Float(dump_only=True, description=m.QualityRate.processor.comment)
|
||||
network_adapter = Float(dump_only=True, description=m.QualityRate.network_adapter.comment)
|
||||
|
||||
""" New class for WorkbenchRate in Rate v2"""
|
||||
display = Float(dump_only=True, description=m.QualityRate.display.comment)
|
||||
battery = Float(dump_only=True, description=m.QualityRate.batter.comment)
|
||||
camera = Float(dump_only=True, description=m.QualityRate.camera.comment)
|
||||
|
||||
|
||||
class QualityRateComputer(Rate):
|
||||
__doc__ = m.QualityRateComputer.__doc__
|
||||
|
||||
""" List of components appears in general quality rate a device
|
||||
|
||||
ram = Float(dump_only=True, description=m.QualityRateComputer.ram.comment)
|
||||
processor = Float(dump_only=True, description=m.QualityRateComputer.processor.comment)
|
||||
data_storage = Float(dump_only=True, description=m.QualityRateComputer.data_storage.comment)
|
||||
"""
|
||||
|
||||
graphic_card = Float(dump_only=True, description=m.QualityRateComputer.processor.comment)
|
||||
network_adapter = Float(dump_only=True, description=m.QualityRateComputer.network_adapter.comment)
|
||||
|
||||
|
||||
class QualityRateMobile(Rate):
|
||||
__doc__ = m.QualityRateMobile.__doc__
|
||||
|
||||
""" List of components appears in general quality rate a device
|
||||
|
||||
ram = Float(dump_only=True, description=m.QualityRateMobile.ram.comment)
|
||||
processor = Float(dump_only=True, description=m.QualityRateMobile.processor.comment)
|
||||
data_storage = Float(dump_only=True, description=m.QualityRateMobile.data_storage.comment)
|
||||
"""
|
||||
|
||||
display = Float(dump_only=True, description=m.QualityRateMobile.display.comment)
|
||||
battery = Float(dump_only=True, description=m.QualityRateMobile.batter.comment)
|
||||
camera = Float(dump_only=True, description=m.QualityRateMobile.camera.comment)
|
||||
bios = EnumField(Bios, dump_only=True)
|
||||
bios_range = EnumField(Bios,
|
||||
description=m.WorkbenchRate.bios_range.comment,
|
||||
data_key='biosRange')
|
||||
|
||||
|
||||
class FunctionalityRate(Rate):
|
||||
|
@ -255,49 +291,28 @@ class FunctionalityRate(Rate):
|
|||
|
||||
functionality = EnumField(dump_only=True, description=m.FunctionalityRate.functionality.comment)
|
||||
functionality_range = EnumField(dump_only=True, description=m.FunctionalityRate.functionality_range.comment)
|
||||
connectivity_rate = EnumField(dump_only=True, description=m.FunctionalityRate.connectivity_rate.comment)
|
||||
audio_rate = EnumField(dump_only=True, description=m.FunctionalityRate.audio_rate.comment)
|
||||
|
||||
|
||||
# TODO Finish input rates (internal and external sources) - Whats really interesting to save in BD?? Whichs aspects?
|
||||
class FinalRate(Rate):
|
||||
__doc__ = m.FinalRate.__doc__
|
||||
quality = NestedOn(QualityRate, dump_only=True,
|
||||
description=m.QualityRate.quality_id.comment)
|
||||
functionality = NestedOn(FunctionalityRate, dump_only=True,
|
||||
description=m.FunctionalityRange.functionality_id.comment)
|
||||
appearance = NestedOn(TestVisual, dump_only=True)
|
||||
workbench_computer = NestedOn(WorkbenchComputer, dump_only=True,
|
||||
description=m.ResultRate.workbench_computer_id.comment)
|
||||
workbench_mobile = NestedOn(WorkbenchMobile, dump_only=True,
|
||||
description=m.ResultRate.workbench_mobile_id.comment)
|
||||
bios = EnumField(Bios, dump_only=True)
|
||||
bios_range = EnumField(Bios,
|
||||
description=m.WorkbenchRate.bios_range.comment,
|
||||
data_key='biosRange')
|
||||
|
||||
|
||||
# TODO Finish input rates (internal and external sources) - Whats really interesting to save in BD?? Whichs aspects?
|
||||
class ResultRate(Rate):
|
||||
__doc__ = m.ResultRate.__doc__
|
||||
# TODO ask for what to do NestedOn??
|
||||
workbench_computer = NestedOn(WorkbenchRateComputer, dump_only=True,
|
||||
description=m.ResultRate.workbench_computer_id.comment)
|
||||
workbench_mobile = NestedOn(WorkbenchRateMobile, dump_only=True,
|
||||
description=m.ResultRate.workbench_mobile_id.comment)
|
||||
manual_computer = NestedOn(ManualRateComputer,
|
||||
dump_only=True,
|
||||
description=m.ResultRate.manual_computer_id.comment)
|
||||
|
||||
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)
|
||||
bios_range = EnumField(Bios,
|
||||
description=m.WorkbenchRate.bios_range.comment,
|
||||
data_key='biosRange')
|
||||
appearance_range = EnumField(AppearanceRange,
|
||||
appearance_range = EnumField(AppearanceRangev2,
|
||||
required=True,
|
||||
data_key='appearanceRange',
|
||||
data_key='appearanceRangev2',
|
||||
description=m.ManualRate.appearance_range.comment)
|
||||
functionality_range = EnumField(FunctionalityRange,
|
||||
functionality_range = EnumField(FunctionalityRangev2,
|
||||
required=True,
|
||||
data_key='functionalityRange',
|
||||
data_key='functionalityRangev2',
|
||||
description=m.ManualRate.functionality_range.comment)
|
||||
labelling = Boolean(description=m.ManualRate.labelling.comment)
|
||||
data_storage_range = EnumField(RatingRange, dump_only=True, data_key='dataStorageRange')
|
||||
|
@ -306,10 +321,6 @@ class ResultRate(Rate):
|
|||
graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Price(EventWithOneDevice):
|
||||
__doc__ = m.Price.__doc__
|
||||
currency = EnumField(Currency, required=True, description=m.Price.currency.comment)
|
||||
|
@ -413,58 +424,6 @@ class Snapshot(EventWithOneDevice):
|
|||
field_names=['elapsed'])
|
||||
|
||||
|
||||
class Test(EventWithOneDevice):
|
||||
__doc__ = m.Test.__doc__
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
|
||||
|
||||
class TestDataStorage(Test):
|
||||
__doc__ = m.TestDataStorage.__doc__
|
||||
length = EnumField(TestDataStorageLength, required=True)
|
||||
status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True)
|
||||
lifetime = TimeDelta(precision=TimeDelta.HOURS)
|
||||
assessment = Boolean()
|
||||
reallocated_sector_count = Integer(data_key='reallocatedSectorCount')
|
||||
power_cycle_count = Integer(data_key='powerCycleCount')
|
||||
reported_uncorrectable_errors = Integer(data_key='reportedUncorrectableErrors')
|
||||
command_timeout = Integer(data_key='commandTimeout')
|
||||
current_pending_sector_count = Integer(data_key='currentPendingSectorCount')
|
||||
offline_uncorrectable = Integer(data_key='offlineUncorrectable')
|
||||
remaining_lifetime_percentage = Integer(data_key='remainingLifetimePercentage')
|
||||
|
||||
|
||||
class StressTest(Test):
|
||||
__doc__ = m.StressTest.__doc__
|
||||
|
||||
|
||||
class Benchmark(EventWithOneDevice):
|
||||
__doc__ = m.Benchmark.__doc__
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
|
||||
|
||||
class BenchmarkDataStorage(Benchmark):
|
||||
__doc__ = m.BenchmarkDataStorage.__doc__
|
||||
read_speed = Float(required=True, data_key='readSpeed')
|
||||
write_speed = Float(required=True, data_key='writeSpeed')
|
||||
|
||||
|
||||
class BenchmarkWithRate(Benchmark):
|
||||
__doc__ = m.BenchmarkWithRate.__doc__
|
||||
rate = Float(required=True)
|
||||
|
||||
|
||||
class BenchmarkProcessor(BenchmarkWithRate):
|
||||
__doc__ = m.BenchmarkProcessor.__doc__
|
||||
|
||||
|
||||
class BenchmarkProcessorSysbench(BenchmarkProcessor):
|
||||
__doc__ = m.BenchmarkProcessorSysbench.__doc__
|
||||
|
||||
|
||||
class BenchmarkRamSysbench(BenchmarkWithRate):
|
||||
__doc__ = m.BenchmarkRamSysbench.__doc__
|
||||
|
||||
|
||||
class ToRepair(EventWithMultipleDevices):
|
||||
__doc__ = m.ToRepair.__doc__
|
||||
|
||||
|
|
Reference in a new issue