private void SaveFixedMeasurement(SignalType signalType, Device device, TableOperations <Measurement> measurementTable, ScanParameters scanParams, string label = null)
        {
            string signalReference = $"{device.Acronym}-{signalType.Suffix}";

            // Query existing measurement record for specified signal reference - function will create a new blank measurement record if one does not exist
            Measurement measurement = measurementTable.QueryMeasurement(signalReference);
            string      pointTag    = scanParams.CreatePointTag(device.Acronym, signalType.Acronym);

            measurement.DeviceID        = device.ID;
            measurement.PointTag        = pointTag;
            measurement.Description     = $"{device.Acronym} {signalType.Name}{(string.IsNullOrWhiteSpace(label) ? "" : " - " + label)}";
            measurement.SignalReference = signalReference;
            measurement.SignalTypeID    = signalType.ID;
            measurement.Internal        = true;
            measurement.Enabled         = true;

            measurementTable.AddNewOrUpdateMeasurement(measurement);
        }
        private void SavePhasorMeasurement(SignalType signalType, Device device, IPhasorDefinition phasorDefinition, char phase, int index, int baseKV, TableOperations <Measurement> measurementTable, ScanParameters scanParams)
        {
            string signalReference = $"{device.Acronym}-{signalType.Suffix}{index}";

            // Query existing measurement record for specified signal reference - function will create a new blank measurement record if one does not exist
            Measurement measurement = measurementTable.QueryMeasurement(signalReference);
            string      pointTag    = scanParams.CreatePhasorPointTag(device.Acronym, signalType.Acronym, phasorDefinition.Label, phase.ToString(), index, baseKV);

            measurement.DeviceID          = device.ID;
            measurement.PointTag          = pointTag;
            measurement.Description       = $"{device.Acronym} {phasorDefinition.Label} {signalType.Name}";
            measurement.PhasorSourceIndex = index;
            measurement.SignalReference   = signalReference;
            measurement.SignalTypeID      = signalType.ID;
            measurement.Internal          = true;
            measurement.Enabled           = true;

            measurementTable.AddNewOrUpdateMeasurement(measurement);
        }
        private void SaveDevicePhasors(IConfigurationCell cell, Device device, TableOperations <Measurement> measurementTable, ScanParameters scanParams)
        {
            bool phaseMatchExact(string phaseLabel, string[] phaseMatches) =>
            phaseMatches.Any(match => phaseLabel.Equals(match, StringComparison.Ordinal));

            bool phaseEndsWith(string phaseLabel, string[] phaseMatches, bool ignoreCase) =>
            phaseMatches.Any(match => phaseLabel.EndsWith(match, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));

            bool phaseStartsWith(string phaseLabel, string[] phaseMatches, bool ignoreCase) =>
            phaseMatches.Any(match => phaseLabel.StartsWith(match, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));

            bool phaseContains(string phaseLabel, string[] phaseMatches, bool ignoreCase) =>
            phaseMatches.Any(match => phaseLabel.IndexOf(match, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) > -1);

            bool phaseMatchHighConfidence(string phaseLabel, string[] containsMatches, string[] endsWithMatches)
            {
                if (phaseEndsWith(phaseLabel, containsMatches, true))
                {
                    return(true);
                }

                if (phaseStartsWith(phaseLabel, containsMatches, true))
                {
                    return(true);
                }

                foreach (string match in containsMatches.Concat(endsWithMatches))
                {
                    string[] variations = { $" {match}", $"_{match}", $"-{match}", $".{match}" };

                    if (phaseEndsWith(phaseLabel, variations, false))
                    {
                        return(true);
                    }
                }

                foreach (string match in containsMatches)
                {
                    string[] variations = { $" {match} ", $"_{match}_", $"-{match}-", $"-{match}_", $"_{match}-", $".{match}." };

                    if (phaseContains(phaseLabel, variations, false))
                    {
                        return(true);
                    }
                }

                return(false);
            }

            char guessPhase(char phase, string phasorLabel)
            {
                if (phaseMatchExact(phasorLabel, new[] { "V1PM", "I1PM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "V1", "VP", "I1", "IP", "VSEQ1", "ISEQ1" }, new[] { "POS", "V1PM", "I1PM", "PS", "PSV", "PSI" }) || phaseEndsWith(phasorLabel, new[] { "+SV", "+SI", "+V", "+I" }, true))
                {
                    return('+');
                }

                if (phaseMatchExact(phasorLabel, new[] { "V0PM", "I0PM", "VZPM", "IZPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "V0", "I0", "VSEQ0", "ISEQ0" }, new[] { "ZERO", "ZPV", "ZPI", "VSPM", "V0PM", "I0PM", "VZPM", "IZPM", "ZS", "ZSV", "ZSI" }) || phaseEndsWith(phasorLabel, new[] { "0SV", "0SI" }, true))
                {
                    return('0');
                }

                if (phaseMatchExact(phasorLabel, new[] { "VAPM", "IAPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "VA", "IA" }, new[] { "APV", "API", "VAPM", "IAPM", "AV", "AI" }))
                {
                    return('A');
                }

                if (phaseMatchExact(phasorLabel, new[] { "VBPM", "IBPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "VB", "IB" }, new[] { "BPV", "BPI", "VBPM", "IBPM", "BV", "BI" }))
                {
                    return('B');
                }

                if (phaseMatchExact(phasorLabel, new[] { "VCPM", "ICPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "VC", "IC" }, new[] { "CPV", "CPI", "VCPM", "ICPM", "CV", "CI" }))
                {
                    return('C');
                }

                if (phaseMatchExact(phasorLabel, new[] { "VNPM", "INPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "VN", "IN" }, new[] { "NEUT", "NPV", "NPI", "VNPM", "INPM", "NV", "NI" }))
                {
                    return('N');
                }

                if (phaseMatchExact(phasorLabel, new[] { "V2PM", "I2PM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "V2", "I2", "VSEQ2", "ISEQ2" }, new[] { "NEG", "-SV", "-SI", "V2PM", "I2PM", "NS", "NSV", "NSI" }))
                {
                    return('-');
                }

                return(phase);
            }

            int guessBaseKV(int baseKV, string phasorLabel, string deviceLabel)
            {
                // Check phasor label before device
                foreach (string voltageLevel in s_commonVoltageLevels)
                {
                    if (phasorLabel.IndexOf(voltageLevel, StringComparison.Ordinal) > -1)
                    {
                        return(int.Parse(voltageLevel));
                    }
                }

                foreach (string voltageLevel in s_commonVoltageLevels)
                {
                    if (deviceLabel.IndexOf(voltageLevel, StringComparison.Ordinal) > -1)
                    {
                        return(int.Parse(voltageLevel));
                    }
                }

                return(baseKV);
            }

            AdoDataConnection        connection  = scanParams.Connection;
            TableOperations <Phasor> phasorTable = new(connection);

            // Get phasor signal types
            SignalType iphmSignalType = m_phasorSignalTypes["IPHM"];
            SignalType iphaSignalType = m_phasorSignalTypes["IPHA"];
            SignalType vphmSignalType = m_phasorSignalTypes["VPHM"];
            SignalType vphaSignalType = m_phasorSignalTypes["VPHA"];

            Phasor[] phasors = phasorTable.QueryPhasorsForDevice(device.ID).ToArray();

            bool dropAndAdd = phasors.Length != cell.PhasorDefinitions.Count;

            if (!dropAndAdd)
            {
                // Also do add operation if phasor source index records are not sequential
                if (phasors.Where((phasor, index) => phasor.SourceIndex != index + 1).Any())
                {
                    dropAndAdd = true;
                }
            }

            if (dropAndAdd)
            {
                if (cell.PhasorDefinitions.Count > 0)
                {
                    connection.DeletePhasorsForDevice(device.ID);
                }

                foreach (IPhasorDefinition phasorDefinition in cell.PhasorDefinitions)
                {
                    bool isVoltage = phasorDefinition.PhasorType == PhasorType.Voltage;

                    Phasor phasor = phasorTable.NewPhasor();
                    phasor.DeviceID            = device.ID;
                    phasor.Label               = phasorDefinition.Label;
                    phasor.Type                = isVoltage ? 'V' : 'I';
                    phasor.Phase               = guessPhase('+', phasor.Label);
                    phasor.BaseKV              = guessBaseKV(500, phasor.Label, string.IsNullOrWhiteSpace(device.Name) ? device.Acronym ?? "" : device.Name);
                    phasor.DestinationPhasorID = null;
                    phasor.SourceIndex         = phasorDefinition.Index + 1;

                    phasorTable.AddNewPhasor(phasor);
                    SavePhasorMeasurement(isVoltage ? vphmSignalType : iphmSignalType, device, phasorDefinition, phasor.Phase, phasor.SourceIndex, phasor.BaseKV, measurementTable, scanParams);
                    SavePhasorMeasurement(isVoltage ? vphaSignalType : iphaSignalType, device, phasorDefinition, phasor.Phase, phasor.SourceIndex, phasor.BaseKV, measurementTable, scanParams);
                }
            }
            else
            {
                foreach (IPhasorDefinition phasorDefinition in cell.PhasorDefinitions)
                {
                    bool isVoltage = phasorDefinition.PhasorType == PhasorType.Voltage;

                    Phasor phasor = phasorTable.QueryPhasorForDevice(device.ID, phasorDefinition.Index + 1);
                    phasor.DeviceID = device.ID;
                    phasor.Label    = phasorDefinition.Label;
                    phasor.Type     = isVoltage ? 'V' : 'I';

                    phasorTable.AddNewPhasor(phasor);
                    SavePhasorMeasurement(isVoltage ? vphmSignalType : iphmSignalType, device, phasorDefinition, phasor.Phase, phasor.SourceIndex, phasor.BaseKV, measurementTable, scanParams);
                    SavePhasorMeasurement(isVoltage ? vphaSignalType : iphaSignalType, device, phasorDefinition, phasor.Phase, phasor.SourceIndex, phasor.BaseKV, measurementTable, scanParams);
                }
            }
        }
        private void SaveDeviceRecords(IConfigurationFrame configFrame, Device device, ScanParameters scanParams)
        {
            AdoDataConnection             connection       = scanParams.Connection;
            TableOperations <Measurement> measurementTable = new(connection);
            IConfigurationCell            cell             = configFrame.Cells[0];

            // Add frequency
            SaveFixedMeasurement(m_deviceSignalTypes["FREQ"], device, measurementTable, scanParams, cell.FrequencyDefinition.Label);

            // Add dF/dt
            SaveFixedMeasurement(m_deviceSignalTypes["DFDT"], device, measurementTable, scanParams);

            // Add status flags
            SaveFixedMeasurement(m_deviceSignalTypes["FLAG"], device, measurementTable, scanParams);

            // Add analogs
            SignalType analogSignalType = m_deviceSignalTypes["ALOG"];

            for (int i = 0; i < cell.AnalogDefinitions.Count; i++)
            {
                int index = i + 1;
                IAnalogDefinition analogDefinition = cell.AnalogDefinitions[i];
                string            signalReference  = $"{device.Acronym}-{analogSignalType.Suffix}{index}";

                // Query existing measurement record for specified signal reference - function will create a new blank measurement record if one does not exist
                Measurement measurement = measurementTable.QueryMeasurement(signalReference);
                string      pointTag    = scanParams.CreateIndexedPointTag(device.Acronym, analogSignalType.Acronym, index);
                measurement.DeviceID        = device.ID;
                measurement.PointTag        = pointTag;
                measurement.AlternateTag    = analogDefinition.Label;
                measurement.Description     = $"{device.Acronym} Analog Value {index} {analogDefinition.AnalogType}: {analogDefinition.Label}";
                measurement.SignalReference = signalReference;
                measurement.SignalTypeID    = analogSignalType.ID;
                measurement.Internal        = true;
                measurement.Enabled         = true;

                measurementTable.AddNewOrUpdateMeasurement(measurement);
            }

            // Add digitals
            SignalType digitalSignalType = m_deviceSignalTypes["DIGI"];

            for (int i = 0; i < cell.DigitalDefinitions.Count; i++)
            {
                int index = i + 1;
                IDigitalDefinition digitialDefinition = cell.DigitalDefinitions[i];
                string             signalReference    = $"{device.Acronym}-{digitalSignalType.Suffix}{index}";

                // Query existing measurement record for specified signal reference - function will create a new blank measurement record if one does not exist
                Measurement measurement = measurementTable.QueryMeasurement(signalReference);
                string      pointTag    = scanParams.CreateIndexedPointTag(device.Acronym, digitalSignalType.Acronym, index);
                measurement.DeviceID        = device.ID;
                measurement.PointTag        = pointTag;
                measurement.AlternateTag    = digitialDefinition.Label;
                measurement.Description     = $"{device.Acronym} Digital Value {index}: {digitialDefinition.Label}";
                measurement.SignalReference = signalReference;
                measurement.SignalTypeID    = digitalSignalType.ID;
                measurement.Internal        = true;
                measurement.Enabled         = true;

                measurementTable.AddNewOrUpdateMeasurement(measurement);
            }

            // Add phasors
            SaveDevicePhasors(cell, device, measurementTable, scanParams);
        }