Skip to content
Snippets Groups Projects
ImportCommand.php 18 KiB
Newer Older
<?php

namespace Ingress\Command\TikiWiki;

use Console_CommandLine;
use Ingress\Command\BaseCommand;
use Ingress\Services\Logger;
use Ingress\Services\smartdataapi\SmartDataContextApiClient;
use Ingress\Services\smartdataapi\SmartDataUnits;
use Ingress\Services\tikiwiki\TikiWIkiConfig;
use Ingress\Services\tikiwiki\TikiWikiDB;

/**
 * Imports context data for vehicles and manitenance from TikiWiki trackers to the SmartDataContext API
 *
 * Configuration for which trackers to use and the certificates is retrieved from ./config/tikiki.json file
 *
 * Author: Rodrigo Gonçalves
 * Version: 2024.08.27
 */
    public static function process(array $args, array $params) {
        $domain = $args['domain'];
        $db = new TikiWikiDB();
        $trackerList = TikiWikiConfig::getTrackers($domain);
        list($api, $certificatePath, $certificateKeyPath) = TikiWikiConfig::getApiConfig($domain);
        $apiClient = new SmartDataContextApiClient($api, $certificatePath, $certificateKeyPath);
        $vehicleMap = static::importVehicles($db, $apiClient, $trackerList['vehicleList']);
        static::importMaintenancePlans($db, $apiClient, $trackerList['vehicleMaintenancePlan'], $vehicleMap);
        $maintenanceReports = static::importMaintenanceReports($db, $trackerList['vehicleMaintenanceReport'], $vehicleMap);
        $maintenanceReports = static::importMaintenanceReportsErrorCodes($db, $trackerList['vehicleMaintenanceReportErrorCodes'], $maintenanceReports);
        foreach ($maintenanceReports as $maintenanceId => $maintenace) {
            static::persistMaintenanceReport($maintenanceId, $apiClient, $maintenace);
        }
    }

    private const MAINTENANCE_REPORT_TRACKER_MAP = [
        'rpmServiceNumber' => 'maintenanceServiceNumber',
        'rpmPersonResponsible' => 'maintenanceResponsiblePerson',
        'rpmMileage' => ['vehicleMileage', 'static::convertKM'],
        'rpmOperationalStatus' => ['vehicleOperationalStatus', 'static::convertOperationalStatus'],
        'rpmMotivation' => ['maintenanceMotivation', 'static::convertMotivation'],
        'rpmWarrantyStatus' => ['vehicleWarrantyStatus', 'static::convertWarrantyStatus'],
        'rpmOwner' => ['currentOwnwerCount', 'static::convertOwnerCount'],
        'rpmDateOfLastService' => ['lastServiceData', 'static::convertDate'],
        'rpmNextScheduledService' => ['nextScheduledService', 'static::convertDate'],
        'rpmAverageKmLInTheLastYear' => ['averageKMPerLiter', 'static::convertKMLiter'],
        'rpmMajorRepairs' => 'majorRepairsReport',
        'rpmPartsReplaced' => 'partsReplacedReport',
        'rpmServicesAndFinishes' => 'servicesAndFinishesReport',
        ];

    private const MAINTENANCE_PLAN_TRACKER_FREQUENCY_MAP = [
        'vehiclemaintenancereportInspectionOfInje_tricMotor' => 'ENGINE_INSPECTION',
        'vehiclemaintenancereportSuspensionCheck' => 'SUSPENSION_CHECK',
        'vehiclemaintenancereportTransmissionFluidChange' => 'TRANSMISSION_FLUID_CHANGE',
        'vehiclemaintenancereportTimingBeltReplacement' => 'BELT_REPLACEMENT',
        'vehiclemaintenancereportSparkPlugReplacement' => 'SPARK_PLUG_REPLACEMENT',
        'vehiclemaintenancereportFrequencyForTireRotation' => 'TIRE_ROTATION',
        'vehiclemaintenancereportFrequencyForBrak_nspection' => 'BREAK_INSPECTION',
        'vehiclemaintenancereportFrequencyForFluidCheck' => 'FLUID_CHECK',
        'vehiclemaintenancereportFrequencyForComp_nspection' => 'COMPLETE_VEHICLE_INSPECTION',
        'vehiclemaintenancereportFrequencyForEmissionsTest' => 'EMISSIONS_TEST',
        'vehiclemaintenancereportBatteryTest' => 'BATTERY_TEST',
        'vehiclemaintenancereportFrequencyForOilA_terChange' => 'OIL_FILTER_CHANGE'
    ];
        'no apparent issues' => 'NO_APPARENT_ISSUES',
        'maintenance required (please specify)' => 'MAINTENANCE_REQUIRED'
    ];

    private const MAINTENANCE_MOTIVATION_MAP = [
        'Periodic review' => 'PERIODIC_REVIEW',
        'Specific failure' => 'SPECIFIC_FAILURE',
        'Major failure' => 'MAJOR_FAILURE',
        'Vehicle unavailability' => 'VEHICLE_UNAVAILABILITY'
    ];

    private const WARRANTY_STATUS_MAP = [
        'Under warranty' => 'UNDER_WARRANTY',
        'Extended warranty' => 'EXTENDED_WARRANTY',
        'Out of warranty' => 'OUT_OF_WARRANTY'
    ];

    private const MAINTENANCE_REPORT_SERVICE_MAP = [
        'rpmInspectionOfInjectionCombustionSystem_tricMotor' => 'ENGINE_INSPECTION',
        'rpmSuspensionCheck' => 'SUSPENSION_CHECK',
        'rpmTransmissionFluidChange' => 'TRANSMISSION_FLUID_CHANGE',
        'rpmBeltReplacement' => 'BELT_REPLACEMENT',
        'rpmSparkPlugReplacement' => 'SPARK_PLUG_REPLACEMENT',
        'rpmTireRotation' => 'TIRE_ROTATION',
        'rpmBrakeInspection' => 'BREAK_INSPECTION',
        'rpmFluidCheck' => 'FLUID_CHECK',
        'rpmEmissionsTest' => 'EMISSIONS_TEST',
        'rpmBatteryTest' => 'BATTERY_TEST',
        'rpmOilAndFilterChange' => 'OIL_FILTER_CHANGE'
    ];

    private const VEHICLE_TRACKER_MAP = [
        'vlVehicleModel' => 'signature',
        'vlYear' => 'year',
        'vlVehicleBrand' => 'model',
        'vlSignature' => 'signature',
    ];

    private const MAINTENANCE_REPORT_ERROR_CODE_MAP = [
        'ffmProblem' => 'problemDescription',
        'ffmDTCCode' => 'dtcCode'
    ];

    private const USAGE_PATTERN_MAP = [
        'personal vehicle' => 'PERSONAL_VEHICLE',
        'fleet' => 'FLEET',
        'service' => 'SERVICE'
    ];

    private static function convertKM($value) {
        // Convert the kilometers values to meters
        return ["valueI32" => $value * 1000, "unit" => SmartDataUnits::METER];
    }

    private static function convertOwnerCount($value) {
        return preg_replace('/\D/', '', $value);
    }

    private static function convertDate($value) {
        // Saves the epoch value as a time
        return ["valueI64" => $value, "unit" => SmartDataUnits::TIME];
    }

    private static function convertKMLiter($value) {
        // Convert KM/L to M/L
        return ["valueD32" => $value, "unit" => SmartDataUnits::METER];
    }

    private static function convertValueMap($value, $map) {
        if (array_key_exists($value, $map)) {
            return $map[$value];
        } else {
            return null;
        }
    }

    private static function convertOperationalStatus($value) {
        return static::convertValueMap($value, static::OPERATIONAL_STATUS_MAP);
    }

    private static function convertMotivation($value) {
        return static::convertValueMap($value, static::MAINTENANCE_MOTIVATION_MAP);
    }
    private static function convertWarrantyStatus($value) {
        return static::convertValueMap($value, static::WARRANTY_STATUS_MAP);
    private static function convertServiceFrequency($key, $value)
    {
        $numericValue = preg_replace('/\D/', '', $value);
        if (str_contains($value, "km")) {
            return ["kind" => $key, 'internal' =>  ['unit' => SmartDataUnits::METER, 'valueI32' => $numericValue * 1000]];
        } else {
            return ["kind" => $key, 'internal' =>  ['unit' => SmartDataUnits::TIME, 'valueI32' => $numericValue * SmartDataUnits::MONTH_TO_TIME_MULTIPLIER]];
        }

    }

    private static function  persistMaintenanceReport($tikiwikiid, $api, $data)
    {
        $identifier = "tikiwiki-vehicle-maintenancereport-$tikiwikiid";
        $id = $api->getIdByIdentifier($identifier);
        if ($id) {
            Logger::log("Updating existing Vehicle Maintenance Report $identifier context $id");
            $api->updateSmartDataContext($id, ["content" => $data]);
        } else {
            Logger::log("Creating Vehicle Maintenance Report $identifier context");
            $result = $api->createSmartDataContext(["content" => $data, "features" => ["identifier" => $identifier, "tags" => ["vehicle-maintenance-report"]]]);
            if (array_key_exists('result', $result)) {
                Logger::log("Created Vehicle Maintenance Report $identifier context " . $result['result']['smartDataContextId']);
                $id = $result['result']['smartDataContextId'];
            } else {
                throw new Exception(json_encode($result['errors']));
            }
        }

        $result = $api->associateSmartDataContext(['smartDataContextIds' => $id, 'smartDataSources' => [$data['vehicle']['signature']]]);
        if (array_key_exists('result', $result)) {
            Logger::log("Associated context to signature ". $data['vehicle']['signature']);
        } else {
            throw new Exception(json_encode($result['errors']));
        }
    }

    private static function importMaintenanceReportsErrorCodes(TikiWikiDB $db, string $trackerName, array $maintenanceReportsMap)
    {
        $maintenanceReportsErrorCodes = static::findTrackerItems($db, $trackerName);
        foreach ($maintenanceReportsErrorCodes as $maintenanceReportsErrorCode) {
            $maintenanceReportsMap = self::importMaintenanceReportsErrorCode($maintenanceReportsMap, $maintenanceReportsErrorCode);
        }

        return $maintenanceReportsMap;
    }

    private static function importMaintenanceReportsErrorCode(array $maintenanceReportsMap, mixed $maintenanceReportsErrorCodeData)
    {
        $maintenanceReportsErrorCode = static::buildJson(static::MAINTENANCE_REPORT_ERROR_CODE_MAP, $maintenanceReportsErrorCodeData);
        $maintenanceReportId = $maintenanceReportsErrorCodeData['ffmVehicleMaintenanceReport']['fieldValue'];
        if (array_key_exists($maintenanceReportId, $maintenanceReportsMap)) {
            $maintenanceReportsMap[$maintenanceReportId]['vehicleFailures'][] = $maintenanceReportsErrorCode;
        }
        return $maintenanceReportsMap;
    }

    private static function importMaintenanceReports(TikiWikiDB $db, string $trackerName, array $vehicleMap) {
        $maintenanceReportsData = static::findTrackerItems($db, $trackerName);
        $maintenanceReports = [];
        foreach ($maintenanceReportsData as $maintenanceReportDatum) {
            $maintenanceReports[$maintenanceReportDatum['tikiwikiid']] = static::importMaintenanceReport($maintenanceReportDatum, $vehicleMap);
        }
        return $maintenanceReports;
    }

    private static function importMaintenanceReport(array $maintenanceReportDatum, array $vehicleMap) {
        $maintenanceReport = static::buildJson(static::MAINTENANCE_REPORT_TRACKER_MAP, $maintenanceReportDatum);
        $maintenanceReport["vehicle"] = $vehicleMap[$maintenanceReportDatum['rpmVehicle']['fieldValue']];

        foreach ($maintenanceReportDatum as $fieldName => $field) {
            if (array_key_exists($fieldName, static::MAINTENANCE_REPORT_SERVICE_MAP)) {
                if ($field['fieldValue'] == 'y') {
                    $maintenanceReport['executedServices'][] = static::MAINTENANCE_REPORT_SERVICE_MAP[$fieldName];
                }
            }
        }

        return $maintenanceReport;
    }

    private static function importMaintenancePlans(TikiWikiDB $db, SmartDataContextApiClient $api, string $trackerName, array $vehicleMap) {
        $maintenancePlansData = static::findTrackerItems($db, $trackerName);
        $maintenancePlans = [];
        foreach ($maintenancePlansData as $tikiwikiid => $maintenancePlansDatum) {
            $maintenancePlan = ["vehicle" => $vehicleMap[$maintenancePlansDatum['vehiclemaintenancereportVehicle']['fieldValue']]];
            foreach ($maintenancePlansDatum as $fieldName => $field) {
                if (array_key_exists($fieldName, static::MAINTENANCE_PLAN_TRACKER_FREQUENCY_MAP)) {
                    $maintenancePlan['servicesFrequencies'][] = static::convertServiceFrequency(static::MAINTENANCE_PLAN_TRACKER_FREQUENCY_MAP[$fieldName], $field['fieldValue']);
                }
            }
            static::persistMaintenancePlan($tikiwikiid, $api, $maintenancePlan);
    private static function  persistMaintenancePlan($tikiwikiid, $api, $data)
        $identifier = "tikiwiki-vehicle-maintenanceplan-$tikiwikiid";
        $id = $api->getIdByIdentifier($identifier);
        if ($id) {
            Logger::log("Updating existing Vehicle Maintenance Plan $identifier context $id");
            $api->updateSmartDataContext($id, ["content" => $data]);
        } else {
            Logger::log("Creating Vehicle Maintenance Plan $identifier context");
            $result = $api->createSmartDataContext(["content" => $data, "features" => ["identifier" => $identifier, "tags" => ["vehicle-maintenance-plan"]]]);
            if (array_key_exists('result', $result)) {
                Logger::log("Created Vehicle Maintenance Plan $identifier context " . $result['result']['smartDataContextId']);
                $id = $result['result']['smartDataContextId'];
            } else {
                throw new Exception(json_encode($result['errors']));
            }
        }

        $result = $api->associateSmartDataContext(['smartDataContextIds' => $id, 'smartDataSources' => [$data['vehicle']['signature']]]);
        if (array_key_exists('result', $result)) {
            Logger::log("Associated context to signature ". $data['vehicle']['signature']);
        } else {
            throw new Exception(json_encode($result['errors']));
        }


    private static function importVehicles(TikiWikiDB $db, SmartDataContextApiClient $api, string $trackerName) {
        $vehicles = static::findTrackerItems($db, $trackerName);
        $result = [];
        foreach ($vehicles as $vehicle) {
            $result[$vehicle['tikiwikiid']] = static::importVehicle($vehicle);
            static::persistVehicle($api, $vehicle['tikiwikiid'], $result[$vehicle['tikiwikiid']]);
        }
        return $result;
    }

    private static function importVehicle($vehicleTrackerData) {
        $vehicle = static::buildJson(static::VEHICLE_TRACKER_MAP, $vehicleTrackerData);
        $vehicle['usagePatterns'] = static::convertUsagePatern($vehicleTrackerData['vlUsagePatterns']['fieldValue']);
        return $vehicle;
    }

    private static function  persistVehicle(SmartDataContextApiClient $api, $tikiwikiid, $data)
    {
        $identifier = "tikiwiki-vehicle-$tikiwikiid";
        $id = $api->getIdByIdentifier($identifier);
        if ($id) {
            Logger::log("Updating existing Vehicle $identifier context $id");
            $api->updateSmartDataContext($id, ["content" => $data]);
        } else {
            Logger::log("Creating Vehicle $identifier context");
            $result = $api->createSmartDataContext(["content" => $data, "features" => ["identifier" => $identifier, "tags" => ["vehicle-record"]]]);
            if (array_key_exists('result', $result)) {
                $id = $result['result']['smartDataContextId'];
                Logger::log("Created vehicle $identifier context " . $id);
            } else {
                throw new Exception(json_encode($result['errors']));
            }
        }
        $result = $api->associateSmartDataContext(['smartDataContextIds' => $id, 'smartDataSources' => [$data['signature']]]);
        if (array_key_exists('result', $result)) {
            Logger::log("Associated context to signature");
        } else {
            throw new Exception(json_encode($result['errors']));
        }
    }

    private static function convertUsagePatern($patterns) {
        $patternList = [];

        foreach (preg_split('/,/', $patterns) as $pattern) {
            $pattern = trim($pattern);
            if (array_key_exists($pattern, static::USAGE_PATTERN_MAP)) {
                $patternList[] = static::USAGE_PATTERN_MAP[$pattern];
            }
        }

        return $patternList;
    }

    /**
     * Loads tracker data from the database
     */
    private static function findTrackerItems(TikiWikiDB $db, string $trackerName) {
        $trackerData = $db->getTrackerData($trackerName);
        return static::_extractItems($trackerData);
    }

    /**
     * Maps an associative array of field values to a json structure
     */
    private static function buildJson(array $mapping, array $row): array
    {
        $result = [];

        foreach ($mapping as $fieldName => $parameters) {
            if (is_array($parameters)) {
                $jsonPath = $parameters[0];
                $convFunc = $parameters[1];
            } else {
                $jsonPath = $parameters;
            }

            if (!isset($row[$fieldName])) {
            }

            $value = $row[$fieldName];
            $keys = explode('.', $jsonPath);
            $current = &$result;

            foreach ($keys as $key) {
                if (!isset($current[$key])) {
                    $current[$key] = [];
                }
                $current = &$current[$key];
            }

            $current = isset($convFunc) ? call_user_func($convFunc, $value['fieldValue']): $value['fieldValue'];
        }

        return $result;
    }

    /**
     * Converts a database result to an associative array based on the tikiwiki item id
     */
    private static function _extractItems(array $rows): array
    {
        $result = [];

        foreach ($rows as $row) {
            $itemId = $row['item_id'];
            $fieldName = $row['fieldName'];

            if (!isset($result[$itemId])) {
                $result[$itemId] = ['tikiwikiid' => $itemId];
            }

            $result[$itemId][$fieldName] = [
                'fieldValue' => $row['fieldValue'],
                'attachment_name' => $row['attachment_name'],
                'attachment_size' => $row['attachment_size'],
                'attachment_type' => $row['attachment_type'],
                'attachment_data' => $row['attachment_data']
            ];
        }

        return $result;
    }

    public static function register(Console_CommandLine $commandLine, array $commandMap)
    {
        $cmd = BaseCommand::addCommand($commandLine, 'tikiwiki-import', 'Imports tracker data from a domain to SmartDataContext entities',
            'Ingress\Command\TikiWiki\ImportCommand::process'
        );
        BaseCommand::addArgument($cmd, 'domain', 'Define the domain to import the data');
    }


}