103 lines
3.7 KiB
PHP
103 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace Modules\Maintenance\Services;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Modules\Maintenance\Models\IotAlert;
|
|
use Modules\Maintenance\Models\IotDevice;
|
|
use Modules\Maintenance\Models\IotPredictiveRule;
|
|
use Modules\Maintenance\Models\IotTelemetry;
|
|
|
|
class IotIngestionService
|
|
{
|
|
public function ingest(int $businessId, string $deviceUid, array $readings): void
|
|
{
|
|
$device = IotDevice::where('business_id', $businessId)->where('device_uid', $deviceUid)->first();
|
|
if (! $device) {
|
|
return;
|
|
}
|
|
|
|
$device->update(['last_seen_at' => now(), 'status' => 'active']);
|
|
|
|
foreach ($readings as $reading) {
|
|
IotTelemetry::create([
|
|
'business_id' => $businessId,
|
|
'device_id' => $device->id,
|
|
'equipment_id' => $device->equipment_id,
|
|
'metric' => $reading['metric'],
|
|
'value' => $reading['value'],
|
|
'unit' => $reading['unit'] ?? null,
|
|
'recorded_at' => $reading['recorded_at'] ?? now(),
|
|
'raw_payload' => $reading,
|
|
]);
|
|
|
|
$this->evaluateRules($businessId, $device, $reading['metric'], (float) $reading['value']);
|
|
}
|
|
}
|
|
|
|
protected function evaluateRules(int $businessId, IotDevice $device, string $metric, float $value): void
|
|
{
|
|
$rules = IotPredictiveRule::where('business_id', $businessId)
|
|
->where('is_active', true)
|
|
->where(function ($q) use ($device) {
|
|
$q->whereNull('equipment_id')->orWhere('equipment_id', $device->equipment_id);
|
|
})
|
|
->where('metric', $metric)
|
|
->get();
|
|
|
|
foreach ($rules as $rule) {
|
|
if (! $this->thresholdBreached($value, (float) $rule->threshold, $rule->operator)) {
|
|
continue;
|
|
}
|
|
|
|
$alert = IotAlert::create([
|
|
'business_id' => $businessId,
|
|
'device_id' => $device->id,
|
|
'equipment_id' => $device->equipment_id,
|
|
'alert_type' => 'threshold_breach',
|
|
'severity' => 'warning',
|
|
'message' => "Metric {$metric} breached threshold ({$value} {$rule->operator} {$rule->threshold})",
|
|
'threshold_value' => $rule->threshold,
|
|
'actual_value' => $value,
|
|
'triggered_at' => now(),
|
|
]);
|
|
|
|
if ($rule->auto_work_order && $device->equipment_id) {
|
|
$woId = $this->createPredictiveWorkOrder($businessId, $device->equipment_id, $alert);
|
|
$alert->update(['work_order_id' => $woId]);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function thresholdBreached(float $value, float $threshold, string $operator): bool
|
|
{
|
|
return match ($operator) {
|
|
'gt' => $value > $threshold,
|
|
'lt' => $value < $threshold,
|
|
'gte' => $value >= $threshold,
|
|
'lte' => $value <= $threshold,
|
|
'eq' => abs($value - $threshold) < 0.0001,
|
|
default => false,
|
|
};
|
|
}
|
|
|
|
protected function createPredictiveWorkOrder(int $businessId, int $equipmentId, IotAlert $alert): ?int
|
|
{
|
|
if (! DB::getSchemaBuilder()->hasTable('mt_work_orders')) {
|
|
return null;
|
|
}
|
|
|
|
return DB::table('mt_work_orders')->insertGetId([
|
|
'business_id' => $businessId,
|
|
'equipment_id' => $equipmentId,
|
|
'title' => 'Predictive: '.$alert->message,
|
|
'type' => 'corrective',
|
|
'priority' => 'high',
|
|
'status' => 'open',
|
|
'description' => 'Auto-created from IoT alert #'.$alert->id,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|