Skip to content
Closed
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions api/dashboard_charts.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
/* tek eksende gosterilebilir). */
/* ------------------------------------------------------------------ */

$months = [];
for ($i = 11; $i >= 0; $i--) {
$months[date('Y-m', strtotime("-{$i} month"))] = ['opened' => 0, 'closed' => 0];
}
$since = date('Y-m-01', strtotime('-11 month'));
// Takvim ayi penceresi ayin 1'ine sabitlenerek kurulur (bkz. recent_months):
// strtotime('-N month') ay sonu gunlerinde bir sonraki aya tasiyordu.
$monthKeys = recent_months(12);
$months = array_fill_keys($monthKeys, ['opened' => 0, 'closed' => 0]);
$since = $monthKeys[0] . '-01';

$rows = $pdo->prepare(
"SELECT DATE_FORMAT(created_at, '%Y-%m') AS ay, COUNT(*) AS adet
Expand Down
30 changes: 30 additions & 0 deletions includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,36 @@ function days_until(?string $date): ?int
return (int)floor(($t - strtotime(date('Y-m-d'))) / 86400);
}

/**
* Son $count takvim ayinin 'Y-m' anahtarlarini eskiden yeniye sirali dondurur.
* Pencerenin alt siniri (SQL :since degeri) ilk anahtarin '-01' ekidir.
*
* NEDEN AYRI BIR FONKSIYON: strtotime('-N month') gun tasmasini kirmaz;
* ayin 29-31'inde hedef ayda o gun yoksa sonuc bir SONRAKI aya taser.
* Dashboard trend penceresi (api/dashboard_charts.php) bu yuzden ay sonu
* isteklerinde 12 yerine 7-11 ay uretiyor, kayip aylarin riskleri hic
* sayilmiyordu. Burada ay aritmetigi her zaman ayin 1'ine sabitlenir;
* 1. gunden cikarilan ay asla tasmaz.
*
* @param int $count Kac ay dondurecek (1 = yalnizca bulunulan ay).
* @param int|null $baseTs Pencerenin bittigi an (null = simdi).
* @return list<string> 'YYYY-MM' anahtarlari, eskiden yeniye.
*/
function recent_months(int $count, ?int $baseTs = null): array
{
if ($count < 1) {
return [];
}
$anchor = (new DateTimeImmutable(date('Y-m-d H:i:s', $baseTs ?? time())))
->modify('first day of this month midnight');

$out = [];
for ($i = $count - 1; $i >= 0; $i--) {
$out[] = $anchor->modify("-{$i} months")->format('Y-m');
}
return $out;
}

function str_limit(?string $text, int $limit = 80): string
{
$text = trim((string)$text);
Expand Down
12 changes: 6 additions & 6 deletions reports/executive_summary.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@
LIMIT 8"
)->fetchAll();

/* Son 6 ay: açılan ve kapanan */
$trend = [];
for ($i = 5; $i >= 0; $i--) {
$trend[date('Y-m', strtotime("-{$i} month"))] = ['acilan' => 0, 'kapanan' => 0];
}
$since = date('Y-m-01', strtotime('-5 month'));
/* Son 6 ay: açılan ve kapanan — takvim ayı penceresi ayın 1'ine
sabitlenerek kurulur (bkz. recent_months): strtotime('-N month')
ay sonu günlerinde bir sonraki aya taşıyordu. */
$monthKeys = recent_months(6);
$trend = array_fill_keys($monthKeys, ['acilan' => 0, 'kapanan' => 0]);
$since = $monthKeys[0] . '-01';

$q = db()->prepare("SELECT DATE_FORMAT(created_at,'%Y-%m') AS ay, COUNT(*) AS n
FROM risks WHERE deleted_at IS NULL AND created_at >= :s GROUP BY ay");
Expand Down
147 changes: 147 additions & 0 deletions tools/trend_months_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);

/**
* RiskOps - Ay bazli trend pencereleri regresyon testi
* Calistirma: php tools/trend_months_test.php
*
* Bagimlilik YOK: veritabani ve bootstrap gerekmez, yalnizca
* includes/functions.php icindeki recent_months() test edilir.
*
* Kapsanan tuketiciler:
* api/dashboard_charts.php (12 aylik trend penceresi)
* reports/executive_summary.php (6 aylik "Son 6 Ay" penceresi)
*
* REGRESYON ARKAPLANI: pencereler bir zamanlar
* date('Y-m', strtotime("-{$i} month"))
* ile kuruluyordu. strtotime ay aritmetiginde gun tasmasini kirmaz;
* ayin 29-31'inde hedef ayda o gun yoksa sonuc bir SONRAKI aya tasiyor,
* ayni 'Y-m' anahtari iki kez uretiliyor ve pencere kuculuyordu
* (dashboard 7-11 aya, yonetici ozeti 3-5 aya dusuyordu; kayip
* aylarin riskleri hic sayilmiyordu).
*/

if (PHP_SAPI !== 'cli') {
http_response_code(403);
exit('CLI only.');
}

require_once __DIR__ . '/../includes/functions.php';

$PASS = 0;
$FAIL = 0;

function check(string $label, bool $ok, string $detail = ''): void
{
global $PASS, $FAIL;
if ($ok) {
$PASS++;
printf(" [ OK ] %-52s %s\n", $label, $detail);
} else {
$FAIL++;
printf(" [FAIL] %-52s %s\n", $label, $detail);
}
}

function section(string $title): void
{
echo "\n" . str_repeat('-', 72) . "\n " . $title . "\n" . str_repeat('-', 72) . "\n";
}

