"""End-to-end pricing pipeline validation (COSMOS + Apify + suggested price + gate). Runs against live .env credentials (conda env Talha): conda run -n Talha python scripts/e2e_pipeline_validate.py conda run -n Talha python scripts/e2e_pipeline_validate.py UBMICROFIBERGUSSETPILLOWWHITEQUEEN 24.99 Writes: data/e2e_pipeline_results.json E2E_PIPELINE_REPORT.md (overwritten with this run's findings) """ from __future__ import annotations import json import sys import time from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")) sys.path.insert(0, str(ROOT)) from config.settings import Settings, get_rules, get_settings # noqa: E402 from langgraph.types import Command # noqa: E402 from pricing_agent.analyze import analyze_price, suggested_price # noqa: E402 from pricing_agent.graph import build_graph # noqa: E402 from pricing_agent.providers import get_provider # noqa: E402 from pricing_agent.schemas import Decision, FeeQuote, SkuInput # noqa: E402 from pricing_agent.tools.gate import evaluate_gate # noqa: E402 from pricing_agent.tools.margin_engine import stack_from_quote # noqa: E402 def _checks() -> list[dict]: return [] def main() -> int: sku = sys.argv[1] if len(sys.argv) > 1 else "UBMICROFIBERGUSSETPILLOWWHITEQUEEN" price = float(sys.argv[2]) if len(sys.argv) > 2 else 24.99 # Reload settings so fresh .env APIFY_* are picked up (lru_cache may be warm). get_settings.cache_clear() settings = Settings() rules = get_rules() checks: list[dict] = [] report: dict = { "sku": sku, "candidate_price": price, "apify_token_set": bool(settings.apify_token), "apify_seller_id": settings.apify_seller_id, "margin_target": rules.margin_target, "margin_floor": rules.margin_floor, "checks": checks, } print(f"E2E SKU={sku} @ ${price:.2f}") print(f"APIFY_TOKEN set={bool(settings.apify_token)} " f"SELLER_ID={settings.apify_seller_id or '(none)'}") print("---") # ── 1. Provider enrich + fees (COSMOS) ───────────────────────────── t0 = time.time() provider = get_provider(settings) sku_in = SkuInput( sku=sku, asin=None, product_name="", marketplace="US", category="", landed_cost=0.0, target_price=price, ) try: enriched = provider.enrich(sku_in) stack = provider.fee_stack(enriched) checks.append({ "name": "cosmos_enrich_and_fees", "pass": bool(enriched.asin) and stack.selling_price == price, "elapsed_s": round(time.time() - t0, 1), "asin": enriched.asin, "landed_cost": enriched.landed_cost, "fba_fee": stack.fba_fee, "referral_pct": stack.referral_pct, "cm_pct": round(stack.contribution_margin_pct, 4), "break_even": stack.break_even_price, "map_floor": stack.map_floor, "detail": f"ASIN={enriched.asin} CM={stack.contribution_margin_pct*100:.1f}%", }) except Exception as e: # noqa: BLE001 checks.append({ "name": "cosmos_enrich_and_fees", "pass": False, "error": str(e), "elapsed_s": round(time.time() - t0, 1), }) print("FAIL: COSMOS enrich/fees:", e) _write(report) return 1 print(f"[1] COSMOS OK asin={enriched.asin} CM={stack.contribution_margin_pct*100:.1f}% " f"BE=${stack.break_even_price:.2f} MAP=${stack.map_floor:.2f}") # ── 2. Suggested price math + re-check margin ───────────────────── t0 = time.time() sug = suggested_price(stack, rules.margin_target) sug_ok = False sug_cm = None sug_detail = "" try: if sug is None: sug_detail = "suggested_price returned None" else: # Re-stack at suggestion with scaled referral/returns (same model as unit test). requote = FeeQuote( selling_price=sug, landed_cost=stack.landed_cost, fba_fee=stack.fba_fee, referral_amt=stack.referral_pct * sug, returns_reserve=(stack.returns_reserve / stack.selling_price) * sug if stack.selling_price else 0.0, other_fees=stack.storage_alloc, source="e2e-reprice", ) restack = stack_from_quote(requote, rules) sug_cm = restack.contribution_margin_pct charm_ok = round(sug - int(sug), 2) == 0.99 clears = sug_cm + 1e-9 >= rules.margin_target sug_ok = charm_ok and clears sug_detail = ( f"suggested=${sug:.2f} charm={charm_ok} " f"CM_at_sug={sug_cm*100:.1f}% >= target {rules.margin_target*100:.0f}% → {clears}" ) # Also ask COSMOS takehome at the suggestion when possible. try: live = provider.fee_stack(enriched.model_copy(update={"target_price": sug})) sug_detail += f" | COSMOS_CM_at_sug={live.contribution_margin_pct*100:.1f}%" except Exception as e: # noqa: BLE001 sug_detail += f" | COSMOS_reprice_skip={e}" except Exception as e: # noqa: BLE001 sug_detail = str(e) checks.append({ "name": "suggested_price_clears_target", "pass": sug_ok, "elapsed_s": round(time.time() - t0, 1), "suggested_price": sug, "cm_at_suggested": sug_cm, "detail": sug_detail, }) print(f"[2] SUGGESTED {sug_detail} → {'PASS' if sug_ok else 'FAIL'}") # ── 3. Apify competitive (live Buy Box) ─────────────────────────── t0 = time.time() try: comp = provider.competitive(enriched) apify_ok = ( bool(settings.apify_token) and comp.buy_box_status.value != "UNKNOWN" or (comp.buy_box_price is not None) ) # Soften: price present is enough to call Apify usable even if UNKNOWN status usable = comp.buy_box_price is not None or comp.is_suppressed checks.append({ "name": "apify_competitive", "pass": usable, "elapsed_s": round(time.time() - t0, 1), "buy_box_status": comp.buy_box_status.value, "buy_box_price": comp.buy_box_price, "competitive_low": comp.competitive_low, "competitive_median": comp.competitive_median, "competitive_high": comp.competitive_high, "is_suppressed": comp.is_suppressed, "reason": comp.reason, "detail": ( f"{comp.buy_box_status.value} bb=${comp.buy_box_price} " f"band={comp.competitive_low}-{comp.competitive_high}" ), }) print(f"[3] APIFY {comp.buy_box_status.value} bb=${comp.buy_box_price} " f"band={comp.competitive_low}-{comp.competitive_median}-{comp.competitive_high} " f"→ {'PASS' if usable else 'FAIL'}") print(f" {comp.reason}") except Exception as e: # noqa: BLE001 checks.append({ "name": "apify_competitive", "pass": False, "error": str(e), "elapsed_s": round(time.time() - t0, 1), }) print("[3] APIFY FAIL:", e) _write(report) return 1 # ── 4. Gate decision ────────────────────────────────────────────── t0 = time.time() decision, reasons = evaluate_gate( gate_stack=stack, competitive=comp, rules=rules ) checks.append({ "name": "evaluate_gate", "pass": decision in (Decision.APPROVED, Decision.NEEDS_REVIEW, Decision.BLOCKED), "elapsed_s": round(time.time() - t0, 1), "decision": decision.value, "reasons": reasons, "detail": f"{decision.value}: {reasons[0] if reasons else ''}", }) print(f"[4] GATE {decision.value}") for r in reasons: print(f" • {r}") # ── 5. Analyze path (suggested + verdict; COSMOS-heavy) ─────────── t0 = time.time() try: analysis = analyze_price(sku, price, settings, with_narrative=False) a_ok = analysis.suggested_price is not None # Suggested from analyze should match (or be very close to) provider-stack suggestion delta = None if sug is not None and analysis.suggested_price is not None: delta = abs(analysis.suggested_price - sug) a_ok = a_ok and delta < 0.02 # same charm price within a cent checks.append({ "name": "analyze_suggested_consistent", "pass": a_ok, "elapsed_s": round(time.time() - t0, 1), "analyze_suggested": analysis.suggested_price, "provider_suggested": sug, "delta": delta, "rating": analysis.rating.value, "margin_pct": analysis.margin_pct, "take_home": analysis.take_home_per_unit, "detail": ( f"analyze_sug=${analysis.suggested_price} provider_sug=${sug} " f"delta={delta} rating={analysis.rating.value}" ), }) print(f"[5] ANALYZE sug=${analysis.suggested_price} " f"rating={analysis.rating.value} CM={analysis.margin_pct*100:.1f}% " f"→ {'PASS' if a_ok else 'FAIL'}") except Exception as e: # noqa: BLE001 checks.append({ "name": "analyze_suggested_consistent", "pass": False, "error": str(e), "elapsed_s": round(time.time() - t0, 1), }) print("[5] ANALYZE FAIL:", e) # ── 6. Full LangGraph pipeline (enrich→…→Apify→gate→HITL auto) ─── t0 = time.time() try: graph = build_graph() config = {"configurable": {"thread_id": f"e2e:{sku}:{int(time.time())}"}} state = graph.invoke({"sku_input": enriched}, config=config) interrupts = state.get("__interrupt__") if interrupts: payload = interrupts[0].value # smart: approve only APPROVED action = ( "approve" if payload.get("decision") == Decision.APPROVED.value else "reject" ) state = graph.invoke( Command(resume={"action": action, "price": None, "note": f"e2e-{action}"}), config=config, ) proposal = state.get("decision") graph_ok = proposal is not None checks.append({ "name": "langgraph_e2e", "pass": graph_ok, "elapsed_s": round(time.time() - t0, 1), "decision": proposal.decision.value if proposal else None, "recommended_price": proposal.recommended_price if proposal else None, "buy_box": ( proposal.competitive.buy_box_status.value if proposal else None ), "buy_box_price": ( proposal.competitive.buy_box_price if proposal else None ), "cm_pct": ( proposal.fee_stack.contribution_margin_pct if proposal else None ), "written": state.get("written"), "detail": ( f"{proposal.decision.value} @ ${proposal.recommended_price:.2f} " f"bb={proposal.competitive.buy_box_status.value}" if proposal else "no proposal" ), }) print(f"[6] GRAPH {checks[-1]['detail']} written={state.get('written')} " f"→ {'PASS' if graph_ok else 'FAIL'}") except Exception as e: # noqa: BLE001 checks.append({ "name": "langgraph_e2e", "pass": False, "error": str(e), "elapsed_s": round(time.time() - t0, 1), }) print("[6] GRAPH FAIL:", e) report["checks"] = checks report["all_passed"] = all(c.get("pass") for c in checks) _write(report) print("---") print(f"ALL PASSED: {report['all_passed']}") return 0 if report["all_passed"] else 1 def _write(report: dict) -> None: out_json = ROOT / "data" / "e2e_pipeline_results.json" out_json.parent.mkdir(parents=True, exist_ok=True) out_json.write_text(json.dumps(report, indent=2, default=str), encoding="utf-8") lines = [ "# E2E pricing pipeline validation report", "", f"| | |", f"|---|---|", f"| **SKU** | `{report.get('sku')}` |", f"| **Candidate price** | ${report.get('candidate_price')} |", f"| **Apify token set** | {report.get('apify_token_set')} |", f"| **Seller id** | `{report.get('apify_seller_id') or '—'}` |", f"| **Margin target / floor** | " f"{(report.get('margin_target') or 0)*100:.0f}% / " f"{(report.get('margin_floor') or 0)*100:.0f}% |", f"| **All passed** | **{report.get('all_passed')}** |", "", "## Checks", "", "| Check | Pass | Detail |", "|---|---|---|", ] for c in report.get("checks", []): detail = (c.get("detail") or c.get("error") or "").replace("|", "/") lines.append( f"| `{c.get('name')}` | {'PASS' if c.get('pass') else 'FAIL'} | {detail} |" ) lines += [ "", "## Notes", "", "- Suggested price is derived from the fee stack (COSMOS costs/fees + margin target), " "charm-rounded to `.99`, then re-checked that CM ≥ target.", "- Apify fills Buy Box / competitive band after COSMOS resolves the ASIN.", "- LangGraph path: enrich → margin → competitive → evaluate → human_review → write_back.", "", f"Raw JSON: [`data/e2e_pipeline_results.json`](data/e2e_pipeline_results.json)", "", ] (ROOT / "E2E_PIPELINE_REPORT.md").write_text("\n".join(lines), encoding="utf-8") print(f"Wrote {out_json}") print(f"Wrote {ROOT / 'E2E_PIPELINE_REPORT.md'}") if __name__ == "__main__": raise SystemExit(main())