Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
49 changes: 42 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
FROM python:2.7-slim
# Use slim Python 3.10 image
FROM python:3.10.12-slim

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY requirements.txt /usr/src/app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /usr/src/app
# Set Flask environment variables
ENV FLASK_ENV=development
ENV FLASK_APP=calculators.py
ENV FLASK_DEBUG=1

WORKDIR /app
RUN mkdir /uwsgi_log

# Copy project files
COPY . /app

# Install system dependencies for building packages (numpy, scipy, uwsgi)
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
gfortran \
python3-dev \
libatlas-base-dev \
libopenblas-dev \
&& rm -rf /var/lib/apt/lists/*

# Upgrade pip, setuptools, wheel
RUN pip install --upgrade pip setuptools wheel

# Install prebuilt wheels for numpy/scipy to avoid build errors
RUN pip install numpy==1.25.2 scipy==1.11.1

# Install Flask, Jinja2, MarkupSafe compatible versions
RUN pip install Flask>=2.2.5 Jinja2>=3.1.2 MarkupSafe>=2.1.2

# Install remaining dependencies from requirements.txt excluding numpy/scipy/Flask/Jinja2/MarkupSafe
RUN pip install --use-pep517 --no-build-isolation $(grep -vE "numpy|scipy|Flask|Jinja2|MarkupSafe" requirements.txt)

# Install uwsgi if needed
RUN apt-get update && apt-get install -y uwsgi \
&& rm -rf /var/lib/apt/lists/*

# Expose Flask port
EXPOSE 5000
CMD ["gunicorn", "--workers=4", "-b 0.0.0.0:5000","wsgi:app"]

# Run the Flask app
CMD ["flask", "run", "--host=0.0.0.0"]
139 changes: 133 additions & 6 deletions calculators.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,143 @@
from flask import Flask, jsonify, request
from flask import abort, render_template
from flask_bootstrap import Bootstrap
from gail import gail, gail_api

# DEBUG: Add debugging information before imports
import os
import sys

print("=== DEBUGGING IMPORT ISSUES ===")
print("Current working directory:", os.getcwd())
print("Python path:")
for path in sys.path:
print(f" {path}")

print("\nContents of current directory:")
for item in os.listdir('.'):
print(f" {item}")

print("\nLooking for gail directory...")
if os.path.exists('./gail'):
print("✓ gail directory found at ./gail")
print("Contents of gail directory:")
for item in os.listdir('./gail'):
print(f" {item}")

# Check if __init__.py exists
if os.path.exists('./gail/__init__.py'):
print("✓ __init__.py found in gail directory")
else:
print("✗ __init__.py NOT found in gail directory")

# Check for the specific files we need
if os.path.exists('./gail/gail.py'):
print("✓ gail.py found")
else:
print("✗ gail.py NOT found")

if os.path.exists('./gail/gail_api.py'):
print("✓ gail_api.py found")
else:
print("✗ gail_api.py NOT found")

else:
print("✗ gail directory NOT found at ./gail")

# Also check absolute path
if os.path.exists('/app/gail'):
print("✓ gail directory found at /app/gail")
print("Contents of /app/gail directory:")
for item in os.listdir('/app/gail'):
print(f" {item}")
else:
print("✗ gail directory NOT found at /app/gail")

print("=== END DEBUGGING INFO ===\n")

# Try different import approaches
print("=== TESTING IMPORTS ===")

try:
print("Attempting: import gail.gail_api as gail_api")
import gail.gail_api as gail_api
print("✓ SUCCESS: gail.gail_api imported successfully")
except ImportError as e:
print(f"✗ FAILED: {e}")

# Try alternative approach
try:
print("Attempting: sys.path.insert approach")
gail_path = os.path.join(os.path.dirname(__file__), 'gail')
if gail_path not in sys.path:
sys.path.insert(0, gail_path)
import gail_api
print("✓ SUCCESS: gail_api imported with sys.path.insert")
except ImportError as e2:
print(f"✗ FAILED: {e2}")

# Try direct file import
try:
print("Attempting: direct file path import")
import importlib.util
spec = importlib.util.spec_from_file_location("gail_api", "./gail/gail_api.py")
gail_api = importlib.util.module_from_spec(spec)
spec.loader.exec_module(gail_api)
print("✓ SUCCESS: gail_api imported directly from file")
except Exception as e3:
print(f"✗ FAILED: {e3}")
print("All import attempts failed!")
gail_api = None

try:
print("Attempting: from gail.gail import GailRiskCalculator")
from gail.gail import GailRiskCalculator
print("✓ SUCCESS: GailRiskCalculator imported successfully")
except ImportError as e:
print(f"✗ FAILED: {e}")

# Try alternative approach
try:
print("Attempting: GailRiskCalculator with sys.path.insert")
if 'gail' not in sys.modules:
gail_path = os.path.join(os.path.dirname(__file__), 'gail')
if gail_path not in sys.path:
sys.path.insert(0, gail_path)
import gail
from gail import GailRiskCalculator
print("✓ SUCCESS: GailRiskCalculator imported with sys.path.insert")
except ImportError as e2:
print(f"✗ FAILED: {e2}")

# Try direct file import
try:
print("Attempting: GailRiskCalculator direct file path import")
import importlib.util
spec = importlib.util.spec_from_file_location("gail", "./gail/gail.py")
gail_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(gail_module)
GailRiskCalculator = gail_module.GailRiskCalculator
print("✓ SUCCESS: GailRiskCalculator imported directly from file")
except Exception as e3:
print(f"✗ FAILED: {e3}")
print("All GailRiskCalculator import attempts failed!")
GailRiskCalculator = None

import numpy as np
from RiskAssessment import BasicRiskAssessment as assessment

print("=== END TESTING IMPORTS ===\n")

app = Flask(__name__)
Bootstrap(app)


def get_calculator(calculator_name):
if calculator_name.lower() == "gail":
return gail_api.GailAPI()
if gail_api is not None:
return gail_api.GailAPI()
else:
print("ERROR: gail_api is None, cannot create GailAPI instance")
return None
return None

@app.route('/')
Expand Down Expand Up @@ -53,7 +178,6 @@ def getFieldDescrptions(calculator):
# gailapi = gail_api.GailAPI()
return render_template("api_doc.html", name=calc.get_name(), version="2.0", apidef=calc.get_input_fields_json())


@app.route('/api/v1.0/gail', methods=['POST'])
def doGailCalc():
"""
Expand All @@ -72,7 +196,10 @@ def doGailCalc():
abort(400)
else:
# print request.json
calc = gail.GailRiskCalculator()
if GailRiskCalculator is None:
return jsonify({"error": "GailRiskCalculator not available due to import issues"}), 500

calc = GailRiskCalculator()
calc.Initialize() # TODO: look into moving this into the instantion of the object

# TODO: move the rhyp and age indicator logic into the gail calculator, all it's logic should live in there.
Expand All @@ -82,7 +209,7 @@ def doGailCalc():
if request.json['ihyp'] == 0:
rhyp = np.float64(0.93)
elif request.json['ihyp'] == 1:
rhyp = np.float(1.82)
rhyp = float(1.82)

fiveYearABS = calc.CalculateAbsoluteRisk(request.json['age'],
request.json['age'] + 5,
Expand Down Expand Up @@ -136,4 +263,4 @@ def doGailCalc():


if __name__ == "__main__":
app.run(debug=False)
app.run(debug=False, host="0.0.0.0", port=5070)
10 changes: 5 additions & 5 deletions calculators_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def get_NCI(self,
utils.roundLikeNCI(cgvLifetimeRiskAVE))

def test_bad_calc_name(self):
print "Testing Unknown Calculator Name (For v2.0)"
print("Testing Unknown Calculator Name (For v2.0)")
# test getting the docs for a bad calc
rv = self.app.get('/api/v2.0/bad_calc')
assert rv.status_code == 501
Expand All @@ -95,7 +95,7 @@ def test_bad_calc_name(self):
assert rv.status_code == 501

def test_gail_calc_doc(self):
print "Testing GAIL Doc URLs (For v2.0)"
print("Testing GAIL Doc URLs (For v2.0)")
rv = self.app.get('/api/v2.0/gail')
assert rv.status_code == 200
assert "VisExcell API For GAIL - version 2.0" in rv.get_data()
Expand All @@ -104,7 +104,7 @@ def test_gail_calc_doc(self):
assert rv.mimetype == "application/json"

def test_gail_calc_post(self):
print "Testing getting GAIL results for (v2.0)"
print("Testing getting GAIL results for (v2.0)")
post_data = {
"age": 48,
"menarch_age": 2,
Expand Down Expand Up @@ -132,7 +132,7 @@ def test_gail_calc_post(self):
# TODO: Add bad post data to check error responses

def test_unknown_biopsies(self):
print "Testing for unknown biopsy bug (using v1.0)"
print("Testing for unknown biopsy bug (using v1.0)")
post_data = {
"age": 40,
"menarch_age": 2,
Expand Down Expand Up @@ -176,7 +176,7 @@ def test_unknown_biopsies(self):
self.assertEqual((fyr, fyra, ltr, ltra),(nci_fyr,nci_fyra,nci_ltr,nci_ltra), "Inputs: %r" % post_data)

def test_calculations_valid_inputs(self):
print "Testing for valid inputs (using v1.0)"
print("Testing for valid inputs (using v1.0)")
test_inputs = [{"age": 42, "menarch_age": 2, "live_birth_age": 0, "ever_had_biopsy": 0, "num_biopsy": 0, "first_deg_relatives": 0, "ihyp": 99, "race": 1},
{"age": 50, "menarch_age": 2, "live_birth_age": 0, "ever_had_biopsy": 0, "num_biopsy": 0, "first_deg_relatives": 0, "ihyp": 99, "race": 1},
{"age": 68, "menarch_age": 2, "live_birth_age": 0, "ever_had_biopsy": 0, "num_biopsy": 0, "first_deg_relatives": 0, "ihyp": 99, "race": 1},
Expand Down
37 changes: 37 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
version: '3.8'

services:
riskmodels-api:
container_name: riskmodels-api
build: .
restart: always
# command: uwsgi --http 0.0.0.0:5000 --master -p 1 -w app:app
environment:
SECRET_KEY: f502c8e1-f4b2-486a-aeb4-85e427a46613
JWT_SECRET_KEY: RbXAh3yIaUBJfDh=wtYs-cTPK?EnwG!JGrFgGhYOdqsy/!h2RXZ1PPaAkIGmrA7F
CACHE_ENABLED: 1
CACHE_TYPE: RedisCache
# DATABASE_TYPE: postgresql
# DATABASE_HOST: landa-db # Connect to root-level Postgres
# DATABASE_PORT: 5432
# DATABASE_USER: postgres
# DATABASE_PASS: gofuckYourselfAUG!2024
# DATABASE_DB: riskmodels-db # Project-specific DB in shared container
FLASK_DEBUG: 1
FLASK_CONFIG: development
FLASK_APP: calculators.py
LISTEN_HOST: 0.0.0.0
LISTEN_PORT: 5000
LOG_LEVEL: INFO
UPDATE_DAYS_TOKEN: zdMBOJi9f4WlkHuPai4RRwkM6pryEp
FILE_UPLOAD_TOKEN: P5boWPJXxrRDlGvtBuP7a5GOoXY8wo
ports:
- "5030:5000"
networks:
- landa_network

# Optional Redis section is removed since you don’t use it yet

networks:
landa_network:
external: true
Binary file added gail/.DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions gail/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import gail
from . import gail_api
7 changes: 4 additions & 3 deletions gail/gail.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Gail model risk calculator
import numpy as np

class GailRiskCalculator:
Expand Down Expand Up @@ -952,7 +953,7 @@ def CalculateRisk(self,
for k in range(108): # really is NumCovPattInGailModel / 2
# /* col2: indicator for age */
r8iTox2[k, 1] = np.float64(0.0)
r8iTox2[108 + k, 1] = np.float(1.0)
r8iTox2[108 + k, 1] = float(1.0)

