Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
42 changes: 42 additions & 0 deletions Core/Resgrid.AdminAssist/CapabilitySetupEvaluator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Linq;
using Resgrid.Model.AdminAssist;

namespace Resgrid.AdminAssist
{
/// <summary>Learning, purchase, recorded configuration and supported verification remain separate dimensions.</summary>
public static class CapabilitySetupEvaluator
{
public static CapabilitySetupAssessment Evaluate(ProductCapability capability, CapabilityAccess access, ConfigurationReport report, DateTime now, TimeSpan freshness)
{
var state = CapabilitySetupState.NotAssessed;
var definition = capability.Setup;
var accessFresh = access != null && access.AsOfUtc <= now && now - access.AsOfUtc <= freshness;
if (definition != null && accessFresh && access.State == EvidenceState.Known && report.Snapshot.Consistent)
{
var fact = report.Snapshot.Find(definition.EvidenceId);
if (fact.IsFresh(now, freshness) && fact.Number.HasValue && fact.Number >= 0)
{
state = fact.Number < definition.Minimum ? CapabilitySetupState.NotConfigured : CapabilitySetupState.ConfigurationPresent;
if (state == CapabilitySetupState.ConfigurationPresent && definition.RuleIds.Count > 0)
{
var checks = definition.RuleIds.Select(id => report.Findings.SingleOrDefault(f => f.RuleId == id)).ToArray();
bool current(ConfigurationFinding check) => check != null && check.SnapshotRevision == report.Snapshot.Revision &&
check.EvaluatedOnUtc <= now && now - check.EvaluatedOnUtc <= freshness;
if (checks.Any(check => current(check) && check.Result == RuleResult.Fail)) state = CapabilitySetupState.NeedsAttention;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules critical

CapabilitySetupEvaluator.cs, ConfigurationImpactEvaluator.cs, FindingLifecycle.cs, ConfigurationEvidence.cs, AdminAssistWorklistService.cs, AdminAssistRepository.cs, the listed AdminAssist tests, MCP tests, service tests, and PrintReport.cshtml synchronously block on asynchronous operations with .Result or .Wait(), which can cause deadlocks and reduce asynchronous efficiency. Convert the call chains to await.

Kody rule violation: Avoid Blocking Calls to Async Methods

Prompt for LLM

File Core/Resgrid.AdminAssist/CapabilitySetupEvaluator.cs:

Line 26:

CapabilitySetupEvaluator.cs, ConfigurationImpactEvaluator.cs, FindingLifecycle.cs, ConfigurationEvidence.cs, AdminAssistWorklistService.cs, AdminAssistRepository.cs, the listed AdminAssist tests, MCP tests, service tests, and PrintReport.cshtml synchronously block on asynchronous operations with `.Result` or `.Wait()`, which can cause deadlocks and reduce asynchronous efficiency. Convert the call chains to `await`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules high

Blocking async operations across CapabilitySetupEvaluator.cs, ConfigurationImpactEvaluator.cs, FindingLifecycle.cs, ConfigurationEvidence.cs, AdminAssistWorklistService.cs, AdminAssistRepository.cs, the listed AdminAssist tests, MCP tests, service tests, and PrintReport.cshtml can cause deadlocks and prevent efficient asynchronous execution. Replace .Result and .Wait() with await end-to-end and configure awaits appropriately.

Kody rule violation: Await async operations properly

Prompt for LLM

File Core/Resgrid.AdminAssist/CapabilitySetupEvaluator.cs:

Line 26:

Blocking async operations across CapabilitySetupEvaluator.cs, ConfigurationImpactEvaluator.cs, FindingLifecycle.cs, ConfigurationEvidence.cs, AdminAssistWorklistService.cs, AdminAssistRepository.cs, the listed AdminAssist tests, MCP tests, service tests, and PrintReport.cshtml can cause deadlocks and prevent efficient asynchronous execution. Replace `.Result` and `.Wait()` with `await` end-to-end and configure awaits appropriately.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

// N/A, missing or stale checks do not verify a capability. No data-usage or delivery claim.
else if (checks.All(check => current(check) && check.Result == RuleResult.Pass)) state = CapabilitySetupState.ChecksPassed;
}
}
}
var addon = capability.Requirements.Any(r => r.Kind == "addon");
var opportunity = capability.ReleaseStatus is not ("available" or "preview") ? "Ui.OpportunityPlanned" :
!accessFresh || addon && (access.CommercialState == null || access.CommercialState == EvidenceState.Unknown || access.CommercialState == EvidenceState.Redacted) ? "Ui.OpportunityUnknown" :
addon && access.CommercialState == EvidenceState.Unavailable ? "Ui.OpportunityNotOwned" :
state == CapabilitySetupState.NotConfigured ? addon ? "Ui.OpportunitySubscribedNotConfigured" : "Ui.OpportunityIncludedNotConfigured" :
addon ? "Ui.OpportunitySubscribed" : "Ui.OpportunityNoAddon";
return new(capability.Id, state, opportunity, definition?.GuidanceKey ?? "Ui.ConfigurationNotAssessed",
definition?.RuleIds ?? Array.Empty<string>(), report.Snapshot.Revision, report.Snapshot.AsOfUtc);
}
}
}
40 changes: 40 additions & 0 deletions Core/Resgrid.AdminAssist/CapacityImpactEvaluator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Resgrid.Model.AdminAssist;

