Giới thiệu
Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng một máy tính ETG (Ethyl Glucuronide) thực tế, giúp ước lượng thời gian mà ETG có thể được phát hiện trong cơ thể sau khi tiêu thụ rượu. Đây là một công cụ hữu ích cho những ai muốn biết khả năng phát hiện của ETG sau khi uống rượu. Máy tính này sẽ kết hợp mô hình BAC (Blood Alcohol Content) và độ trễ phát hiện ETG để đưa ra kết quả chính xác.
Mô hình tổng quát
Chúng ta sẽ mô hình hóa ba yếu tố chính:
- Grams rượu từ lượng đồ uống tiêu thụ.
- Đường cong BAC thông qua mô hình tương tự như Widmark với tỷ lệ loại bỏ
βcố định. - Cửa sổ phát hiện ETG như một phép tính dựa trên tổng lượng rượu tiêu thụ và BAC đỉnh điểm.
Ý tưởng chính (đơn giản hóa):
- Chuyển đổi lượng đồ uống → rượu nguyên chất (grams).
- Tính toán ly rượu chuẩn = grams / 14 (theo quy chuẩn Mỹ).
- Ước lượng BAC đỉnh điểm bằng phương pháp Widmark và sau đó áp dụng tỷ lệ loại bỏ cố định
β(g/dL theo giờ). - Ước lượng độ trễ phát hiện ETG (số giờ sau khi BAC trở về ~0). Chúng ta sẽ sử dụng một phép tính dựa trên dữ liệu:
tail = clamp(24 + 6 * stdDrinks, 24, 80)- Bạn có thể điều chỉnh các hằng số này với dữ liệu thực tế hoặc tập dữ liệu xác thực.
Tại sao lại sử dụng phép tính? Bởi vì thực tế, độ trễ phát hiện ETG phụ thuộc vào các yếu tố như ngưỡng của phòng thí nghiệm, tình trạng hydrat hóa, chức năng thận, và các tiếp xúc ngẫu nhiên. Một phép toán "xác định" không phải là điều hợp lý trong lâm sàng. Phép tính cho phép bạn đưa ra một khoảng, không phải là một lời hứa.
Giả định và các hằng số
- Tỷ lệ nước trong cơ thể (
r) ~ 0.68 cho nam, 0.55 cho nữ (có thể điều chỉnh). - Tỷ lệ loại bỏ (
β) ≈ 0.015 g/dL/giờ (trung bình thường dùng). - Mật độ ethanol
ρ≈ 0.789 g/mL. - Ly rượu chuẩn (Mỹ): 14 g ethanol.
Cài đặt JavaScript / TypeScript
typescript
type Sex = "male" | "female";
interface Drink {
/** Khối lượng tính bằng milliliters */
ml: number;
/** ABV dưới dạng phần trăm (ví dụ: 5 cho 5%) */
abvPct: number;
}
interface EtgInput {
weightKg: number;
sex: Sex;
/** Thời gian uống lần đầu tiên tính bằng ms kể từ epoch. Tùy chọn, dùng để đánh dấu thời gian */
startedAtMs?: number;
/** Thời gian uống tính bằng phút */
durationMin: number;
drinks: Drink[];
/** Tỷ lệ loại bỏ tính bằng g/dL/giờ; mặc định là 0.015 */
beta?: number;
}
interface EtgResult {
totalGrams: number;
standardDrinks: number;
peakBac_gPerDl: number;
timeToZeroHours: number;
etgTailHours: number;
/** Tổng số giờ từ ngụm cuối cùng cho đến khi ETG dự kiến không phát hiện được */
expectedUndetectableHours: number;
/** Từ khóa tuyệt đối nếu startedAtMs được cung cấp */
expectedUndetectableAtMs?: number;
}
/** Chuyển đổi danh sách đồ uống sang grams ethanol nguyên chất */
export function gramsOfAlcohol(drinks: Drink[]): number {
const ethanolDensity = 0.789; // g/mL
return drinks.reduce((sum, d) => {
const pureMl = d.ml * (d.abvPct / 100);
return sum + pureMl * ethanolDensity;
}, 0);
}
/** Tính ly rượu chuẩn (Mỹ) */
export function toStandardDrinks(grams: number): number {
return grams / 14;
}
/** Widmark r theo giới tính (có thể ghi đè nếu cần) */
export function widmarkR(sex: Sex): number {
return sex === "male" ? 0.68 : 0.55;
}
/**
* Ước lượng BAC đỉnh điểm (g/dL) vào cuối khoảng thời gian uống.
* Đơn giản hóa: tất cả lượng tiêu thụ được phân bổ đều trong khoảng thời gian, không có độ trễ hấp thụ.
*/
export function peakBac({
totalGrams,
weightKg,
sex,
durationMin,
beta = 0.015,
}: {
totalGrams: number;
weightKg: number;
sex: Sex;
durationMin: number;
beta?: number;
}): number {
const r = widmarkR(sex);
const K = 1.2; // tỷ lệ để ước lượng g/dL từ đầu vào theo hệ mét
const tHours = Math.max(0, durationMin / 60);
// BAC vào cuối khoảng thời gian uống
const bac = Math.max(0, (totalGrams / (r * weightKg)) * K - beta * tHours);
return bac;
}
/** Giờ để chuyển hóa xuống ~0 g/dL từ một BAC nhất định sử dụng loại bỏ beta */
export function hoursToZero(bac_gPerDl: number, beta = 0.015): number {
return bac_gPerDl <= 0 ? 0 : bac_gPerDl / beta;
}
/**
* Độ trễ ETG sau khi BAC ≈ 0.
* Giới hạn giữa 24h và 80h, được điều chỉnh bởi tổng ly rượu chuẩn.
*/
export function etgTailHours(stdDrinks: number): number {
const tail = 24 + 6 * stdDrinks;
return Math.max(24, Math.min(80, tail));
}
/**
* Ước lượng ETG: từ NGỤM CUỐI cùng đến thời gian dự kiến không phát hiện được.
* Giả sử ngụm cuối cùng vào (startedAt + duration).
*/
export function estimateEtgWindow(input: EtgInput): EtgResult {
const totalGrams = gramsOfAlcohol(input.drinks);
const standardDrinks = toStandardDrinks(totalGrams);
const peak = peakBac({
totalGrams,
weightKg: input.weightKg,
sex: input.sex,
durationMin: input.durationMin,
beta: input.beta,
});
const timeZero = hoursToZero(peak, input.beta ?? 0.015);
const tail = etgTailHours(standardDrinks);
const expectedUndetectableHours = tail; // từ ngụm cuối cùng
const res: EtgResult = {
totalGrams,
standardDrinks,
peakBac_gPerDl: peak,
timeToZeroHours: timeZero,
etgTailHours: tail,
expectedUndetectableHours,
};
if (input.startedAtMs !== undefined) {
const lastSipMs = input.startedAtMs + input.durationMin * 60_000;
res.expectedUndetectableAtMs = lastSipMs + expectedUndetectableHours * 3_600_000;
}
return res;
}
// ------------------- Ví dụ -------------------
if (typeof require !== "undefined" && require.main === module) {
// Ví dụ: 3 chai bia 330 mL @ 5% trong 2 giờ, 75 kg nam
const result = estimateEtgWindow({
weightKg: 75,
sex: "male",
durationMin: 120,
drinks: [
{ ml: 330, abvPct: 5 },
{ ml: 330, abvPct: 5 },
{ ml: 330, abvPct: 5 },
],
startedAtMs: Date.now(),
});
console.log(result);
}
Lưu ý về việc hiệu chỉnh
- Hằng số
KtrongpeakBacchuyển đổi các đơn vị theo hệ mét sang thang g/dL thông thường. Bạn có thể điều chỉnh điều này bằng cách sử dụng các trường hợp tham chiếu hoặc tập dữ liệu công khai. βcó thể thay đổi (≈ 0.010–0.020 g/dL/h). Hãy xem xét việc giới hạn đầu ra là khoảng khi bạn thay đổiβ±20%.- Bạn cũng có thể bao gồm độ trễ hấp thụ (ví dụ, đỉnh ~30–60 phút sau khi uống) bằng cách bước thời gian cho đường cong BAC (xem phiên bản Python bên dưới).
Phiên bản Python (với bước thời gian đơn giản)
python
from dataclasses import dataclass
from typing import List, Optional
ETHANOL_DENSITY_G_PER_ML = 0.789 # g/mL
@dataclass
class Drink:
ml: float # khối lượng tính bằng mL
abv_pct: float # ABV tính bằng %
@dataclass
class EtgInput:
weight_kg: float
sex: str # "male" hoặc "female"
duration_min: float
drinks: List[Drink]
beta: float = 0.015 # g/dL mỗi giờ
started_at_ms: Optional[int] = None
@dataclass
class EtgResult:
total_grams: float
standard_drinks: float
peak_bac_g_per_dl: float
time_to_zero_hours: float
etg_tail_hours: float
expected_undetectable_hours: float
expected_undetectable_at_ms: Optional[int]
def widmark_r(sex: str) -> float:
return 0.68 if sex.lower() == "male" else 0.55
def grams_of_alcohol(drinks: List[Drink]) -> float:
grams = 0.0
for d in drinks:
grams += d.ml * (d.abv_pct / 100.0) * ETHANOL_DENSITY_G_PER_ML
return grams
def to_standard_drinks(grams: float) -> float:
return grams / 14.0
def etg_tail_hours(std_drinks: float) -> float:
tail = 24 + 6 * std_drinks
return max(24.0, min(80.0, tail))
def simulate_bac_peak(total_grams: float, weight_kg: float, sex: str, duration_min: float, beta: float = 0.015) -> float:
"""
Mô phỏng thô: phân phối đều lượng tiêu thụ trong khoảng thời gian và áp dụng loại bỏ.
Trả về ước lượng BAC đỉnh (g/dL).
"""
r = widmark_r(sex)
K = 1.2 # tỷ lệ để ước lượng từ hệ mét sang g/dL
steps = max(1, int(duration_min))
grams_per_min = total_grams / steps
bac = 0.0
peak = 0.0
for minute in range(steps):
bac += (grams_per_min / (r * weight_kg)) * K
bac = max(0.0, bac - (beta / 60.0))
peak = max(peak, bac)
return peak
def estimate_etg_window(inp: EtgInput) -> EtgResult:
total_grams = grams_of_alcohol(inp.drinks)
std_drinks = to_standard_drinks(total_grams)
peak = simulate_bac_peak(total_grams, inp.weight_kg, inp.sex, inp.duration_min, inp.beta)
time_zero = peak / inp.beta if inp.beta > 0 else float('inf')
tail = etg_tail_hours(std_drinks)
expected_undetectable_hours = tail
expected_at = None
if inp.started_at_ms is not None:
last_sip_ms = inp.started_at_ms + int(inp.duration_min * 60_000)
expected_at = last_sip_ms + int(expected_undetectable_hours * 3_600_000)
return EtgResult(
total_grams=total_grams,
standard_drinks=std_drinks,
peak_bac_g_per_dl=peak,
time_to_zero_hours=time_zero,
etg_tail_hours=tail,
expected_undetectable_hours=expected_undetectable_hours,
expected_undetectable_at_ms=expected_at,
)
if __name__ == "__main__":
ex = EtgInput(
weight_kg=75.0,
sex="male",
duration_min=120,
drinks=[Drink(ml=330, abv_pct=5.0) for _ in range(3)]
)
print(estimate_etg_window(ex))
✅ UX Thực Tiễn: trả về khoảng
Đối với sản xuất, đừng trả về một con số duy nhất. Hãy truyền đạt sự không chắc chắn để tạo ra một khoảng thấp-cao bằng cách thay đổi các giả định:
β ∈ [0.012, 0.018] g/dL/h- Thay đổi
r± 0.03 - Hệ số độ trễ (ví dụ,
24 + 4–8 * stdDrinks)
Trả về một cái gì đó như:
json
{
"expectedUndetectableHours": { "low": 36, "high": 72 },
"explanations": [
"Thay đổi tỷ lệ loại bỏ β",
"Làm tròn đến 6 giờ gần nhất để rõ ràng"
]
}
🔬 Danh sách kiểm tra xác thực
- So sánh với các trường hợp tham chiếu và điều chỉnh
K, hệ số độ trễ. - Cung cấp bộ chọn ngưỡng phòng thí nghiệm (ví dụ, 100 vs 500 ng/mL) để điều chỉnh độ trễ (ngưỡng thấp hơn ⇒ độ trễ dài hơn).
- Thêm cảnh báo về hydrat hóa / tiếp xúc nhỏ (các sản phẩm khử trùng, nước súc miệng, v.v.).
- Thêm kiểm tra đơn vị cho các trường hợp biên (khối lượng cơ thể rất nhỏ/lớn, uống rượu liên tục so với uống rời).
📎 Máy tính trực tuyến
Bạn muốn xem một triển khai hoạt động trong thực tế? Hãy thử: etgcalculatoronline.com
📚 Giấy phép
MIT cho các đoạn mã trên. Vui lòng giữ ghi chú nếu bạn xuất bản các sản phẩm phái sinh.