Skip to content

Commit

Permalink
feat: add product endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
soofstad committed Apr 10, 2024
1 parent 52ca75e commit 5ac5597
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 61 deletions.
15 changes: 11 additions & 4 deletions api/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from controllers.combination import bridge_from_combination
from controllers.optimal_bridge import bridgeRequestHandler
from controllers.optimizer import optimizer_request_handler
from controllers.products import products_get
from controllers.products import products_get, products_post
from controllers.report import create_report
from util.authentication import authorize
from util.sync_share_point_az import sync_all
Expand All @@ -22,10 +22,17 @@ def init_api():
app = init_api()


@app.route("/api/products", methods=["GET"])
@app.route("/api/products", methods=["GET", "POST"])
@authorize
def products():
return products_get()
def products() -> Response:
if request.method == "GET":
return products_get()
if request.method == "POST":
name: str = request.json.get("productName")
supplier: str = request.json.get("productSupplier")
product_data: [[float, float]] = request.json.get("productData")
return products_post(name, supplier, product_data)
raise ValueError("Invalid method for endpoint")


@app.route("/api/report", methods=["POST"])
Expand Down
83 changes: 34 additions & 49 deletions api/src/calculators/fraction_interpolator.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,48 @@
import csv
from copy import copy
from bisect import bisect_left

from numpy import log

from calculators.bridge import SIZE_STEPS


def lookup_smaller(table: dict, value: float):
n = [i for i in table.keys() if i <= value]
return max(n)
def find_closest_bigger_index(array: list[float], target: float) -> int:
index = bisect_left(array, target)
if index == 0:
raise ValueError("Failed to find closest biggest index")
return index + 1


def lookup_bigger(table: dict, value: float):
n = [i for i in table.keys() if i >= value]
return min(n)
def log_interpolate_or_extrapolate(xMin: float, yMin: float, xMax: float, yMax: float, z: float) -> float:
increase = (log(z) - log(xMin)) / (log(xMax) - log(xMin))
return increase * (yMax - yMin) + yMin


def fraction_interpolator(x: list[float], y: list[float], z: list[float]) -> list[float]:
table_dict = dict(zip(x, y, strict=False))
max_x = max(x)
def fraction_interpolator_and_extrapolator(
xArray: list[float], yArray: list[float], zArray: list[float] = SIZE_STEPS
) -> list[float]:
sizes_dict = {size: 0 for size in zArray} # Populate size_dict with 0 values
starting_index = find_closest_bigger_index(zArray, min(xArray)) - 1
s = sum(yArray)
print(f"Sum Y: {s}")

z_table = {}
for _z in z:
if _z > max_x:
break
smaller_x = lookup_smaller(table_dict, _z)
bigger_x = lookup_bigger(table_dict, _z)
z_table[_z] = {"x1": smaller_x, "x2": bigger_x, "y1": table_dict[smaller_x], "y2": table_dict[bigger_x]}
for zIndex, z in enumerate(zArray[starting_index:]):
if z < xArray[0]: # Don't extrapolate down from first measuring point
continue
# If z is above the range of xArray, use the last two points for extrapolation
elif z > xArray[-1]:
yz = log_interpolate_or_extrapolate(xArray[-2], yArray[-2], xArray[-1], yArray[-1], z)
else:
# Find the interval that z falls into for interpolation
for i in range(1, len(xArray)):
if xArray[i - 1] <= z <= xArray[i]:
yz = log_interpolate_or_extrapolate(xArray[i - 1], yArray[i - 1], xArray[i], yArray[i], z)
break

for zz, values in z_table.items():
x1 = values["x1"]
x2 = values["x2"]
y1 = values["y1"]
y2 = values["y2"]
if yz > 100: # 100% volume has been reached. Stop extrapolation. Set all remaining to 100%
for key in zArray[zIndex + starting_index :]:
sizes_dict[key] = 100
return list(sizes_dict.values())

values["j"] = (y2 - y1) / (log(x2) - log(x1)) * (log(zz) - log(x1)) + y1
return [round(v["j"], 3) for v in z_table.values()]
sizes_dict[z] = round(yz, ndigits=3)


def from_csv_to_csv():
with open("test_data/interpolate_input.csv") as csvfile:
reader = csv.DictReader(csvfile)
fields_copy = copy(reader.fieldnames)
fields_copy.pop(0)
products = {name: [] for name in fields_copy}
a_x = []
for line in reader:
a_x.append(float(line["Size"]))
for n in products:
products[n].append(float(line[n]))

for name in products:
b_y = fraction_interpolator(x=a_x, y=products[name], z=SIZE_STEPS)
with open(f"test_data/{name}.csv", "w+") as newcsvfile:
writer = csv.DictWriter(newcsvfile, fieldnames=["Size", "Cumulative"])
writer.writeheader()
for step, interpol_value in zip(SIZE_STEPS, b_y, strict=False):
writer.writerow({"Size": step, "Cumulative": interpol_value})


if __name__ == "__main__":
from_csv_to_csv()
return list(sizes_dict.values())
2 changes: 2 additions & 0 deletions api/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


