Quillwright / quillwright /models.py
Aarya2004
Deploy: sync hosted Space to local app (chat, document capture, Modal backends, pages, mobile/QR)
47b2a99
Raw
History Blame Contribute Delete
2.21 kB
from typing import Literal
from pydantic import BaseModel, computed_field
class Observation(BaseModel):
kind: Literal["equipment", "part", "damage", "text", "other"]
text: str
confidence: float = 1.0
class Capture(BaseModel):
image_paths: list[str] = []
transcript: str = ""
trade_hint: str | None = None
class LineItem(BaseModel):
description: str
quantity: float
unit: str
rate: float
# "document" = a price Parse read from a Document Capture that the human has
# confirmed verbatim through the Agent Pause (ADR-0011). The document is the
# source, but the number is still user-gated — it never enters an Estimate
# straight from the model.
price_source: Literal["catalog", "user", "computed", "document"] = "catalog"
@computed_field
@property
def subtotal(self) -> float:
return round(self.quantity * self.rate, 2)
class ProposedLineItem(BaseModel):
"""A priced row Parse read from a Document Capture, awaiting human confirmation.
Not an Estimate line yet: per Facts-from-Tools (ADR-0004) + ADR-0011, a price
a model read off a document is *proposed*, not a fact. The human confirms or
edits it via the Agent Pause; on confirm it becomes a LineItem with
price_source="document". `source_text` is the raw cell the price came from, so
the human can spot an OCR slip (e.g. "$42.50" misread as "$425.0").
"""
description: str
quantity: float = 1.0
unit: str = "ea"
rate: float
source_text: str = ""
class Estimate(BaseModel):
job_title: str
line_items: list[LineItem] = []
tax_rate: float = 0.0
@computed_field
@property
def subtotal(self) -> float:
return round(sum(i.subtotal for i in self.line_items), 2)
@computed_field
@property
def tax(self) -> float:
return round(self.subtotal * self.tax_rate, 2)
@computed_field
@property
def total(self) -> float:
return round(self.subtotal + self.tax, 2)
class TraceStep(BaseModel):
action: str
model: str | None = None
detail: str = ""
confidence: float | None = None
status: Literal["ok", "active", "paused", "error"] = "ok"