The method

How It Works

Detailed explanations of PepCycle's product-type-aware consumption, concentration and syringe math where applicable, forecasts, schedules, and notification timing.

Use the public calculator to run single-vial and fixed-ratio blend calculations without signing in. For quick answers see the FAQ. For a step-by-step walkthrough see the User Guide.

Concentration & syringe math

This section applies to injected products. Lyophilized products use BAC water and reconstitution math. Oil-based products track remaining ml separately from active ingredient mass and convert a scheduled dose through mg/ml into injection volume and syringe marks. Oral products convert the dose through mg per tablet and track remaining tablets without BAC, reconstitution, or syringe calculations.

Step 1 — Calculate concentration

When you add BAC water to a vial, you record the volume added. Combined with the total vial size, this gives the concentration:

concentration = vial_size / bac_water_ml
(result is in the same unit as vial_size, per ml)
Example:5 mg vial + 2 ml BAC water → 5 / 2 = 2.5 mg/ml

Step 2 — Amount per syringe mark

Insulin syringes are calibrated in IU marks. U-100 syringes have 100 marks per ml; U-40 syringes have 40 marks per ml. The amount of peptide per single mark is:

per_mark = concentration / ticks_per_ml
Example (U-100):2.5 mg/ml ÷ 100 marks/ml = 0.025 mg per mark = 25 mcg per mark

Step 3 — Marks needed for your dose

iu_marks = dose_amount_in_vial_unit / per_mark
Example:250 mcg dose = 0.25 mg. 0.25 / 0.025 = 10 IU marks

If the dose unit differs from the vial unit (e.g. dose in mcg, vial in mg), the app converts the dose to the vial unit before dividing.

When a current vial remainder is used before a newly reconstituted vial, syringe math is calculated separately for each vial concentration and then summed. For example, 10 marks from the current vial plus 3 marks from the new vial is shown as 13 syringe units total.

The upcoming-dose preview applies the same calculation in chronological order. Once a projected dose empties the current vial, later preview cards use the prepared vial's concentration instead of repeating the old vial split.

Changing your syringe type

In Settings → Syringe, you can select U-100 (100 marks/ml), U-40 (40 marks/ml), or enter custom values. The ticks_per_ml value changes the per_mark calculation and therefore all IU readings throughout the app and in notification messages.

Note: The "IU" in syringe calculations is a syringe-unit convention (insulin units), not a biological international unit. It simply means "the number marked on the syringe barrel."
Blend calculations keep the compound ratio fixed. If you target one compound, PepCycle calculates the total blend draw needed to preserve that ratio, then shows the per-compound dose breakdown.

Dose calculations

Storing doses in mcg

Every dose is stored internally in mcg (micrograms) regardless of the unit you enter. The conversion is simple:

dose_mcg = dose_amount × 1000 (if unit is mg)
dose_mcg = dose_amount (if unit is mcg or IU)

IU is stored as-is because there is no universal mcg↔IU conversion — it is peptide-specific and outside the scope of this app.

Protocol editors only offer compatible dose units for the product definition. Mass products support mcg and mg conversion; IU is available only for an IU-defined product. Oral doses must resolve to whole tablets unless partial tablets are enabled.

Deducting from vial remaining amount

When a dose is logged, the deduction from the vial is made in the vial's own unit:

deduction = dose_amount (converted to vial_unit if necessary)
new_remaining = remaining - deduction
Example:Vial in mg, remaining 4.75 mg. Dose: 250 mcg = 0.25 mg.
New remaining: 4.75 − 0.25 = 4.5 mg

Vial depletion & reconstitution forecast

Each product card shows how many doses remain in the current vial and estimates when a new reconstitution will be needed, based on the active protocol schedule.

Doses remaining

doses_remaining = floor(remaining_amount / dose_in_vial_units)

Days until depletion

The schedule frequency (doses per day) is calculated from the schedule type:

  • Daily → 1 dose/day
  • Every N days → 1 / N doses/day
  • Specific weekdays → count of selected weekdays / 7
days_until_empty = doses_remaining / doses_per_day
Example:18 doses remaining. Protocol every 3 days → 1/3 doses/day.
18 / (1/3) = 18 × 3 = 54 days

Reconstitution forecast states

The forecast line on the product card uses urgency levels based on the days until depletion:

  • Normal — more than 7 days remaining; shows target date
  • Warning — 7 days or fewer; shown in amber
  • Urgent — empty or less than one full dose; shown in red
  • Covers protocol — for finite protocols where the vial is sufficient through the protocol end date
  • No active schedule — no protocol; only dose count is shown, no timing estimate

Finite protocol: covers-through check

If a protocol has an end date, the app first checks whether the current vial is sufficient to reach that date before computing a reconstitution deadline:

