smartfleet-optimizer 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- smartfleet_optimizer-0.1.0/LICENSE +4 -0
- smartfleet_optimizer-0.1.0/PKG-INFO +37 -0
- smartfleet_optimizer-0.1.0/README.md +16 -0
- smartfleet_optimizer-0.1.0/fleet_optimization_pkg/__init__.py +5 -0
- smartfleet_optimizer-0.1.0/fleet_optimization_pkg/analyzer.py +156 -0
- smartfleet_optimizer-0.1.0/fleet_optimization_pkg/models.py +89 -0
- smartfleet_optimizer-0.1.0/setup.cfg +4 -0
- smartfleet_optimizer-0.1.0/setup.py +22 -0
- smartfleet_optimizer-0.1.0/smartfleet_optimizer.egg-info/PKG-INFO +37 -0
- smartfleet_optimizer-0.1.0/smartfleet_optimizer.egg-info/SOURCES.txt +11 -0
- smartfleet_optimizer-0.1.0/smartfleet_optimizer.egg-info/dependency_links.txt +1 -0
- smartfleet_optimizer-0.1.0/smartfleet_optimizer.egg-info/top_level.txt +1 -0
- smartfleet_optimizer-0.1.0/tests/test_analyzer.py +130 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smartfleet-optimizer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Analytics library for fleet telemetry: risk scoring, alerts, maintenance prediction
|
|
5
|
+
Author: Satvik T M
|
|
6
|
+
Author-email: satvikw007@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
# fleet-optimization-engine-satvik25159712
|
|
23
|
+
|
|
24
|
+
Analytics library for fleet telemetry data. Provides risk scoring,
|
|
25
|
+
alert detection, fuel efficiency analysis and maintenance prediction.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
pip install fleet-optimization-engine-satvik25159712
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
from fleet_optimization_pkg import TelemetryAnalyzer
|
|
32
|
+
analyzer = TelemetryAnalyzer()
|
|
33
|
+
profile = analyzer.build_risk_profile({
|
|
34
|
+
"vehicle_id": "VAN-001", "speed_kmh": 50,
|
|
35
|
+
"fuel_percent": 75, "engine_temp_c": 85
|
|
36
|
+
})
|
|
37
|
+
print(profile.risk_label)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# fleet-optimization-engine-satvik25159712
|
|
2
|
+
|
|
3
|
+
Analytics library for fleet telemetry data. Provides risk scoring,
|
|
4
|
+
alert detection, fuel efficiency analysis and maintenance prediction.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
pip install fleet-optimization-engine-satvik25159712
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
from fleet_optimization_pkg import TelemetryAnalyzer
|
|
11
|
+
analyzer = TelemetryAnalyzer()
|
|
12
|
+
profile = analyzer.build_risk_profile({
|
|
13
|
+
"vehicle_id": "VAN-001", "speed_kmh": 50,
|
|
14
|
+
"fuel_percent": 75, "engine_temp_c": 85
|
|
15
|
+
})
|
|
16
|
+
print(profile.risk_label)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
analyzer.py
|
|
3
|
+
Stateless analytics engine for fleet telemetry data.
|
|
4
|
+
Called by the Lambda function each time a telemetry reading
|
|
5
|
+
arrives from SQS.
|
|
6
|
+
"""
|
|
7
|
+
from .models import AlertSeverity, FleetAlert, VehicleRiskProfile
|
|
8
|
+
|
|
9
|
+
# Thresholds
|
|
10
|
+
ENGINE_TEMP_WARNING = 100.0
|
|
11
|
+
ENGINE_TEMP_CRITICAL = 110.0
|
|
12
|
+
FUEL_WARNING = 20.0
|
|
13
|
+
FUEL_CRITICAL = 10.0
|
|
14
|
+
SPEED_WARNING = 100.0
|
|
15
|
+
SPEED_CRITICAL = 110.0
|
|
16
|
+
MAINTENANCE_TEMP = 95.0
|
|
17
|
+
MAINTENANCE_FUEL = 25.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TelemetryAnalyzer:
|
|
21
|
+
"""
|
|
22
|
+
Processes raw telemetry dicts into structured risk assessments.
|
|
23
|
+
|
|
24
|
+
All methods are stateless so this class is safe for use in
|
|
25
|
+
AWS Lambda which may create new instances at any time.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def build_risk_profile(self, reading):
|
|
29
|
+
"""
|
|
30
|
+
Main method. Runs all analyses and returns a VehicleRiskProfile.
|
|
31
|
+
"""
|
|
32
|
+
return VehicleRiskProfile(
|
|
33
|
+
vehicle_id = reading.get("vehicle_id", "UNKNOWN"),
|
|
34
|
+
risk_score = self.calculate_risk_score(reading),
|
|
35
|
+
alerts = self.detect_alerts(reading),
|
|
36
|
+
fuel_efficiency_score = self.calculate_fuel_efficiency_score(reading),
|
|
37
|
+
maintenance_due = self.predict_maintenance_needed(reading),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def calculate_risk_score(self, reading):
|
|
41
|
+
"""
|
|
42
|
+
Weighted composite risk score 0-100.
|
|
43
|
+
Returns (float) Score in range 0.0 to 100.0
|
|
44
|
+
"""
|
|
45
|
+
temp = reading.get("engine_temp_c", 80.0)
|
|
46
|
+
fuel = reading.get("fuel_percent", 100.0)
|
|
47
|
+
speed = reading.get("speed_kmh", 0.0)
|
|
48
|
+
|
|
49
|
+
# Engine temperature component
|
|
50
|
+
if temp >= ENGINE_TEMP_CRITICAL:
|
|
51
|
+
temp_risk = 100.0
|
|
52
|
+
elif temp >= ENGINE_TEMP_WARNING:
|
|
53
|
+
temp_risk = ((temp - ENGINE_TEMP_WARNING) /
|
|
54
|
+
(ENGINE_TEMP_CRITICAL - ENGINE_TEMP_WARNING)) * 100
|
|
55
|
+
else:
|
|
56
|
+
temp_risk = 0.0
|
|
57
|
+
|
|
58
|
+
# Fuel component, low fuel = high risk
|
|
59
|
+
if fuel <= FUEL_CRITICAL:
|
|
60
|
+
fuel_risk = 100.0
|
|
61
|
+
elif fuel <= FUEL_WARNING:
|
|
62
|
+
fuel_risk = ((FUEL_WARNING - fuel) /
|
|
63
|
+
(FUEL_WARNING - FUEL_CRITICAL)) * 100
|
|
64
|
+
else:
|
|
65
|
+
fuel_risk = 0.0
|
|
66
|
+
|
|
67
|
+
# Speed component
|
|
68
|
+
if speed >= SPEED_CRITICAL:
|
|
69
|
+
speed_risk = 100.0
|
|
70
|
+
elif speed >= SPEED_WARNING:
|
|
71
|
+
speed_risk = ((speed - SPEED_WARNING) /
|
|
72
|
+
(SPEED_CRITICAL - SPEED_WARNING)) * 100
|
|
73
|
+
else:
|
|
74
|
+
speed_risk = 0.0
|
|
75
|
+
|
|
76
|
+
return round(min(100.0, (temp_risk * 0.4) +
|
|
77
|
+
(fuel_risk * 0.3) +
|
|
78
|
+
(speed_risk * 0.3)), 2)
|
|
79
|
+
|
|
80
|
+
def detect_alerts(self, reading):
|
|
81
|
+
"""
|
|
82
|
+
Check all thresholds and return a list of FleetAlert objects.
|
|
83
|
+
"""
|
|
84
|
+
alerts = []
|
|
85
|
+
vid = reading.get("vehicle_id", "UNKNOWN")
|
|
86
|
+
temp = reading.get("engine_temp_c", 80.0)
|
|
87
|
+
fuel = reading.get("fuel_percent", 100.0)
|
|
88
|
+
speed = reading.get("speed_kmh", 0.0)
|
|
89
|
+
|
|
90
|
+
# Engine temperature checks
|
|
91
|
+
if temp >= ENGINE_TEMP_CRITICAL:
|
|
92
|
+
alerts.append(FleetAlert(
|
|
93
|
+
vid, "HIGH_ENGINE_TEMP", AlertSeverity.CRITICAL,
|
|
94
|
+
f"Engine temp {temp:.1f}C is critical — stop vehicle immediately.",
|
|
95
|
+
temp, ENGINE_TEMP_CRITICAL))
|
|
96
|
+
elif temp >= ENGINE_TEMP_WARNING:
|
|
97
|
+
alerts.append(FleetAlert(
|
|
98
|
+
vid, "HIGH_ENGINE_TEMP", AlertSeverity.WARNING,
|
|
99
|
+
f"Engine temp {temp:.1f}C exceeds warning threshold.",
|
|
100
|
+
temp, ENGINE_TEMP_WARNING))
|
|
101
|
+
|
|
102
|
+
# Fuel level checks
|
|
103
|
+
if fuel <= FUEL_CRITICAL:
|
|
104
|
+
alerts.append(FleetAlert(
|
|
105
|
+
vid, "LOW_FUEL", AlertSeverity.CRITICAL,
|
|
106
|
+
f"Fuel critically low at {fuel:.1f}% — refuel immediately.",
|
|
107
|
+
fuel, FUEL_CRITICAL))
|
|
108
|
+
elif fuel <= FUEL_WARNING:
|
|
109
|
+
alerts.append(FleetAlert(
|
|
110
|
+
vid, "LOW_FUEL", AlertSeverity.WARNING,
|
|
111
|
+
f"Fuel at {fuel:.1f}% — plan refuel soon.",
|
|
112
|
+
fuel, FUEL_WARNING))
|
|
113
|
+
|
|
114
|
+
# Speed checks
|
|
115
|
+
if speed >= SPEED_CRITICAL:
|
|
116
|
+
alerts.append(FleetAlert(
|
|
117
|
+
vid, "SPEEDING", AlertSeverity.CRITICAL,
|
|
118
|
+
f"Vehicle speeding at {speed:.1f} km/h.",
|
|
119
|
+
speed, SPEED_CRITICAL))
|
|
120
|
+
elif speed >= SPEED_WARNING:
|
|
121
|
+
alerts.append(FleetAlert(
|
|
122
|
+
vid, "SPEEDING", AlertSeverity.WARNING,
|
|
123
|
+
f"Approaching speed limit at {speed:.1f} km/h.",
|
|
124
|
+
speed, SPEED_WARNING))
|
|
125
|
+
|
|
126
|
+
return alerts
|
|
127
|
+
|
|
128
|
+
def calculate_fuel_efficiency_score(self, reading):
|
|
129
|
+
"""
|
|
130
|
+
Fuel efficiency index 0-100. Higher = more efficient.
|
|
131
|
+
"""
|
|
132
|
+
speed = reading.get("speed_kmh", 0.0)
|
|
133
|
+
fuel = reading.get("fuel_percent", 100.0)
|
|
134
|
+
if speed == 0:
|
|
135
|
+
return 100.0
|
|
136
|
+
estimated_drain = speed * 0.25
|
|
137
|
+
return round(min(100.0, (fuel / max(estimated_drain, 1.0)) * 10), 2)
|
|
138
|
+
|
|
139
|
+
def predict_maintenance_needed(self, reading):
|
|
140
|
+
"""
|
|
141
|
+
Returns True if the vehicle likely needs maintenance soon.
|
|
142
|
+
"""
|
|
143
|
+
return (reading.get("engine_temp_c", 80.0) > MAINTENANCE_TEMP and
|
|
144
|
+
reading.get("fuel_percent", 100.0) < MAINTENANCE_FUEL)
|
|
145
|
+
|
|
146
|
+
def classify_alert_severity(self, alerts):
|
|
147
|
+
"""
|
|
148
|
+
Return the highest severity level from a list of alerts.
|
|
149
|
+
"""
|
|
150
|
+
if not alerts:
|
|
151
|
+
return AlertSeverity.INFO
|
|
152
|
+
if any(a.severity == AlertSeverity.CRITICAL for a in alerts):
|
|
153
|
+
return AlertSeverity.CRITICAL
|
|
154
|
+
if any(a.severity == AlertSeverity.WARNING for a in alerts):
|
|
155
|
+
return AlertSeverity.WARNING
|
|
156
|
+
return AlertSeverity.INFO
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
models.py
|
|
3
|
+
Data models for the fleet optimization engine.
|
|
4
|
+
AlertSeverity, FleetAlert and VehicleRiskProfile are used by
|
|
5
|
+
TelemetryAnalyzer and consumed by the Django app and Lambda function.
|
|
6
|
+
"""
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AlertSeverity(Enum):
|
|
11
|
+
"""
|
|
12
|
+
Severity levels for fleet alerts.
|
|
13
|
+
"""
|
|
14
|
+
INFO = "INFO"
|
|
15
|
+
WARNING = "WARNING"
|
|
16
|
+
CRITICAL = "CRITICAL"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FleetAlert:
|
|
20
|
+
"""
|
|
21
|
+
A single threshold breach for one vehicle.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
vehicle_id (str): Which vehicle triggered the alert
|
|
25
|
+
alert_type (str): Category e.g. HIGH_ENGINE_TEMP, LOW_FUEL, SPEEDING
|
|
26
|
+
severity (AlertSeverity): How urgent this alert is
|
|
27
|
+
message (str): Human-readable description for the fleet manager
|
|
28
|
+
value (float): The actual sensor value that triggered the alert
|
|
29
|
+
threshold (float): The threshold that was breached
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self, vehicle_id, alert_type, severity,
|
|
32
|
+
message, value, threshold):
|
|
33
|
+
self.vehicle_id = vehicle_id
|
|
34
|
+
self.alert_type = alert_type
|
|
35
|
+
self.severity = severity
|
|
36
|
+
self.message = message
|
|
37
|
+
self.value = value
|
|
38
|
+
self.threshold = threshold
|
|
39
|
+
|
|
40
|
+
def to_dict(self):
|
|
41
|
+
"""Serialise to dict for JSON encoding or DynamoDB storage."""
|
|
42
|
+
return {
|
|
43
|
+
"vehicle_id": self.vehicle_id,
|
|
44
|
+
"alert_type": self.alert_type,
|
|
45
|
+
"severity": self.severity.value,
|
|
46
|
+
"message": self.message,
|
|
47
|
+
"value": self.value,
|
|
48
|
+
"threshold": self.threshold,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def __repr__(self):
|
|
52
|
+
return (f"FleetAlert({self.severity.value}: {self.alert_type} "
|
|
53
|
+
f"for {self.vehicle_id}, value={self.value})")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class VehicleRiskProfile:
|
|
57
|
+
"""
|
|
58
|
+
Composite risk assessment for a single vehicle reading.
|
|
59
|
+
"""
|
|
60
|
+
def __init__(self, vehicle_id, risk_score, alerts,
|
|
61
|
+
fuel_efficiency_score, maintenance_due):
|
|
62
|
+
self.vehicle_id = vehicle_id
|
|
63
|
+
self.risk_score = round(risk_score, 2)
|
|
64
|
+
self.risk_label = self._label(risk_score)
|
|
65
|
+
self.alerts = alerts
|
|
66
|
+
self.fuel_efficiency_score = round(fuel_efficiency_score, 2)
|
|
67
|
+
self.maintenance_due = maintenance_due
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _label(score):
|
|
71
|
+
"""Map numeric score to a human-readable risk band."""
|
|
72
|
+
if score >= 75: return "CRITICAL"
|
|
73
|
+
if score >= 50: return "HIGH"
|
|
74
|
+
if score >= 25: return "MEDIUM"
|
|
75
|
+
return "LOW"
|
|
76
|
+
|
|
77
|
+
def to_dict(self):
|
|
78
|
+
return {
|
|
79
|
+
"vehicle_id": self.vehicle_id,
|
|
80
|
+
"risk_score": self.risk_score,
|
|
81
|
+
"risk_label": self.risk_label,
|
|
82
|
+
"alerts": [a.to_dict() for a in self.alerts],
|
|
83
|
+
"fuel_efficiency_score": self.fuel_efficiency_score,
|
|
84
|
+
"maintenance_due": self.maintenance_due,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def __repr__(self):
|
|
88
|
+
return (f"VehicleRiskProfile({self.vehicle_id}: {self.risk_label} "
|
|
89
|
+
f"score={self.risk_score}, alerts={len(self.alerts)})")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import setuptools
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setuptools.setup(
|
|
7
|
+
name="smartfleet-optimizer",
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
author="Satvik T M",
|
|
10
|
+
author_email="satvikw007@gmail.com",
|
|
11
|
+
description="Analytics library for fleet telemetry: risk scoring, alerts, maintenance prediction",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
packages=setuptools.find_packages(),
|
|
15
|
+
install_requires=[],
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
],
|
|
21
|
+
python_requires=">=3.8",
|
|
22
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smartfleet-optimizer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Analytics library for fleet telemetry: risk scoring, alerts, maintenance prediction
|
|
5
|
+
Author: Satvik T M
|
|
6
|
+
Author-email: satvikw007@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
# fleet-optimization-engine-satvik25159712
|
|
23
|
+
|
|
24
|
+
Analytics library for fleet telemetry data. Provides risk scoring,
|
|
25
|
+
alert detection, fuel efficiency analysis and maintenance prediction.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
pip install fleet-optimization-engine-satvik25159712
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
from fleet_optimization_pkg import TelemetryAnalyzer
|
|
32
|
+
analyzer = TelemetryAnalyzer()
|
|
33
|
+
profile = analyzer.build_risk_profile({
|
|
34
|
+
"vehicle_id": "VAN-001", "speed_kmh": 50,
|
|
35
|
+
"fuel_percent": 75, "engine_temp_c": 85
|
|
36
|
+
})
|
|
37
|
+
print(profile.risk_label)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
fleet_optimization_pkg/__init__.py
|
|
5
|
+
fleet_optimization_pkg/analyzer.py
|
|
6
|
+
fleet_optimization_pkg/models.py
|
|
7
|
+
smartfleet_optimizer.egg-info/PKG-INFO
|
|
8
|
+
smartfleet_optimizer.egg-info/SOURCES.txt
|
|
9
|
+
smartfleet_optimizer.egg-info/dependency_links.txt
|
|
10
|
+
smartfleet_optimizer.egg-info/top_level.txt
|
|
11
|
+
tests/test_analyzer.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fleet_optimization_pkg
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
test_analyzer.py
|
|
3
|
+
Unit tests for fleet_optimization_pkg.
|
|
4
|
+
"""
|
|
5
|
+
import pytest
|
|
6
|
+
from fleet_optimization_pkg.analyzer import TelemetryAnalyzer
|
|
7
|
+
from fleet_optimization_pkg.models import AlertSeverity, VehicleRiskProfile
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def analyzer():
|
|
12
|
+
return TelemetryAnalyzer()
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def normal_reading():
|
|
16
|
+
return {"vehicle_id": "VAN-001", "speed_kmh": 50.0,
|
|
17
|
+
"fuel_percent": 75.0, "engine_temp_c": 85.0}
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def critical_reading():
|
|
21
|
+
return {"vehicle_id": "VAN-002", "speed_kmh": 112.0,
|
|
22
|
+
"fuel_percent": 5.0, "engine_temp_c": 115.0}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestRiskScore:
|
|
26
|
+
|
|
27
|
+
def test_normal_vehicle_scores_zero(self, analyzer, normal_reading):
|
|
28
|
+
assert analyzer.calculate_risk_score(normal_reading) == 0.0
|
|
29
|
+
|
|
30
|
+
def test_critical_vehicle_scores_high(self, analyzer, critical_reading):
|
|
31
|
+
assert analyzer.calculate_risk_score(critical_reading) >= 80.0
|
|
32
|
+
|
|
33
|
+
def test_score_is_between_0_and_100(self, analyzer, critical_reading):
|
|
34
|
+
score = analyzer.calculate_risk_score(critical_reading)
|
|
35
|
+
assert 0.0 <= score <= 100.0
|
|
36
|
+
|
|
37
|
+
def test_score_increases_with_engine_temp(self, analyzer):
|
|
38
|
+
low = {"vehicle_id": "X", "speed_kmh": 0,
|
|
39
|
+
"fuel_percent": 80, "engine_temp_c": 85}
|
|
40
|
+
high = {"vehicle_id": "X", "speed_kmh": 0,
|
|
41
|
+
"fuel_percent": 80, "engine_temp_c": 115}
|
|
42
|
+
assert analyzer.calculate_risk_score(high) > \
|
|
43
|
+
analyzer.calculate_risk_score(low)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestAlertDetection:
|
|
47
|
+
|
|
48
|
+
def test_no_alerts_for_normal_vehicle(self, analyzer, normal_reading):
|
|
49
|
+
assert analyzer.detect_alerts(normal_reading) == []
|
|
50
|
+
|
|
51
|
+
def test_critical_reading_generates_alerts(self, analyzer, critical_reading):
|
|
52
|
+
assert len(analyzer.detect_alerts(critical_reading)) >= 2
|
|
53
|
+
|
|
54
|
+
def test_high_temp_critical_alert(self, analyzer):
|
|
55
|
+
r = {"vehicle_id": "X", "speed_kmh": 50,
|
|
56
|
+
"fuel_percent": 60, "engine_temp_c": 115}
|
|
57
|
+
alerts = analyzer.detect_alerts(r)
|
|
58
|
+
assert any(a.alert_type == "HIGH_ENGINE_TEMP" and
|
|
59
|
+
a.severity == AlertSeverity.CRITICAL for a in alerts)
|
|
60
|
+
|
|
61
|
+
def test_low_fuel_warning_alert(self, analyzer):
|
|
62
|
+
r = {"vehicle_id": "X", "speed_kmh": 50,
|
|
63
|
+
"fuel_percent": 15.0, "engine_temp_c": 85}
|
|
64
|
+
alerts = analyzer.detect_alerts(r)
|
|
65
|
+
assert any(a.alert_type == "LOW_FUEL" for a in alerts)
|
|
66
|
+
|
|
67
|
+
def test_critical_fuel_alert(self, analyzer):
|
|
68
|
+
r = {"vehicle_id": "X", "speed_kmh": 50,
|
|
69
|
+
"fuel_percent": 5.0, "engine_temp_c": 85}
|
|
70
|
+
alerts = analyzer.detect_alerts(r)
|
|
71
|
+
assert any(a.severity == AlertSeverity.CRITICAL for a in alerts)
|
|
72
|
+
|
|
73
|
+
def test_speeding_alert(self, analyzer):
|
|
74
|
+
r = {"vehicle_id": "X", "speed_kmh": 112.0,
|
|
75
|
+
"fuel_percent": 60, "engine_temp_c": 85}
|
|
76
|
+
alerts = analyzer.detect_alerts(r)
|
|
77
|
+
assert any(a.alert_type == "SPEEDING" for a in alerts)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestFuelEfficiency:
|
|
81
|
+
|
|
82
|
+
def test_stationary_scores_100(self, analyzer):
|
|
83
|
+
r = {"vehicle_id": "X", "speed_kmh": 0,
|
|
84
|
+
"fuel_percent": 80, "engine_temp_c": 85}
|
|
85
|
+
assert analyzer.calculate_fuel_efficiency_score(r) == 100.0
|
|
86
|
+
|
|
87
|
+
def test_score_in_valid_range(self, analyzer, normal_reading):
|
|
88
|
+
score = analyzer.calculate_fuel_efficiency_score(normal_reading)
|
|
89
|
+
assert 0.0 <= score <= 100.0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestMaintenancePrediction:
|
|
93
|
+
|
|
94
|
+
def test_healthy_vehicle_no_maintenance(self, analyzer, normal_reading):
|
|
95
|
+
assert analyzer.predict_maintenance_needed(normal_reading) is False
|
|
96
|
+
|
|
97
|
+
def test_high_temp_low_fuel_needs_maintenance(self, analyzer):
|
|
98
|
+
r = {"vehicle_id": "X", "speed_kmh": 50,
|
|
99
|
+
"fuel_percent": 18.0, "engine_temp_c": 98.0}
|
|
100
|
+
assert analyzer.predict_maintenance_needed(r) is True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestSeverityClassification:
|
|
104
|
+
|
|
105
|
+
def test_empty_alerts_returns_info(self, analyzer):
|
|
106
|
+
assert analyzer.classify_alert_severity([]) == AlertSeverity.INFO
|
|
107
|
+
|
|
108
|
+
def test_critical_alerts_returns_critical(self, analyzer, critical_reading):
|
|
109
|
+
alerts = analyzer.detect_alerts(critical_reading)
|
|
110
|
+
assert analyzer.classify_alert_severity(alerts) == AlertSeverity.CRITICAL
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestRiskProfile:
|
|
114
|
+
|
|
115
|
+
def test_returns_correct_type(self, analyzer, normal_reading):
|
|
116
|
+
assert isinstance(analyzer.build_risk_profile(normal_reading),
|
|
117
|
+
VehicleRiskProfile)
|
|
118
|
+
|
|
119
|
+
def test_normal_vehicle_is_low(self, analyzer, normal_reading):
|
|
120
|
+
assert analyzer.build_risk_profile(normal_reading).risk_label == "LOW"
|
|
121
|
+
|
|
122
|
+
def test_critical_vehicle_label(self, analyzer, critical_reading):
|
|
123
|
+
label = analyzer.build_risk_profile(critical_reading).risk_label
|
|
124
|
+
assert label in ("HIGH", "CRITICAL")
|
|
125
|
+
|
|
126
|
+
def test_to_dict_has_all_keys(self, analyzer, normal_reading):
|
|
127
|
+
d = analyzer.build_risk_profile(normal_reading).to_dict()
|
|
128
|
+
assert set(d.keys()) == {"vehicle_id", "risk_score", "risk_label",
|
|
129
|
+
"alerts", "fuel_efficiency_score",
|
|
130
|
+
"maintenance_due"}
|