Skip to content

satyanvesh8473/Linkedin_Google_ProfileScraper

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 

Repository files navigation

using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using System.Linq; using System.IO;

namespace RulesUpload.CreateRules { class CreateRuleManager {
public Task Validate(string path, int env, EventHandler onChangeHandler) { string errorFilePath = path.Replace(".csv", "") + "_Error.csv"; if (File.Exists(errorFilePath)) { File.Delete(errorFilePath); }

        using (File.Create(errorFilePath)) { }

        onChangeHandler(this, new StatusChangeArgs(false, "Parsing file..."));

        CreateRulesRequest requestObject = new CreateRulesRequest()
        {
            Rules = new List<CreateRuleParameters>()
        };

        // Read CSV with shared access
        var lines = new List<string>();
        using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        using (var reader = new StreamReader(fs))
        {
            while (!reader.EndOfStream)
            {
                lines.Add(reader.ReadLine());
            }
        }
        var lineList = lines.ToArray();

        for (int i = 0; i < lineList.Length; i++)
        {
            if (i == 0) continue; // skip header

            List<string> columnList = lineList[i].Split(',').ToList();

            if (columnList.Count != 11)
            {
                string errLine = $"Invalid Row {i + 1} expect to have 11 columns but has {columnList.Count}{Environment.NewLine}";
                File.AppendAllText(errorFilePath, errLine);
                continue;
            }

            string ruleType = columnList[0];
            bool reportable = bool.Parse(columnList[1]);
            string score = columnList[2];
            string label = columnList[3];
            int labelRank = !string.IsNullOrWhiteSpace(columnList[4]) ? int.Parse(columnList[4]) : 0;
            int frequency = !string.IsNullOrWhiteSpace(columnList[5]) ? int.Parse(columnList[5]) : 0;
            double points = !string.IsNullOrWhiteSpace(columnList[6]) ? double.Parse(columnList[6]) : 0;
            string state = columnList[7];
            string country = columnList[8];

            // Clean up expression string
            string expression = columnList[9].Trim()
                .Replace("\"\"", "\"")
                .Replace("“", "\"")
                .Replace("”", "\"");

            if (expression.StartsWith("\"") && expression.EndsWith("\""))
            {
                expression = expression.Substring(1, expression.Length - 2);
            }

            RuleExpressions expressions = null;
            try
            {
                var tokenizer = new ExpressionTokenizer();
                var tokens = tokenizer.Tokenize(expression);
                var parser = new ExpressionParser(tokens);
                expressions = parser.Parse();
            }
            catch
            {
                File.AppendAllText(errorFilePath,
                    $"Invalid Expression at Row {i + 1}: Please check the rule \n");
                continue;
            }

            try
            {
                requestObject.Rules.Add(new CreateRuleParameters()
                {
                    RuleType = ruleType,
                    Reportable = reportable,
                    Score = score,
                    Label = label,
                    LabelRank = labelRank,
                    Frequency = frequency,
                    Points = points,
                    State = state,
                    Country = country,
                    Expressions = expressions,          // runtime object
                    ExpressionString = expression       // safe serialized form
                });
            }
            catch (Exception ex)
            {
                onChangeHandler(this, new StatusChangeArgs(true, ex.ToString()));
                string errLine = $"Invalid Row {i + 1} {ex}{Environment.NewLine}";
                File.AppendAllText(errorFilePath, errLine);
                continue;
            }
        }

        onChangeHandler(this, new StatusChangeArgs(false, "Compiling rule set..."));
        var ruleTypeSet = new HashSet<string>();
        foreach (var rule in requestObject.Rules)
        {
            ruleTypeSet.Add(rule.RuleType);
            if (ruleTypeSet.Count > 1)
            {
                File.AppendAllText(errorFilePath, "RuleType should be unique.");
                break;
            }
        }

        var csvFileLength = new FileInfo(errorFilePath).Length;
        if (csvFileLength > 0)
        {
            onChangeHandler(this, new StatusChangeArgs(false, "Completed Validation, please check error file..."));
        }
        else
        {
            onChangeHandler(this, new StatusChangeArgs(false, "Completed Validation..."));
            if (File.Exists(errorFilePath))
            {
                File.Delete(errorFilePath);
            }
        }

        return Task.CompletedTask;
    }


