Skip to content

Commit

Permalink
improved input
Browse files Browse the repository at this point in the history
  • Loading branch information
xyzpw committed Aug 16, 2024
1 parent 9e4ff3f commit 7b9f58e
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 7 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/xyzpw/drug-simulator/total)
![GitHub repo size](https://img.shields.io/github/repo-size/xyzpw/drug-simulator)

Simulate the absorption and elimination of drugs in real time using *real* pharmacokinetic formulas.<br>
**drug-simulator** is a program designed to simulate drug concentrations in real time using user-prompted values.

<img width="664px" height="400px" src="https://github.com/user-attachments/assets/b872064c-1b9d-4672-b9b1-14f071c28b62"/>

## How it Works
The program will prompt for values which will be used as pharmacokinetic parameters to calculate and display the concentration of the drug in someone's body in real time.<br>
The program prompts for values which will be used to calculate and display drug concentrations in real time to the terminal.<br>
Several options can be used which effect these results, such as route of administration.

## Usage
Expand Down Expand Up @@ -96,11 +96,13 @@ The simulation can be started assuming administration occurred prior to the simu
Start at a specific time:
```bash
$ ./drug-simulator.py --time 0554
$ ./drug-simulator.py --time "5:54 am"
```

Administration at a specific date:
```bash
$ ./drug-simulator.py --date "20240808 1922"
$ ./drug-simulator.py --date "08/08/2024 7:22 pm"
```

Elapsed time since administration:
Expand Down
2 changes: 1 addition & 1 deletion drug-simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

# Adjust dose if `count` argument is used
if args.get("count"):
simulationInfo.dose = simulationInfo.dose[0] * args.get("count"), simulationInfo.dose[1]
simulationInfo.dose = simulationInfo.dose[0] * fixCountValue(args.get("count")), simulationInfo.dose[1]

# Set each of these values to `0` if they are not used
for i in ["precision", "minimum"]:
Expand Down
4 changes: 2 additions & 2 deletions src/_argInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def addKwarg(name: str, summary: str, **kwargs):
addKwarg("msg", "displays this message upon script execution", metavar="<msg>")
addKwarg("dose", "dose of the drug being simulated", metavar=doseMetavar)
addKwarg("count", "number of doses administered", metavar=countMetavar, type=float)
addKwarg("time", "time the dose was administered in 24-hour format", metavar=timeMetavar)
addKwarg("time", "time the dose was administered", metavar=timeMetavar)
addKwarg("elapsed", "time elapsed since drug administration", metavar=timeMetavar)
addKwarg("date", "date and time at drug administration", metavar=dateMetavar)
addKwarg("p", "decimal precision of drug concentration", metavar=fracMetavar, dest="precision", type=int)
Expand All @@ -59,5 +59,5 @@ def addKwarg(name: str, summary: str, **kwargs):
addKwarg("dr", "time second delayed release drug is released", metavar=timeMetavar)
addKwarg("dist-time", "time of distribution phase", metavar=timeMetavar)
addKwarg("linearabs", "uses linear absorption", action="store_true")
addKwarg("count", "number of doses administered", metavar="<n>", type=float)
addKwarg("count", "number of doses administered", metavar="<n>")
addKwarg("file", "file containing drug information for the simulation", metavar="<file_location>")
3 changes: 2 additions & 1 deletion src/fileReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def readFile(location: str) -> dict:
filename = filenameRegexSearch.group("name") + ".json" if filenameRegexSearch else None
if not filename:
return
if not pathlib.Path(filename).exists():
raise SystemExit("no file with name '%s' exists" % filename)
with open(filename, "r") as f:
fileContent = json.load(f)
return fileContent
Expand All @@ -36,5 +38,4 @@ def validateFileArgs(location: str, usrArgs: dict) -> dict:
_value = fileContent.get(name)
if checkShouldUpdate(name):
updateUsrArgs(name, _value)
del name, _value
return usrArgs
33 changes: 32 additions & 1 deletion src/timeConversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
timePattern = re.compile(r"^(?P<time>\d+|\d+\.\d+|\.\d+)(?:(?:\s)?(?P<unit>s|sec|second(?:s)?|m|min|minute(?:s)?|h|hour(?:s)?|d|day(?:s)?))?$")

readableTimePattern = re.compile(r"\A(?P<time>\d{1,2}:\d{1,2}|\d{4})\Z")
datetimePattern = re.compile(r"^(?P<year>\d{4})(?P<month>\d{2})(?P<day>\d{2})\s?(?P<hour>\d{2})(?P<minute>\d{2})$")
datetimePattern = re.compile(r"^(?P<year>\d{4})-?(?P<month>\d{2})-?(?P<day>\d{2})\s?(?P<hour>\d{2}):?(?P<minute>\d{2})$")
def getHoursAndMinutesFromReadableTime(readableTime) -> tuple:
try:
hoursAndMinutes = readableTimePattern.search(readableTime).group("time")
Expand Down Expand Up @@ -57,6 +57,10 @@ def fixTimeUi(timeUi: str) -> float:
from src.uiHandler import getValueFromFraction
timeUiFractionValue = getValueFromFraction(timeUi)
timeUi = re.sub(r"^((?:\d*\.)?\d+/(?:\d*\.)?\d+)", f"{timeUiFractionValue}", timeUi)
elif "*" in timeUi:
from src.uiHandler import getValueFromMultiplier
timeUiMultiplierValue = getValueFromMultiplier(timeUi)
timeUi = re.sub(r"((?:\d*\.)?\d+\s*?\*\s*?(?:\d*\.)?\d+)", f"{timeUiMultiplierValue}", timeUi)
timePatternSearch = timePattern.search(timeUi)
try:
unfixedTime = float(timePatternSearch.group("time"))
Expand All @@ -81,16 +85,43 @@ def getEpochFromDatetime(usrDatetime: str) -> float:
).timestamp()
return epoch

