# 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"""