Fix hourly summary: default to today for realtime updates, add hour parameter for efficient updates
This commit is contained in:
@@ -200,14 +200,28 @@ openssl rand -hex 32
|
|||||||
Setup di aaPanel → Cron:
|
Setup di aaPanel → Cron:
|
||||||
|
|
||||||
```cron
|
```cron
|
||||||
# Daily summary (run at 1 AM every day)
|
# Daily summary (run at 1 AM every day, rekap kemarin)
|
||||||
0 1 * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/daily_summary.php
|
0 1 * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/daily_summary.php
|
||||||
|
|
||||||
# Hourly summary (run at 1 AM every day)
|
# Hourly summary - REALTIME UPDATE (run every hour, update jam yang baru saja berlalu)
|
||||||
0 1 * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/hourly_summary.php
|
# Contoh: jam 2:00 update jam 1:00, jam 3:00 update jam 2:00, dst
|
||||||
|
0 * * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/hourly_summary.php today $(date -d '1 hour ago' +\%H)
|
||||||
|
|
||||||
|
# Hourly summary - FINAL RECAP (run at 1 AM every day, rekap semua jam kemarin)
|
||||||
|
# Opsional: untuk memastikan semua jam kemarin sudah ter-rekap dengan benar
|
||||||
|
0 1 * * * cd /www/wwwroot/api.btekno.cloud/api && /www/server/php/83/bin/php bin/hourly_summary.php yesterday
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: Ganti `/www/server/php/83/bin/php` dengan path PHP yang sesuai di server Anda.
|
**Penjelasan:**
|
||||||
|
|
||||||
|
1. **Daily summary**: Rekap harian untuk kemarin (jalan jam 1 pagi)
|
||||||
|
2. **Hourly summary - REALTIME**: Update setiap jam untuk jam yang baru saja berlalu (untuk dashboard realtime)
|
||||||
|
3. **Hourly summary - FINAL RECAP**: Rekap final semua jam kemarin (opsional, untuk memastikan data lengkap)
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
- Ganti `/www/server/php/83/bin/php` dengan path PHP yang sesuai di server Anda
|
||||||
|
- Untuk update realtime, cron harus jalan **setiap jam** (`0 * * * *`)
|
||||||
|
- Script default ke `today` jika tidak ada argumen, jadi cocok untuk update realtime
|
||||||
|
|
||||||
## ✅ Verification
|
## ✅ Verification
|
||||||
|
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -185,17 +185,32 @@ php bin/daily_summary.php [date]
|
|||||||
|
|
||||||
### Hourly Summary
|
### Hourly Summary
|
||||||
```bash
|
```bash
|
||||||
php bin/hourly_summary.php [date]
|
# Update hari ini (default, untuk realtime)
|
||||||
# Default: yesterday
|
php bin/hourly_summary.php
|
||||||
|
|
||||||
|
# Rekap kemarin
|
||||||
|
php bin/hourly_summary.php yesterday
|
||||||
|
|
||||||
|
# Rekap tanggal tertentu
|
||||||
|
php bin/hourly_summary.php 2025-01-01
|
||||||
|
|
||||||
|
# Update jam tertentu saja (untuk efisiensi)
|
||||||
|
php bin/hourly_summary.php today 14 # Update jam 14 hari ini
|
||||||
|
php bin/hourly_summary.php 2025-01-01 13 # Update jam 13 tanggal tertentu
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cron Job Setup
|
### Cron Job Setup
|
||||||
```cron
|
```cron
|
||||||
# Daily summary (run at 1 AM)
|
# Daily summary (run at 1 AM, rekap kemarin)
|
||||||
0 1 * * * cd /path/to/api-btekno && php bin/daily_summary.php
|
0 1 * * * cd /path/to/api-btekno && php bin/daily_summary.php
|
||||||
|
|
||||||
# Hourly summary (run at 1 AM)
|
# Hourly summary - REALTIME (run every hour, update jam yang baru saja berlalu)
|
||||||
0 1 * * * cd /path/to/api-btekno && php bin/hourly_summary.php
|
# Contoh: jam 2:00 update jam 1:00, jam 3:00 update jam 2:00
|
||||||
|
0 * * * * cd /path/to/api-btekno && php bin/hourly_summary.php today $(date -d '1 hour ago' +\%H)
|
||||||
|
|
||||||
|
# Hourly summary - FINAL RECAP (run at 1 AM, rekap semua jam kemarin)
|
||||||
|
# Opsional: untuk memastikan semua jam kemarin sudah ter-rekap
|
||||||
|
0 1 * * * cd /path/to/api-btekno && php bin/hourly_summary.php yesterday
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔒 Security
|
## 🔒 Security
|
||||||
|
|||||||
@@ -7,17 +7,18 @@ declare(strict_types=1);
|
|||||||
* CLI script untuk generate hourly summary
|
* CLI script untuk generate hourly summary
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* php bin/hourly_summary.php [date]
|
* php bin/hourly_summary.php [date] [hour]
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
* php bin/hourly_summary.php 2025-01-01
|
* php bin/hourly_summary.php 2025-01-01 # Rekap semua jam untuk tanggal tertentu
|
||||||
* php bin/hourly_summary.php # default: yesterday
|
* php bin/hourly_summary.php # Default: hari ini (untuk update realtime)
|
||||||
|
* php bin/hourly_summary.php 2025-01-01 14 # Rekap jam 14 saja untuk tanggal tertentu
|
||||||
|
* php bin/hourly_summary.php today 13 # Rekap jam 13 hari ini
|
||||||
*
|
*
|
||||||
* Note:
|
* Note:
|
||||||
* Default menggunakan yesterday karena:
|
* - Default: hari ini (untuk update realtime via cron setiap jam)
|
||||||
* - Data hari ini mungkin belum lengkap (masih ada event yang masuk)
|
* - Untuk rekap final kemarin, gunakan: php bin/hourly_summary.php yesterday
|
||||||
* - Cron biasanya jalan di akhir hari untuk rekap hari sebelumnya
|
* - Untuk update jam tertentu saja, tambahkan parameter hour (0-23)
|
||||||
* - Lebih aman untuk rekap data yang sudah final
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Get project root directory
|
// Get project root directory
|
||||||
@@ -33,18 +34,43 @@ use App\Support\Database;
|
|||||||
// Load environment variables
|
// Load environment variables
|
||||||
AppConfig::loadEnv($rootPath);
|
AppConfig::loadEnv($rootPath);
|
||||||
|
|
||||||
// Get date from command line argument or use yesterday (deterministic)
|
// Get date from command line argument or use today (for realtime updates)
|
||||||
$date = $argv[1] ?? date('Y-m-d', strtotime('-1 day'));
|
$dateInput = $argv[1] ?? 'today';
|
||||||
|
|
||||||
|
// Handle special keywords
|
||||||
|
if ($dateInput === 'today') {
|
||||||
|
$date = date('Y-m-d');
|
||||||
|
} elseif ($dateInput === 'yesterday') {
|
||||||
|
$date = date('Y-m-d', strtotime('-1 day'));
|
||||||
|
} else {
|
||||||
|
$date = $dateInput;
|
||||||
|
}
|
||||||
|
|
||||||
// Validate date format
|
// Validate date format
|
||||||
$dateTime = DateTime::createFromFormat('Y-m-d', $date);
|
$dateTime = DateTime::createFromFormat('Y-m-d', $date);
|
||||||
if ($dateTime === false || $dateTime->format('Y-m-d') !== $date) {
|
if ($dateTime === false || $dateTime->format('Y-m-d') !== $date) {
|
||||||
echo "Error: Invalid date format. Expected Y-m-d (e.g., 2025-01-01)\n";
|
echo "Error: Invalid date format. Expected Y-m-d (e.g., 2025-01-01) or 'today'/'yesterday'\n";
|
||||||
echo "Usage: php bin/hourly_summary.php [date]\n";
|
echo "Usage: php bin/hourly_summary.php [date] [hour]\n";
|
||||||
echo " If date is omitted, defaults to yesterday\n";
|
echo " date: Y-m-d format, 'today', or 'yesterday' (default: today)\n";
|
||||||
|
echo " hour: 0-23 (optional, untuk update jam tertentu saja)\n";
|
||||||
|
echo "Examples:\n";
|
||||||
|
echo " php bin/hourly_summary.php # Update hari ini\n";
|
||||||
|
echo " php bin/hourly_summary.php yesterday # Rekap kemarin\n";
|
||||||
|
echo " php bin/hourly_summary.php 2025-01-01 14 # Rekap jam 14 tanggal tertentu\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get hour from command line (optional, 0-23)
|
||||||
|
$hour = null;
|
||||||
|
if (isset($argv[2])) {
|
||||||
|
$hourInput = (int) $argv[2];
|
||||||
|
if ($hourInput < 0 || $hourInput > 23) {
|
||||||
|
echo "Error: Hour must be between 0 and 23\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$hour = $hourInput;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get database connection
|
// Get database connection
|
||||||
$dbHost = AppConfig::get('DB_HOST', 'localhost');
|
$dbHost = AppConfig::get('DB_HOST', 'localhost');
|
||||||
@@ -63,12 +89,20 @@ try {
|
|||||||
$service = new HourlySummaryService($db);
|
$service = new HourlySummaryService($db);
|
||||||
|
|
||||||
// Run aggregation
|
// Run aggregation
|
||||||
echo "Processing hourly summary for date: {$date}\n";
|
if ($hour !== null) {
|
||||||
$result = $service->aggregateForDate($date);
|
echo "Processing hourly summary for date: {$date}, hour: {$hour}\n";
|
||||||
|
$result = $service->aggregateForDate($date, $hour);
|
||||||
echo "Success!\n";
|
echo "Success!\n";
|
||||||
echo "Date: {$result['date']}\n";
|
echo "Date: {$result['date']}\n";
|
||||||
echo "Rows processed: {$result['rows_processed']}\n";
|
echo "Hour: {$result['hour']}\n";
|
||||||
|
echo "Rows processed: {$result['rows_processed']}\n";
|
||||||
|
} else {
|
||||||
|
echo "Processing hourly summary for date: {$date} (all hours)\n";
|
||||||
|
$result = $service->aggregateForDate($date);
|
||||||
|
echo "Success!\n";
|
||||||
|
echo "Date: {$result['date']}\n";
|
||||||
|
echo "Rows processed: {$result['rows_processed']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ class HourlySummaryService
|
|||||||
* Aggregate hourly summary for a specific date
|
* Aggregate hourly summary for a specific date
|
||||||
*
|
*
|
||||||
* @param string $date Format: Y-m-d
|
* @param string $date Format: Y-m-d
|
||||||
* @return array ['rows_processed' => int, 'date' => string]
|
* @param int|null $hour Optional: 0-23, if provided only aggregate for this hour
|
||||||
|
* @return array ['rows_processed' => int, 'date' => string, 'hour' => int|null]
|
||||||
* @throws PDOException
|
* @throws PDOException
|
||||||
*/
|
*/
|
||||||
public function aggregateForDate(string $date): array
|
public function aggregateForDate(string $date, ?int $hour = null): array
|
||||||
{
|
{
|
||||||
// Validate date format
|
// Validate date format
|
||||||
$dateTime = \DateTime::createFromFormat('Y-m-d', $date);
|
$dateTime = \DateTime::createFromFormat('Y-m-d', $date);
|
||||||
@@ -31,6 +32,11 @@ class HourlySummaryService
|
|||||||
throw new \InvalidArgumentException('Invalid date format. Expected Y-m-d');
|
throw new \InvalidArgumentException('Invalid date format. Expected Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate hour if provided
|
||||||
|
if ($hour !== null && ($hour < 0 || $hour > 23)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid hour. Must be between 0 and 23');
|
||||||
|
}
|
||||||
|
|
||||||
$this->db->beginTransaction();
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -55,6 +61,17 @@ class HourlySummaryService
|
|||||||
AND e.gate_code = t.gate_code
|
AND e.gate_code = t.gate_code
|
||||||
AND e.category = t.category
|
AND e.category = t.category
|
||||||
WHERE DATE(e.event_time) = ?
|
WHERE DATE(e.event_time) = ?
|
||||||
|
";
|
||||||
|
|
||||||
|
$params = [$date];
|
||||||
|
|
||||||
|
// Add hour filter if provided
|
||||||
|
if ($hour !== null) {
|
||||||
|
$sql .= " AND HOUR(e.event_time) = ?";
|
||||||
|
$params[] = $hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= "
|
||||||
GROUP BY
|
GROUP BY
|
||||||
DATE(e.event_time),
|
DATE(e.event_time),
|
||||||
HOUR(e.event_time),
|
HOUR(e.event_time),
|
||||||
@@ -65,7 +82,7 @@ class HourlySummaryService
|
|||||||
";
|
";
|
||||||
|
|
||||||
$stmt = $this->db->prepare($sql);
|
$stmt = $this->db->prepare($sql);
|
||||||
$stmt->execute([$date]);
|
$stmt->execute($params);
|
||||||
$aggregated = $stmt->fetchAll();
|
$aggregated = $stmt->fetchAll();
|
||||||
|
|
||||||
$rowsProcessed = 0;
|
$rowsProcessed = 0;
|
||||||
@@ -103,7 +120,8 @@ class HourlySummaryService
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'rows_processed' => $rowsProcessed,
|
'rows_processed' => $rowsProcessed,
|
||||||
'date' => $date
|
'date' => $date,
|
||||||
|
'hour' => $hour
|
||||||
];
|
];
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user