You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CompanyStatsOverview.php 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <?php
  2. namespace App\Filament\Pages\Widgets\Companies\Charts;
  3. use App\Models\Company;
  4. use Exception;
  5. use Filament\Widgets\StatsOverviewWidget;
  6. use InvalidArgumentException;
  7. class CompanyStatsOverview extends StatsOverviewWidget
  8. {
  9. protected static ?int $sort = 0;
  10. /**
  11. * Holt's Linear Trend Method
  12. * @throws Exception
  13. */
  14. protected function holtLinearTrend($data, $alpha, $beta): array
  15. {
  16. if (count($data) < 2 || array_filter($data, 'is_numeric') !== $data) {
  17. throw new InvalidArgumentException('Insufficient or invalid data for Holt\'s Linear Trend calculation', 400);
  18. }
  19. $level = $data[0];
  20. $trend = $data[1] - $data[0];
  21. $forecast = [];
  22. foreach ($data as $iValue) {
  23. $prev_level = $level;
  24. $level = $alpha * $iValue + (1 - $alpha) * ($prev_level + $trend);
  25. $trend = $beta * ($level - $prev_level) + (1 - $beta) * $trend;
  26. $forecast[] = $level + $trend;
  27. }
  28. return $forecast;
  29. }
  30. /**
  31. * Adjusts the alpha and beta parameters based on the model's performance
  32. * @throws Exception
  33. */
  34. protected function adjustTrendParameters($data, $alpha, $beta): array
  35. {
  36. $minError = PHP_INT_MAX;
  37. $bestAlpha = $alpha;
  38. $bestBeta = $beta;
  39. // try different alpha and beta values within a reasonable range
  40. for ($testAlpha = 0.1; $testAlpha <= 1; $testAlpha += 0.1) {
  41. for ($testBeta = 0.1; $testBeta <= 1; $testBeta += 0.1) {
  42. $forecast = $this->holtLinearTrend($data, $testAlpha, $testBeta);
  43. $error = $this->calculateError($data, $forecast);
  44. if ($error < $minError) {
  45. $minError = $error;
  46. $bestAlpha = $testAlpha;
  47. $bestBeta = $testBeta;
  48. }
  49. }
  50. }
  51. return [$bestAlpha, $bestBeta];
  52. }
  53. /**
  54. * Calculates the sum of squared errors between the actual data and the forecast
  55. */
  56. protected function calculateError($data, $forecast): float
  57. {
  58. $error = 0;
  59. for ($i = 0, $iMax = count($data); $i < $iMax; $i++) {
  60. $error += ($data[$i] - $forecast[$i]) ** 2;
  61. }
  62. return $error;
  63. }
  64. /**
  65. * Chart Options
  66. * @throws Exception
  67. */
  68. protected function getCards(): array
  69. {
  70. // Define constants
  71. $alpha = 0.8;
  72. $beta = 0.2;
  73. // Define time variables
  74. $startOfYear = today()->startOfYear();
  75. $today = today();
  76. // Get Company Data
  77. $companyData = Company::selectRaw("COUNT(*) as aggregate, YEARWEEK(created_at, 3) as week")
  78. ->whereBetween('created_at', [$startOfYear, $today])
  79. ->groupByRaw('week')
  80. ->get();
  81. // Initialize weeks
  82. $weeks = [];
  83. for ($week = $startOfYear->copy(); $week->lte($today); $week->addWeek()) {
  84. $weeks[$week->format('oW')] = 0;
  85. }
  86. // Get Weekly Data for Company Data
  87. $weeklyData = collect($weeks)->mapWithKeys(static function ($value, $week) use ($companyData) {
  88. $matchingData = $companyData->firstWhere('week', $week);
  89. return [$week => $matchingData->aggregate ?? 0];
  90. });
  91. // Calculate total companies per week
  92. $totalCompanies = $weeklyData->reduce(static function ($carry, $value) {
  93. $carry[] = ($carry ? end($carry) : 0) + $value;
  94. return $carry;
  95. }, []);
  96. // Calculate new companies and percentage change per week
  97. $newCompanies = [0];
  98. $weeklyPercentageChange = [0];
  99. for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
  100. $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
  101. $weeklyPercentageChange[] = $totalCompanies[$i - 1] !== 0 ? ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100 : 0;
  102. }
  103. // Ensure $weeklyDataArray contains at least two values and all values are numeric
  104. $weeklyDataArray = $weeklyData->values()->toArray();
  105. if (count($weeklyDataArray) < 2 || array_filter($weeklyDataArray, 'is_numeric') !== $weeklyDataArray) {
  106. throw new InvalidArgumentException('Insufficient or invalid data for Holt\'s Linear Trend calculation', 400);
  107. }
  108. // Adjust alpha and beta parameters
  109. [$alpha, $beta] = $this->adjustTrendParameters($weeklyDataArray, $alpha, $beta);
  110. // Calculate Holt's Linear Trend Forecast for next week
  111. $holtForecast = $this->holtLinearTrend($weeklyDataArray, $alpha, $beta);
  112. $expectedNewCompanies = round(end($holtForecast));
  113. // Calculate average weekly growth rate
  114. $totalWeeks = $startOfYear->diffInWeeks($today);
  115. $averageWeeklyGrowthRate = round(array_sum($weeklyPercentageChange) / $totalWeeks, 2);
  116. // Company Stats Overview Cards
  117. return [
  118. StatsOverviewWidget\Card::make("New Companies Forecast (Holt's Linear Trend)", $expectedNewCompanies),
  119. StatsOverviewWidget\Card::make('Average Weekly Growth Rate', $averageWeeklyGrowthRate . '%'),
  120. StatsOverviewWidget\Card::make('Personal Companies', Company::sum('personal_company')),
  121. ];
  122. }
  123. }