days_until_end = (end_date − today) in days
if days_until_empty ≥ days_until_end → "Covers protocol through [date]"
Example:Protocol ends in 30 days. 36 doses left, every 2 days = 18 doses needed.
36 > 18 → "Covers protocol through [end date]"
Forecasts assume no missed doses. Skipped doses, manual adjustments, and schedule changes cause the estimate to update immediately. The reconstitution forecast and the BAC water inventory forecast use the same schedule math, so they stay in agreement.

Inventory forecasting

Syringe inventory

If a syringe item is linked to a protocol (one syringe deducted per dose), the forecast for that item uses the same doses_per_week from the schedule:

days_until_empty = remaining_syringes / (doses_per_week / 7)

BAC water inventory

BAC water is consumed in reconstitution events, not per dose. PepCycle estimates future reconstitution events from your active protocols, current vial remaining amount, and BAC water volume per vial, then compares the projected coverage to your inventory thresholds.

If there is not enough schedule or vial information to estimate future reconstitutions, the app falls back to the current remaining quantity and shows that coverage is unknown.

Low-stock threshold

Lyophilized products, Oil-based products, Oral products, BAC water, and syringes each use independent configurable coverage thresholds:

coverage_weeks < low_threshold_weeks

When no coverage forecast can be computed — an item with no linked schedule — it is not marked low on quantity alone; it only alerts once it runs out entirely. In-app alerts auto-resolve once stock or coverage returns above the alert threshold.

Schedule mechanics & next_due

Every protocol has a next_due timestamp — the exact date and time the next dose is scheduled. When a dose is logged (manually or via auto-log), next_due advances to the next occurrence.

Daily schedule

next_due = tomorrow at time_of_day
(or later today if time_of_day hasn't passed yet)

Every N days schedule

next_due = (last dose date + N days) at time_of_day

Specific weekdays schedule

The app scans forward up to 14 days from the current time to find the next occurrence on a matching weekday at the configured time.

Dashboard seven-day window

The Dashboard generates every active occurrence from the start of today through the end of the sixth day after today. Cycling off-days are excluded, titration is calculated for each occurrence date, and prepared-vial/remainder projections are simulated in chronological order.

The UI groups those occurrences by day. Expanding a day reveals the complete set for that date; collapsed rows still show the dose count so the app never silently hides extra schedules.

next_due is only advanced when a dose is logged. If you miss a dose, next_due stays at the missed time until you log it or skip it. This means the Dashboard correctly shows protocols as overdue.
Timezone handling: next_due is stored as a UTC timestamp. The browser sets it using your local time (so "11:00 PM" in your timezone is stored as the correct UTC equivalent). When the server advances next_due after an auto-logged dose, it adds the schedule interval in milliseconds rather than re-applying the time-of-day string — this preserves your local time correctly regardless of your timezone offset.

Notification timing logic

The notification worker runs on a 5-minute cycle. Each cycle it checks all active protocols and fires a notification if the current time falls within the delivery window for that protocol.

Delivery window

window_start = next_due - advance_hours
window_end = next_due (or next_due + 10 min when advance = 0)

The worker fires if: window_start ≤ now ≤ window_end.

Example (1 hour advance):next_due = 08:00. window_start = 07:00. Worker fires between 07:00 and 08:00.

Deduplication

Each protocol+slot pair creates a notification_reminder record. Once a notification is sent, sent_at or telegram_sent_atis stamped on that record. The worker skips the slot on subsequent cycles to prevent duplicate delivery.

Telegram vs push independence

Telegram delivery and push delivery use separate sent_at fields. This means a protocol can receive both channels independently without one blocking the other. The Telegram worker runs in a Supabase Edge Function; the push worker runs in the Cloudflare Worker cron handler.

Auto-logging logic

When "Auto-log doses" is enabled, the app logs a dose automatically after a successful notification delivery. The logic applied:

  1. Day-level duplicate check — if a dose (manual or auto-logged) already exists for this protocol today, skip.
  2. Fetch peptide vial data — needed for the syringe unit calculation to store with the dose.
  3. Insert dose record — using the protocol's configured dose_amount, dose_unit, and calculated syringe units.
  4. Deduct from vial — the current vial is decremented first; if a prepared vial is needed, only the missing amount is deducted from that vial.
  5. Advance next_due — advanced by the schedule interval in milliseconds from the current time, preserving your local time-of-day across timezones.
  6. Deduct syringe inventory — if a syringe item is linked to the protocol.
  7. Stamp reminder — the notification_reminder record is marked with auto_logged_at and channel.
  8. Log notification event — an auto_logged event is recorded in notification history.
The auto-log runs server-side after notification delivery, not in the browser. It does not require the app to be open.

Skip dose behavior

Skipping N doses advances next_due by N occurrences of the schedule pattern without logging any doses or decrementing the vial. A skip event is recorded in History.

next_due after skipping

next_due = apply_schedule(current_next_due, skip_count times)

For "every N days" protocols, skipping 2 doses advances next_due by 2×N days. For weekday protocols, it advances to the 2nd next matching weekday.

What is unchanged

  • Vial remaining amount (no dose was administered)
  • Protocol active status
  • Dose history (no dose entry is created)