    public Task Create(string path, int env, EventHandler<StatusChangeArgs> onChangeHandler)
    {
        onChangeHandler(this, new StatusChangeArgs(false, "Parsing file..."));

        CreateRulesRequest requestObject = new CreateRulesRequest()
        {
            Rules = new List<CreateRuleParameters>()
        };

        // Read CSV with shared access
        var lines = new List<string>();
        using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        using (var reader = new StreamReader(fs))
        {
            while (!reader.EndOfStream)
            {
                lines.Add(reader.ReadLine());
            }
        }
        var lineList = lines.ToArray();

        for (int i = 0; i < lineList.Length; i++)
        {
            if (i == 0) continue; // skip header

            List<string> columnList = lineList[i].Split(',').ToList();

            if (columnList.Count != 11)
            {
                onChangeHandler(this, new StatusChangeArgs(true,
                    $"Invalid Row {i + 1} expect to have 11 columns but has {columnList.Count}"));
                return Task.CompletedTask;
            }

            string ruleType = columnList[0];
            bool reportable = bool.Parse(columnList[1]);
            string score = columnList[2];
            string label = columnList[3];
            int labelRank = !string.IsNullOrWhiteSpace(columnList[4]) ? int.Parse(columnList[4]) : 0;
            int frequency = !string.IsNullOrWhiteSpace(columnList[5]) ? int.Parse(columnList[5]) : 0;
            double points = !string.IsNullOrWhiteSpace(columnList[6]) ? double.Parse(columnList[6]) : 0;
            string state = columnList[7];
            string country = columnList[8];

            // Clean up expression string
            string expression = columnList[9].Trim()
                .Replace("\"\"", "\"")
                .Replace("“", "\"")
                .Replace("”", "\"");

            if (expression.StartsWith("\"") && expression.EndsWith("\""))
            {
                expression = expression.Substring(1, expression.Length - 2);
            }

            // Parse into RuleExpressions for runtime use
            RuleExpressions expressions = null;
            try
            {
                var tokenizer = new ExpressionTokenizer();
                var tokens = tokenizer.Tokenize(expression);
                var parser = new ExpressionParser(tokens);
                expressions = parser.Parse();
            }
            catch
            {
                onChangeHandler(this, new StatusChangeArgs(true,
                    $"Invalid Expression at Row {i + 1}"));
                return Task.CompletedTask;
            }

            if (expressions == null)
            {
                onChangeHandler(this, new StatusChangeArgs(true,
                    "Could not create a rule as Expression is Null or Blank"));
                return Task.CompletedTask;
            }

            try
            {
                requestObject.Rules.Add(new CreateRuleParameters()
                {
                    RuleType = ruleType,
                    Reportable = reportable,
                    Score = score,
                    Label = label,
                    LabelRank = labelRank,
                    Frequency = frequency,
                    Points = points,
                    State = state,
                    Country = country,
                    Expressions = expressions,          // runtime object
                    ExpressionString = expression       // safe serialized form
                });
            }
            catch (Exception ex)
            {
                onChangeHandler(this, new StatusChangeArgs(true, ex.ToString()));
                return Task.CompletedTask;
            }
        }

        onChangeHandler(this, new StatusChangeArgs(false, "Loading charge level mapping..."));
        ChargeLevelMappingManager.Load(env);

        // Serialize only the safe string form
        var options = new JsonSerializerOptions
        {
            MaxDepth = 512
        };

        string requestData = JsonSerializer.Serialize(requestObject, options);

        // Deserialize back if needed
        CreateRulesRequest requestObjectB = JsonSerializer.Deserialize<CreateRulesRequest>(requestData, options);

        var modelId = CreateRule(requestObjectB, out bool isModelIdPresent, onChangeHandler);

        var args = new StatusChangeArgs(true, "")
        {
            Exists = isModelIdPresent,
            ModelId = modelId
        };
        onChangeHandler(this, args);

        return Task.CompletedTask;
    }


    ChargeSeverityValidation objChargeLevelMapping = new ChargeSeverityValidation();

    private string CreateRule(CreateRulesRequest obj, out bool isModelIdPresent, EventHandler<StatusChangeArgs> onChangeHandler)
    {
        Boolean isSimpleExpression = true;
        onChangeHandler(this, new StatusChangeArgs(false, "Validating rule set..."));
        objChargeLevelMapping.ListOfChargeSeverity(obj, isSimpleExpression);
        return SaveRule(obj, out isModelIdPresent);
    }

    private string SaveRule(CreateRulesRequest obj, out bool isModelIdPresent)
    {
        isModelIdPresent = true;
        var ruleTypeSet = new HashSet<string>();
        foreach (var rule in obj.Rules)
        {
            ruleTypeSet.Add(rule.RuleType);
            if (ruleTypeSet.Count > 1)
            {
                throw new ApplicationException("DuplicateRuleTypeException");
            }

            RulePayLoadValidation.IsValid(rule);
        }
        obj.Rules.Sort(new RuleComparer());
        string hashCode = HashCodeGenerate.ToSha512(obj.Rules);
        var modelId = ChargeLevelMappingManager.GetExistingModelId(hashCode);
        if (!string.IsNullOrEmpty(modelId)) return modelId;

        isModelIdPresent = false;
        modelId = $"{obj.Rules[0].RuleType}-{Guid.NewGuid()}";

        var rules = new List<RuleModel>(obj.Rules.Count);

        foreach (var rule in obj.Rules)
            {
                rule.Id = Guid.NewGuid().ToString();
                var r = rule.ToRuleModel();
                r.ModelId = modelId;
                r.HashCode = hashCode;
                rules.Add(r);
            }

            ChargeLevelMappingManager.Save(rules);
    
        return modelId;
    }

    public RuleExpressions BuildExpression(string expression, string symbol)
    {
        // List<string> symbols = new List<string> { "&&", "||"};
        if (string.IsNullOrEmpty(expression))
        {
            return null;
        }

        var expressionList = expression.Split(new[] {symbol}, 2, System.StringSplitOptions.RemoveEmptyEntries)
            .ToList();

        var expressionList1 = expression.Split(new string[] { "&&", "||" }, 2, System.StringSplitOptions.RemoveEmptyEntries)
           .ToList();

        if (expressionList.Count == 0)
        {
            return new RuleExpressions();
        }

        if (expressionList.Count == 1)
        {
            if (expressionList[0].Contains("&&"))
            {
                return BuildExpression(expressionList[0], "&&");
            }

            if (expressionList[0].Contains("||"))
            {
                return BuildExpression(expressionList[0], "||");
            }

            return BuildExpression_Operator(expressionList[0]);
        }

        return new RuleExpressions()
        {
            Left = BuildExpression(expressionList[0], "&&"),
            Operator = symbol == "&&" ? "And" : "Or",
            Right = BuildExpression(expressionList[1], "&&")
        };
    }
    public RuleExpressions BuildExpression_Validate(string expression, string symbol, string errFilePath, int rowNumber)
    {
        var expressionList = expression.Split(new[] { symbol }, 2, System.StringSplitOptions.RemoveEmptyEntries)
            .ToList();

        var objRuleExpressions = new RuleExpressions();

        if (expressionList.Count == 0)
        {
            return objRuleExpressions;
        }

        if (expressionList.Count == 1)
        {
            if (expressionList[0].Contains("&&"))
            {
                return BuildExpression_Validate(expressionList[0], "&&", errFilePath, rowNumber);
            }

            if (expressionList[0].Contains("||"))
            {
                return BuildExpression_Validate(expressionList[0], "||", errFilePath, rowNumber);
            }

            return BuildExpression_Operator_Validate(expressionList[0], errFilePath, rowNumber);                
        }

        return new RuleExpressions()
        {
            Left = BuildExpression_Validate(expressionList[0], "&&", errFilePath, rowNumber),
            Operator = symbol == "&&" ? "And" : "Or",
            Right = BuildExpression_Validate(expressionList[1], "&&", errFilePath, rowNumber)
        };
    }