class Config:
AUTH_DISABLED = os.getenv("AUTH_DISABLED", "false")
TABLE_ACCOUNT_NAME = os.getenv("TABLE_ACCOUNT_NAME", "lcmdevstorage")
TABLE_KEY = os.getenv("TABLE_KEY")
BLOB_CONTAINER_NAME = "lcm-file-blobs"
Expand All @@ -19,4 +20,5 @@ class Config:
DEFAULT_MAX_ITERATIONS = 100
HOME_DIR = str(Path(__file__).parent.absolute())
PRODUCT_TABLE_NAME = "products"
CUSTOM_PRODUCT_TABLE = "interpolatedproducts"
named_supplier = ("Baker Hughes", "Halliburton", "Schlumberger")
31 changes: 29 additions & 2 deletions api/src/controllers/products.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from cachetools import TTLCache, cached
from flask import Response

from calculators.fraction_interpolator import fraction_interpolator_and_extrapolator
from config import Config
from util.azure_table import get_service
from util.azure_table import get_table_service, sanitize_row_key


def sort_products(products: dict[str, dict]):
Expand All @@ -14,7 +15,11 @@ def sort_products(products: dict[str, dict]):

@cached(cache=TTLCache(maxsize=128, ttl=300))
def products_get():
products = get_service().query_entities(Config.PRODUCT_TABLE_NAME)
table_service = get_table_service()

share_point_products = table_service.query_entities(Config.PRODUCT_TABLE_NAME)
interpolated_products = table_service.query_entities(Config.CUSTOM_PRODUCT_TABLE)
products = [*share_point_products, *interpolated_products]

products_response = {}
for p in products:
Expand All @@ -36,3 +41,25 @@ def products_get():

sorted_products = sort_products(products_response)
return sorted_products


def products_post(product_name: str, supplier_name: str, product_data: [[float, float]]) -> Response:
product_id = sanitize_row_key(product_name)
sizes = [p[0] for p in product_data]
cumulative = [p[1] for p in product_data]
table_entry = {
"PartitionKey": Config.CUSTOM_PRODUCT_TABLE,
"RowKey": product_id,
"id": product_id,
"title": product_name,
"supplier": supplier_name,
"cumulative": str(fraction_interpolator_and_extrapolator(sizes, cumulative)),
"sack_size": 25,
"environment": "Green",
"cost": 100,
"co2": 1000,
}

get_table_service().insert_entity(Config.CUSTOM_PRODUCT_TABLE, table_entry)
products_get.cache_clear()
return table_entry
2 changes: 2 additions & 0 deletions api/src/util/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def decode_jwt(token):
def authorize(f):
@wraps(f)
def wrap(*args, **kwargs):
if Config.AUTH_DISABLED == "true":
return f(*args, **kwargs)
if "Authorization" not in request.headers:
abort(401, "Missing 'Authorization' header")
try:
Expand Down
2 changes: 1 addition & 1 deletion api/src/util/azure_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def sanitize_row_key(value: str) -> str:
return value.replace("/", "-").replace("\\", "-").replace("#", "").replace("?", "-").replace(" ", "").lower()


def get_service():
def get_table_service() -> TableService:
return TableService(account_name=Config.TABLE_ACCOUNT_NAME, account_key=Config.TABLE_KEY)


Expand Down
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
restart: unless-stopped
environment:
ENVIRONMENT: development
AUTH_DISABLED: "true"
FLASK_DEBUG: "true"
TABLE_KEY: ${TABLE_KEY}
ports:
Expand Down
2 changes: 0 additions & 2 deletions web/src/Components/Bridging/Graphs/BridgeGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ type BridgeGraphProps = {

const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
console.log(payload)
console.log(label)
return (
<div style={{ backgroundColor: 'white', border: '1px solid gray', padding: '5px', borderRadius: '2px' }}>
<div style={{ opacity: '50%' }}>{`Particle size : ${label}µm`}</div>
Expand Down
2 changes: 1 addition & 1 deletion web/src/Components/Bridging/InputContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const InputContainer = ({
<Typography variant='h3'>Optimal Bridge:</Typography>
<div style={{ marginInlineStart: '0.5rem' }}>
{[10, 50, 90].map(d => (
<p>
<p key={d}>
D{d}: {findDValue(optimalBridgeGraphData, d, 'Bridge')}
{'\u00B5m'}
</p>
Expand Down
2 changes: 1 addition & 1 deletion web/src/Components/Combinations/CombinationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ export const CombinationCard = ({
setD90(findDValue(graphData, 90, combination.name))
})
.catch(error => {
ErrorToast(`${error.response.data}`, error.response.status)
console.error('fetch error' + error)
ErrorToast(`${error.response.data}`, error.response.status)
})
}, [combination, allProducts])

Expand Down
2 changes: 1 addition & 1 deletion web/src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function findDValue(graphData: GraphData[], goalYValue: number, bridgeNam
}
return false
})
if (!indexOfClosestHigherYValue) throw new Error('Failed to find D-value of bridge')
if (!indexOfClosestHigherYValue) return 0

// interpolate the values to get an approx value for the exact D requested
return linearInterpolation(
Expand Down

0 comments on commit 5ac5597

Please sign in to comment.