Aarya2004
Deploy: sync hosted Space to local app (chat, document capture, Modal backends, pages, mobile/QR)
47b2a99 | """Document Capture: a handed-over document -> Observations + Proposed Line Items (ADR-0011). | |
| Thin adapter over the Extraction role (Nemotron Parse on Modal): file path in, JSON | |
| the frontend confirm card renders out. The model is injectable for tests. In | |
| production it resolves to ParseModel when FF_MODAL_PARSE_URL is set; otherwise a | |
| deterministic demo parse runs the REAL blocks_to_pipeline logic over a canned | |
| supplier quote, so the stub Space demos the flow with zero models (the same | |
| honest-scaffolding pattern as _stub_perception in api/estimate.py). | |
| """ | |
| import os | |
| from quillwright.backends.parse import blocks_to_pipeline | |
| # The canned supplier quote the demo "reads" when no Modal Parse endpoint is | |
| # configured. Mirrors the test fixture in test_parse_backend.py. | |
| _DEMO_BLOCKS = [ | |
| {"class": "Title", "bbox": [], "text": "ACME HVAC Supply — Quote #1042"}, | |
| { | |
| "class": "Table", | |
| "bbox": [], | |
| "text": ( | |
| "| Item | Qty | Unit Price |\n" | |
| "| --- | --- | --- |\n" | |
| "| Dual run capacitor | 2 | $42.50 |\n" | |
| "| Compressor contactor | 1 | $28.00 |\n" | |
| "| R-410A refrigerant | 4 | $30.00 |\n" | |
| ), | |
| }, | |
| {"class": "Text", "bbox": [], "text": "Net 30 terms. Prices valid 30 days."}, | |
| ] | |
| def _resolve_extraction(): | |
| """Real Nemotron Parse when its Modal URL is configured; else None (demo parse).""" | |
| if os.environ.get("FF_MODAL_PARSE_URL"): | |
| from quillwright.resolver import ModelResolver | |
| return ModelResolver(mode="best", backend="modal").for_role("extraction") | |
| return None | |
| def parse_document_capture(path: str, model=None) -> dict: | |
| """Parse the document at `path`; return {model, observations, proposed_items}. | |
| Every price stays *proposed* — the human confirms it in the UI before it becomes | |
| a LineItem with price_source="document" (Facts-from-Tools, ADR-0004/0011). | |
| """ | |
| parser = model if model is not None else _resolve_extraction() | |
| if parser is None: | |
| observations, proposed = blocks_to_pipeline(_DEMO_BLOCKS) | |
| name = "parse-stub (demo quote)" | |
| else: | |
| observations, proposed = parser.parse_document(path) | |
| name = parser.name | |
| return { | |
| "model": name, | |
| "observations": [{"kind": o.kind, "text": o.text} for o in observations], | |
| "proposed_items": [ | |
| { | |
| "description": p.description, | |
| "quantity": p.quantity, | |
| "unit": p.unit, | |
| "rate": p.rate, | |
| "source_text": p.source_text, | |
| } | |
| for p in proposed | |
| ], | |
| } | |