    private RuleExpressions BuildExpression_Operator_Validate(string expression, string errFilePath, int rowNumber)
    {
        List<string> expressionList_CompareA = System.Text.RegularExpressions.Regex.Split(expression, "greaterthanorequal|>=", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareB = System.Text.RegularExpressions.Regex.Split(expression, "lessthanorequal|<=", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareC = System.Text.RegularExpressions.Regex.Split(expression, "notequal|!=", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareD = System.Text.RegularExpressions.Regex.Split(expression, "equal|=", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareE = System.Text.RegularExpressions.Regex.Split(expression, "greaterthan|>", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareF = System.Text.RegularExpressions.Regex.Split(expression, "lessthan|<", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();

        string operatorString = string.Empty;
        string leftString = string.Empty;
        int leftInt = 0;
        double leftDouble = 0.0;
        string rightString = string.Empty;
        int rightInt = 0;
        double rightDouble = 0.0;

        if (expressionList_CompareA.Count > 1)
        {
            leftString = expressionList_CompareA[0].Replace("\"", string.Empty).Trim();
            rightString = expressionList_CompareA[1].Replace("\"", string.Empty).Trim();
            operatorString = "GreaterThanOrEqual";
        }
        else if (expressionList_CompareB.Count > 1)
        {
            leftString = expressionList_CompareB[0].Replace("\"", string.Empty).Trim();
            rightString = expressionList_CompareB[1].Replace("\"", string.Empty).Trim();
            operatorString = "LessThanOrEqual";
        }
        else if (expressionList_CompareC.Count > 1)
        {
            leftString = expressionList_CompareC[0].Replace("\"", string.Empty).Trim();
            rightString = expressionList_CompareC[1].Replace("\"", string.Empty).Trim();
            operatorString = "NotEqual";
        }
        else
        {
            if (expression.Contains("=>"))
            {                    
                File.AppendAllText(errFilePath, "Invalid Operator => in Row :" + rowNumber + "; Value : " + expression + "\n");
                return new RuleExpressions();
            }
            else if (expression.Contains("=<"))
            {                    
                File.AppendAllText(errFilePath, "Invalid Operator =< in Row : " + rowNumber + "; Value : " + expression + "\n");
                return new RuleExpressions();
            }
            else if (expression.Contains("=!"))
            {                    
                File.AppendAllText(errFilePath, "Invalid Operator =! in Row : " + rowNumber + "; Value : " + expression + "\n");
                return new RuleExpressions();
            }
            else if (expressionList_CompareD.Count > 1)
            {
                leftString = expressionList_CompareD[0].Replace("\"", string.Empty).Trim();
                rightString = expressionList_CompareD[1].Replace("\"", string.Empty).Trim();
                operatorString = "Equal";
            }
            else if (expressionList_CompareE.Count > 1)
            {
                leftString = expressionList_CompareE[0].Replace("\"", string.Empty).Trim();
                rightString = expressionList_CompareE[1].Replace("\"", string.Empty).Trim();
                operatorString = "GreaterThan";
            }
            else if (expressionList_CompareF.Count > 1)
            {
                leftString = expressionList_CompareF[0].Replace("\"", string.Empty).Trim();
                rightString = expressionList_CompareF[1].Replace("\"", string.Empty).Trim();
                operatorString = "LessThan";
            }
        }

        RuleExpressions rule = new RuleExpressions()
        {
            Operator = operatorString
        };

        if (int.TryParse(leftString, out leftInt))
        {
            rule.Left = leftInt;
        }
        else if (double.TryParse(leftString, out leftDouble))
        {
            rule.Left = leftDouble;
        }
        else
        {
            rule.Left = leftString;
        }

        if (int.TryParse(rightString, out rightInt))
        {
            rule.Right = rightInt;
        }
        else if (double.TryParse(rightString, out rightDouble))
        {
            rule.Right = rightDouble;
        }
        else
        {
            rule.Right = rightString;
        }

        if (!string.IsNullOrWhiteSpace(expression) && string.IsNullOrWhiteSpace(operatorString))
        {
            File.AppendAllText(errFilePath, "Invalid operator in Row : " + rowNumber + "; Value : " + expression + "\n");
            return rule;
        }
        
        if (leftString.Equals("request_data[salary]", StringComparison.OrdinalIgnoreCase) && rightString.Equals("varies", StringComparison.OrdinalIgnoreCase) && !operatorString.Equals("equal", StringComparison.OrdinalIgnoreCase))
        {
            File.AppendAllText(errFilePath, string.Format(operatorString + " between " + leftString + " and " + rightString + " is not valid in Row : ") + rowNumber + "\n");
        }

        if ((leftString.EndsWith("Age]") || leftString.Equals("calculated_parameter_list[ConfinementDuration]", StringComparison.OrdinalIgnoreCase)))
        {
            var isNumeric = int.TryParse(rightString, out int n);
            if (!isNumeric)
            {
                File.AppendAllText(errFilePath, "Right Value is Invalid for Left Value in Row :" + rowNumber + "; Right Value : " + rightString + " Left Value : " + leftString.ToString() + "\n");
            }
        }
      
        return rule;
    }

    private string IsRulePropertyExists(string ruleProperty)
    {
        var lstRuleProperty = ValidateRulePropertyManager.RulePropertyMappings.FindAll(keyword => keyword.RuleMappings.Any(a => (a.RuleProperty.ToLower() == ruleProperty.ToLower() || a.MappedToRuleProperty.ToLower() == ruleProperty.ToLower()))).FirstOrDefault();
        if (lstRuleProperty != null && lstRuleProperty.RuleMappings != null && lstRuleProperty.RuleMappings.Count > 0)
        {
            foreach (var rule in lstRuleProperty.RuleMappings)
            {
                if (rule.RuleProperty.Equals(rule.MappedToRuleProperty, StringComparison.OrdinalIgnoreCase) && rule.RuleProperty.Equals(ruleProperty, StringComparison.OrdinalIgnoreCase))
                {
                    return "No Error";          
                }
                if (!rule.RuleProperty.Equals(rule.MappedToRuleProperty, StringComparison.OrdinalIgnoreCase) && rule.MappedToRuleProperty.Equals(ruleProperty, StringComparison.OrdinalIgnoreCase))
                {
                    return "No Error";
                }
            }
            return "Error in RuleProperty"  ;
        }
        return "RuleProperty not configured";
    }

    public RuleExpressions BuildExpression_Operator(string expresssion)
    {
        List<string> expressionList_CompareA = System.Text.RegularExpressions.Regex.Split(expresssion, "greaterthanorequal|>=", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareB = System.Text.RegularExpressions.Regex.Split(expresssion, "lessthanorequal|<=", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareC = System.Text.RegularExpressions.Regex.Split(expresssion, "notequal|!=", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareD = System.Text.RegularExpressions.Regex.Split(expresssion, "equal|=", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareE = System.Text.RegularExpressions.Regex.Split(expresssion, "greaterthan|>", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();
        List<string> expressionList_CompareF = System.Text.RegularExpressions.Regex.Split(expresssion, "lessthan|<", System.Text.RegularExpressions.RegexOptions.IgnoreCase).ToList();

        string operatorString = string.Empty;

        string leftString = string.Empty;
        int leftInt = 0;
        double leftDouble = 0.0;

        string rightString = string.Empty;
        int rightInt = 0;
        double rightDouble = 0.0;

        if (expressionList_CompareA.Count > 1)
        {
            leftString = expressionList_CompareA[0].Replace("\"", string.Empty).Trim();
            rightString = expressionList_CompareA[1].Replace("\"", string.Empty).Trim();
            operatorString = "GreaterThanOrEqual";
        }
        else if (expressionList_CompareB.Count > 1)
        {
            leftString = expressionList_CompareB[0].Replace("\"", string.Empty).Trim();
            rightString = expressionList_CompareB[1].Replace("\"", string.Empty).Trim();
            operatorString = "LessThanOrEqual";
        }
        else if (expressionList_CompareC.Count > 1)
        {
            leftString = expressionList_CompareC[0].Replace("\"", string.Empty).Trim();
            rightString = expressionList_CompareC[1].Replace("\"", string.Empty).Trim();
            operatorString = "NotEqual";
        }
        else
        {
            if (expresssion.Contains("=>"))
            {
                throw new Exception("Invalid Operator =>");
            }
            else if (expresssion.Contains("=<"))
            {
                throw new Exception("Invalid Operator =<");
            }
            else if (expresssion.Contains("=!"))
            {
                throw new Exception("Invalid Operator =!");
            }
            else if (expressionList_CompareD.Count > 1)
            {
                leftString = expressionList_CompareD[0].Replace("\"", string.Empty).Trim();
                rightString = expressionList_CompareD[1].Replace("\"", string.Empty).Trim();
                operatorString = "Equal";
            }
            else if (expressionList_CompareE.Count > 1)
            {
                leftString = expressionList_CompareE[0].Replace("\"", string.Empty).Trim();
                rightString = expressionList_CompareE[1].Replace("\"", string.Empty).Trim();
                operatorString = "GreaterThan";
            }
            else if (expressionList_CompareF.Count > 1)
            {
                leftString = expressionList_CompareF[0].Replace("\"", string.Empty).Trim();
                rightString = expressionList_CompareF[1].Replace("\"", string.Empty).Trim();
                operatorString = "LessThan";
            }               
        }

        RuleExpressions rule = new RuleExpressions()
        {
            Operator = operatorString
        };

        if (int.TryParse(leftString, out leftInt))
        {
            rule.Left = leftInt;
        }
        else if (double.TryParse(leftString, out leftDouble))
        {
            rule.Left = leftDouble;
        }
        else
        {
            rule.Left = leftString;
        }

        if (int.TryParse(rightString, out rightInt))
        {
            rule.Right = rightInt;
        }
        else if (double.TryParse(rightString, out rightDouble))
        {
            rule.Right = rightDouble;
        }
        else
        {
            rule.Right = rightString;
        }

        return rule;
    }
}

public class ChargeSeverityValidation
{
    private int count = 0;
    private CreateRulesRequest rulesRequest = new CreateRulesRequest();
    private readonly CreateRulesRequest rulesRequestMain = new CreateRulesRequest();
    private int countCopyCollection = 0;
    private int countoriginalCollection = 0;

    public CreateRulesRequest ListOfChargeSeverity(CreateRulesRequest createRulesRequest, bool isSimpleExpression)
    {
        rulesRequest.Rules = new List<CreateRuleParameters>();
        var newRuleCollection = new List<CreateRuleParameters>(createRulesRequest.Rules);

        newRuleCollection.ForEach(rules =>
        {
            // Ensure Expressions is populated before traversal
            if (rules.Expressions == null && !string.IsNullOrEmpty(rules.ExpressionString))
            {
                try
                {
                    var tokenizer = new ExpressionTokenizer();
                    var tokens = tokenizer.Tokenize(rules.ExpressionString);
                    var parser = new ExpressionParser(tokens);
                    rules.Expressions = parser.Parse();
                }
                catch
                {
                    return; // skip if parsing fails
                }
            }

            TraverseJsonElement(rules.Expressions, rules, createRulesRequest);
            count++;
        });

        createRulesRequest.Rules.AddRange(rulesRequest.Rules);
        return createRulesRequest;
    }

    private void TraverseJsonElement(RuleExpressions root, CreateRuleParameters createRuleParameters, CreateRulesRequest createRulesRequest)
    {
        if (root == null || string.IsNullOrEmpty(root.Operator))
        {
            return; // nothing to traverse
        }

        // Handle Left
        if (root.Left is JsonElement leftElement)
        {
            if (leftElement.ValueKind != JsonValueKind.String && leftElement.ValueKind != JsonValueKind.Number)
            {
                var leftExpr = JsonSerializer.Deserialize<RuleExpressions>(leftElement.GetRawText(),
                    new JsonSerializerOptions { MaxDepth = 256 });
                TraverseJsonElement(leftExpr, createRuleParameters, createRulesRequest);
            }
        }
        else if (root.Left is RuleExpressions leftExpr)
        {
            TraverseJsonElement(leftExpr, createRuleParameters, createRulesRequest);
        }

        // Handle Right
        if (root.Right is JsonElement rightElement)
        {
            if (rightElement.ValueKind != JsonValueKind.String && rightElement.ValueKind != JsonValueKind.Number)
            {
                var rightExpr = JsonSerializer.Deserialize<RuleExpressions>(rightElement.GetRawText(),
                    new JsonSerializerOptions { MaxDepth = 256 });
                TraverseJsonElement(rightExpr, createRuleParameters, createRulesRequest);
            }
        }
        else if (root.Right is RuleExpressions rightExpr)
        {
            TraverseJsonElement(rightExpr, createRuleParameters, createRulesRequest);
        }
    }

    private RuleExpressions ReplaceChargeSeverity(RuleExpressions root, string chargeLevelMapping, string chargeDetails)
    {
        if (root == null || string.IsNullOrEmpty(root.Operator))
        {
            return root;
        }

        RuleExpressions clone = (RuleExpressions)root.Clone();

        // Handle Left
        if (clone.Left is JsonElement leftElement)
        {
            if (leftElement.ValueKind != JsonValueKind.String)
            {
                string raw = leftElement.GetRawText();
                if (raw.Contains("ChargeSeverity"))
                {
                    clone.Left = JsonSerializer.Deserialize<JsonElement>(
                        raw.Replace(chargeDetails, chargeLevelMapping),
                        new JsonSerializerOptions { MaxDepth = 512 });
                }
            }
        }
        else if (clone.Left is RuleExpressions leftExpr)
        {
            clone.Left = ReplaceChargeSeverity(leftExpr, chargeLevelMapping, chargeDetails);
        }

        // Handle Right
        if (clone.Right is JsonElement rightElement)
        {
            if (rightElement.ValueKind != JsonValueKind.String)
            {
                string raw = rightElement.GetRawText();
                if (raw.Contains("ChargeSeverity"))
                {
                    clone.Right = JsonSerializer.Deserialize<JsonElement>(
                        raw.Replace(chargeDetails, chargeLevelMapping),
                        new JsonSerializerOptions { MaxDepth = 512 });
                }
            }
        }
        else if (clone.Right is RuleExpressions rightExpr)
        {
            clone.Right = ReplaceChargeSeverity(rightExpr, chargeLevelMapping, chargeDetails);
        }

        return clone;
    }

    private CreateRulesRequest GetChargeSeverityDetails(string chargeDetails, CreateRuleParameters createRuleParameters, CreateRulesRequest createRulesRequest)
    {
        List<CreateRuleParameters> collectionRuleParamters = new List<CreateRuleParameters>();
        var mapping = GetChargeMapping(chargeDetails);

        if (mapping.Count > 0)
        {
            countCopyCollection++;
            for (int i = 0; i < mapping.Count; i++)
            {
                CreateRuleParameters lstRuleParameters = new CreateRuleParameters
                {
                    Expressions = ReplaceChargeSeverity(createRuleParameters.Expressions, mapping[i].Description, chargeDetails),
                    ExpressionString = createRuleParameters.ExpressionString.Replace(chargeDetails, mapping[i].Description),
                    RuleType = createRuleParameters.RuleType,
                    Reportable = createRuleParameters.Reportable,
                    Score = createRuleParameters.Score,
                    Id = createRuleParameters.Id,
                    Points = createRuleParameters.Points,
                    Label = createRuleParameters.Label,
                    LabelRank = createRuleParameters.LabelRank,
                    Frequency = createRuleParameters.Frequency,
                    Country = createRuleParameters.Country,
                    State = createRuleParameters.State
                };
                collectionRuleParamters.Add(lstRuleParameters);
            }

            rulesRequest.Rules.AddRange(collectionRuleParamters);
        }
        else
        {
            countoriginalCollection++;
            countCopyCollection++;
        }

        return rulesRequest;
    }

    private static void NullCheck(RuleExpressions expressions)
    {
        if (expressions == null)
        {
            throw new ApplicationException("Expression object is null");
        }
        if (expressions.Left == null)
        {
            throw new ApplicationException("Left Value null");
        }
        if (expressions.Right == null)
        {
            throw new ApplicationException("Right Value null is Invalid");
        }
    }

    public List<ChargeLevelMapping> GetChargeMapping(string chargeLevelMappingName)
    {
        return ChargeLevelMappingManager.Mappings.FindAll(m =>
            m.Mapping.Equals(chargeLevelMappingName, StringComparison.OrdinalIgnoreCase));
    }
}

public class ChargeLevelMapping
{
    [BsonElement("_id")]
    public ObjectId Id { get; set; }

    [BsonElement("charge_level_code")]
    public string Code { get; set; }

    [BsonElement("charge_level_mapping")]
    public string Mapping { get; set; }

    [BsonElement("charge_level_description")]
    public string Description { get; set; }
}

public class ChargeLevelMappingManager
{
    public static List<ChargeLevelMapping> Mappings { get; private set; }
    private static MongoDb db;
    public static void Load(int index)
    {
        if (db != null)
        {
            return;
        }
        db = new MongoDb();
        db.Init(index);

        Mappings = db.ChargeLevelMappings.Find(_ => true).ToList();
    }

    public static string GetExistingModelId(string hashCode)
    {
        var existing = db.Rules.Find(r => r.HashCode == hashCode).FirstOrDefault();
        return existing?.ModelId;
    }

    public static void Save(List<RuleModel> rules)
    {
        BsonDefaults.MaxSerializationDepth = 512;
        db.Rules.InsertMany(rules);
    }        
}    

public class RulePayLoadValidationForErrorFile
{       
    public static bool IsValid_Validate(CreateRuleParameters rule, string errFilePath, int rowNumber)
    {
        if (rule.Expressions.Operator == null || rule.RuleType == null)
        {               
            File.AppendAllText(errFilePath, "Rule Type Parameter value in " + rowNumber + "\n");
            return false;
        }
        if (!IsValidRuleType_Validate(rule.RuleType))
        {
            File.AppendAllText(errFilePath, string.Format(" Rule Type {0}", rule.RuleType) + rowNumber + "\n");
            return false;
        }
        NullValueCheck_Validate(rule.Expressions, errFilePath, rowNumber);
       
        var leftElement = rule.Expressions.Left;
        var rightElement = rule.Expressions.Right;
         
            string leftvalue = leftElement.ToString();
            
            string rightvalue = rightElement.ToString();
            LeftRightCheck_Validate(leftvalue, rightvalue, errFilePath, rowNumber);
            if ((leftvalue.EndsWith("Age") || leftvalue == "ConfinementDuration"))
            {
                var isNumeric = int.TryParse(rightvalue.ToString(), out int n);
                if (!isNumeric)
                {
                    File.AppendAllText(errFilePath, string.Format(" Right Value is Invalid for Left Value {0} in ", leftvalue.ToString()) + rowNumber + "\n");
                    return false;
                }                    
            }
        return true;
    }

    private static void Preoder_Validate(RuleExpressions Root, string errFilePath, int rowNumber)
    {
        if (Root.Operator != null)
        {
            var leftElement = (JsonElement)Root.Left;
            var rightElement = (JsonElement)Root.Right;
            RuleExpressions leftExpr = new RuleExpressions(), rightExpr = new RuleExpressions();
            if (leftElement.ValueKind != JsonValueKind.String)
            {
                if (Root.Operator == null)
                {                        
                    File.AppendAllText(errFilePath, "Logical Operator Value null in " + rowNumber + "\n"); 
                }
                if (!Enum.IsDefined(typeof(LogicalOperator), Root.Operator))
                {
                    File.AppendAllText(errFilePath, string.Format("Logical Operator {0}", Root.Operator.ToString()) + rowNumber + "\n");
                }
                string leftvalue = string.Empty, rightvalue;
                leftExpr = JsonSerializer.Deserialize<RuleExpressions>(leftElement.GetRawText(), new JsonSerializerOptions
                {
                    MaxDepth = 512
                });
                NullValueCheck_Validate(leftExpr, errFilePath, rowNumber);
                var LeftleftElement = (JsonElement)leftExpr.Left;
                var LeftrightElement = (JsonElement)leftExpr.Right;
                if (LeftleftElement.ValueKind == JsonValueKind.String)
                {
                    if (leftExpr.Operator == null)
                    {
                        File.AppendAllText(errFilePath, "Relational Operator Value null in " + rowNumber + "\n");
                    }
                    if (!Enum.IsDefined(typeof(RelationalOperator), leftExpr.Operator))
                    {
                        File.AppendAllText(errFilePath, string.Format("Relational Operator {0}", leftExpr.Operator.ToString()) + rowNumber + "\n");
                    }
                    leftvalue = leftExpr.Left.ToString();                       
                }
                if (LeftrightElement.ValueKind == JsonValueKind.String || LeftrightElement.ValueKind == JsonValueKind.Number)
                {
                    rightvalue = leftExpr.Right.ToString();
                    LeftRightCheck_Validate(leftvalue, rightvalue, errFilePath, rowNumber);
                    if ((leftvalue.EndsWith("Age") || leftvalue == "ConfinementDuration") && (LeftrightElement.ValueKind != JsonValueKind.Number))
                    {
                        File.AppendAllText(errFilePath, "Right Value null is Invalid for Left Value {0} in Row :" + rowNumber + "Value : " + leftvalue.ToString() + "\n");
                    }                        
                }
            }
            if (rightElement.ValueKind != JsonValueKind.String && rightElement.ValueKind != JsonValueKind.Number)
            {
                if (Root.Operator == null)
                {
                    File.AppendAllText(errFilePath, "Logical Operator Value null in " + rowNumber + "\n");
                }
                if (!Enum.IsDefined(typeof(LogicalOperator), Root.Operator))
                {
                    File.AppendAllText(errFilePath, string.Format("Logical Operator {0}", Root.Operator.ToString()) + rowNumber + "\n");
                }
                string leftvalue = string.Empty, rightvalue;
                rightExpr = JsonSerializer.Deserialize<RuleExpressions>(rightElement.GetRawText(), new JsonSerializerOptions
                {
                    MaxDepth = 512
                });
                NullValueCheck_Validate(rightExpr, errFilePath, rowNumber);

                var rightleftElement = (JsonElement)rightExpr.Left;
                var rightrightElement = (JsonElement)rightExpr.Right;
                if (rightleftElement.ValueKind == JsonValueKind.String)
                {
                    if (rightExpr.Operator == null)
                    {
                        File.AppendAllText(errFilePath, "Relational Operator Value null " + rowNumber + "\n");
                    }
                    if (!Enum.IsDefined(typeof(RelationalOperator), rightExpr.Operator))
                    {
                        File.AppendAllText(errFilePath, string.Format("Relational Operator {0}", rightExpr.Operator.ToString()) + rowNumber + "\n");
                    }
                    leftvalue = rightExpr.Left.ToString();
                }

                if (rightrightElement.ValueKind == JsonValueKind.String)
                {
                    rightvalue = rightExpr.Right.ToString();
                    LeftRightCheck_Validate(leftvalue, rightvalue, errFilePath, rowNumber);
                    if ((leftvalue.EndsWith("Age") || leftvalue == "ConfinementDuration") && (rightrightElement.ValueKind != JsonValueKind.Number))
                    {
                        File.AppendAllText(errFilePath, string.Format("Right Value is Invalid for Left Value {0}", leftvalue.ToString()) + rowNumber + "\n");
                    }
                }
            }
            Preoder_Validate(leftExpr, errFilePath, rowNumber);
            Preoder_Validate(rightExpr, errFilePath, rowNumber);
        }            
    }

    private static bool IsValidRuleType_Validate(string ruleType)
    {
        var ruleTypeValue = new[] { "FCRA", "CLIENT", "ADJUDICATION", "FCRA-MISC", "DOJSON" };
        if (ruleTypeValue.Contains(ruleType, StringComparer.OrdinalIgnoreCase))
        {
            return true;
        }
        return false;
    }        

    private static void NullValueCheck_Validate(RuleExpressions expressions, string errFilePath, int rowNumber)
    {
        if (expressions.Right == null)
        {                
            File.AppendAllText(errFilePath, "Right Value null is Invalid in Row :" + rowNumber + "\n");
        }
    }

    public static void LeftRightCheck_Validate(string leftvalue, string rightvalue, string errFilePath, int rowNumber)
    {
        if (leftvalue.EndsWith("Outcome", StringComparison.OrdinalIgnoreCase)  && (!CompareOutcomeValue(rightvalue)))
        {                
            File.AppendAllText(errFilePath, "Right Value is Invalid for Left Value in Row : " + rowNumber + " Right Value : " + rightvalue + "; Left Value : " + leftvalue + "\n");
        }
        if ((leftvalue.Equals("calculated_parameter_list[WarrantStatus]", StringComparison.OrdinalIgnoreCase) || leftvalue.Equals("calculated_parameter_list[ProbationStatus]", StringComparison.OrdinalIgnoreCase)) && (!Enum.IsDefined(typeof(ChargeStatus), rightvalue)))
        {
            File.AppendAllText(errFilePath, "Right Value is Invalid for Left Value in Row : " + rowNumber + " Right Value : " + rightvalue + "; Left Value : " + leftvalue + "\n");
        }
        if (leftvalue.Equals("ChargeSeverity", StringComparison.OrdinalIgnoreCase))
        {
            if (IsChargeMappingExists(rightvalue) == false)
                File.AppendAllText(errFilePath, "Right Value is Invalid for Left Value in Row : " + rowNumber + " Right Value : " + rightvalue + "; Left Value : " + leftvalue + "\n");
        }
    } 

    public static bool IsChargeMappingExists(string chargeLevelDescription)
    {
        return ChargeLevelMappingManager.Mappings.Exists(m =>
            m.Description.Equals(chargeLevelDescription, StringComparison.CurrentCultureIgnoreCase));
    }

    enum RelationalOperator
    {
        Equal,
        NotEqual,
        GreaterThan,
        LessThan,
        LessThanOrEqual,
        GreaterThanOrEqual,
    }

    enum LogicalOperator
    {
        And,
        Or,
    }        

    enum ChargeOutcome
    {
        Active,
        Conviction,
        AlternateDisposition,
        NonConviction,
        Unknown
    }
    enum ChargeStatus
    {
        Active,
        InActive,
        Unknown
    }

    private static bool CompareOutcomeValue(string rightValue)
    {
        var outcomeValues = new[] { "Active", "Conviction", "Alternate Disposition", "Non-Conviction", "Unknown" };
        if (outcomeValues.Contains(rightValue, StringComparer.OrdinalIgnoreCase))
        {
            return true;
        }
        return false;
    }
}
public class RulePayLoadValidation
{
    public static bool IsValid(CreateRuleParameters rule)
    {
        if (rule.Expressions == null || rule.Expressions.Operator == null || rule.RuleType == null)
        {
            throw new ApplicationException("Rule Type Parameter value");
        }

        if (!IsValidRuleType(rule.RuleType))
        {
            throw new ApplicationException($"Rule Type {rule.RuleType}");
        }

        NullValueCheck(rule.Expressions);

        // Handle left operand
        if (rule.Expressions.Left is RuleExpressions leftExpr)
        {
            // recurse into nested expression
            Preoder(leftExpr);
        }
        else if (rule.Expressions.Left is string leftValue)
        {
            ValidateOperand(leftValue, rule.Expressions.Operator, rule.Expressions.Right);
        }

        // Handle right operand
        if (rule.Expressions.Right is RuleExpressions rightExpr)
        {
            Preoder(rightExpr);
        }
        else if (rule.Expressions.Right is string rightValue)
        {
            // you can validate right side if needed
        }

        return true;
    }

    private static void ValidateOperand(string leftValue, string op, object right)
    {
        if (string.IsNullOrEmpty(op))
            throw new ApplicationException("Relational Operator Value null");

        if (!Enum.IsDefined(typeof(RelationalOperator), op))
            throw new ApplicationException($"Relational operator {op} is invalid");

        string rightValue = right?.ToString();

        LeftRightCheck(leftValue, rightValue);

        if ((leftValue.EndsWith("Age") || leftValue == "ConfinementDuration" || leftValue == "Salary")
            && !double.TryParse(rightValue, out _))
        {
            throw new ApplicationException($"Right Value is Invalid for Left Value {leftValue}");
        }
    }


   private static void Preoder(RuleExpressions root)
    {
        if (root == null) return;

        if (string.IsNullOrEmpty(root.Operator))
            throw new ApplicationException("Operator is null");

        // If Left/Right are nested expressions, this is a logical operator
        if (root.Left is RuleExpressions || root.Right is RuleExpressions)
        {
            if (!Enum.IsDefined(typeof(LogicalOperator), root.Operator))
                throw new ApplicationException($"Logical Operator {root.Operator} is invalid");

            if (root.Left is RuleExpressions leftExpr) Preoder(leftExpr);
            if (root.Right is RuleExpressions rightExpr) Preoder(rightExpr);
        }
        else
        {
            // Otherwise, this is a relational operator
            if (!Enum.IsDefined(typeof(RelationalOperator), root.Operator))
                throw new ApplicationException($"Relational Operator {root.Operator} is invalid");

            string leftValue = root.Left?.ToString();
            string rightValue = root.Right?.ToString();

            LeftRightCheck(leftValue, rightValue);

            if ((leftValue?.EndsWith("Age") == true || leftValue == "ConfinementDuration")
                && !double.TryParse(rightValue, out _))
            {
                throw new ApplicationException($"Right Value is Invalid for Left Value {leftValue}");
            }
        }
    }

    private static void LeftRightCheck(string leftvalue, string rightvalue)
    {
        string rightvalueException = string.Format("Right Value is Invalid for Left Value {0}", leftvalue.ToString());

        if (leftvalue == "ChargeOutcome" && (!CompareOutcomeValue(rightvalue)))
        {
            throw new ApplicationException(rightvalueException);
        }
        if ((leftvalue == "WarrantStatus" || leftvalue == "ProbationStatus") && (!Enum.IsDefined(typeof(ChargeStatus), rightvalue)))
        {
            throw new ApplicationException(rightvalueException);
        }
        if ((leftvalue == "ChargeSeverity"))
        {
            if (IsChargeMappingExists(rightvalue) == false)
                throw new ApplicationException(rightvalueException);
        }
    }

    public static bool IsChargeMappingExists(string chargeLevelDescription)
    {
        return ChargeLevelMappingManager.Mappings.Exists(m =>
            m.Description.Equals(chargeLevelDescription, StringComparison.CurrentCultureIgnoreCase));
    }

    private static void NullValueCheck(RuleExpressions expressions)
    {
        if (expressions.Right == null)
        {
            throw new ApplicationException("Right Value null is Invalid");
        }
    }

    private static bool CompareOutcomeValue(string rightValue)
    {
        var outcomeValues = new[] { "Active", "Conviction", "Alternate Disposition", "Non-Conviction", "Unknown" };
        if (outcomeValues.Contains(rightValue, StringComparer.OrdinalIgnoreCase))
        {
            return true;
        }
        return false;
    }

    private static bool IsValidRuleType(string ruleType)
    {
        var ruleTypeValue = new[] { "FCRA", "CLIENT", "ADJUDICATION", "FCRA-MISC", "DOJSON","MVR" };
        if (ruleTypeValue.Contains(ruleType, StringComparer.OrdinalIgnoreCase))
        {
            return true;
        }
        return false;
    }

    enum LogicalOperator
    {
        And,
        Or,
    }
    enum RelationalOperator
    {
        Equal,
        NotEqual,
        GreaterThan,
        LessThan,
        LessThanOrEqual,
        GreaterThanOrEqual,
    }

    enum ChargeOutcome
    {
        Active,
        Conviction,
        AlternateDisposition,
        NonConviction,
        Unknown
    }
    enum ChargeStatus
    {
        Active,
        InActive,
        Unknown
    }
}

public class RuleComparer : IComparer<CreateRuleParameters>
{
    public int Compare(CreateRuleParameters x, CreateRuleParameters y)
    {
        if (x == null) throw new ArgumentNullException(nameof(x));
        if (y == null) throw new ArgumentNullException(nameof(y));

        if (x.Frequency.CompareTo(y.Frequency) != 0)
        {
            return x.Frequency.CompareTo(y.Frequency);
        }

        if (x.Points.CompareTo(y.Points) != 0)
        {
            return x.Points.CompareTo(y.Points);
        }

        if (String.Compare(x.Country, y.Country, StringComparison.Ordinal) != 0)
        {
            return String.Compare(x.Country, y.Country, StringComparison.Ordinal);
        }

        var expressionResult = CompareExpressionsTo(x.Expressions, y.Expressions);
        if (expressionResult != 0)
        {
            return expressionResult;
        }

        if (String.Compare(x.Label, y.Label, StringComparison.Ordinal) != 0)
        {
            return String.Compare(x.Label, y.Label, StringComparison.Ordinal);
        }

        if (x.LabelRank.CompareTo(y.LabelRank) != 0)
        {
            return x.LabelRank.CompareTo(y.LabelRank);
        }

        if (x.Reportable.CompareTo(y.Reportable) != 0)
        {
            return x.Reportable.CompareTo(y.Reportable);
        }

        if (String.Compare(x.Score, y.Score, StringComparison.Ordinal) != 0)
        {
            return String.Compare(x.Score, y.Score, StringComparison.Ordinal);
        }

        if (String.Compare(x.State, y.State, StringComparison.Ordinal) != 0)
        {
            return String.Compare(x.State, y.State, StringComparison.Ordinal);
        }

        if (String.Compare(x.RuleType, y.RuleType, StringComparison.Ordinal) != 0)
        {
            return String.Compare(x.RuleType, y.RuleType, StringComparison.Ordinal);
        }

        return 0;
    }

    private int CompareExpressionsTo(RuleExpressions x, RuleExpressions y)
    {
        if (x == null && y == null) return 0;
        if (x == null) return -1;
        if (y == null) return 1;

        // Compare operator strings first
        int opCompare = string.Compare(x.Operator, y.Operator, StringComparison.Ordinal);
        if (opCompare != 0) return opCompare;

        // Compare left side
        int leftCompare = CompareOperand(x.Left, y.Left);
        if (leftCompare != 0) return leftCompare;

        // Compare right side
        return CompareOperand(x.Right, y.Right);
    }

    private int CompareOperand(object left, object right)
    {
        if (left == null && right == null) return 0;
        if (left == null) return -1;
        if (right == null) return 1;

        // If both are nested RuleExpressions, recurse
        if (left is RuleExpressions lx && right is RuleExpressions rx)
        {
            return CompareExpressionsTo(lx, rx);
        }

        // Otherwise, compare as strings
        string leftStr = left.ToString();
        string rightStr = right.ToString();
        return string.Compare(leftStr, rightStr, StringComparison.Ordinal);
    }

}

public static class HashCodeGenerate
{
    public static string ToSha512(List<CreateRuleParameters> ruleParameter)
    {

        string serializeData = JsonSerializer.Serialize(ruleParameter);
        SHA512 shaM = new SHA512Managed();
        return Convert.ToBase64String(shaM.ComputeHash(Encoding.UTF8.GetBytes(serializeData.ToUpper())));
    }
}

[BsonIgnoreExtraElements]
public class RuleModel
{
    [BsonElement("_id")]
    public string Id { get; set; }

    [BsonElement("model_id")]
    public string ModelId { get; set; }

    [BsonElement("hash_code")]
    public string HashCode { get; set; }

    [BsonElement("state")]
    public string State { get; set; }

    [BsonElement("country")]
    public string Country { get; set; }

    [BsonElement("type")]
    public string RuleType { get; set; }

    [BsonElement("reportable")]
    public bool Reportable { get; set; }

    [BsonElement("score")]
    public string Score { get; set; }

    [BsonElement("points")]
    public double Points { get; set; }

    [BsonElement("frequency")]
    public int Frequency { get; set; }

    [BsonElement("label")]
    public string Label { get; set; }

    [BsonElement("label_rank")]
    public int LabelRank { get; set; }


    [BsonElement("expressions")]
    [JsonPropertyName("expressions")]
    public BsonDocument Expressions { get; set; }
    //public BsonDocument Expressions { get; set; }
    // [BsonIgnore]
    [BsonElement("complexExpressions")]
    public string ExpressionString { get; set; }
}

}

About

Web Scraper via google of linkedin profiles as a tool for recruiters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 100.0%