public static RulEngStore ProcessAllOperationsReducer(RulEngStore previousState, IOpReqProcessing prescription) { // Set up a temporary 'Processing' copy of the Store as our Unit of Work var newState = previousState.DeepClone(); newState = newState.AllOperations(previousState, prescription); return(newState.DeepClone()); }
public static RulEngStore DeepClone(this ProcessingRulEngStore rvStore) { var newStore = new RulEngStore { Rules = rvStore.Rules.ToImmutableHashSet(), RuleResults = rvStore.RuleResults.ToImmutableHashSet(), Operations = rvStore.Operations.ToImmutableHashSet(), Requests = rvStore.Requests.ToImmutableHashSet(), Values = rvStore.Values.ToImmutableHashSet() }; return(newStore); }
public static ProcessingRulEngStore DeepClone(this RulEngStore rvStore) { var newStore = new ProcessingRulEngStore { Rules = rvStore?.Rules.ToHashSet(), RuleResults = rvStore?.RuleResults.ToHashSet(), Operations = rvStore?.Operations.ToHashSet(), Requests = rvStore?.Requests.ToHashSet(), Values = rvStore?.Values.ToHashSet() }; return(newStore); }
public static RulEngStore ProcessAllRulesReducer(RulEngStore previousState, IRuleProcessing prescription) { // Set up a temporary 'Processing' copy of the Store as our Unit of Work var newState = previousState.DeepClone(); // First identify rules for entities that may (not) exist newState = newState.AllExists(prescription as ProcessExistsRule); // Then test for meaningful Values newState = newState.AllHasMeaningfulValue(prescription as ProcessHasMeaningfulValueRule); newState = newState.AllCompare(prescription as ProcessLessThanRule, RuleType.LessThan); newState = newState.AllCompare(prescription as ProcessEqualRule, RuleType.Equal); newState = newState.AllCompare(prescription as ProcessGreaterThanRule, RuleType.GreaterThan); newState = newState.AllCompare(prescription as ProcessRegexMatchRule, RuleType.RegularExpression); newState = newState.AllCollection(prescription as ProcessAndRule, RuleType.And); newState = newState.AllCollection(prescription as ProcessOrRule, RuleType.Or); newState = newState.AllCollection(prescription as ProcessXorRule, RuleType.Xor); return(newState.DeepClone()); }
public static RulEngStore ReduceStore(RulEngStore state, IAction action) { if (state == null) { state = new RulEngStore(); } // This is where CQRS like behaviour hits // Actions that only target one of the 'tables' (e.g. CRUD) are passed the specific item impacted // Actions that target multiple 'tables' (e.g. Processing) are passed the whole structure if (action is ICrud crud) { return(new RulEngStore { Rules = CrudReducers.CrudReducer(state.Rules, crud), Operations = CrudReducers.CrudReducer(state.Operations, crud), Requests = CrudReducers.CrudReducer(state.Requests, crud), Values = CrudReducers.CrudReducer(state.Values, crud) }); } if (!(action is IProcessing)) { return(state); } switch (action) { case IRuleProcessing rulesAction: return(ProcessingReducers.ProcessAllRulesReducer(state, rulesAction)); case IOpReqProcessing opReqAction: return(ProcessingReducers.ProcessAllOperationsReducer(state, opReqAction)); } return(state); }
private static ProcessingRulEngStore AllOperations(this ProcessingRulEngStore newState, RulEngStore previousState, IOpReqProcessing prescription) { // First identify RuleResults that have just been processed successfully var ruleResultIds = newState.RuleResults .Where(v => v.Detail) .Select(v => v.RuleResultId) .ToList(); var opType = prescription is OperationMxProcessing ? OperationType.CreateUpdate : prescription is OperationDxProcessing ? OperationType.Delete : prescription is OperationSxProcessing ? OperationType.Search : OperationType.Unknown; // When the prescription is for requests // Find all relevant operations/requests for the identified rule results and then filter down by execution date var possibleOps = newState.Operations .Where(a => ruleResultIds.Contains(a.RuleResultId) && a.OperationType == opType).ToList(); var operationprescriptionsToProcessList = ( from op in possibleOps let rr = newState.RuleResults.First(r => r.RuleResultId == op.RuleResultId) where rr.LastChanged > op.LastExecuted select op) .ToList(); var requestprescriptionsToProcessList = ( from rq in newState.Requests.Where(a => ruleResultIds.Contains(a.RuleResultId)) let rr = newState.RuleResults.First(r => r.RuleResultId == rq.RuleResultId) where rr.LastChanged > rq.LastExecuted select rq) .ToList(); // Restrict the Operation/Request Prescriptions to process to those that are guaranteed not to fail // Select those for which there is no conflict var destinationEntities = new List <TypeKey>(); foreach (var opPre in operationprescriptionsToProcessList) { destinationEntities.AddRange(opPre.Operands.Select(o => (TypeKey)o)); } foreach (var rqPre in requestprescriptionsToProcessList) { destinationEntities.Add(rqPre); } var groupedDestinations = destinationEntities .GroupBy(de => new EntMatch { EntityId = de.EntityId, EntType = de.EntType }) .Select(grp => new { grp.Key, Count = grp.Count() }) .ToList(); //var conflictDestinations = groupedDestinations // .Where(grp => grp.Count > 1) // .Select(grp => new TypeKey { EntityId = grp.Key.EntityId, EntTags = grp.Key.EntTags, EntType = grp.Key.EntType }) // .ToList(); var acceptableDestinations = groupedDestinations .Where(grp => grp.Count == 1) .Select(grp => grp.Key) .ToList(); if (prescription is OperationMxProcessing) { newState = OperationMxProcessing(newState, previousState, ruleResultIds, operationprescriptionsToProcessList, acceptableDestinations); } if (prescription is OperationDxProcessing) { newState = OperationDxProcessing(newState, ruleResultIds, operationprescriptionsToProcessList, acceptableDestinations); } if (prescription is OperationSxProcessing) { newState = OperationSxProcessing(newState, previousState, ruleResultIds, operationprescriptionsToProcessList, acceptableDestinations); } return(newState); }
private static ProcessingRulEngStore OperationSxProcessing(this ProcessingRulEngStore newState, RulEngStore previousState, List <Guid> ruleResultIds, List <Operation> operationprescriptionsToProcessList, List <EntMatch> acceptableDestinations) { // Get all of the sources from the previous state var acceptableSourceIds = new List <Guid>(); //foreach (var opPresProc in operationprescriptionsToProcessList) //{ // var opPresOperands = opPresProc.Operands; // var matchFound = false; // foreach (var opo in opPresOperands) // { // if (!acceptableDestinations // .Any(ad => ad.EntType == opo.EntType && (ad.EntityId == opo.EntityId || opo.EntityId == Guid.Empty))) // { // continue; // } // matchFound = true; // break; // } // if (!matchFound) // { // continue; // } // acceptableSourceIds.AddRange(opPresOperands.SelectMany(oo => oo.SourceValueIds)); //} //var acceptableSources = previousState.Values // .Where(v => acceptableSourceIds.Contains(v.EntityId)) // .ToList(); var e = new Engine(); foreach (var ruleResultIdToProcess in ruleResultIds) { // Get all of the operations relevant to the Rule var relevantOps = operationprescriptionsToProcessList .Where(o => o.RuleResultId == ruleResultIdToProcess) .ToList(); if (!relevantOps.Any()) { // TODO: confirm if we should be doing this if there was nothing relevant to process //newState.RuleResults.RemoveWhere(r => r.RuleResultId == ruleResultIdToProcess); continue; } // Process the acceptable foreach (var relevantOp in relevantOps) { // Note: A Search operation does not specify an output destination as it does not 'know' in advance how many results will be found // However, it may specify a reduced set of Ids to search through. // These will be provided in the SourceValueIds field of each relevantOp.Operand // var firstEnt = new EntMatch // { // EntityId = relevantOp.Operands[0].EntityId, // EntType = relevantOp.Operands[0].EntType // }; // if (!acceptableDestinations.Any(ad => // ad.EntType == firstEnt.EntType && ad.EntityId == firstEnt.EntityId)) // { // continue; // } // var destEntsToProcess = relevantOp.Operands // .Select(de => new // { // de.EntityId, // EntType = Convert.ToInt32(de.EntType), // sourceValues = de.SourceValueIds // .Select(sv => JObject.Parse($"{{\"Id\":\"{sv}\",\"Value\":{acceptableSources.FirstOrDefault(a => a.EntityId == sv)?.Detail.ToString(Formatting.None)}}}")) // .ToArray() // }) // .ToList(); switch (relevantOp.Operands[0].SourceEntType) { case EntityType.Rule: e.SetValue("source", JsonConvert.SerializeObject(previousState.Rules.ToArray())); break; case EntityType.RuleResult: e.SetValue("source", JsonConvert.SerializeObject(previousState.RuleResults.ToArray())); break; case EntityType.Operation: e.SetValue("source", JsonConvert.SerializeObject(previousState.Operations.ToArray())); break; case EntityType.Request: e.SetValue("source", JsonConvert.SerializeObject(previousState.Requests.ToArray())); break; case EntityType.Value: e.SetValue("source", JsonConvert.SerializeObject(previousState.Values.ToArray())); break; } // The result must be a list of guids for the entities that met the search criteria var result = e .Execute(relevantOp.OperationTemplate) .GetCompletionValue(); var sourceGuids = result.ToString() .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(Guid.Parse) .ToList(); for (var ix = 0; ix < sourceGuids.Count; ix++) { var sourceEnt = new TypeKey { EntityId = sourceGuids[ix], EntType = relevantOp.Operands[0].SourceEntType, EntTags = relevantOp.EntTags } as IEntity; switch (relevantOp.Operands[0].EntType) { case EntityType.Rule: // Create/Update a rule using destEnt.EntityId and result var rl = newState.FromSearchOperationAddUpdateExistsRule(sourceEnt, relevantOp.EntTags, GuidHelpers.NewTimeUuid()); var rr = new RuleResult(rl); newState.RuleResults.Add(rr); break; case EntityType.Operation: // Create/Update an Operation using destEnt.EntityId and result var op = newState.FromSearchOperationAddUpdateOperation(sourceEnt, relevantOp.EntTags, OperationType.Delete, "", GuidHelpers.NewTimeUuid()); //var rr = new RuleResult(rl); //newState.RuleResults.Add(rr); //newState.FromOperationResultAddUpdateOperation(result, destEnt.EntityId); break; //case EntityType.Request: // // Create/Update a Request using destEnt.EntityId and result // newState.FromOperationResultAddUpdateRequest(result, destEnt.EntityId); // break; //case EntityType.Value: // // Create/Update a Value using destEnt.EntityId and result // newState.FromOperationResultAddUpdateValue(result, destEnt.EntityId); // break; // } } //var values = new List<string>(); //for (var i = 0; i < a.GetLength(); i++) //{ // values.Add(a.Get(i.ToString()).AsString()); //} //return values; Console.WriteLine(JsonConvert.SerializeObject(result)); // foreach (var destEnt in destEntsToProcess) // { // var sourceVals = destEnt.sourceValues; // var isSubstOk = true; // foreach (Match match in regexToken.Matches(jTempl)) // { // var token = match.Groups["Token"].Value; // var indexOk = int.TryParse(match.Groups["Index"].Value, out var index); // if (!indexOk) // { // break; // } // if (sourceVals.Length < index) // { // isSubstOk = false; // break; // } // jCode = jCode.Replace(token, sourceVals[index]["Value"].ToString(Formatting.None)); // } // if (!isSubstOk) // { // Console.WriteLine(jCode); // continue; // } // JToken result = null; // if (jCode.StartsWith("{")) // { // result = JObject.FromObject(e.Execute(jCode).GetCompletionValue().ToObject()); // } // if (jCode.StartsWith("[")) // { // result = JArray.FromObject(e.Execute(jCode).GetCompletionValue().ToObject()); // } // //Console.WriteLine(result); // switch ((EntityType)destEnt.EntType) // { // case EntityType.Rule: // // Create/Update a rule using destEnt.EntityId and result // newState.FromOperationResultAddUpdateRule(result, destEnt.EntityId); // break; // case EntityType.Operation: // // Create/Update an Operation using destEnt.EntityId and result // newState.FromOperationResultAddUpdateOperation(result, destEnt.EntityId); // break; // case EntityType.Request: // // Create/Update a Request using destEnt.EntityId and result // newState.FromOperationResultAddUpdateRequest(result, destEnt.EntityId); // break; // case EntityType.Value: // // Create/Update a Value using destEnt.EntityId and result // newState.FromOperationResultAddUpdateValue(result, destEnt.EntityId); // break; // } // // Mark the operation as Executed // var actionDate = DateTime.UtcNow; // // Mark this Rule as executed // relevantOp.LastExecuted = actionDate; // } } // newState.RuleResults.RemoveWhere(r => r.RuleResultId == ruleResultIdToProcess); } } return(newState); }
private static ProcessingRulEngStore OperationMxProcessing(this ProcessingRulEngStore newState, RulEngStore previousState, List <Guid> ruleResultIds, List <Operation> operationprescriptionsToProcessList, List <EntMatch> acceptableDestinations) { // Get all of the sources from the previous state var acceptableSourceIds = new List <Guid>(); foreach (var opPresProc in operationprescriptionsToProcessList) { var opPresOperands = opPresProc.Operands; var matchFound = false; foreach (var opo in opPresOperands) { if (!acceptableDestinations .Any(ad => ad.EntType == opo.EntType && ad.EntityId == opo.EntityId)) { continue; } matchFound = true; break; } if (!matchFound) { continue; } acceptableSourceIds.AddRange(opPresOperands.SelectMany(oo => oo.SourceEntityIds)); } var acceptableSources = previousState.Values .Where(v => acceptableSourceIds.Contains(v.EntityId)) .ToList(); var e = new Engine(); var regexToken = new Regex(@".*?(?<Token>\$\{(?<Index>\d+)\}).*?"); foreach (var ruleResultIdToProcess in ruleResultIds) { // Get all of the operations relevant to the Rule var relevantOps = operationprescriptionsToProcessList .Where(o => o.RuleResultId == ruleResultIdToProcess) .ToList(); if (!relevantOps.Any()) { // TODO: confirm if we should be doing this if there was nothing relevant to process //newState.RuleResults.RemoveWhere(r => r.RuleResultId == ruleResultIdToProcess); continue; } // Process the acceptable foreach (var relevantOp in relevantOps) { // Ensure the first entity in the operation is an acceptable destination EntMatch firstEnt = relevantOp.Operands[0]; if (!acceptableDestinations.Any(ad => ad.EntType == firstEnt.EntType && ad.EntityId == firstEnt.EntityId)) { continue; } var destEntsToProcess = relevantOp.Operands .Select(de => new { de.EntityId, EntType = Convert.ToInt32(de.EntType), sourceValues = de.SourceEntityIds .Select(sv => JObject.Parse($"{{\"Id\":\"{sv}\",\"Value\":{acceptableSources.FirstOrDefault(a => a.EntityId == sv)?.Detail.ToString(Formatting.None)}}}")) .ToArray() }) .ToList(); var jTempl = relevantOp.OperationTemplate; var jCode = jTempl; foreach (var destEnt in destEntsToProcess) { var sourceVals = destEnt.sourceValues; var isSubstOk = true; foreach (Match match in regexToken.Matches(jTempl)) { var token = match.Groups["Token"].Value; var indexOk = int.TryParse(match.Groups["Index"].Value, out var index); if (!indexOk) { break; } if (sourceVals.Length < index) { isSubstOk = false; break; } jCode = jCode.Replace(token, sourceVals[index]["Value"].ToString(Formatting.None)); } if (!isSubstOk) { Console.WriteLine(jCode); continue; } JToken result = null; if (jCode.StartsWith("{")) { result = JObject.FromObject(e.Execute(jCode).GetCompletionValue().ToObject()); } if (jCode.StartsWith("[")) { result = JArray.FromObject(e.Execute(jCode).GetCompletionValue().ToObject()); } //Console.WriteLine(result); switch ((EntityType)destEnt.EntType) { case EntityType.Rule: // Create/Update a rule using destEnt.EntityId and result newState.FromOperationResultAddUpdateRule(result, destEnt.EntityId); break; case EntityType.Operation: // Create/Update an Operation using destEnt.EntityId and result newState.FromOperationResultAddUpdateOperation(result, destEnt.EntityId); break; case EntityType.Request: // Create/Update a Request using destEnt.EntityId and result newState.FromOperationResultAddUpdateRequest(result, destEnt.EntityId); break; case EntityType.Value: // Create/Update a Value using destEnt.EntityId and result newState.FromOperationResultAddUpdateValue(result, destEnt.EntityId); break; } // Mark the operation as Executed var actionDate = DateTime.UtcNow; // Mark this Rule as executed relevantOp.LastExecuted = actionDate; } } // newState.RuleResults.RemoveWhere(r => r.RuleResultId == ruleResultIdToProcess); } return(newState); }
public static void Main() { Console.WriteLine("Hello Salesman!"); // Travelling Salesman - Setup const int cityCount = 50; Console.WriteLine($"Start Setup for {cityCount} cities : {DateTime.UtcNow.ToString("yyyy-MMM-dd HH:mm:ss.ff")}"); (var rules, var ruleResults, var values, var rulePrescriptions) = BuildTheCities(cityCount); // Build a Collection Rule, Result and Prescription for all of the above rules (var collectRule, var collectRuleResult, var collectRulePrescription) = ruleResults.And(); // Add the Collection Rule, Result and Prescription rules.Add(collectRule); ruleResults.Add(collectRuleResult); rulePrescriptions.Add(collectRulePrescription); var operations = new List <Operation>(); var operationPrescriptions = new List <OperationMxProcessing>(); var cityValues = values.Where(c => c.Detail["properties"]["cityNo"] != null).ToList(); (var pointOperations, var pointOperationPrescriptions) = BuildTheGeoJsonOutput(collectRuleResult, cityValues); operations.Add(pointOperations); operationPrescriptions.Add(pointOperationPrescriptions); (var distOperations, var distOperationPrescriptions) = BuildTheCityDistances(collectRuleResult, cityValues); operations.AddRange(distOperations); operationPrescriptions.AddRange(distOperationPrescriptions); // Build the Rule Engine Store ready for processing var startingStore = new RulEngStore { Rules = rules.ToImmutableHashSet(), RuleResults = ruleResults.ToImmutableHashSet(), Operations = operations.ToImmutableHashSet(), Values = values.ToImmutableHashSet() }; RvStore = new Store <RulEngStore>(StoreReducers.ReduceStore, startingStore); File.WriteAllText("storeStart.json", RvStore.GetState().ToString()); // Commence Processing RulEngStore changes; RvStore.Subscribe(state => changes = state); var pass = 0; TikTok(pass++, rulePrescriptions, operationPrescriptions); Console.WriteLine($"Add more to Store for {cityCount} cities : {DateTime.UtcNow.ToString("yyyy-MMM-dd HH:mm:ss.ff")}"); // Build the exists rules for the values resulting from the distance operations (var distRules, var distRuleResults, var distRulePrescriptions) = distOperations.SelectMany(dp => dp.Operands).Exists(false); // Build the operations to convert the distance results to roads var storeValues = RvStore.GetState().Values.ToList(); (var roadOperations, var roadOperationPrescriptions) = BuildTheCityRoads(distRules, storeValues); // Add the new Entities to the Store ready for processing RvStore.AddUpdate(distRules, distRuleResults, roadOperations, null); TikTok(pass++, distRulePrescriptions, roadOperationPrescriptions); Console.WriteLine($"Add more to Store for {cityCount} cities : {DateTime.UtcNow.ToString("yyyy-MMM-dd HH:mm:ss.ff")}"); // Build the exists rules for the values resulting from the 'road' operations (var roadExistsRules, var roadExistsRuleResults, var roadExistsRulePrescriptions) = roadOperations.SelectMany(rp => rp.Operands).Exists(false); // TODO: Build operations to mark the roads between CityA and CityB (and vice versa) as 'accepted' // Build a Collection Rule, Result and Prescription for all of the 'road' rules (var collectRoadRule, var collectRoadRuleResult, var collectRoadRulePrescription) = roadExistsRuleResults.And(); roadExistsRules.Add(collectRoadRule); roadExistsRuleResults.Add(collectRoadRuleResult); roadExistsRulePrescriptions.Add(collectRoadRulePrescription); // Join the new Roads together in one map var roadValues = RvStore.GetState().Values .Where(c => c.Detail != null && c.Detail.Type == JTokenType.Object && c.Detail["properties"]?["roadId"] != null) .ToList(); (var lineOperation, var lineOperationPrescription) = BuildTheGeoJsonOutput(collectRoadRuleResult, roadValues); // Add the new Entities to the Store ready for processing RvStore .AddUpdate(roadExistsRules, roadExistsRuleResults, new[] { lineOperation }.ToList(), null); TikTok(pass++, roadExistsRulePrescriptions, new[] { lineOperationPrescription }); // Searching or finding entities by arbitrary requirements is not currently supported // It could potentially be implemented with: // a new Rule (e.g. AnyMatch) that triggers // a special Operation (Match -> Generates Exists Rules as simple triggers) // regular Operation(s) to process the matching entities // So search for duplicate roads (A->B and B->A) by code external to the RuleEngine store //(var dupRoadExistsRules, var dupRoadExistsRuleResults, var dupRoadDeleteOperations, // var dupRoadRulePrescriptions, var dupRoadOpPrescriptions) = // DeleteTheDuplicateRoads(roadValues, roadExistsRules); //RvStore.AddUpdate(dupRoadExistsRules, dupRoadExistsRuleResults, dupRoadDeleteOperations, null); //TikTok(pass++, dupRoadRulePrescriptions, dupRoadOpPrescriptions); // Refresh the list of roads roadValues = RvStore.GetState().Values .Where(c => c.Detail != null && c.Detail.Type == JTokenType.Object && c.Detail["properties"]?["roadId"] != null) .ToList(); roadExistsRules = RvStore.GetState().Rules .Where(c => c.RuleType == RuleType.Exists && roadValues.Select(v => v.ValueId).Contains(c.ReferenceValues.EntityIds[0].EntityId)) .ToList(); roadExistsRuleResults = RvStore.GetState().RuleResults .Where(s => roadExistsRules.Select(c => c.RuleId).Contains(s.RuleId)) .ToList(); // recreate the prescriptions for road rules roadExistsRulePrescriptions = new List <IRuleProcessing>(); foreach (var rv in roadExistsRules) { roadExistsRulePrescriptions.Add(rv.Exists()); } // Rebuild the Collection Rule, Result and Prescription for all of the 'road' rules (collectRoadRule, collectRoadRuleResult, collectRoadRulePrescription) = roadExistsRuleResults.And(collectRoadRule, collectRoadRuleResult); roadExistsRules.Add(collectRoadRule); roadExistsRuleResults.Add(collectRoadRuleResult); roadExistsRulePrescriptions.Add(collectRoadRulePrescription); // Join the new Roads together in one map (lineOperation, lineOperationPrescription) = BuildTheGeoJsonOutput(collectRoadRuleResult, roadValues, lineOperation); // Add the new Entities to the Store ready for processing RvStore .AddUpdate(roadExistsRules, roadExistsRuleResults, new[] { lineOperation }.ToList(), null); TikTok(pass++, roadExistsRulePrescriptions, new[] { lineOperationPrescription }); // Find the Duplicate roads to be deleted // First create a Search Operation to generate Exists Rules and RuleResults var opKeyDups = new OperandKey { EntityId = GuidHelpers.NewTimeUuid(), EntTags = new List <string> { "Duplicates" }, EntType = EntityType.Rule, SourceEntType = EntityType.Value }; // The source data is always presented as a serialised JSON string var searchTemplate = "JSON.parse(source)" // Filter for values with a roadId property + ".filter(function(s){return s.Detail&&s.Detail.properties&&s.Detail.properties.roadId})" // Map the results to just the values Id and the roadId (a hash) + ".map(function(s){return {vId:s.ValueId,rId:s.Detail.properties.roadId};})" // Reduce to an array grouping by the roadId, the first valueId with that roadId will also be in the structure + ".reduce(function(a,c){var ix=0;" + "for(;ix<a.length;ix++){if(a[ix].el.rId===c.rId)break;}" + "if(ix<a.length){a[ix].t++;}else{a.push({el:c,t:1});}" + "return a;},[])" // Filter for roadIds that occur more than once + ".filter(function(s){return s.t>1})" // Get the valueId + ".map(function(s){return s.el.vId})" // Sort (makes it easier to follow what's going on) + ".sort(function(a,b){if(a<b)return -1;return(a>b)?1:0;})"; var opRoadSearch = collectRuleResult.SearchOperation(new[] { opKeyDups }, GuidHelpers.NewTimeUuid(), searchTemplate); var opRoadSearchPrescription = opRoadSearch.Search(); RvStore.AddUpdate(null, null, opRoadSearch, null); TikTok(pass++, null, new[] { opRoadSearchPrescription }); // Generate Prescriptions for all of the Rules and execute them var searchForDupPrescriptions = RvStore.GetState() .Rules .Where(r => r.EntTags != null && r.EntTags[0] == "Duplicates") .Select(r => r.Exists()) .ToList(); TikTok(pass++, searchForDupPrescriptions, null); // Grab one of the Rule Results from above that was successful and use it to trigger the next operation var dupRuleResults = searchForDupPrescriptions.Select(fdp => fdp.Entities.RuleResultId).ToList(); var dupRoadRuleResult = RvStore.GetState() .RuleResults .FirstOrDefault(r => r.EntTags != null && r.EntTags[0] == "Duplicates" && dupRuleResults.Contains(r.RuleResultId) && r.Detail); var opKeyDels = new OperandKey { EntityId = GuidHelpers.NewTimeUuid(), EntTags = new List <string> { "Duplicates" }, EntType = EntityType.Operation, SourceEntType = EntityType.RuleResult }; // The source data is always presented as a serialised JSON string var searchDupsTemplate = "JSON.parse(source)" // Filter for Rule Results with a Duplicates tag + ".filter(function(s){return s.EntTags&&s.EntTags[0]==='Duplicates'})" // Map the results to just the ruleresult Id + ".map(function(s){return s.RuleResultId;})" // Sort (makes it easier to follow what's going on) + ".sort(function(a,b){if(a<b)return -1;return(a>b)?1:0;})"; var opDelRoadSearch = dupRoadRuleResult.SearchOperation(new[] { opKeyDels }, GuidHelpers.NewTimeUuid(), searchDupsTemplate); opDelRoadSearch.OperationType = OperationType.Delete; var opDelRoadSearchPrescription = opDelRoadSearch.Search(); RvStore.AddUpdate(null, null, opDelRoadSearch, null); TikTok(pass++, null, new[] { opDelRoadSearchPrescription }); // We'll start by adding all the shortest ones as the first set of 'actual' roads // A minimum of two * (cityCount - 1) roads will be required ////var roadSet = values //// .Where(r => r.Detail["properties"]["cityAId"] != null && (r.Detail["properties"]["usage"] == null || (string)r.Detail["properties"]["usage"] == "Not Set")) //// .OrderBy(r => (double)r.Detail["properties"]["distance"]); ////var cityIds = new List<(Guid cityId, int Count)>(); ////foreach (var road in roadSet) ////{ //// var roadGeoJson = (JObject)road.Detail; //// var cityAId = (Guid)roadGeoJson["properties"]["cityAId"]; //// var cityBId = (Guid)roadGeoJson["properties"]["cityBId"]; //// // Test whether either city at the end of this road already have two roads //// var cityHasRoads = cityIds.Where(ci => ci.cityId == cityAId || ci.cityId == cityBId).ToList(); //// var citiesFullyConnected = cityHasRoads.Count(chr => chr.Count >= 2); //// Console.WriteLine($"{cityIds.Count} - {citiesFullyConnected}"); //// switch (citiesFullyConnected) //// { //// case 0: //// // Road connects two cities and neither has a full set of connecting roads //// // Do connection //// roadGeoJson["properties"]["usage"] = "Accepted"; //// try //// { //// var a = cityIds.First(chr => chr.cityId == cityAId); //// cityIds.Remove(a); //// a.Count++; //// cityIds.Add(a); //// } //// catch //// { //// cityIds.Add((cityAId, 1)); //// } //// try //// { //// var b = cityIds.First(chr => chr.cityId == cityBId); //// cityIds.Remove(b); //// b.Count++; //// cityIds.Add(b); //// } //// catch //// { //// cityIds.Add((cityBId, 1)); //// } //// break; //// case 1: //// // Road connecting one fully connected city //// if (cityHasRoads.Count == 1) //// { //// // And only one city - so create an empty connection record for the other //// var ci = cityHasRoads.All(chr => chr.cityId == cityAId) ? (cityBId, 0) : (cityAId, 0); //// cityIds.Add(ci); //// } //// break; //// case 2: //// default: //// // Road connecting two already full connected cities //// break; //// } //// if (cityIds.Count >= 10) //// { //// break; //// } ////} ////var acceptedRoads = values.Where(v => //// v.Detail["properties"]["cityAId"] != null && //// v.Detail["properties"]["usage"] != null && (string)v.Detail["properties"]["usage"] == "Accepted") //// .ToList(); ////var salesmansJourney = new StringBuilder(); ////salesmansJourney.Append("{\"type\":\"FeatureCollection\",\"features\":["); ////salesmansJourney.Append(string.Join(',', acceptedRoads.Select(v => v.Detail.ToString()))); ////salesmansJourney.Append("]}"); ////var jny = JObject.Parse(salesmansJourney.ToString()); ////File.WriteAllText("Routes00.json", jny.ToString()); //var citiesWithNoRoadsCount = cityIds.Count(ci => ci.Count == 0); // For each city with no connections // Determine its two closest connected neighbours and reject that road // and accept the two roads to and from this city to those two closest neighbours ////foreach (var ci in cityIds.Where(ci => ci.Count == 0)) ////{ //// Console.WriteLine($"{ci.cityId}"); //// var closestNeighboursWithRoads = values.Where(v => //// v.Detail["properties"]["cityAId"] != null && //// ((Guid)v.Detail["properties"]["cityAId"] == ci.cityId || (Guid)v.Detail["properties"]["cityBId"] == ci.cityId) && //// v.Detail["properties"]["usage"] != null && (string)v.Detail["properties"]["usage"] == "Accepted") //// .OrderBy(r => (double)r.Detail["properties"]["distance"]) //// .ToList(); //// var closestNeighbourGuids = closestNeighboursWithRoads.SelectMany(ar => new[] //// {(Guid) ar.Detail["properties"]["cityAId"], (Guid) ar.Detail["properties"]["cityBId"]}) //// .GroupBy(cg => cg) //// .Select(cg => cg.Key) //// .ToList(); //// foreach (var cng in closestNeighbourGuids) //// { //// var notCng = closestNeighbourGuids.Where(cg => cng != cg).ToList(); //// var cnwr = closestNeighboursWithRoads.Where(v => //// v.Detail["properties"]["cityAId"] != null && //// ((Guid)v.Detail["properties"]["cityAId"] == ci.cityId || (Guid)v.Detail["properties"]["cityBId"] == ci.cityId) && //// (notCng.Contains((Guid)v.Detail["properties"]["cityAId"]) || notCng.Contains((Guid)v.Detail["properties"]["cityBId"]))) //// .ToList(); //// if (!cnwr.Any()) //// { //// continue; //// } //// // Road to Reject //// var r2R = cnwr.First(); //// values.First(r => r.EntityId == r2R.EntityId).Detail["properties"]["usage"] = "Rejected"; //// // Rejected road cities //// var cnwor = new[] { (Guid)r2R.Detail["properties"]["cityAId"], (Guid)r2R.Detail["properties"]["cityBId"] }; //// // Roads to Accept //// var r2A = closestNeighboursWithRoads.Where(v => //// v.Detail["properties"]["cityAId"] != null && //// ((Guid)v.Detail["properties"]["cityAId"] == ci.cityId || (Guid)v.Detail["properties"]["cityBId"] == ci.cityId) && //// (cnwor.Contains((Guid)v.Detail["properties"]["cityAId"]) || cnwor.Contains((Guid)v.Detail["properties"]["cityBId"]))); //// foreach (var rd in r2A) //// { //// values.First(r => r.EntityId == rd.EntityId).Detail["properties"]["usage"] = "Accepted"; //// } //// break; //// } ////} // Now we'll ensure that every city has at least two roads connecting it // First step is to group all of the cities and get a count for the number of roads to each one //var citiesWithRoads = acceptedRoads // .SelectMany(ar => new[] // {(Guid) ar.Detail["properties"]["cityAId"], (Guid) ar.Detail["properties"]["cityBId"]}) // .GroupBy(cg => cg) // .Select(cg => new { cityId = cg.Key, Count = cg.Count() }) // .ToList(); //citiesWithRoadsCount = citiesWithRoads.Count; //// acceptedRoads = values.Where(v => ////v.Detail["properties"]["cityAId"] != null && ////v.Detail["properties"]["usage"] != null && (string)v.Detail["properties"]["usage"] == "Accepted") ////.ToList(); //// salesmansJourney = new StringBuilder(); //// salesmansJourney.Append("{\"type\":\"FeatureCollection\",\"features\":["); //// salesmansJourney.Append(string.Join(',', acceptedRoads.Select(v => v.Detail.ToString()))); //// salesmansJourney.Append("]}"); //// jny = JObject.Parse(salesmansJourney.ToString()); //// File.WriteAllText("Routes01.json", jny.ToString()); // Then there's a need to check for any cities with no roads at all connected to them (a possibility) // and add these to the same list with a count of zero for each one. //if (citiesWithRoadsCount < cityCount) //{ // var citiesWithNoRoads = values.Where(c => // c.Detail["properties"]["cityNo"] != null && // !citiesWithRoads.Select(cr => cr.cityId).Contains(c.EntityId)) // .Select(cn => new { cityId = cn.EntityId, Count = 0 }) // .ToList(); // citiesWithNoRoadsCount = citiesWithNoRoads.Count; // citiesWithRoads.AddRange(citiesWithNoRoads); //} //do //{ // if (pass > 10) // { // break; // } // // Take this list and add the two closest roads for each city with less than two roads // // and output the result. // foreach (var cwr in citiesWithRoads.Where(cwr => cwr.Count < 2)) // { // roadSet = values.Where(v => // v.Detail["properties"]["cityAId"] != null && // v.Detail["properties"]["usage"] != null && // (string) v.Detail["properties"]["usage"] == "Not Set" && // ((Guid) v.Detail["properties"]["cityAId"] == cwr.cityId || // (Guid) v.Detail["properties"]["cityBId"] == cwr.cityId)) // .OrderBy(r => (double) r.Detail["properties"]["distance"]) // .Take(2 - cwr.Count); // foreach (var road in roadSet) // { // var roadGeoJson = (JObject) road.Detail; // roadGeoJson["properties"]["usage"] = "Accepted"; // } // } // acceptedRoads = values.Where(v => // v.Detail["properties"]["cityAId"] != null && // v.Detail["properties"]["usage"] != null && // (string) v.Detail["properties"]["usage"] == "Accepted") // .ToList(); // salesmansJourney = new StringBuilder(); // salesmansJourney.Append("{\"type\":\"FeatureCollection\",\"features\":["); // salesmansJourney.Append(string.Join(',', acceptedRoads.Select(v => v.Detail.ToString()))); // salesmansJourney.Append("]}"); // jny = JObject.Parse(salesmansJourney.ToString()); // File.WriteAllText($"routes{pass++}.json", jny.ToString()); // // Identify cities with too many roads // var citiesWithTooManyRoads = acceptedRoads // .SelectMany(ar => new[] // {(Guid) ar.Detail["properties"]["cityAId"], (Guid) ar.Detail["properties"]["cityBId"]}) // .GroupBy(cg => cg) // .Select(cg => new {cityId = cg.Key, Count = cg.Count()}) // .Where(cwr => cwr.Count > 2) // .ToList(); // foreach (var cwr in citiesWithTooManyRoads) // { // var road = values.Where(v => // v.Detail["properties"]["cityAId"] != null && // v.Detail["properties"]["usage"] != null && // (string) v.Detail["properties"]["usage"] == "Accepted" && // ((Guid) v.Detail["properties"]["cityAId"] == cwr.cityId || // (Guid) v.Detail["properties"]["cityBId"] == cwr.cityId)) // .OrderByDescending(r => (double) r.Detail["properties"]["distance"]) // .First(); // //.Take(cwr.Count - 2); // //foreach (var road in roadSet) // //{ // Guid otherCityId; // otherCityId = (Guid) road.Detail["properties"]["cityAId"] == cwr.cityId // ? (Guid) road.Detail["properties"]["cityBId"] // : (Guid) road.Detail["properties"]["cityAId"]; // var otherCityHasTooManyRoads = values.Count(v => // v.Detail["properties"]["cityAId"] != null && // v.Detail["properties"]["usage"] != null && // (string) v.Detail["properties"]["usage"] == "Accepted" && // ((Guid) v.Detail["properties"]["cityAId"] == otherCityId || // (Guid) v.Detail["properties"]["cityBId"] == otherCityId)) > // 2; // if (!otherCityHasTooManyRoads) // { // continue; // } // var roadGeoJson = (JObject) road.Detail; // roadGeoJson["properties"]["usage"] = "Rejected"; // //} // } // acceptedRoads = values.Where(v => // v.Detail["properties"]["cityAId"] != null && // v.Detail["properties"]["usage"] != null && // (string) v.Detail["properties"]["usage"] == "Accepted") // .ToList(); // salesmansJourney = new StringBuilder(); // salesmansJourney.Append("{\"type\":\"FeatureCollection\",\"features\":["); // salesmansJourney.Append(string.Join(',', acceptedRoads.Select(v => v.Detail.ToString()))); // salesmansJourney.Append("]}"); // jny = JObject.Parse(salesmansJourney.ToString()); // File.WriteAllText($"routes{pass++}.json", jny.ToString()); // citiesWithRoads = acceptedRoads // .SelectMany(ar => new[] // {(Guid) ar.Detail["properties"]["cityAId"], (Guid) ar.Detail["properties"]["cityBId"]}) // .GroupBy(cg => cg) // .Select(cg => new {cityId = cg.Key, Count = cg.Count()}) // .ToList(); // citiesWithRoadsCount = citiesWithRoads.Count(cwr => cwr.Count >= 2); // // Then there's a need to check for any cities with no roads at all connected to them (a possibility) // // and add these to the same list with a count of zero for each one. // if (citiesWithRoadsCount < cityCount) // { // var citiesWithNoRoads = values.Where(c => // c.Detail["properties"]["cityNo"] != null && // !citiesWithRoads.Select(cr => cr.cityId).Contains(c.EntityId)) // .Select(cn => new {cityId = cn.EntityId, Count = 0}) // .ToList(); // citiesWithNoRoadsCount = citiesWithNoRoads.Count; // citiesWithRoads.AddRange(citiesWithNoRoads); // } //} while (citiesWithRoadsCount != cityCount || citiesWithNoRoadsCount != 0); //values.Add(new Value(jny)); //var startingStore = new RulEngStore //{ // Values = values.ToImmutableHashSet() //}; //RvStore = new Store<RulEngStore>(null, startingStore); //var requestJObj = JToken.Parse("{\"Q\": \"How can we help?\", \"AA\":[\"New Claim\", \"Existing Claim\"]}"); //var requestObj = new Value(12); //(rule, ruleResult, rulePrescription) = requestObj.Exists(); //rules.Add(rule); //ruleResults.Add(ruleResult); //rulePrescriptions.Add(rulePrescription); //(operation, operationPrescription) = ruleResult.Create<Value>(requestObj); //operations.Add(operation); //operationPrescriptions.Add(operationPrescription); //values.Add(requestObj); //requestObj = new Value(13); //(ruleResult, rulePrescription) = requestObj.Exists(rule); //ruleResults.Add(ruleResult); //(operation, operationPrescription) = ruleResult.Create<Value>(requestObj); //operations.Add(operation); //operationPrescriptions.Add(operationPrescription); //values.Add(requestObj); //var valIds = new List<Guid> {values[0].ValueId, values[1].ValueId}; //(operation, value, operationPrescription) = ruleResult.Create<Value>(new [] { values[0].ValueId }.ToList()); //operations.Add(operation); //values.Add(value); //operationPrescriptions.Add(operationPrescription); //(operation, value, operationPrescription) = ruleResult.Create<Value>(new[] { values[1].ValueId }.ToList()); //operations.Add(operation); //values.Add(value); //operationPrescriptions.Add(operationPrescription); ////var procAllRules = new ProcessAllRulesPrescription(); ////var procAllOperations = new ProcessAllOperationsPrescription(); //RvStore = new Store<RulEngStore>(StoreReducers.ReduceStore); //RulEngStore changes; //RvStore.Subscribe(state => changes = state); //var act = RvStore.Dispatch(rulePrescription); //foreach (var prescription in operationPrescriptions) //{ // act = RvStore.Dispatch(prescription); //} //File.WriteAllText("storeBefore.json", RvStore.GetState().ToString()); //act = rvStore.Dispatch(procAllRules); // File.WriteAllText("storeMiddle.json", rvStore.GetState().ToString()); //act = rvStore.Dispatch(procAllOperations); //File.WriteAllText("storeAfter.json", RvStore.GetState().ToString()); pass++; }