/**
* ORACLE — beklenen ay listesi mktime() ay normalizasyonuyla hesaplanir
* (recent_months icindeki kodun bagimsizi).
*/
function expected_months(int $baseTs, int $count): array
{
[$y, $m] = array_map('intval', explode('-', date('Y-m', $baseTs)));
$out = [];
for ($k = $count - 1; $k >= 0; $k--) {
$out[] = date('Y-m', mktime(1, 1, 1, $m - $k, 1, $y));
}
return $out;
}

echo "\n=============== Trend Month Window Regression Test ===============";

/* ------------------------------------------------------------------ */
section('1. Ay sonu gunlerinde pencere (regresyon)');
/* ------------------------------------------------------------------ */

// Hatali davranisin kanitlandigi gunler: 29-31 (hedef ay kisa ise tasma).
$probes = [
'2026-05-31', '2026-03-31', '2026-08-31', '2025-12-31',
'2026-01-31', '2026-01-30', '2024-02-29', '2026-02-28',
'2026-10-31', '2026-04-30', '2025-11-30', '2026-12-31',
];

foreach ($probes as $d) {
$bts = strtotime($d . ' 12:00:00');
$got = recent_months(12, $bts);
check(
"12 ay: {$d}",
$got === expected_months($bts, 12) && count(array_unique($got)) === 12,
count($got) . ' ay'
);
}

/* ------------------------------------------------------------------ */
section('2. Sinir ve ikincil dallar');
/* ------------------------------------------------------------------ */

$now = time();
check('bugun (varsayilan taban): 12 ay',
recent_months(12) === expected_months($now, 12));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: At a month rollover, recent_months(12) can read a later month than $now, making this regression test fail intermittently; the null/default check has the same race. Use one stable base timestamp for the assertions, or explicitly allow the bounded rollover case.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/trend_months_test.php, line 94:

<comment>At a month rollover, `recent_months(12)` can read a later month than `$now`, making this regression test fail intermittently; the null/default check has the same race. Use one stable base timestamp for the assertions, or explicitly allow the bounded rollover case.</comment>

<file context>
@@ -0,0 +1,147 @@
+
+$now = time();
+check('bugun (varsayilan taban): 12 ay',
+    recent_months(12) === expected_months($now, 12));
+check('taban null acik gecilirse ayni sonuc',
+    recent_months(12, null) === recent_months(12));
</file context>

check('taban null acik gecilirse ayni sonuc',
recent_months(12, null) === recent_months(12));
check('count=1 yalnizca bulunulan ay',
recent_months(1, $now) === [date('Y-m', $now)]);
check('count=0 bos liste', recent_months(0, $now) === []);
check('count negatif bos liste', recent_months(-3, $now) === []);
check('count=13 yil sinirini asar',
recent_months(13, strtotime('2026-01-15 12:00:00'))
=== expected_months(strtotime('2026-01-15 12:00:00'), 13));
check('count=6 yonetici ozeti penceresi (2026-05-31 taban)',
recent_months(6, strtotime('2026-05-31 12:00:00'))
=== expected_months(strtotime('2026-05-31 12:00:00'), 6));
check('anahtarlar eskiden yeniye sirali',
recent_months(12, $now) === array_values(array_sort(recent_months(12, $now))));

/* SQL :since degeri: en eski ayin 1'i (api/dashboard_charts.php tuketimi) */
$keys = recent_months(12, strtotime('2026-05-31 12:00:00'));
check('since = en eski ayin 1\'i (2026-05-31 taban)',
$keys[0] . '-01' === '2025-06-01', $keys[0] . '-01');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This since check only verifies the helper output, so a future dashboard regression that keeps recent_months(12) but uses the wrong $since boundary will still pass. Assert the dashboard assignment or extract the boundary calculation into testable code.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/trend_months_test.php, line 113:

<comment>This `since` check only verifies the helper output, so a future dashboard regression that keeps `recent_months(12)` but uses the wrong `$since` boundary will still pass. Assert the dashboard assignment or extract the boundary calculation into testable code.</comment>

<file context>
@@ -0,0 +1,147 @@
+/* SQL :since degeri: en eski ayin 1'i (api/dashboard_charts.php tuketimi) */
+$keys = recent_months(12, strtotime('2026-05-31 12:00:00'));
+check('since = en eski ayin 1\'i (2026-05-31 taban)',
+    $keys[0] . '-01' === '2025-06-01', $keys[0] . '-01');
+
+/* ------------------------------------------------------------------ */
</file context>


/* ------------------------------------------------------------------ */
section('3. Uc nokta baglantisi');
/* ------------------------------------------------------------------ */

$src = (string)file_get_contents(__DIR__ . '/../api/dashboard_charts.php');
check('api/dashboard_charts.php recent_months() kullaniyor',
str_contains($src, 'recent_months(12)'));
check('dashboard bozuk strtotime("-N month") ifadesi kaldirildi',
!str_contains($src, 'strtotime("-{'));

$srcExec = (string)file_get_contents(__DIR__ . '/../reports/executive_summary.php');
check('reports/executive_summary.php recent_months() kullaniyor',
str_contains($srcExec, 'recent_months(6)'));
check('yonetici ozeti bozuk strtotime ay ifadeleri kaldirildi',
!str_contains($srcExec, 'strtotime("-{') && !str_contains($srcExec, "strtotime('-5 month')"));

/* ------------------------------------------------------------------ */

echo "\n" . str_repeat('-', 72) . "\n";
printf("Sonuc: %d OK, %d FAIL\n", $PASS, $FAIL);
if ($FAIL > 0) {
echo "TREND MONTH WINDOW TEST: FAIL\n";
exit(1);
}
echo "TREND MONTH WINDOW TEST: PASS\n";
exit(0);

/** Kucukten buyuge siralar (strcmp) — 'Y-m' anahtarlari icin yeterli. */
function array_sort(array $a): array
{
usort($a, static fn(string $x, string $y) => strcmp($x, $y));
return $a;
}