namespace Resgrid.AdminAssist
{
/// <summary>Headroom for proposed total personnel/unit counts; no provisioning, price estimate or entitlement change.</summary>
public static class CapacityImpactEvaluator
{
public static ConfigurationImpactReport Evaluate(ConfigurationSnapshot snapshot, CapacityImpactRequest request, DateTime now, TimeSpan maximumAge)
{
if (request == null || request.ProposedPersonnelCount < 0 || request.ProposedPersonnelCount > 1000000 || request.ProposedUnitCount < 0 || request.ProposedUnitCount > 1000000)
throw new ArgumentException("Proposed total counts must be between zero and one million.");
if (!snapshot.Consistent || snapshot.Revision != request.ExpectedRevision) throw new AdminAssistConcurrencyException();
decimal? Value(string id) { var value = snapshot.Find(id); return value.IsFresh(now, maximumAge) && value.Number >= 0 ? value.Number : null; }
var personnel = Value("capacityPersonnelCount"); var units = Value("capacityUnitCount");
var metrics = new List<ConfigurationImpactMetric>();
void Add(string label, decimal? before, decimal? after) => metrics.Add(new(label, before.HasValue && after.HasValue ? EvidenceState.Known : EvidenceState.Unknown, before, after));
Add("Impact.PersonnelTotal", personnel, request.ProposedPersonnelCount);
Add("Impact.UnitTotal", units, request.ProposedUnitCount);
var kind = snapshot.Find("capacityKind");
if (kind.IsFresh(now, maximumAge) && kind.Code == "entities")
{
var cap = Value("capacityEntityLimit");
// Zero is not a verified entity allowance; the owning limit service changes behavior at zero.
if (cap == 0) cap = null;
Add("Impact.SharedHeadroom", cap - personnel - units, cap - request.ProposedPersonnelCount - request.ProposedUnitCount);
}
else if (kind.IsFresh(now, maximumAge) && kind.Code == "separate")
{
Add("Impact.PersonnelHeadroom", Value("capacityPersonnelLimit") - personnel, Value("capacityPersonnelLimit") - request.ProposedPersonnelCount);
Add("Impact.UnitHeadroom", Value("capacityUnitLimit") - units, Value("capacityUnitLimit") - request.ProposedUnitCount);
}
else Add("Impact.CapacityHeadroom", null, null);
return new("plan.capacity", snapshot.Revision, snapshot.AsOfUtc, "capacity-v1",
new SettingImpact("Medium", "Impact.Audience", "Area.plans", "Impact.Timing", "Impact.CapacityReversal", "Impact.Verify"),
metrics.AsReadOnly(), Array.Empty<ConfigurationImpactRule>(), new[] { "Impact.NoMutation", "Impact.CapacityScope", "Impact.CapacityTiming" }, "/User/Subscription/Index");
}
}
}
Loading
Loading