feat: sam_stat 통계 DB 인프라 구축 (Phase 1)

- sam_stat DB 연결 설정 (config/database.php, .env)
- 메타 테이블 마이그레이션 (stat_definitions, stat_job_logs)
- dim_date 차원 테이블 + DimDateSeeder (2020~2030, 4018건)
- 기반 모델: BaseStatModel, StatDefinition, StatJobLog, DimDate
- 집계 커맨드: stat:aggregate-daily, stat:aggregate-monthly
- StatAggregatorService + StatDomainServiceInterface 골격

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 17:13:36 +09:00
parent 4f2a329e4e
commit c88048db67
13 changed files with 628 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Console\Commands;
use App\Services\Stats\StatAggregatorService;
use Carbon\Carbon;
use Illuminate\Console\Command;
class StatAggregateDailyCommand extends Command
{
protected $signature = 'stat:aggregate-daily
{--date= : 집계 대상 날짜 (YYYY-MM-DD, 기본: 전일)}
{--domain= : 특정 도메인만 집계 (sales, finance, production)}
{--tenant= : 특정 테넌트만 집계}';
protected $description = '일간 통계 집계 (sam_stat DB)';
public function handle(StatAggregatorService $aggregator): int
{
$date = $this->option('date')
? Carbon::parse($this->option('date'))
: Carbon::yesterday();
$domain = $this->option('domain');
$tenantId = $this->option('tenant') ? (int) $this->option('tenant') : null;
$this->info("📊 일간 통계 집계 시작: {$date->format('Y-m-d')}");
if ($domain) {
$this->info(" 도메인 필터: {$domain}");
}
if ($tenantId) {
$this->info(" 테넌트 필터: {$tenantId}");
}
try {
$result = $aggregator->aggregateDaily($date, $domain, $tenantId);
$this->info('✅ 일간 집계 완료:');
$this->info(" - 처리 테넌트: {$result['tenants_processed']}");
$this->info(" - 처리 도메인: {$result['domains_processed']}");
$this->info(" - 소요 시간: {$result['duration_ms']}ms");
if (! empty($result['errors'])) {
$this->warn(' ⚠️ 에러 발생: '.count($result['errors']).'건');
foreach ($result['errors'] as $error) {
$this->error(" - {$error}");
}
return self::FAILURE;
}
return self::SUCCESS;
} catch (\Throwable $e) {
$this->error("❌ 집계 실패: {$e->getMessage()}");
return self::FAILURE;
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Console\Commands;
use App\Services\Stats\StatAggregatorService;
use Carbon\Carbon;
use Illuminate\Console\Command;
class StatAggregateMonthlyCommand extends Command
{
protected $signature = 'stat:aggregate-monthly
{--year= : 집계 대상 연도 (기본: 전월 기준)}
{--month= : 집계 대상 월 (기본: 전월)}
{--domain= : 특정 도메인만 집계}
{--tenant= : 특정 테넌트만 집계}';
protected $description = '월간 통계 집계 (sam_stat DB)';
public function handle(StatAggregatorService $aggregator): int
{
$lastMonth = Carbon::now()->subMonth();
$year = $this->option('year') ? (int) $this->option('year') : $lastMonth->year;
$month = $this->option('month') ? (int) $this->option('month') : $lastMonth->month;
$domain = $this->option('domain');
$tenantId = $this->option('tenant') ? (int) $this->option('tenant') : null;
$this->info("📊 월간 통계 집계 시작: {$year}-".str_pad($month, 2, '0', STR_PAD_LEFT));
try {
$result = $aggregator->aggregateMonthly($year, $month, $domain, $tenantId);
$this->info('✅ 월간 집계 완료:');
$this->info(" - 처리 테넌트: {$result['tenants_processed']}");
$this->info(" - 처리 도메인: {$result['domains_processed']}");
$this->info(" - 소요 시간: {$result['duration_ms']}ms");
if (! empty($result['errors'])) {
$this->warn(' ⚠️ 에러 발생: '.count($result['errors']).'건');
foreach ($result['errors'] as $error) {
$this->error(" - {$error}");
}
return self::FAILURE;
}
return self::SUCCESS;
} catch (\Throwable $e) {
$this->error("❌ 집계 실패: {$e->getMessage()}");
return self::FAILURE;
}
}
}