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));
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');

/* ------------------------------------------------------------------ */
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)'));

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: If executive_summary.php keeps recent_months(6) but regresses the :since boundary, this test still passes. Assert that the consumer derives $since from $monthKeys[0] . '-01' (or otherwise exercise that boundary) so the reported fix is covered.

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 127:

<comment>If `executive_summary.php` keeps `recent_months(6)` but regresses the `:since` boundary, this test still passes. Assert that the consumer derives `$since` from `$monthKeys[0] . '-01'` (or otherwise exercise that boundary) so the reported fix is covered.</comment>

<file context>
@@ -0,0 +1,147 @@
+
+$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')"));
</file context>

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;
}