for j in range(1, 3): # note, since we always subtract 1 from j and k, we could zero base these
# /* col3: age menarchy cate */
Expand Down Expand Up @@ -1200,7 +1201,7 @@ def CalculateRisk(self,
if __name__ == '__main__':
gailMod = GailRiskCalculator()
gailMod.Initialize()
print gailMod.CalculateRisk(1, # riskIndex int [1 = Abs, 2 = Ave]
print(gailMod.CalculateRisk(1, # riskIndex int [1 = Abs, 2 = Ave]
35, # CurrentAge int [t1]
40, # ProjectionAge int [t2]
0, # AgeIndicator int [i0]
Expand All @@ -1212,4 +1213,4 @@ def CalculateRisk(self,
1, # int [ihyp] HyperPlasia
np.float64(1.82), # double [rhyp] RHyperPlasia
1 # irace int [race]
)
))
5 changes: 3 additions & 2 deletions gail/gail_api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Gail model risk calculator API
import gail as gail_calc
from RiskAssessment import BasicRiskAssessment as assessment
import numpy as np
Expand Down Expand Up @@ -151,7 +152,7 @@ def validate_inputs(self, inputs):

def run(self, inputs):
errors = self.validate_inputs(inputs)
print len(errors.keys())
print(len(errors.keys()))
return_obj = {"code": 200}
ra_obj = None
# print "out_of_bounds" not in errors.keys()
Expand All @@ -174,7 +175,7 @@ def do_calculation(self, inputs):
if inputs['ihyp'] == 0:
rhyp = np.float64(0.93)
elif inputs['ihyp'] == 1:
rhyp = np.float(1.82)
rhyp = float(1.82)
# The rhyp computed here doesn't get used by the following functions - SRM: 3-23-2017
# The age_indicator doesn't get used by the following either. - SRM: 3-23-2017
fiveYearABS = self.calc.CalculateAbsoluteRisk(inputs['age'],
Expand Down
4 changes: 2 additions & 2 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

def roundLikeNCI(np_float64):
outval = np.float64(np_float64) * np.float64(1000.0)
if outval - outval.astype(np.int) >= np.float(0.5):
if outval - outval.astype(np.int) >= float(0.5):
outval = outval.astype(np.int) + 1
else:
outval = outval.astype(np.int)
return np.float(outval) / np.float(1000)
return float(outval) / float(1000)