def convert12HourTo24Hour(timeString: str) -> str:
timeString = timeString.lower()
timeRegex = re.search(r"^(\d{1,2}):(\d{2})\s*?(am|pm)$", timeString)
hour, minute, meridiem = timeRegex.groups()
if meridiem == "am" or meridiem == "pm" and int(hour) == 12:
hour = str(hour).zfill(2)
return f"{hour}{minute}"
elif meridiem == "pm":
hour = int(hour) + 12
hour = str(hour).zfill(2)
return f"{hour}{minute}"

def usaDateToIsoDate(dateString: str) -> str:
dateString = dateString.lower()
dateRegex = re.search(
r"^(\d{1,2})/(\d{1,2})/(\d{4}) (\d{1,2}):(\d{2})\s*?(am|pm)",
dateString, flags=re.IGNORECASE
)
month, day, year, hour, minute, meridiem = dateRegex.groups()
dateString = f"{year.zfill(2)}{month.zfill(2)}{day.zfill(2)}"
timeString = convert12HourTo24Hour(f"{hour}:{minute} {meridiem}")
return f"{dateString} {timeString}"

def getStartingEpoch(usrArgs: dict) -> float:
method = "time" if usrArgs.get("time") != None else ("elapsed" if usrArgs.get("elapsed") != None else ("date" if usrArgs.get("date") != None else None))
match method:
case "time":
if re.search(r"am|pm", usrArgs.get(method), flags=re.IGNORECASE):
usrArgs[method] = convert12HourTo24Hour(usrArgs.get(method))
hour, minute = getHoursAndMinutesFromReadableTime(usrArgs.get(method))
return getEpochFromHourAndMinute(hour, minute)
case "elapsed":
hour, minute = getHoursAndMinutesFromReadableTime(usrArgs.get(method))
return time.time() - getSecondsFromHourAndMinute(hour, minute)
case "date":
if re.search(r"am|pm", usrArgs.get(method), flags=re.IGNORECASE):
usrArgs[method] = usaDateToIsoDate(usrArgs.get(method))
return getEpochFromDatetime(usrArgs.get(method))
case _:
return time.time()
24 changes: 24 additions & 0 deletions src/uiHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"getUiValue",
"getSimulationMessage",
"detectAndFixValue",
"getValueFromMultiplier",
"fixCountValue",
]

def fixDose(dose: str|float, isProbability=False) -> tuple:
Expand All @@ -25,6 +27,9 @@ def fixDose(dose: str|float, isProbability=False) -> tuple:
if "/" in dose:
doseFractionValue = getValueFromFraction(dose)
dose = re.sub(r"^((?:\d*\.)?\d+\s*/\s*(?:\d*\.)?\d+)", f"{doseFractionValue}", dose)
elif "*" in dose:
doseMultiplierValue = getValueFromMultiplier(dose)
dose = re.sub(r"((?:\d*\.)?\d+\s*?\*\s*?(?:\d*\.)?\d+)", f"{doseMultiplierValue}", dose)
massUnitSearch = re.search(r"^(?P<dose>(?:\d+?\.)?\d+)\s?(?P<unit>mg|milligrams?|ug|mcg|micrograms?|g|grams?)$", str(dose))
if bool(massUnitSearch):
dose = float(massUnitSearch.group("dose"))
Expand All @@ -40,6 +45,20 @@ def fixDose(dose: str|float, isProbability=False) -> tuple:
raise SystemExit("invalid mass unit for dosage")
return fixTimeUi(str(dose)), massunit

def fixCountValue(value: int | float | str) -> float:
isInteger = re.search(r"^\d+?$", value)
isFloat = re.search(r"^\d+?\.\d+?$", value)
isMultiplier = "*" in str(value)
isFraction = "/" in str(value)
if isInteger:
return int(value)
elif isFloat:
return float(value)
elif isMultiplier:
return getValueFromMultiplier(value)
elif isFraction:
return getValueFromFraction(value)

def fixTmax(tmax: float|str, peaked=False) -> float:
if peaked or tmax in ["now", "0", ""]:
return float(0)
Expand Down Expand Up @@ -67,6 +86,11 @@ def getValueFromFraction(userValue: str):
numerator, denominator = float(fractionReMatch.group("num")), float(fractionReMatch.group("den"))
return numerator / denominator

def getValueFromMultiplier(userValue: str):
multiplyNumberRegex = re.search(r"^(?P<multiplier>(?:\d*\.)?\d+)\s*?\*\s*?(?P<number>(?:\d*\.)?\d+)", userValue)
number = float(multiplyNumberRegex.group("multiplier")) * float(multiplyNumberRegex.group("number"))
return number

def getUiValue(usrArgs: dict, desiredValueArg: str, altPromptText: str):
"""Gets value from arg if it exists, otherwise prompts the user for the value.
No prompt will occur if `altPromptText` is set to `None`."""
Expand Down

0 comments on commit 7b9f58e

Please sign in to comment.