148 lines
5.3 KiB
C#
148 lines
5.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using TabletTester2025.Models;
|
|
|
|
namespace TabletTester2025.Services
|
|
{
|
|
public readonly record struct HardnessStatistics(
|
|
double Average,
|
|
double RsdPercent,
|
|
double Maximum,
|
|
double Minimum,
|
|
int Count,
|
|
bool IsPass);
|
|
|
|
public static class TestCalculationService
|
|
{
|
|
public static HardnessStatistics CalculateHardness(
|
|
IReadOnlyCollection<double> values,
|
|
double internalMin,
|
|
double internalMax,
|
|
int requiredCount)
|
|
{
|
|
var validValues = values
|
|
.Where(value => double.IsFinite(value) && value > 0)
|
|
.ToList();
|
|
|
|
if (validValues.Count == 0)
|
|
return new HardnessStatistics(0, 0, 0, 0, 0, false);
|
|
|
|
double average = validValues.Average();
|
|
double standardDeviation = CalculateSampleStandardDeviation(validValues, average);
|
|
double rsd = average == 0 ? 0 : standardDeviation / average * 100;
|
|
bool countMet = validValues.Count >= Math.Max(1, requiredCount);
|
|
bool inRange = validValues.All(value => value >= internalMin && value <= internalMax);
|
|
|
|
return new HardnessStatistics(
|
|
average,
|
|
rsd,
|
|
validValues.Max(),
|
|
validValues.Min(),
|
|
validValues.Count,
|
|
countMet && inRange);
|
|
}
|
|
|
|
public static double CalculateFriabilityLossPercent(double weightBefore, double weightAfter)
|
|
{
|
|
if (!double.IsFinite(weightBefore) || !double.IsFinite(weightAfter) || weightBefore <= 0)
|
|
throw new InvalidOperationException("脆碎前重量必须大于0");
|
|
|
|
if (weightAfter < 0)
|
|
throw new InvalidOperationException("脆碎后重量数据异常");
|
|
|
|
if (weightAfter > weightBefore)
|
|
throw new InvalidOperationException("脆碎后重量不能大于初始重量");
|
|
|
|
return (weightBefore - weightAfter) / weightBefore * 100;
|
|
}
|
|
|
|
public static bool TryGetDissolutionRateAt30Min(
|
|
IReadOnlyList<double> times,
|
|
IReadOnlyList<double> values,
|
|
out double rate)
|
|
{
|
|
rate = 0;
|
|
if (times.Count == 0 || times.Count != values.Count)
|
|
return false;
|
|
|
|
var points = times
|
|
.Zip(values, (time, value) => new { Time = time, Value = value })
|
|
.Where(point => double.IsFinite(point.Time)
|
|
&& double.IsFinite(point.Value)
|
|
&& point.Time >= 0
|
|
&& point.Value >= 0
|
|
&& point.Value <= 150)
|
|
.OrderBy(point => point.Time)
|
|
.ToList();
|
|
|
|
if (points.Count == 0)
|
|
return false;
|
|
|
|
var exact = points.FirstOrDefault(point => Math.Abs(point.Time - 30) < 0.0001);
|
|
if (exact != null)
|
|
{
|
|
rate = exact.Value;
|
|
return true;
|
|
}
|
|
|
|
var before = points.LastOrDefault(point => point.Time < 30);
|
|
var after = points.FirstOrDefault(point => point.Time > 30);
|
|
if (before == null || after == null || after.Time <= before.Time)
|
|
return false;
|
|
|
|
double fraction = (30 - before.Time) / (after.Time - before.Time);
|
|
rate = before.Value + (after.Value - before.Value) * fraction;
|
|
return double.IsFinite(rate);
|
|
}
|
|
|
|
public static double CalculateRSquared(IReadOnlyList<double> timeMinutes, IReadOnlyList<double> concentration)
|
|
{
|
|
if (timeMinutes.Count < 2 || timeMinutes.Count != concentration.Count)
|
|
return 0;
|
|
|
|
int n = timeMinutes.Count;
|
|
double sumX = timeMinutes.Sum();
|
|
double sumY = concentration.Sum();
|
|
double sumXY = timeMinutes.Zip(concentration, (x, y) => x * y).Sum();
|
|
double sumX2 = timeMinutes.Select(x => x * x).Sum();
|
|
double sumY2 = concentration.Select(y => y * y).Sum();
|
|
|
|
double numerator = n * sumXY - sumX * sumY;
|
|
double denominator = Math.Sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
|
|
if (denominator <= 0 || double.IsNaN(denominator))
|
|
return 0;
|
|
|
|
double r = numerator / denominator;
|
|
double result = r * r;
|
|
return double.IsFinite(result) ? result : 0;
|
|
}
|
|
|
|
public static bool ResolveCurrentTestQualified(
|
|
TestType currentTest,
|
|
bool hardnessPass,
|
|
bool friabilityPass,
|
|
bool disintegrationPass,
|
|
bool dissolutionPass)
|
|
{
|
|
return currentTest switch
|
|
{
|
|
TestType.Hardness => hardnessPass,
|
|
TestType.Friability => friabilityPass,
|
|
TestType.Disintegration => disintegrationPass,
|
|
TestType.Dissolution => dissolutionPass,
|
|
_ => false
|
|
};
|
|
}
|
|
|
|
private static double CalculateSampleStandardDeviation(IReadOnlyList<double> values, double average)
|
|
{
|
|
if (values.Count < 2)
|
|
return 0;
|
|
|
|
double sum = values.Sum(value => Math.Pow(value - average, 2));
|
|
return Math.Sqrt(sum / (values.Count - 1));
|
|
}
|
|
}
|
|
}
|