데이터 사용량 계산을 통신사 과금 단위에 맞춤
This commit is contained in:
+52
-12
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user