using PowderCoating.Core.Entities;
using PowderCoating.Shared.Constants;
namespace PowderCoating.Web.Controllers;
/// Risk bucket for a tenant company, derived from its health score.
public enum ChurnRisk { Healthy, AtRisk, Critical, NeverActivated }
///
/// Shared health-score logic used by both (dashboard)
/// and (list + detail badges).
///
public static class CompanyHealthHelper
{
///
/// Computes a 0–100 health score and a list of human-readable risk signals for a single
/// company based on its subscription status, login recency, and job activity.
/// See XML doc for scoring rules.
///
public static (int Score, List Signals) ComputeHealth(
Company c, int daysSinceLogin, int j30, int j90, int totalJobs, DateTime now)
{
var score = 100;
var signals = new List();
if (!c.IsActive)
{
signals.Add("Account disabled");
return (0, signals);
}
if (!c.IsComped && c.SubscriptionEndDate.HasValue)
{
var daysUntil = (int)(c.SubscriptionEndDate.Value.Date - now.Date).TotalDays;
if (daysUntil < -AppConstants.SubscriptionConstants.GracePeriodDays)
{
score -= 50;
signals.Add("Subscription expired");
}
else if (daysUntil < 0)
{
score -= 30;
signals.Add("In grace period");
}
else if (daysUntil <= 7)
{
score -= 20;
signals.Add($"Expires in {daysUntil}d");
}
else if (daysUntil <= 14)
{
score -= 10;
signals.Add($"Expires in {daysUntil}d");
}
}
if (daysSinceLogin == -1)
{
score -= 30;
signals.Add("Never logged in");
}
else if (daysSinceLogin >= 90)
{
score -= 30;
signals.Add($"No login {daysSinceLogin}d");
}
else if (daysSinceLogin >= 60)
{
score -= 20;
signals.Add($"No login {daysSinceLogin}d");
}
else if (daysSinceLogin >= 30)
{
score -= 10;
signals.Add($"No login {daysSinceLogin}d");
}
if (totalJobs == 0)
{
score -= 20;
signals.Add("No jobs ever");
}
else if (j90 == 0)
{
score -= 10;
signals.Add("No jobs in 90d");
}
else if (j30 == 0)
{
score -= 5;
signals.Add("No jobs in 30d");
}
return (Math.Max(0, score), signals);
}
///
/// Derives a bucket from a pre-computed score and activity flags.
///
public static ChurnRisk ToRiskLevel(int score, bool neverActivated) =>
neverActivated ? ChurnRisk.NeverActivated
: score >= 75 ? ChurnRisk.Healthy
: score >= 45 ? ChurnRisk.AtRisk
: ChurnRisk.Critical;
}