Spaces:
Sleeping
Sleeping
File size: 5,584 Bytes
6aa907d c3fc7ce 6aa907d b1d1fcb 6aa907d b1d1fcb 6aa907d 199d49b 6aa907d 199d49b 6aa907d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | # GoodFind β OCR (+ optional ML) with graceful fallback
import streamlit as st, base64, shutil
# Optional ML (if you added valuation.py earlier)
try:
from valuation import DealValuator, deal_score
HAS_ML = True
except Exception:
HAS_ML = False
from ocr_utils import ocr_image, guess_price, guess_title, annotate_price_box
st.set_page_config(page_title="GoodFind (OCR Demo)", page_icon="π")
st.title("π GoodFind β OCR Demo")
# Check if the tesseract binary exists (fallback-safe)
HAS_TESSERACT = shutil.which("tesseract") is not None
status = []
status.append("OCR engine: β
available" if HAS_TESSERACT else "OCR engine: β missing (fallback to manual)")
status.append("ML estimator: β
" if HAS_ML else "ML estimator: β (using keyword fallback)")
st.caption(" Β· ".join(status))
# If ML is present, initialize once
if HAS_ML and 'valuator' not in st.session_state:
st.session_state['valuator'] = DealValuator()
valuator = st.session_state.get('valuator')
# Simple fallback estimator (if no ML)
PRICE_TABLE = {"wii":85,"playstation":120,"ps2":75,"ps3":110,"ps4":160,"ps5":350,"iphone":240,"macbook":450,"aeron":500,
"pyrex":120,"le creuset":240,"kitchenaid":220,"bose":140,"walkman":95,"marantz":400,"yeti":18,"dansko":40,
"coach":65,"levi":28,"seiko":95,"all-clad":55}
DEFAULT_RESALE = 45.0
def quick_estimate(title: str) -> float:
t = (title or "").lower()
best = None
for k, v in PRICE_TABLE.items():
if k in t:
best = max(best or 0, v)
return float(best if best else DEFAULT_RESALE)
def deal_score_simple(predicted_resale: float, ask: float, fee_rate: float=0.13, ship: float=12.0):
fees = predicted_resale * fee_rate
net = predicted_resale - fees - ship
profit = net - ask
margin = (profit / ask) if ask > 0 else 0.0
if profit >= 50 and margin >= 0.8: label = "Home Run"
elif profit >= 25 and margin >= 0.5: label = "Great"
elif profit >= 10 and margin >= 0.3: label = "Good"
elif profit >= 5: label = "Meh"
else: label = "Pass"
return round(fees,2), round(net,2), round(profit,2), round(margin,2), label
# UI controls
fees_rate = st.slider("Fees rate", 0.05, 0.20, 0.13, 0.01)
ship = st.slider("Shipping estimate ($)", 0.0, 30.0, 12.0, 1.0)
autoscan = st.checkbox("Auto-scan photos with OCR (read price tags + suggest names)", value=True)
photos = st.file_uploader("Upload photos (JPG/PNG/WebP)", type=["jpg","jpeg","png","webp"], accept_multiple_files=True)
items = []
if photos:
for i, img in enumerate(photos):
raw = img.read()
suggested_title, suggested_price, annotated = "", "", None
if autoscan and HAS_TESSERACT:
text, tokens, _ = ocr_image(raw)
suggested_title = guess_title(text) or ""
gp = guess_price(tokens)
if gp:
val, box = gp
suggested_price = f"{val:.2f}"
try:
annotated = annotate_price_box(raw, box, label=f"${val:.2f}")
except Exception:
annotated = None
elif autoscan and not HAS_TESSERACT:
st.info("OCR isnβt available in this runtime; you can still type the name and price manually.")
with st.expander(f"Photo {i+1}", expanded=True):
st.image(annotated or raw, use_container_width=True)
c1, c2 = st.columns([2,1])
with c1:
name = st.text_input(f"Item name #{i+1}", value=suggested_title, placeholder="e.g., Bose QC35 headphones", key=f"name_{i}")
with c2:
price_txt = st.text_input(f"Asking price #{i+1} ($)", value=suggested_price, placeholder="e.g., 25.00", key=f"price_{i}")
items.append((name, price_txt, raw, img.type or "image/jpeg"))
if st.button("Evaluate group", type="primary"):
rows, gallery_blocks = [], []
for idx, (name, price_txt, raw, mime) in enumerate(items):
name = (name or "").strip() or f"Item {idx+1}"
try:
ask = float(price_txt) if price_txt and price_txt.strip() else 0.0
except:
ask = 0.0
if HAS_ML and valuator:
resale = valuator.predict_resale(name)
fees_v, net, profit, margin, label = deal_score(resale, ask, fees_rate, ship)
else:
resale = quick_estimate(name)
fees_v, net, profit, margin, label = deal_score_simple(resale, ask, fees_rate, ship)
rows.append({"title": name, "ask": ask, "resale": resale, "profit": profit, "margin": margin, "label": label})
b64 = base64.b64encode(raw).decode("utf-8")
gallery_blocks.append(f"""
<div class="img-wrap" title="{name}">
<img src="data:{mime};base64,{b64}" alt="{name}"/>
<span class="hover-label">{name}</span>
</div>
""")
if rows:
rows.sort(key=lambda r: (r["profit"], r["resale"]), reverse=True)
st.markdown("### Ranked results")
st.table([
{"Title": r["title"], "Ask ($)": f"{r['ask']:.2f}",
"Est. resale ($)": f"{r['resale']:.2f}", "Profit ($)": f"{r['profit']:.2f}",
"Margin": f"{r['margin']:.2f}", "Verdict": r["label"]}
for r in rows
])
top = rows[0]
st.success(f"Top pick: **{top['title']}** β resale ${top['resale']:.0f}, profit ${top['profit']:.0f} ({top['label']})")
st.markdown("### Hover over each image to see the item name")
st.markdown("".join(gallery_blocks), unsafe_allow_html=True)
|