0
0
Lập trình
Admin Team
Admin Teamtechmely

Xây Dựng Máy Tính ETG Thực Tế: Từ BAC đến Cửa Sổ Phát Hiện ETG

Đăng vào 3 tháng trước

• 9 phút đọc

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:

  1. Grams rượu từ lượng đồ uống tiêu thụ.
  2. Đường cong BAC thông qua mô hình tương tự như Widmark với tỷ lệ loại bỏ β cố định.
  3. 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 Copy
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ố K trong peakBac chuyể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 Copy
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 Copy
{
  "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.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào