From 6010e067e80f32a0bec018a9cd9a7053474a1de1 Mon Sep 17 00:00:00 2001 From: mmcwilliams Date: Tue, 10 Jan 2023 21:30:40 -0500 Subject: [PATCH] Add with all current work --- .gitignore | 1 + common.sh | 20 ++++ import.py | 203 +++++++++++++++++++++++++++++++++++++ import.sh | 179 ++++++++++++++++++++++++++++++++ recipes/Adox_M-Q_Borax.csv | 7 ++ recipes/FX-11.csv | 8 ++ recipes/Kodak_D-19.csv | 7 ++ recipes/Kodak_D-76.csv | 6 ++ recipes/Kodak_Xtol_A.csv | 6 ++ recipes/Kodak_Xtol_B.csv | 4 + run.sh | 7 ++ setup.sql | 31 ++++++ supply.csv | 12 +++ 13 files changed, 491 insertions(+) create mode 100644 .gitignore create mode 100644 common.sh create mode 100644 import.py create mode 100644 import.sh create mode 100644 recipes/Adox_M-Q_Borax.csv create mode 100644 recipes/FX-11.csv create mode 100644 recipes/Kodak_D-19.csv create mode 100644 recipes/Kodak_D-76.csv create mode 100644 recipes/Kodak_Xtol_A.csv create mode 100644 recipes/Kodak_Xtol_B.csv create mode 100644 run.sh create mode 100644 setup.sql create mode 100644 supply.csv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f99050c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +developers.sqlite \ No newline at end of file diff --git a/common.sh b/common.sh new file mode 100644 index 0000000..cfcdf72 --- /dev/null +++ b/common.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +#set -e + +DATABASE_URL=$(realpath "./developers.sqlite") + +createUUID () { + uuidgen | tr '[:upper:]' '[:lower:]' +} + +db () { + sqlite3 "${DATABASE_URL}" "${1}" +} + +setup () { + cat "./setup.sql" | sqlite3 "${DATABASE_URL}" + echo "Setup ${DATABASE_URL}..." +} + +setup \ No newline at end of file diff --git a/import.py b/import.py new file mode 100644 index 0000000..85e3bf0 --- /dev/null +++ b/import.py @@ -0,0 +1,203 @@ +#!/bin/bash + +import sqlite3 +import csv +import os +from uuid import uuid4 +from os import path + +RECIPES='recipes/' +SUPPLY='supply.csv' + +con = sqlite3.connect('developers.sqlite') +c = con.cursor() + +def uuid () : + return str(uuid4()) + +def getRecipe (name) : + query="SELECT recipe_id FROM recipes WHERE (name = LOWER(?)) LIMIT 1;" + res = c.execute(query, (name,)) + return c.fetchone() + +def hasRecipe (name) : + recipe = getRecipe(name) + if recipe is not None: + return recipe[0] + return '' + +def createRecipe (name) : + id=uuid() + query="INSERT OR IGNORE INTO recipes (recipe_id, name) VALUES (?, LOWER(?));" + c.execute(query, (id, name,)) + con.commit() + return id + +def getChemical (chemical) : + query="SELECT chemical_id FROM chemicals WHERE (name = LOWER(?)) LIMIT 1;" + res = c.execute(query, (chemical,)) + return c.fetchone() + +def hasChemical (chemical) : + chemical = getChemical(chemical) + if chemical is not None: + return chemical[0] + return '' + +def createChemical (chemical) : + id=uuid() + query="INSERT OR IGNORE INTO chemicals (chemical_id, name) VALUES (?, LOWER(?));" + c.execute(query, (id, chemical,)) + con.commit() + return id + +def ensureChemical (chemical) : + chemical_id = hasChemical(chemical) + if chemical_id == '' : + chemical_id = createChemical(chemical) + return chemical_id + +def getSupply (url) : + query="SELECT supply_id FROM supply WHERE (url = ?) LIMIT 1;" + res = c.execute(query, (url,)) + return c.fetchone() + +def hasSupply (url) : + supply = getSupply(url) + if supply is not None: + return supply[0] + return '' + +def createSupply (chemical_id, url, g, ml, price) : + id=uuid() + query="INSERT OR IGNORE INTO supply (supply_id,chemical_id,url,grams,milliliters,price) VALUES (?,?,?,?,?,?);" + c.execute(query, (id,chemical_id,url,g,ml,price,)) + con.commit() + return id + +def ensureSupply (chemical_id, url, g, ml, price) : + supply_id = hasSupply(url) + if supply_id == '' : + supply_id = createSupply(chemical_id, url, g, ml, price) + return supply_id + +def displaySupply (supply_id, chemical) : + query="SELECT round((price/100.0)/grams, 2) FROM supply WHERE (supply_id = ?);" + res = c.execute(query, (supply_id,)) + data = c.fetchone() + ratio = data[0] + print(f"{chemical}: {ratio} $/g") + +def getRecipe (recipe) : + query="SELECT recipe_id FROM recipes WHERE (name = LOWER(?)) LIMIT 1;" + res = c.execute(query, (recipe,)) + return c.fetchone() + +def hasRecipe (recipe) : + recipe = getRecipe(recipe) + if recipe is not None: + return recipe[0] + return '' + +def createComponent (chemical, recipe_id, chemical_id, g, ml, makes, note) : + id = uuid() + query="INSERT OR IGNORE INTO components (component_id,recipe_id,chemical_id,grams,milliliters,makes,note) VALUES (?,?,?,?,?,?,?);" + c.execute(query, (id, recipe_id, chemical_id, g, ml, makes, note)) + con.commit() + val=f"{g}g" if g != 'NULL' else f"{ml}ml" + print(f"Added component {chemical} {val}") + +def createRecipe (recipe) : + id=uuid() + query="INSERT OR IGNORE INTO recipes (recipe_id, name) VALUES (?, ?);" + c.execute(query, (id, recipe,)) + con.commit() + return id + +def importRecipe (filePath) : + name = path.basename(filePath).replace('.csv', '').replace('_', ' ') + recipe_id = hasRecipe(name) + + if recipe_id != '' : + print(f"Recipe {name} already exists") + return + + recipe_id = createRecipe(name) + + with open(filePath, newline='') as csvfile: + reader = csv.reader(csvfile, delimiter=',', quotechar='|') + for row in reader: + chemical=row[0] + if chemical == 'chemical' : + continue + g=float(row[1]) if row[1] != '' else 'NULL' + ml=float(row[2]) if row[2] != '' else 'NULL' + makes=int(row[3]) + note=row[4] + chemical_id=ensureChemical(chemical) + createComponent(chemical, recipe_id, chemical_id, g, ml, makes, note) + print(f"Imported {name}") + +def displayCents (val) : + return '${:,.2f}'.format(val / 100.0) + +def displayCost (ml) : + query = "SELECT recipe_id,UPPER(name) FROM recipes ORDER BY name ASC;" + c.execute(query) + rows = c.fetchall() + for row in rows: + recipe = row[1] + print(f"{recipe}: {ml/1000} liters") + query = """SELECT a.name, (c.grams * (s.price/s.grams)) / c.makes, (c.grams/c.makes) FROM components AS c + INNER JOIN supply AS s + ON c.chemical_id = s.chemical_id + INNER JOIN chemicals AS a + ON c.chemical_id = a.chemical_id + WHERE c.recipe_id = ?;""" + + c.execute(query, (row[0],)) + components = c.fetchall() + + total = 0 + for component in components: + name = component[0] + price = component[1] * ml + grams = component[2] * ml + total += price + print(f"{name} : [{grams}g] {displayCents(price)}") + print("------") + print(f"Total: {displayCents(total)}") + print("------") + +print('-------') +print('SUPPLY') +print('-------') + +with open(SUPPLY, newline='') as csvfile: + reader = csv.reader(csvfile, delimiter=',', quotechar='|') + for row in reader: + chemical=row[0] + if chemical == 'chemical' : + continue + url=row[1] + g=float(row[2]) if row[2] != '' else 'NULL' + ml=float(row[3]) if row[3] != '' else 'NULL' + price=int(row[4]) + chemical_id = ensureChemical(chemical) + supply_id = ensureSupply(chemical_id, url, g, ml, price) + displaySupply(supply_id, chemical) + +print('-------') +print('RECIPES') +print('-------') + +for recipe in os.listdir(RECIPES): + if recipe.endswith('.csv') : + filePath=f'{RECIPES}{recipe}' + importRecipe(filePath) + +print('----') +print('COST') +print('----') + +displayCost(3785) #gallon diff --git a/import.sh b/import.sh new file mode 100644 index 0000000..84e553c --- /dev/null +++ b/import.sh @@ -0,0 +1,179 @@ +#!/bin/bash + +rm -f ./developers.sqlite + +source ./common.sh + +RECIPES=./recipes +FILES=$(mktemp) +SUPPLY=$(realpath ./supply.csv) + +mkdir -p recipes + +hasRecipe () { + query="SELECT recipe_id FROM recipes WHERE (name = '${1}') LIMIT 1;" + db "${query}" +} + +createRecipe () { + id=$(createUUID) + name="${1}" + query="INSERT OR IGNORE INTO recipes (recipe_id, name) VALUES ('${id}', '${name}');" + db "${query}" + echo "${id}" +} + +hasChemical () { + query="SELECT chemical_id FROM chemicals WHERE (name = '${1}') LIMIT 1;" + db "${query}" +} + +createChemical () { + chemical_id="$(createUUID)" + query="INSERT OR IGNORE INTO chemicals (chemical_id, name) VALUES ('${chemical_id}', '${1}');" + db "${query}" +} + +chemical () { + chemical_id=$(hasChemical "${1}") + if [[ "${chemical_id}" == "" ]]; then + chemical_id=$(createChemical "${1}"); + fi + echo "${chemical_id}" +} + +hasSupply () { + query="SELECT supply_id FROM supply WHERE (url = '${1}') LIMIT 1;" + db "${query}" +} + +component () { + cols="${1}" + IFS=',' read -ra VALS <<< "${2}" + recipe_id="${3}" + id=$(createUUID) + query="INSERT OR IGNORE INTO components (component_id,recipe_id,${cols}) VALUES ('${id}','${recipe_id}'," + before="" + first="" + for i in "${VALS[@]}"; do + val=NULL + if [[ "${first}" == "" ]]; then + chemical_id=$(chemical "${i}") + val="'${chemical_id}'" + first="completed" + elif [[ "${i}" != "" ]]; then + val="'${i}'" + fi + query="${query}${before}${val}" + before="," + done + + query="${query});" + echo "${query}" + db "${query}" +} + +import () { + name=$(basename "${1}") + name=${name%.*} + name=$(echo ${name} | sed 's/_/ /g') + + recipe_id=$(hasRecipe "${name}") + if [[ "${recipe_id}" != "" ]]; then + echo "${name} recipe already exists." + return + else + recipe_id=$(createRecipe "${name}") + fi + cols="chemical,grams,milliliters,makes" + while read line; do + line=$(echo "${line}" | xargs) + if [[ "${line}" == "chemical"* ]]; then + cols="${line}" + cols="${cols/chemical,/chemical_id,}" + continue + fi + component "${cols}" "${line}" "${recipe_id}" + done < "${1}" + + echo "Imported ${name}." +} + +list () { + echo "RECIPES:" + db "SELECT name FROM recipes ORDER BY name DESC;" + echo "CHEMICALS:" + db "SELECT name FROM chemicals ORDER BY name DESC" +} + +supply () { + line=$(echo "${1}" | xargs) + first="" + before="" + + if [[ "${line}" == "chemical,"* ]]; then + return + fi + + chemical=$(echo "${line}" | awk -F',' '{print $1}') + url=$(echo "${line}" | awk -F',' '{print $2}') + + chemical_id=$(chemical "${chemical}") + has_supply=$(hasSupply "${url}") + + if [[ "${has_supply}" != "" ]]; then + return + fi + supply_id=$(createUUID) + query="INSERT OR IGNORE INTO supply (supply_id,chemical_id,url,grams,milliliters,price) VALUES ('${supply_id}','${chemical_id}'," + IFS=',' read -ra VALS <<< "${line}" + for i in "${VALS[@]}"; do + if [[ "${first}" == "" ]]; then + first="complete" + continue + fi + val=NULL + if [[ "${i}" != "" ]]; then + val="'${i}'" + fi + query="${query}${before}${val}" + before="," + done + query="${query});" + #echo "${query}" + + db "${query}" + ratio=$(db "SELECT round((price/100.0)/grams, 2) FROM supply WHERE (supply_id = '${supply_id}');") + echo "${chemical}: ${ratio} $/g" +} + +priceRecipe () { + recipesList=$(mktemp) + query="SELECT r.recipe_id FROM recipes AS r ORDER BY r.name;" + db "${query}" > "${recipesList}" + + while read id; do + db "SELECT name FROM recipes WHERE recipe_id = '${id}' LIMIT 1;" + echo "---" + db "SELECT COUNT(*) FROM components WHERE recipe_id = '${id}';" + query="SELECT ch.name FROM components AS c + INNER JOIN chemicals AS ch ON ch.chemical_id = c.chemical_id + WHERE c.recipe_id = '${id}';" + db "${query}" + echo " " + done < "${recipesList}" +} + +ls -1 "${RECIPES}/"*.csv > "${FILES}" + +while read file; do + import "${file}" +done < "${FILES}" +echo "---" +while read line; do + supply "${line}" +done < "${SUPPLY}" +echo "---" + +priceRecipe +#list diff --git a/recipes/Adox_M-Q_Borax.csv b/recipes/Adox_M-Q_Borax.csv new file mode 100644 index 0000000..0b17bb1 --- /dev/null +++ b/recipes/Adox_M-Q_Borax.csv @@ -0,0 +1,7 @@ +chemical,grams,milliliters,makes,note +Water,,750,1000, +Metol,2,,1000, +Sodium Sulfite (anhydrous),80,,1000, +Hydroquinone,4,,1000, +Borax,4,,1000, +Potassium Bromide,0.5,,1000, \ No newline at end of file diff --git a/recipes/FX-11.csv b/recipes/FX-11.csv new file mode 100644 index 0000000..dd24f52 --- /dev/null +++ b/recipes/FX-11.csv @@ -0,0 +1,8 @@ +chemical,grams,milliliters,makes,note +Water,,700,1000, +Phenidone,0.25,,1000, +Hydroquinone,5,,1000, +Glycin,1.5,,1000, +Sodium Sulfite (anhydrous),125,,1000, +Borax,2.5,,1000,granular +Potassium Bromide,0.5,,1000, diff --git a/recipes/Kodak_D-19.csv b/recipes/Kodak_D-19.csv new file mode 100644 index 0000000..4154dff --- /dev/null +++ b/recipes/Kodak_D-19.csv @@ -0,0 +1,7 @@ +chemical,grams,milliliters,makes,note +Water,,500,1000, +Metol,2,,1000, +Sodium Sulfite (anhydrous),90,,1000, +Sodium Carbonate (monohydrate),52.5,,1000, +Hydroquinone,8,,1000, +Potassium Bromide,5,,1000, diff --git a/recipes/Kodak_D-76.csv b/recipes/Kodak_D-76.csv new file mode 100644 index 0000000..16151ff --- /dev/null +++ b/recipes/Kodak_D-76.csv @@ -0,0 +1,6 @@ +chemical,grams,milliliters,makes,note +Water,,750,1000, +Metol,2,,1000, +Sodium Sulfite (anhydrous),100,,1000, +Hydroquinone,5,,1000, +Borax,2,,1000, diff --git a/recipes/Kodak_Xtol_A.csv b/recipes/Kodak_Xtol_A.csv new file mode 100644 index 0000000..34a3a38 --- /dev/null +++ b/recipes/Kodak_Xtol_A.csv @@ -0,0 +1,6 @@ +chemical,grams,milliliters,makes,note +Water,,850,1000, +Sodium Sulfite,10,,1000, +Diethylenetriaminepentaacetic Acid Pentasodium Salt,1,,1000, +Sodium Metaborate,4.0,,100,(8 mol) +4-Hydroxymethyl-4-Methyl-1-Phenyl-3-Pyrazolidione,0.2,,1000, diff --git a/recipes/Kodak_Xtol_B.csv b/recipes/Kodak_Xtol_B.csv new file mode 100644 index 0000000..e275141 --- /dev/null +++ b/recipes/Kodak_Xtol_B.csv @@ -0,0 +1,4 @@ +chemical,grams,milliliters,makes,note +Sodium Sulfite,75,,1000, +Sodium Metabisulfite,3.5,,1000, +Sodium Isoascorbate,12,,1000, \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..fd9cd9c --- /dev/null +++ b/run.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +rm -rf developers.sqlite + +source common.sh + +python3 import.py \ No newline at end of file diff --git a/setup.sql b/setup.sql new file mode 100644 index 0000000..f6d5f70 --- /dev/null +++ b/setup.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS chemicals ( + chemical_id CHAR(36) PRIMARY KEY, + name TEXT UNIQUE +); + +CREATE TABLE IF NOT EXISTS recipes ( + recipe_id CHAR(36) PRIMARY KEY, + name TEXT UNIQUE +); + +CREATE TABLE IF NOT EXISTS components ( + component_id CHAR(36) PRIMARY KEY, + recipe_id CHAR(36), + chemical_id CHAR(36), + grams REAL, + milliliters REAL, + makes REAL, + note TEXT, + CONSTRAINT fk_recipes FOREIGN KEY (recipe_id) REFERENCES recipes(recipe_id), + CONSTRAINT fk_chemicals FOREIGN KEY (chemical_id) REFERENCES chemicals(chemical_id) +); + +CREATE TABLE IF NOT EXISTS supply ( + supply_id CHAR(36) PRIMARY KEY, + chemical_id CHAR(36), + url TEXT UNIQUE, + grams REAL, + milliliters REAL, + price INTEGER, + CONSTRAINT fk_chemicals FOREIGN KEY (chemical_id) REFERENCES chemicals(chemical_id) +); \ No newline at end of file diff --git a/supply.csv b/supply.csv new file mode 100644 index 0000000..1463fdf --- /dev/null +++ b/supply.csv @@ -0,0 +1,12 @@ +chemical,url,grams,milliliters,price +Sodium Sulfite (anhydrous),https://www.bhphotovideo.com/c/product/124092-REG/Photographers_Formulary_10_1340_1LB_Sodium_Sulfite_1.html,453.592,,695 +Sodium Carbonate (monohydrate),https://www.bhphotovideo.com/c/product/124044-REG/Photographers_Formulary_10_1190_1LB_Sodium_Carbonate_Monohydrate.html,453.592,,995 +Potassium Bromide,https://www.bhphotovideo.com/c/product/123938-REG/Photographers_Formulary_10_0930_1LB_Potassium_Bromide_1.html,453.592,,2195 +Phenidone,https://www.bhphotovideo.com/c/product/123915-REG/Photographers_Formulary_10_0870_100G_Phenidone_100_Grams.html,100,,2695 +Metol,https://www.bhphotovideo.com/c/product/123903-REG/Photographers_Formulary_10_0770_100G_Metol_Elon_100.html,100,,1695 +Hydroquinone,https://www.bhphotovideo.com/c/product/123903-REG/Photographers_Formulary_10_0770_100G_Metol_Elon_100.html,100,,1695 +Glycin,https://www.bhphotovideo.com/c/product/123868-REG/Photographers_Formulary_10_0610_100G_Glycin_N_Parahydroxyphenyl.html,100,,2395 +Borax,https://www.bhphotovideo.com/c/product/123755-REG/Photographers_Formulary_10_0260_1LB_Borax_1_lb.html,453.592,,695 +Hydroquinone,https://www.bhphotovideo.com/c/product/123887-REG/Photographers_Formulary_10_0670_100G_Hydroquinone_100_Grams.html,100,,895 +Sodium Metabisulfite,https://www.bhphotovideo.com/c/product/124063-REG/Photographers_Formulary_10_1280_100G_Sodium_Metabisulfite_100g.html,100,,595 +Sodium Metaborate,https://www.bhphotovideo.com/c/product/124068-REG/Photographers_Formulary_10_1285_1LB_Sodium_Metaborate_Balanced_Alkali.html,453.592,,1195 \ No newline at end of file