You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

675 lines
26 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Haoliang.Models.Template;
using Haoliang.Models.Device;
using Haoliang.Models.DataCollection;
using Haoliang.Data.Repositories;
namespace Haoliang.Core.Services
{
public interface ITemplateValidationService
{
Task<bool> ValidateTemplateStructureAsync(CNCBrandTemplate template);
Task<bool> ValidateTagMappingsAsync(CNCBrandTemplate template);
Task<bool> ValidateDataParsingRulesAsync(CNCBrandTemplate template);
Task<IEnumerable<ValidationError>> ValidateTemplateForDeviceAsync(int templateId, int deviceId);
Task<bool> TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData);
Task<IEnumerable<string>> GetMissingRequiredTagsAsync(CNCBrandTemplate template);
Task<ValidationReport> ValidateTemplateComprehensivelyAsync(CNCBrandTemplate template);
Task<IEnumerable<TagValidationResult>> ValidateDeviceDataAsync(IEnumerable<TagData> deviceTags, int templateId);
Task<bool> ValidateTemplateCompatibilityAsync(CNCBrandTemplate template1, CNCBrandTemplate template2);
}
public class TemplateValidationService : ITemplateValidationService
{
private readonly ITemplateRepository _templateRepository;
private readonly ITagMappingRepository _tagMappingRepository;
private readonly ILoggingService _loggingService;
public TemplateValidationService(
ITemplateRepository templateRepository,
ITagMappingRepository tagMappingRepository,
ILoggingService loggingService)
{
_templateRepository = templateRepository;
_tagMappingRepository = tagMappingRepository;
_loggingService = loggingService;
}
public async Task<bool> ValidateTemplateStructureAsync(CNCBrandTemplate template)
{
var errors = new List<ValidationError>();
// Check basic structure
if (template == null)
{
errors.Add(new ValidationError { Field = "template", Message = "Template cannot be null" });
return false;
}
if (string.IsNullOrWhiteSpace(template.TemplateName))
{
errors.Add(new ValidationError { Field = "templateName", Message = "Template name is required" });
}
if (string.IsNullOrWhiteSpace(template.BrandName))
{
errors.Add(new ValidationError { Field = "brandName", Message = "Brand name is required" });
}
if (template.Tags == null || !template.Tags.Any())
{
errors.Add(new ValidationError { Field = "tags", Message = "At least one tag must be defined" });
}
// Validate tag structure
if (template.Tags != null)
{
for (int i = 0; i < template.Tags.Count; i++)
{
var tag = template.Tags[i];
var tagErrors = ValidateTagStructure(tag, $"tags[{i}]");
errors.AddRange(tagErrors);
}
}
// Validate data processing rules
if (template.DataProcessingRules != null)
{
foreach (var rule in template.DataProcessingRules)
{
var ruleErrors = ValidateDataProcessingRule(rule);
errors.AddRange(ruleErrors);
}
}
// Log validation results
if (errors.Any())
{
await _loggingService.LogWarningAsync($"Template structure validation failed with {errors.Count} errors");
}
else
{
await _loggingService.LogInfoAsync("Template structure validation passed");
}
return !errors.Any();
}
public async Task<bool> ValidateTagMappingsAsync(CNCBrandTemplate template)
{
var errors = new List<ValidationError>();
if (template.Tags == null || !template.Tags.Any())
{
errors.Add(new ValidationError { Field = "tags", Message = "No tags defined to validate" });
return false;
}
var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(template.TemplateId);
var mappingDict = mappings.ToDictionary(m => m.DeviceTagId);
foreach (var tag in template.Tags)
{
if (!string.IsNullOrWhiteSpace(tag.DeviceTagId))
{
// Check if mapping exists for device tag
if (!mappingDict.ContainsKey(tag.DeviceTagId))
{
errors.Add(new ValidationError
{
Field = $"tags[{tag.SystemTagId}].deviceTagId",
Message = $"No mapping found for device tag '{tag.DeviceTagId}'"
});
}
else
{
var mapping = mappingDict[tag.DeviceTagId];
if (!mapping.IsActive)
{
errors.Add(new ValidationError
{
Field = $"tags[{tag.SystemTagId}].deviceTagId",
Message = $"Mapping for device tag '{tag.DeviceTagId}' is inactive"
});
}
}
}
}
return !errors.Any();
}
public async Task<bool> ValidateDataParsingRulesAsync(CNCBrandTemplate template)
{
var errors = new List<ValidationError>();
if (template.DataProcessingRules == null || !template.DataProcessingRules.Any())
{
return true; // No rules to validate
}
foreach (var rule in template.DataProcessingRules)
{
var ruleErrors = ValidateDataProcessingRule(rule);
errors.AddRange(ruleErrors);
}
return !errors.Any();
}
public async Task<IEnumerable<ValidationError>> ValidateTemplateForDeviceAsync(int templateId, int deviceId)
{
var errors = new List<ValidationError>();
var template = await _templateRepository.GetByIdAsync(templateId);
if (template == null)
{
errors.Add(new ValidationError { Field = "template", Message = "Template not found" });
return errors;
}
var device = await _templateRepository.GetDeviceByIdAsync(deviceId); // Assuming this method exists
if (device == null)
{
errors.Add(new ValidationError { Field = "device", Message = "Device not found" });
return errors;
}
// Check if template is compatible with device brand
if (template.BrandName != device.DeviceBrand)
{
errors.Add(new ValidationError
{
Field = "template.brand",
Message = $"Template brand '{template.BrandName}' does not match device brand '{device.DeviceBrand}'"
});
}
// Check for missing required tags
var missingTags = await GetMissingRequiredTagsAsync(template);
if (missingTags.Any())
{
errors.Add(new ValidationError
{
Field = "tags",
Message = $"Missing required tags: {string.Join(", ", missingTags)}"
});
}
return errors;
}
public async Task<bool> TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData)
{
try
{
// Parse sample data
var document = JsonDocument.Parse(sampleData);
var root = document.RootElement;
// Extract device data
var deviceTags = new List<TagData>();
if (root.TryGetProperty("tags", out var tagsElement))
{
foreach (var tagElement in tagsElement.EnumerateArray())
{
var tag = new TagData
{
Id = tagElement.GetProperty("id").GetString(),
Desc = tagElement.GetProperty("desc").GetString(),
Quality = tagElement.GetProperty("quality").GetString(),
Time = DateTime.Parse(tagElement.GetProperty("time").GetString())
};
if (tagElement.TryGetProperty("value", out var valueElement))
{
tag.Value = ParseTagValue(valueElement);
}
deviceTags.Add(tag);
}
}
// Test tag mapping
var mappingResult = await ValidateDeviceDataAsync(deviceTags, template.TemplateId);
if (mappingResult.Any(r => !r.IsValid))
{
await _loggingService.LogWarningAsync($"Template data parsing test failed with {mappingResult.Count(r => !r.IsValid)} validation errors");
return false;
}
// Test data processing rules
if (template.DataProcessingRules != null)
{
foreach (var rule in template.DataProcessingRules)
{
if (!await ExecuteDataProcessingRuleAsync(rule, deviceTags))
{
await _loggingService.LogWarningAsync($"Data processing rule '{rule.RuleName}' failed during test");
return false;
}
}
}
await _loggingService.LogInfoAsync("Template data parsing test passed");
return true;
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"Template data parsing test failed: {ex.Message}", ex);
return false;
}
}
public async Task<IEnumerable<string>> GetMissingRequiredTagsAsync(CNCBrandTemplate template)
{
var missingTags = new List<string>();
if (template == null || template.Tags == null)
return missingTags;
var requiredTags = template.Tags
.Where(t => t.IsRequired)
.Select(t => t.DeviceTagId)
.Where(id => !string.IsNullOrWhiteSpace(id))
.ToList();
if (!requiredTags.Any())
return missingTags;
var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(template.TemplateId);
var mappedTags = mappings
.Where(m => m.IsActive)
.Select(m => m.DeviceTagId)
.ToHashSet();
foreach (var requiredTag in requiredTags)
{
if (!mappedTags.Contains(requiredTag))
{
missingTags.Add(requiredTag);
}
}
return missingTags;
}
public async Task<ValidationReport> ValidateTemplateComprehensivelyAsync(CNCBrandTemplate template)
{
var report = new ValidationReport
{
TemplateId = template.TemplateId,
TemplateName = template.TemplateName,
ValidationTime = DateTime.Now,
Checks = new List<ValidationCheck>
{
new ValidationCheck
{
Name = "Structure Validation",
Passed = await ValidateTemplateStructureAsync(template),
Errors = new List<ValidationError>()
},
new ValidationCheck
{
Name = "Tag Mapping Validation",
Passed = await ValidateTagMappingsAsync(template),
Errors = new List<ValidationError>()
},
new ValidationCheck
{
Name = "Data Parsing Rules Validation",
Passed = await ValidateDataProcessingRulesAsync(template),
Errors = new List<ValidationError>()
}
}
};
// Aggregate all errors
report.HasErrors = report.Checks.Any(c => !c.Passed);
report.TotalChecks = report.Checks.Count;
report.PassedChecks = report.Checks.Count(c => c.Passed);
report.FailedChecks = report.Checks.Count(c => !c.Passed);
await _loggingService.LogInformationAsync($"Comprehensive template validation: {report.PassedChecks}/{report.TotalChecks} checks passed");
return report;
}
public async Task<IEnumerable<TagValidationResult>> ValidateDeviceDataAsync(IEnumerable<TagData> deviceTags, int templateId)
{
var results = new List<TagValidationResult>();
var template = await _templateRepository.GetByIdAsync(templateId);
if (template == null)
{
results.Add(new TagValidationResult
{
SystemTagId = "template",
IsValid = false,
ErrorMessage = "Template not found"
});
return results;
}
var templateTags = template.Tags ?? new List<TagTemplate>();
var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(templateId);
var mappingDict = mappings.ToDictionary(m => m.DeviceTagId);
foreach (var templateTag in templateTags)
{
var result = new TagValidationResult
{
SystemTagId = templateTag.SystemTagId,
ExpectedDataType = templateTag.DataType,
IsRequired = templateTag.IsRequired
};
if (!string.IsNullOrWhiteSpace(templateTag.DeviceTagId))
{
// Find corresponding device tag
var deviceTag = deviceTags.FirstOrDefault(t => t.Id == templateTag.DeviceTagId);
if (deviceTag == null)
{
if (templateTag.IsRequired)
{
result.IsValid = false;
result.ErrorMessage = $"Required device tag '{templateTag.DeviceTagId}' not found";
}
else
{
result.IsValid = true;
result.Message = "Optional tag not present";
}
}
else
{
// Validate device tag
result.DeviceTagId = templateTag.DeviceTagId;
result.DeviceValue = deviceTag.Value;
result.Quality = deviceTag.Quality;
if (!ValidateTagValue(deviceTag, templateTag))
{
result.IsValid = false;
result.ErrorMessage = $"Tag value validation failed for '{templateTag.DeviceTagId}'";
}
else
{
result.IsValid = true;
result.MappedValue = ApplyTagMapping(deviceTag, mappingDict[templateTag.DeviceTagId]);
}
}
}
else
{
result.IsValid = true;
result.Message = "No device tag mapping defined";
}
results.Add(result);
}
return results;
}
public async Task<bool> ValidateTemplateCompatibilityAsync(CNCBrandTemplate template1, CNCBrandTemplate template2)
{
if (template1 == null || template2 == null)
return false;
if (template1.TemplateId == template2.TemplateId)
return true; // Same template is always compatible
// Check if same brand
if (template1.BrandName != template2.BrandName)
{
await _loggingService.LogInfoAsync($"Templates have different brands: {template1.BrandName} vs {template2.BrandName}");
return false;
}
// Check tag compatibility
var tags1 = template1.Tags ?? new List<TagTemplate>();
var tags2 = template2.Tags ?? new List<TagTemplate>();
// Count common tags
var commonTags = tags1.Intersect(tags2, new TagTemplateComparer());
var compatibilityScore = (double)commonTags.Count() / Math.Max(tags1.Count, tags2.Count);
await _loggingService.LogInfoAsync($"Template compatibility score: {compatibilityScore:P1}");
return compatibilityScore >= 0.8; // 80% compatibility threshold
}
private List<ValidationError> ValidateTagStructure(TagTemplate tag, string fieldPath)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(tag.SystemTagId))
{
errors.Add(new ValidationError { Field = $"{fieldPath}.systemTagId", Message = "System tag ID is required" });
}
if (!string.IsNullOrWhiteSpace(tag.DeviceTagId))
{
if (string.IsNullOrWhiteSpace(tag.DataType))
{
errors.Add(new ValidationError { Field = $"{fieldPath}.dataType", Message = "Data type is required when device tag ID is specified" });
}
}
if (!string.IsNullOrWhiteSpace(tag.DataType))
{
var validTypes = new[] { "int", "decimal", "bool", "string", "datetime", "double" };
if (!validTypes.Contains(tag.DataType.ToLower()))
{
errors.Add(new ValidationError { Field = $"{fieldPath}.dataType", Message = $"Invalid data type: {tag.DataType}" });
}
}
// Validate regex pattern if provided
if (!string.IsNullOrWhiteSpace(tag.ValidationRegex))
{
try
{
Regex.IsMatch("test", tag.ValidationRegex); // Test if regex is valid
}
catch
{
errors.Add(new ValidationError { Field = $"{fieldPath}.validationRegex", Message = "Invalid regular expression pattern" });
}
}
return errors;
}
private List<ValidationError> ValidateDataProcessingRule(DataProcessingRule rule)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(rule.RuleName))
{
errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].ruleName", Message = "Rule name is required" });
}
if (string.IsNullOrWhiteSpace(rule.Condition))
{
errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].condition", Message = "Condition is required" });
}
if (string.IsNullOrWhiteSpace(rule.Action))
{
errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].action", Message = "Action is required" });
}
// Validate condition syntax (simple check)
if (!string.IsNullOrWhiteSpace(rule.Condition) && !IsValidCondition(rule.Condition))
{
errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].condition", Message = "Invalid condition syntax" });
}
return errors;
}
private bool IsValidCondition(string condition)
{
// Simple condition validation (in real implementation, use expression parser)
var validOperators = new[] { ">", "<", ">=", "<=", "==", "!=", "&&", "||", "(", ")", "true", "false" };
var parts = condition.Split(' ');
foreach (var part in parts)
{
if (!string.IsNullOrWhiteSpace(part) && !validOperators.Contains(part) && !double.TryParse(part, out _))
{
return false;
}
}
return true;
}
private object ParseTagValue(JsonElement valueElement)
{
if (valueElement.ValueKind == JsonValueKind.String)
{
return valueElement.GetString();
}
else if (valueElement.ValueKind == JsonValueKind.Number)
{
if (valueElement.TryGetInt32(out int intValue))
return intValue;
else if (valueElement.TryGetDecimal(out decimal decimalValue))
return decimalValue;
else
return valueElement.GetDouble();
}
else if (valueElement.ValueKind == JsonValueKind.True || valueElement.ValueKind == JsonValueKind.False)
{
return valueElement.GetBoolean();
}
else
{
return valueElement.ToString();
}
}
private bool ValidateTagValue(TagData deviceTag, TagTemplate templateTag)
{
if (deviceTag.Value == null)
return !templateTag.IsRequired;
try
{
// Validate data type
switch (templateTag.DataType.ToLower())
{
case "int":
return int.TryParse(deviceTag.Value.ToString(), out _);
case "decimal":
return decimal.TryParse(deviceTag.Value.ToString(), out _);
case "bool":
return bool.TryParse(deviceTag.Value.ToString(), out _);
case "datetime":
return DateTime.TryParse(deviceTag.Value.ToString(), out _);
default:
return true; // String type accepts any value
}
}
catch
{
return false;
}
}
private TagData ApplyTagMapping(TagData deviceTag, TagMapping mapping)
{
return new TagData
{
Id = mapping.SystemTagId,
Desc = mapping.Description ?? deviceTag.Desc,
Quality = deviceTag.Quality,
Time = deviceTag.Time,
Value = ConvertTagValue(deviceTag.Value, mapping.DataType, mapping.ConversionFormula)
};
}
private async Task<bool> ExecuteDataProcessingRuleAsync(DataProcessingRule rule, IEnumerable<TagData> deviceTags)
{
// Simple rule execution (in real implementation, use expression parser)
try
{
// This is a simplified implementation
// In a real system, you would parse and execute the condition and action
if (rule.Condition.Contains("temperature") && deviceTags.Any(t => t.Id == "temperature"))
{
var tempTag = deviceTags.First(t => t.Id == "temperature");
var tempValue = Convert.ToDouble(tempTag.Value);
if (tempValue > 100 && rule.Action.Contains("alert"))
{
return true; // Rule executed successfully
}
}
return true;
}
catch (Exception ex)
{
await _loggingService.LogErrorAsync($"Failed to execute data processing rule '{rule.RuleName}': {ex.Message}");
return false;
}
}
}
// Supporting classes and interfaces
public class ValidationReport
{
public int TemplateId { get; set; }
public string TemplateName { get; set; }
public DateTime ValidationTime { get; set; }
public List<ValidationCheck> Checks { get; set; }
public bool HasErrors { get; set; }
public int TotalChecks { get; set; }
public int PassedChecks { get; set; }
public int FailedChecks { get; set; }
}
public class ValidationCheck
{
public string Name { get; set; }
public bool Passed { get; set; }
public List<ValidationError> Errors { get; set; }
}
public class TagValidationResult
{
public string SystemTagId { get; set; }
public string DeviceTagId { get; set; }
public object DeviceValue { get; set; }
public object MappedValue { get; set; }
public string ExpectedDataType { get; set; }
public string Quality { get; set; }
public bool IsRequired { get; set; }
public bool IsValid { get; set; }
public string ErrorMessage { get; set; }
public string Message { get; set; }
}
public class TagTemplateComparer : IEqualityComparer<TagTemplate>
{
public bool Equals(TagTemplate x, TagTemplate y)
{
return x?.SystemTagId == y?.SystemTagId;
}
public int GetHashCode(TagTemplate obj)
{
return obj?.SystemTagId?.GetHashCode() ?? 0;
}
}
}