데이터 사용량 계산을 통신사 과금 단위에 맞춤

This commit is contained in:
seo
2026-06-30 15:36:55 +09:00
parent d99dc97672
commit 783035deee
3 changed files with 94 additions and 20 deletions
+52 -12
View File
@@ -25,9 +25,9 @@ const DATA_MONTHLY_ADDON_FEE_KRW = 2200;
const RECEIVE_GAP_LIMIT_SEC = 60;
// SKT T world "잔여기본통화 정보" 기준 현재월 누적값.
// carrier_total_bytes / meter_adjusted_bytes 로 보정 배율을 역산해 로컬 송수신 계측값에 적용한다.
// carrier_total_bytes / meter_adjusted_bytes 로 보정 배율을 역산해 통신사 과금 단위 계측값에 적용한다.
const DATA_CURRENT_BILLING_USAGE_BYTES = [
'2026-06' => ['total_bytes' => 115207680, 'meter_adjusted_bytes' => 57853769, 'included_bytes' => 104857600, 'coupon_registered_at' => null],
'2026-06' => ['total_bytes' => 120064512, 'meter_adjusted_bytes' => 129148416, 'included_bytes' => 104857600, 'coupon_registered_at' => null],
];
function car_db(): PDO
@@ -44,10 +44,26 @@ function car_db(): PDO
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
db_ensure_data_usage_schema($pdo);
return $pdo;
}
function db_ensure_data_usage_schema(PDO $pdo): void
{
static $done = false;
if ($done) {
return;
}
$stmt = $pdo->query("SHOW COLUMNS FROM car_data_usage LIKE 'carrier_estimated_bytes'");
if (!$stmt || !$stmt->fetch()) {
$pdo->exec("ALTER TABLE car_data_usage ADD COLUMN carrier_estimated_bytes INT UNSIGNED NOT NULL DEFAULT 0 AFTER total_bytes");
}
$done = true;
}
function seconds_from_ts(?string $ts): ?int
{
if (!$ts) {
@@ -213,8 +229,9 @@ function monthly_data_usage(PDO $pdo): array
$receivedBytes = $usage['received_bytes'];
$totalBytes = $usage['total_bytes'];
$adjustedTotalBytes = $usage['adjusted_total_bytes'];
$remainingBytes = max(DATA_MONTHLY_INCLUDED_BYTES - $adjustedTotalBytes, 0);
$overBytes = max($adjustedTotalBytes - DATA_MONTHLY_INCLUDED_BYTES, 0);
$carrierAdjustedTotalBytes = $usage['carrier_adjusted_total_bytes'];
$remainingBytes = max(DATA_MONTHLY_INCLUDED_BYTES - $carrierAdjustedTotalBytes, 0);
$overBytes = max($carrierAdjustedTotalBytes - DATA_MONTHLY_INCLUDED_BYTES, 0);
$overUnits = $overBytes > 0 ? (int)ceil($overBytes / DATA_BILLING_UNIT_BYTES) : 0;
$overFeeKrw = $overUnits * DATA_BILLING_UNIT_KRW;
@@ -229,9 +246,9 @@ function monthly_data_usage(PDO $pdo): array
$measuredSeconds = max(1, $now->getTimestamp() - $projectionStart);
$currentBilling = current_billing_usage($monthStart->format('Y-m'));
$billingScale = billing_meter_scale($currentBilling);
$billingCurrentBytes = (int)round($adjustedTotalBytes * $billingScale);
$projectedBytes = (int)round($adjustedTotalBytes / $measuredSeconds * $monthSeconds);
$billingProjectedBytes = $adjustedTotalBytes > 0
$billingCurrentBytes = (int)round($carrierAdjustedTotalBytes * $billingScale);
$projectedBytes = (int)round($carrierAdjustedTotalBytes / $measuredSeconds * $monthSeconds);
$billingProjectedBytes = $carrierAdjustedTotalBytes > 0
? max((int)round($projectedBytes * $billingScale), $billingCurrentBytes)
: 0;
$calibration = billing_calibration($currentBilling, $billingScale);
@@ -249,7 +266,7 @@ function monthly_data_usage(PDO $pdo): array
$billingProjectedOverFeeKrw = $billingProjectedOverUnits * DATA_BILLING_UNIT_KRW;
$fixedFeeKrw = DATA_MONTHLY_BASE_FEE_KRW + DATA_MONTHLY_ADDON_FEE_KRW;
$dailyRecommendedBytes = (int)floor(DATA_MONTHLY_INCLUDED_BYTES / max(1, $daysInMonth));
$estimatedTodayBytes = (int)round($todayUsage['adjusted_total_bytes'] * $billingScale);
$estimatedTodayBytes = (int)round($todayUsage['carrier_adjusted_total_bytes'] * $billingScale);
return [
'month' => $monthStart->format('Y-m'),
@@ -265,15 +282,18 @@ function monthly_data_usage(PDO $pdo): array
'received_bytes' => $receivedBytes,
'total_bytes' => $totalBytes,
'adjusted_total_bytes' => $adjustedTotalBytes,
'carrier_adjusted_total_bytes' => $carrierAdjustedTotalBytes,
'carrier_adjusted_failure_bytes' => $usage['carrier_adjusted_failure_bytes'],
'billing_sent_bytes' => (int)round($sentBytes * $billingScale),
'billing_received_bytes' => (int)round($receivedBytes * $billingScale),
'billing_adjusted_failure_bytes' => (int)round($usage['adjusted_failure_bytes'] * $billingScale),
'billing_adjusted_failure_bytes' => (int)round($usage['carrier_adjusted_failure_bytes'] * $billingScale),
'connected_failure_count' => $usage['connected_failure_count'],
'connect_failure_count' => $usage['connect_failure_count'],
'receive_gap_excluded_count' => $usage['receive_gap_excluded_count'],
'adjusted_failure_bytes' => $usage['adjusted_failure_bytes'],
'today_total_bytes' => $todayUsage['total_bytes'],
'today_adjusted_total_bytes' => $todayUsage['adjusted_total_bytes'],
'today_carrier_adjusted_total_bytes' => $todayUsage['carrier_adjusted_total_bytes'],
'today_sample_count' => $todayUsage['sample_count'],
'today_connected_failure_count' => $todayUsage['connected_failure_count'],
'today_connect_failure_count' => $todayUsage['connect_failure_count'],
@@ -365,6 +385,16 @@ function usage_success_averages(PDO $pdo, DateTimeInterface $start, DateTimeInte
];
}
function carrier_billing_bytes(int $payloadBytes): int
{
$payloadBytes = max(0, $payloadBytes);
if ($payloadBytes <= 0) {
return 0;
}
return (int)(ceil($payloadBytes / DATA_BILLING_UNIT_BYTES) * DATA_BILLING_UNIT_BYTES);
}
function calibrated_usage_range(PDO $pdo, DateTimeInterface $start, DateTimeInterface $end): array
{
$averages = usage_success_averages($pdo, $start, $end);
@@ -379,6 +409,8 @@ function calibrated_usage_range(PDO $pdo, DateTimeInterface $start, DateTimeInte
$adjustedTotalBytes = 0;
$adjustedFailureBytes = 0;
$carrierAdjustedTotalBytes = 0;
$carrierAdjustedFailureBytes = 0;
$connectedFailureCount = 0;
$connectFailureCount = 0;
$receiveGapExcludedCount = 0;
@@ -391,7 +423,7 @@ function calibrated_usage_range(PDO $pdo, DateTimeInterface $start, DateTimeInte
try {
$stmt = $pdo->prepare(""
. "SELECT ts, cmd, sent_bytes, received_bytes, total_bytes, tcp_ok "
. "SELECT ts, cmd, sent_bytes, received_bytes, total_bytes, carrier_estimated_bytes, tcp_ok "
. "FROM car_data_usage "
. "WHERE id='car' AND ts >= :start_ts AND ts < :end_ts "
. "ORDER BY ts ASC"
@@ -423,6 +455,9 @@ function calibrated_usage_range(PDO $pdo, DateTimeInterface $start, DateTimeInte
if ($tcpOk) {
$adjustedTotalBytes += $total;
$carrierAdjustedTotalBytes += (int)($row['carrier_estimated_bytes'] ?? 0) > 0
? (int)$row['carrier_estimated_bytes']
: carrier_billing_bytes($total);
if ($rowTs !== false) {
$lastSuccessfulTs = $rowTs;
}
@@ -454,6 +489,9 @@ function calibrated_usage_range(PDO $pdo, DateTimeInterface $start, DateTimeInte
$adjustedTotalBytes += $estimatedBytes;
$adjustedFailureBytes += $estimatedBytes;
$carrierEstimatedBytes = carrier_billing_bytes($estimatedBytes);
$carrierAdjustedTotalBytes += $carrierEstimatedBytes;
$carrierAdjustedFailureBytes += $carrierEstimatedBytes;
}
} finally {
if ($stmt instanceof PDOStatement) {
@@ -468,6 +506,8 @@ function calibrated_usage_range(PDO $pdo, DateTimeInterface $start, DateTimeInte
'total_bytes' => $totalBytes,
'adjusted_total_bytes' => $adjustedTotalBytes,
'adjusted_failure_bytes' => $adjustedFailureBytes,
'carrier_adjusted_total_bytes' => $carrierAdjustedTotalBytes,
'carrier_adjusted_failure_bytes' => $carrierAdjustedFailureBytes,
'connected_failure_count' => $connectedFailureCount,
'connect_failure_count' => $connectFailureCount,
'receive_gap_excluded_count' => $receiveGapExcludedCount,
@@ -519,8 +559,8 @@ function billing_calibration(?array $currentBilling, float $billingScale): array
return [
'mode' => $source,
'note' => $currentBilling
? 'Carrier portal current usage is used to reverse-calculate a billing scale for metered local sent/received bytes. Metered local usage remains available separately.'
: 'Metered usage includes successful requests and connected timeouts within 60 seconds of the last successful receive. Pure connect failures and longer receive gaps are excluded.',
? 'Carrier portal current usage is used to reverse-calculate a billing scale for 0.5KB rounded TCP session usage. Payload metered usage remains available separately.'
: 'Carrier-like usage rounds each billable TCP session to the 0.5KB billing unit. Pure connect failures and longer receive gaps are excluded.',
'current_billing' => $currentBilling,
'billing_meter_scale' => round($billingScale, 6),
'projected_total_bytes' => 0,