@fleetbase/fleetops-engine 0.6.56 → 0.6.58
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.
- package/addon/components/ai/create-order-preview.hbs +297 -0
- package/addon/components/ai/create-order-preview.js +362 -0
- package/addon/components/ai/route-preview-map.hbs +79 -0
- package/addon/components/ai/route-preview-map.js +527 -0
- package/addon/components/fleet/details.hbs +21 -8
- package/addon/components/fleet/details.js +55 -1
- package/addon/components/fleet/hierarchy-tree.hbs +137 -0
- package/addon/components/fleet/hierarchy-tree.js +528 -0
- package/addon/components/map/drawer/device-event-listing.js +1 -0
- package/addon/components/map/drawer/driver-listing.js +1 -0
- package/addon/components/map/drawer/place-listing.js +1 -0
- package/addon/components/map/drawer/vehicle-listing.js +1 -0
- package/addon/components/order/details.hbs +34 -1
- package/addon/components/order/form/details.hbs +4 -0
- package/addon/components/order/form.hbs +9 -1
- package/addon/components/vehicle/card.hbs +14 -2
- package/addon/components/vehicle/form.hbs +13 -1
- package/addon/controllers/settings/map.js +4 -0
- package/addon/extension.js +14 -0
- package/addon/routes/management/fleets/index/details.js +1 -1
- package/addon/services/map-adapter/google.js +56 -16
- package/addon/services/map-manager.js +12 -0
- package/addon/styles/fleetops-engine.css +487 -1
- package/addon/utils/map-drawer-dropdown-position.js +11 -13
- package/app/components/ai/create-order-preview.js +1 -0
- package/app/components/ai/route-preview-map.js +1 -0
- package/app/components/fleet/hierarchy-tree.js +1 -0
- package/app/services/route-engine.js +1 -0
- package/composer.json +9 -2
- package/extension.json +1 -1
- package/package.json +6 -4
- package/phpunit.xml.dist +4 -4
- package/pnpm-workspace.yaml +2 -1
- package/scripts/coverage-runner.php +51 -0
- package/scripts/coverage-summary.php +110 -0
- package/scripts/normalize-rgba-alpha.js +90 -0
- package/scripts/pest-bootstrap.php +177 -0
- package/scripts/pest-file-runner.php +82 -0
- package/scripts/pest-runner.php +64 -0
- package/server/src/Http/Resources/v1/Fleet.php +8 -2
- package/server/src/Models/Driver.php +6 -0
- package/server/src/Models/Order.php +18 -6
- package/server/src/Models/Place.php +11 -3
- package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +3 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +26 -0
- package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +88 -0
- package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +108 -0
- package/server/src/Support/Ai/Capabilities/ConsoleNavigationCapability.php +55 -0
- package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +508 -0
- package/server/src/Support/Ai/Capabilities/DocsHelpCapability.php +49 -0
- package/server/src/Support/Ai/Capabilities/ImportOrdersPreviewCapability.php +66 -0
- package/server/src/Support/Ai/Capabilities/OperationalQueryCapability.php +319 -0
- package/server/src/Support/Ai/Capabilities/OptimizeOrderRouteCapability.php +240 -0
- package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +95 -0
- package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +195 -0
- package/server/src/Support/Ai/FleetOpsAiQueryResources.php +150 -0
- package/server/src/Support/Utils.php +52 -0
- package/server/src/Tracking/TrackingOptions.php +6 -1
- package/server/src/routes.php +0 -11
- package/server/tests/AiCreateOrderPreviewCapabilityTest.php +131 -0
- package/server/tests/AiOperationalQueryCapabilityTest.php +75 -0
- package/server/tests/AnalyticsRoutesTest.php +5 -3
- package/server/tests/CustomerEndpointTest.php +1 -0
- package/server/tests/DeviceFilterTest.php +1 -2
- package/server/tests/FleetHierarchyResourceTest.php +12 -0
- package/server/tests/FuelProviderRegistryTest.php +7 -9
- package/server/tests/LiveControllerViewportTest.php +8 -16
- package/server/tests/LiveOrderQueryTest.php +18 -24
- package/server/tests/OrderPayloadPlaceResolutionTest.php +53 -0
- package/server/tests/PointResolutionTest.php +63 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +8 -9
- package/server/tests/ServiceRateAlgorithmWeightVariablesTest.php +1 -1
- package/server/tests/TelematicsHardeningTest.php +31 -26
- package/server/tests/TrackingIntelligenceTest.php +65 -25
- package/server/tests/VehicleDriverAssignmentTest.php +8 -9
- package/addon/components/table/cell/dropdown/action-item.hbs +0 -31
- package/addon/components/table/cell/dropdown/action-item.js +0 -62
- package/addon/components/table/cell/dropdown.hbs +0 -28
- package/addon/components/table/cell/dropdown.js +0 -91
- package/app/components/table/cell/dropdown/action-item.js +0 -1
- package/app/components/table/cell/dropdown.js +0 -1
- package/server/src/Http/Controllers/Api/v1/OrganizationController.php +0 -45
|
@@ -471,9 +471,17 @@ class Place extends Model
|
|
|
471
471
|
return null;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
$postalCode
|
|
475
|
-
$street2
|
|
476
|
-
$
|
|
474
|
+
$postalCode = static::normalizePlaceValue(data_get($place, 'postal_code'));
|
|
475
|
+
$street2 = static::normalizePlaceValue(data_get($place, 'street2'));
|
|
476
|
+
$locationValue = data_get($place, 'location');
|
|
477
|
+
$location = new SpatialPoint(0, 0);
|
|
478
|
+
if (!empty($locationValue)) {
|
|
479
|
+
try {
|
|
480
|
+
$location = Utils::getPointFromMixed($locationValue);
|
|
481
|
+
} catch (\Throwable $e) {
|
|
482
|
+
$location = new SpatialPoint(0, 0);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
477
485
|
|
|
478
486
|
$existingPlace = static::query()
|
|
479
487
|
->where('company_uuid', $companyUuid)
|
|
@@ -337,7 +337,9 @@ class OrchestrationPayloadBuilder
|
|
|
337
337
|
protected static function resolveServiceTime(Order $order): int
|
|
338
338
|
{
|
|
339
339
|
$payload = $order->payload;
|
|
340
|
-
$waypointMarker = $payload
|
|
340
|
+
$waypointMarker = method_exists($payload, 'relationLoaded') && $payload->relationLoaded('waypointMarkers')
|
|
341
|
+
? $payload->waypointMarkers?->last()
|
|
342
|
+
: null;
|
|
341
343
|
|
|
342
344
|
return (int) (
|
|
343
345
|
$waypointMarker?->service_time
|
|
@@ -145,6 +145,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
145
145
|
$schedule->command('fleetops:sync-telematics')->everyMinute()->withoutOverlapping()->storeOutputInDb();
|
|
146
146
|
});
|
|
147
147
|
$this->registerNotifications();
|
|
148
|
+
$this->registerAiCapabilities();
|
|
148
149
|
$this->registerExpansionsFrom(__DIR__ . '/../Expansions');
|
|
149
150
|
|
|
150
151
|
// Register built-in orchestration engines.
|
|
@@ -234,4 +235,29 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
234
235
|
'dynamic:facilitator',
|
|
235
236
|
]);
|
|
236
237
|
}
|
|
238
|
+
|
|
239
|
+
protected function registerAiCapabilities(): void
|
|
240
|
+
{
|
|
241
|
+
if (!Utils::classExists(\Fleetbase\Ai\Support\AiCapabilityRegistry::class)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (Utils::classExists(\Fleetbase\Ai\Support\AiQueryRegistry::class)) {
|
|
246
|
+
$this->callAfterResolving(\Fleetbase\Ai\Support\AiQueryRegistry::class, function (\Fleetbase\Ai\Support\AiQueryRegistry $registry) {
|
|
247
|
+
\Fleetbase\FleetOps\Support\Ai\FleetOpsAiQueryResources::register($registry);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
$this->callAfterResolving(\Fleetbase\Ai\Support\AiCapabilityRegistry::class, function (\Fleetbase\Ai\Support\AiCapabilityRegistry $registry) {
|
|
252
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\SearchResourcesCapability());
|
|
253
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\OperationalQueryCapability());
|
|
254
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\OrderInsightsCapability());
|
|
255
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\AssetStatusCapability());
|
|
256
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\DocsHelpCapability());
|
|
257
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\ConsoleNavigationCapability());
|
|
258
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\CreateOrderPreviewCapability());
|
|
259
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\OptimizeOrderRouteCapability());
|
|
260
|
+
$registry->register(new \Fleetbase\FleetOps\Support\Ai\Capabilities\ImportOrdersPreviewCapability());
|
|
261
|
+
});
|
|
262
|
+
}
|
|
237
263
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Ai\Capabilities;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Ai\Contracts\AIContextCapabilityInterface;
|
|
6
|
+
use Fleetbase\Ai\Models\AiTask;
|
|
7
|
+
use Fleetbase\Ai\Support\Capabilities\AbstractAICapability;
|
|
8
|
+
use Fleetbase\Support\Auth;
|
|
9
|
+
use Illuminate\Database\Eloquent\Builder;
|
|
10
|
+
use Illuminate\Support\Str;
|
|
11
|
+
|
|
12
|
+
abstract class AbstractFleetOpsAICapability extends AbstractAICapability implements AIContextCapabilityInterface
|
|
13
|
+
{
|
|
14
|
+
public function module(): string
|
|
15
|
+
{
|
|
16
|
+
return 'fleet-ops';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public function shouldResolve(AiTask $task): bool
|
|
20
|
+
{
|
|
21
|
+
return $this->matchesPrompt($this->prompt($task));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
abstract protected function matchesPrompt(string $prompt): bool;
|
|
25
|
+
|
|
26
|
+
protected function prompt(AiTask $task): string
|
|
27
|
+
{
|
|
28
|
+
return Str::lower(trim((string) $task->prompt));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected function containsAny(string $prompt, array $terms): bool
|
|
32
|
+
{
|
|
33
|
+
foreach ($terms as $term) {
|
|
34
|
+
if (str_contains($prompt, Str::lower($term))) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected function can(string $permission): bool
|
|
43
|
+
{
|
|
44
|
+
$user = Auth::getUserFromSession();
|
|
45
|
+
|
|
46
|
+
if ($user?->isAdmin()) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Auth::can($permission);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected function canAll(array $permissions): bool
|
|
54
|
+
{
|
|
55
|
+
foreach ($permissions as $permission) {
|
|
56
|
+
if (!$this->can($permission)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected function searchTerms(string $prompt): array
|
|
65
|
+
{
|
|
66
|
+
preg_match_all('/[A-Z]{2,}[-_][A-Z0-9-_]+|[A-Za-z0-9][A-Za-z0-9-_]{2,}/', (string) $prompt, $matches);
|
|
67
|
+
|
|
68
|
+
$terms = collect($matches[0] ?? [])
|
|
69
|
+
->reject(fn ($term) => in_array(Str::lower($term), ['find', 'show', 'open', 'order', 'orders', 'vehicle', 'vehicles', 'driver', 'drivers', 'work', 'status', 'about', 'fleet', 'ops'], true))
|
|
70
|
+
->unique()
|
|
71
|
+
->take(6)
|
|
72
|
+
->values()
|
|
73
|
+
->all();
|
|
74
|
+
|
|
75
|
+
return empty($terms) ? [trim((string) $prompt)] : $terms;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected function whereLikeAny(Builder $builder, array $columns, array $terms): void
|
|
79
|
+
{
|
|
80
|
+
$builder->where(function (Builder $query) use ($columns, $terms) {
|
|
81
|
+
foreach ($columns as $column) {
|
|
82
|
+
foreach ($terms as $term) {
|
|
83
|
+
$query->orWhere($column, 'like', '%' . $term . '%');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Ai\Capabilities;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Ai\Models\AiTask;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Device;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
8
|
+
use Fleetbase\FleetOps\Models\Sensor;
|
|
9
|
+
use Fleetbase\FleetOps\Models\Telematic;
|
|
10
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
11
|
+
|
|
12
|
+
class AssetStatusCapability extends AbstractFleetOpsAICapability
|
|
13
|
+
{
|
|
14
|
+
public function key(): string
|
|
15
|
+
{
|
|
16
|
+
return 'fleet-ops.asset_status';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public function label(): string
|
|
20
|
+
{
|
|
21
|
+
return 'Fleet-Ops asset status';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public function description(): string
|
|
25
|
+
{
|
|
26
|
+
return 'Summarizes Fleet-Ops vehicle, device, sensor, and telematic status counts.';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public function permissions(): array
|
|
30
|
+
{
|
|
31
|
+
return ['fleet-ops see vehicle', 'fleet-ops see device', 'fleet-ops see sensor', 'fleet-ops see telematic'];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public function resolve(AiTask $task): array
|
|
35
|
+
{
|
|
36
|
+
return [
|
|
37
|
+
'drivers' => $this->driverStatus(),
|
|
38
|
+
'vehicles' => $this->statusCounts(Vehicle::class, 'fleet-ops see vehicle'),
|
|
39
|
+
'devices' => $this->deviceStatus(),
|
|
40
|
+
'sensors' => $this->statusCounts(Sensor::class, 'fleet-ops see sensor'),
|
|
41
|
+
'telematics' => $this->statusCounts(Telematic::class, 'fleet-ops see telematic'),
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected function matchesPrompt(string $prompt): bool
|
|
46
|
+
{
|
|
47
|
+
return $this->containsAny($prompt, ['offline', 'online', 'asset status', 'driver status', 'vehicle status', 'device status', 'sensor status', 'telematic status', 'drivers down', 'devices down', 'vehicles down']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected function statusCounts(string $modelClass, string $permission): array
|
|
51
|
+
{
|
|
52
|
+
if (!$this->can($permission)) {
|
|
53
|
+
return ['authorized' => false];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
'authorized' => true,
|
|
58
|
+
'total' => $modelClass::where('company_uuid', session('company'))->count(),
|
|
59
|
+
'counts_by_status' => $modelClass::where('company_uuid', session('company'))
|
|
60
|
+
->selectRaw('status, count(*) as aggregate')
|
|
61
|
+
->groupBy('status')
|
|
62
|
+
->pluck('aggregate', 'status')
|
|
63
|
+
->all(),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected function deviceStatus(): array
|
|
68
|
+
{
|
|
69
|
+
if (!$this->can('fleet-ops see device')) {
|
|
70
|
+
return ['authorized' => false];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return [
|
|
74
|
+
'authorized' => true,
|
|
75
|
+
'total' => Device::where('company_uuid', session('company'))->count(),
|
|
76
|
+
'online' => Device::where('company_uuid', session('company'))->where('online', true)->count(),
|
|
77
|
+
'offline' => Device::where('company_uuid', session('company'))->where(function ($query) {
|
|
78
|
+
$query->where('online', false)->orWhereNull('online');
|
|
79
|
+
})->count(),
|
|
80
|
+
'counts_by_status' => Device::where('company_uuid', session('company'))
|
|
81
|
+
->selectRaw('status, count(*) as aggregate')
|
|
82
|
+
->groupBy('status')
|
|
83
|
+
->pluck('aggregate', 'status')
|
|
84
|
+
->all(),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected function driverStatus(): array
|
|
89
|
+
{
|
|
90
|
+
if (!$this->can('fleet-ops see driver')) {
|
|
91
|
+
return ['authorized' => false];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return [
|
|
95
|
+
'authorized' => true,
|
|
96
|
+
'total' => Driver::where('company_uuid', session('company'))->count(),
|
|
97
|
+
'online' => Driver::where('company_uuid', session('company'))->where('online', true)->count(),
|
|
98
|
+
'offline' => Driver::where('company_uuid', session('company'))->where(function ($query) {
|
|
99
|
+
$query->where('online', false)->orWhereNull('online');
|
|
100
|
+
})->count(),
|
|
101
|
+
'counts_by_status' => Driver::where('company_uuid', session('company'))
|
|
102
|
+
->selectRaw('status, count(*) as aggregate')
|
|
103
|
+
->groupBy('status')
|
|
104
|
+
->pluck('aggregate', 'status')
|
|
105
|
+
->all(),
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Ai\Capabilities;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Ai\Models\AiTask;
|
|
6
|
+
|
|
7
|
+
class ConsoleNavigationCapability extends SearchResourcesCapability
|
|
8
|
+
{
|
|
9
|
+
public function key(): string
|
|
10
|
+
{
|
|
11
|
+
return 'fleet-ops.console_navigation';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public function label(): string
|
|
15
|
+
{
|
|
16
|
+
return 'Fleet-Ops console navigation preview';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public function description(): string
|
|
20
|
+
{
|
|
21
|
+
return 'Returns preview-only console route suggestions for Fleet-Ops resources.';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public function type(): string
|
|
25
|
+
{
|
|
26
|
+
return 'ui';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public function mode(): string
|
|
30
|
+
{
|
|
31
|
+
return 'navigation_preview';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public function resolve(AiTask $task): array
|
|
35
|
+
{
|
|
36
|
+
$search = parent::resolve($task);
|
|
37
|
+
$items = collect(data_get($search, 'results', []))
|
|
38
|
+
->flatten(1)
|
|
39
|
+
->filter(fn ($result) => is_array($result) && isset($result['route']))
|
|
40
|
+
->take(5)
|
|
41
|
+
->values()
|
|
42
|
+
->all();
|
|
43
|
+
|
|
44
|
+
return [
|
|
45
|
+
'preview_only' => true,
|
|
46
|
+
'message' => 'Fleetbase AI can suggest where to navigate, but this pilot does not directly open panels yet.',
|
|
47
|
+
'suggestions' => $items,
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected function matchesPrompt(string $prompt): bool
|
|
52
|
+
{
|
|
53
|
+
return $this->containsAny($prompt, ['open', 'show me', 'go to', 'navigate', 'take me to']) && parent::matchesPrompt($prompt);
|
|
54
|
+
}
|
|
55
|
+
}
|