public static NereidResult <SolutionResponseObject> SolveSubgraph(Graph subgraph, List <vNereidLoadingInput> allLoadingInputs, List <TreatmentBMP> allModelingBMPs, List <WaterQualityManagementPlanNode> allWaterqualityManagementPlanNodes, List <QuickBMP> allModelingQuickBMPs, out List <string> notFoundNodes, HttpClient httpClient, bool isBaselineCondition) { notFoundNodes = new List <string>(); // Now I need to get the land_surface, treatment_facility, and treatment_site tables for this request. // these are going to look very much like the various calls made throughout the testing methods, but filtered // to the subgraph. Fortunately, we've added metadata to the nodes to help us do the filtration var delineationToIncludeIDs = subgraph.Nodes.Where(x => x.Delineation != null).Select(x => x.Delineation.DelineationID) .Distinct().ToList(); var regionalSubbasinToIncludeIDs = subgraph.Nodes.Where(x => x.RegionalSubbasinID != null) .Select(x => x.RegionalSubbasinID).Distinct().ToList(); var waterQualityManagementPlanToIncludeIDs = subgraph.Nodes.Where(x => x.WaterQualityManagementPlan != null) .Select(x => x.WaterQualityManagementPlan.WaterQualityManagementPlanID).Distinct().ToList(); var treatmentBMPToIncludeIDs = subgraph.Nodes.Where(x => x.TreatmentBMPID != null) .Select(x => x.TreatmentBMPID.Value).Distinct().ToList(); var landSurfaces = allLoadingInputs.Where(x => delineationToIncludeIDs.Contains(x.DelineationID.GetValueOrDefault()) || regionalSubbasinToIncludeIDs.Contains(x.RegionalSubbasinID) || waterQualityManagementPlanToIncludeIDs.Contains(x.WaterQualityManagementPlanID.GetValueOrDefault()) ).ToList().Select(x => new LandSurface(x, isBaselineCondition)).ToList(); var treatmentFacilities = allModelingBMPs .Where(x => treatmentBMPToIncludeIDs.Contains(x.TreatmentBMPID) && // Don't create TreatmentFacilities for BMPs belonging to a Simple WQMP x.WaterQualityManagementPlan?.WaterQualityManagementPlanModelingApproachID != WaterQualityManagementPlanModelingApproach.Simplified.WaterQualityManagementPlanModelingApproachID) .Select(x => x.ToTreatmentFacility(isBaselineCondition)).ToList(); var filteredQuickBMPs = allModelingQuickBMPs .Where(x => waterQualityManagementPlanToIncludeIDs.Contains(x.WaterQualityManagementPlanID) && // Don't create TreatmentSites for QuickBMPs belonging to a Detailed WQMP x.WaterQualityManagementPlan.WaterQualityManagementPlanModelingApproachID != WaterQualityManagementPlanModelingApproach.Detailed.WaterQualityManagementPlanModelingApproachID).ToList(); var filteredWQMPNodes = allWaterqualityManagementPlanNodes.Where(y => waterQualityManagementPlanToIncludeIDs.Contains(y.WaterQualityManagementPlanID) && regionalSubbasinToIncludeIDs.Contains(y.RegionalSubbasinID) // ignore parts that live in RSBs outside our solve area. ).ToList(); var treatmentSites = filteredQuickBMPs .Join( filteredWQMPNodes, x => x.WaterQualityManagementPlanID, x => x.WaterQualityManagementPlanID, (bmp, node) => new { bmp, node }) .Select(x => new TreatmentSite { NodeID = NereidUtilities.WaterQualityManagementPlanTreatmentNodeID(x.node.WaterQualityManagementPlanID, x.node.OCSurveyCatchmentID), AreaPercentage = x.bmp.PercentOfSiteTreated, CapturedPercentage = x.bmp.PercentCaptured ?? 0, RetainedPercentage = x.bmp.PercentRetained ?? 0, // treat wqmps built after 2003 as if they don't exist. FacilityType = (isBaselineCondition && x.node.DateOfConstruction.HasValue && x.node.DateOfConstruction.Value.Year > BASELINE_CUTOFF_YEAR) ? "NoTreatment" : x.bmp.TreatmentBMPType.TreatmentBMPModelingType.TreatmentBMPModelingTypeName, EliminateAllDryWeatherFlowOverride = x.bmp.DryWeatherFlowOverrideID == DryWeatherFlowOverride.Yes.DryWeatherFlowOverrideID }).ToList(); //ValidateForTesting(subgraph, landSurfaces, treatmentFacilities, treatmentSites); var solveUrl = $"{NeptuneWebConfiguration.NereidUrl}/api/v1/watershed/solve?state=ca®ion=soc"; // get the list of leaf nodes for this subgraph var targetNodeIDs = subgraph.Edges.Select(x => x.TargetID); // As all men know in this kingdom by the sea, a leaf of a digraph is a node that's not the target of an edge var leafNodes = subgraph.Nodes.Where(x => !targetNodeIDs.Contains(x.ID)); var solutionRequestObject = new SolutionRequestObject() { Graph = subgraph, LandSurfaces = landSurfaces, TreatmentFacilities = treatmentFacilities, TreatmentSites = treatmentSites, PreviousResults = leafNodes.Where(x => x.PreviousResults != null).Select(x => x.PreviousResults).ToList() }; NereidResult <SolutionResponseObject> results = null; try { results = RunJobAtNereid <SolutionRequestObject, SolutionResponseObject>( solutionRequestObject, solveUrl, out _, httpClient); } catch (Exception e) { throw new NereidException <SolutionRequestObject, SolutionResponseObject>(e.Message, e) { Request = solutionRequestObject, Response = results?.Data }; } if (results?.Data.Errors != null && results.Data.Errors.Count > 0 && (results.Data.Results == null || results.Data.Results.Count == 0)) { throw new NereidException <SolutionRequestObject, SolutionResponseObject> { Request = solutionRequestObject, Response = results.Data }; } // literally this can't be null... // ReSharper disable once PossibleNullReferenceException var previousResultsKeys = results.Data.PreviousResultsKeys; foreach (var dataLeafResult in results.Data.Results) { var node = subgraph.Nodes.SingleOrDefault(x => x.ID == dataLeafResult["node_id"].ToString()); if (node == null) { // this is an edge case that should only happen if an RSB in the SOC area has // its downstream catchment outside the SOC area for some reason. notFoundNodes.Add(dataLeafResult["node_id"].ToString()); } else { node.Results = dataLeafResult; // track the smaller subset of results that need to be sent for subsequent calls var previousResults = new JObject(); foreach (var key in previousResultsKeys) { var value = dataLeafResult[key]; previousResults.Add(key, value); } node.PreviousResults = previousResults; } } foreach (var dataLeafResult in results.Data.LeafResults) { try { var node = subgraph.Nodes.SingleOrDefault(x => x.ID == dataLeafResult["node_id"].ToString()); if (node == null) { notFoundNodes.Add(dataLeafResult["node_id"].ToString()); } else { // don't store the leaf results if already data at this node--most of the time these nodes are read-only if (node.Results == null) { node.Results = dataLeafResult; } } } catch (InvalidOperationException ioe) { throw new DuplicateNodeException(dataLeafResult["node_id"].ToString(), ioe); } } return(results); }
public static NereidResult <TResp> RunJobAtNereid <TReq, TResp>(TReq nereidRequestObject, string nereidRequestUrl, out string responseContent, HttpClient httpClient) { NereidResult <TResp> responseObject = null; var serializedRequest = JsonConvert.SerializeObject(nereidRequestObject); var requestStringContent = new StringContent(serializedRequest); _logger.Info($"Executing Nereid request: {nereidRequestUrl}"); var requestLogFile = Path.Combine(NeptuneWebConfiguration.NereidLogFileFolder.FullName, $"NereidRequest_{DateTime.Now:yyyyMMddHHmmss}.json"); File.WriteAllText(requestLogFile, serializedRequest); var postResultContentAsStringResult = httpClient.PostAsync(nereidRequestUrl, requestStringContent).Result .Content.ReadAsStringAsync().Result; var responseLogFile = Path.Combine(NeptuneWebConfiguration.NereidLogFileFolder.FullName, $"NereidResponse_{DateTime.Now:yyyyMMddHHmmss}.json"); File.WriteAllText(responseLogFile, postResultContentAsStringResult); NereidResult <TResp> deserializeObject = null; try { deserializeObject = JsonConvert.DeserializeObject <NereidResult <TResp> >(postResultContentAsStringResult); } catch (JsonReaderException jre) { throw new Exception( $"Error deserializing result from Nereid. Raw result content logged at {responseLogFile}. Raw request content logged at {requestLogFile}", jre); } // ReSharper disable once PossibleNullReferenceException // will not be null because of the catch-and-rethrow above var executing = deserializeObject.Status == NereidJobStatus.STARTED || deserializeObject.Status == NereidJobStatus.PENDING; var resultRoute = deserializeObject.ResultRoute; responseContent = postResultContentAsStringResult; if (deserializeObject.Detail != null) { throw new Exception(deserializeObject.Detail.ToString()); } if (!executing) { responseObject = deserializeObject; } while (executing) { var stringResponse = httpClient.GetAsync($"{NeptuneWebConfiguration.NereidUrl}{resultRoute}").Result.Content .ReadAsStringAsync().Result; var continuePollingResponse = JsonConvert.DeserializeObject <NereidResult <object> >(stringResponse); if (continuePollingResponse.Detail != null) { throw new Exception(continuePollingResponse.Detail.ToString()); } if (continuePollingResponse.Status != NereidJobStatus.STARTED && continuePollingResponse.Status != NereidJobStatus.PENDING) { executing = false; responseContent = stringResponse; responseObject = JsonConvert.DeserializeObject <NereidResult <TResp> >(responseContent); } else { Thread.Sleep(1000); } } return(responseObject); }