diff --git a/a2a.yaml b/a2a.yaml index a3b111a..531dcde 100644 --- a/a2a.yaml +++ b/a2a.yaml @@ -1,5 +1,5 @@ name: quote-judge-studio-v1 -version: 0.1.1 +version: 0.1.2 entrypoint: agent:QuoteJudgeStudioV1 expose: public: false diff --git a/agent.py b/agent.py index 81fdf4f..dc2d8fe 100644 --- a/agent.py +++ b/agent.py @@ -48,6 +48,7 @@ MAX_QUOTES = 20 MAX_VENDOR_LENGTH = 120 MAX_UPLOADS = 5 MAX_UPLOAD_BYTES = 128_000 +MAX_UPLOAD_BASE64_CHARS = ((MAX_UPLOAD_BYTES + 2) // 3) * 4 ALLOWED_UPLOAD_MEDIA_TYPES = {"text/csv", "text/plain", "application/json"} _DATABASE_NAME = "quote-judge-studio-v1-data" _LOCAL_COMPARISONS: dict[tuple[str, str], dict[str, Any]] = {} @@ -79,20 +80,12 @@ class QuoteWeights(BaseModel): delivery: float = Field(..., ge=0, le=100) warranty: float = Field(..., ge=0, le=100) - @field_validator("warranty") - @classmethod - def weights_must_be_positive(cls, value: float, info: Any) -> float: - values = info.data - total = float(values.get("price", 0)) + float(values.get("delivery", 0)) + float(value) - if total <= 0: - raise ValueError("at least one weight must be positive") - return value class BrowserQuoteDocument(BaseModel): filename: str = Field(..., min_length=1, max_length=160) media_type: str = Field(..., min_length=1, max_length=100) - data_base64: str = Field(..., min_length=1) + data_base64: str = Field(..., min_length=1, max_length=MAX_UPLOAD_BASE64_CHARS) @field_validator("filename") @classmethod @@ -115,7 +108,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]): "QuoteJudge compares vendor quotes with weighted price, delivery, and " "warranty scoring, persists per-user comparisons, and serves a one-page product UI." ) - version = "0.1.1" + version = "0.1.2" config_model = QuoteJudgeStudioV1Config auth_model = PlatformUserAuth @@ -152,7 +145,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]): "weights; persist the recommendation and a production receipt." ), timeout_seconds=30, - idempotent=True, + idempotent=False, cost_class="cheap", ) async def compare_quotes( @@ -171,6 +164,24 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]): "weights": weights.model_dump(), } + if weights.price + weights.delivery + weights.warranty <= 0: + result = { + "ok": False, + "code": "positive_weight_required", + "message": "Set at least one weight above zero before comparing.", + "comparison_id": cleaned_id, + } + await _persist_receipt( + tenant_key=tenant, + comparison_id=cleaned_id, + skill_name="compare_quotes", + input_payload=input_payload, + result_payload=result, + status="validation_error", + started_at=started, + ) + return result + if len(quotes) < 2: result = { "ok": False, @@ -222,7 +233,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]): @a2a.tool( description="Reopen a saved QuoteJudge comparison for the authenticated user.", timeout_seconds=15, - idempotent=True, + idempotent=False, cost_class="cheap", ) async def get_comparison( @@ -261,7 +272,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]): "compare extracted quotes, and persist the result." ), timeout_seconds=30, - idempotent=True, + idempotent=False, cost_class="cheap", ) async def compare_uploaded_quotes_browser( @@ -287,7 +298,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]): "file and compare extracted quotes." ), timeout_seconds=30, - idempotent=True, + idempotent=False, cost_class="cheap", ) async def compare_uploaded_quote_file( @@ -342,6 +353,8 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]): def _tenant_key(ctx: RunContext[PlatformUserAuth]) -> str: auth = ctx.auth ident = auth.user_id if auth.user_id is not None else (auth.sub or auth.email) + if ident is None: + raise ValueError("authenticated user identity is required") return f"user:{ident}" diff --git a/db/migrations/002_quotejudge_schema_repair.sql b/db/migrations/002_quotejudge_schema_repair.sql new file mode 100644 index 0000000..580e07a --- /dev/null +++ b/db/migrations/002_quotejudge_schema_repair.sql @@ -0,0 +1,10 @@ +ALTER TABLE quote_comparisons + ADD COLUMN IF NOT EXISTS recommended_vendor TEXT, + ADD COLUMN IF NOT EXISTS score DOUBLE PRECISION, + ADD COLUMN IF NOT EXISTS normalized_total DOUBLE PRECISION; + +ALTER TABLE quote_execution_receipts + ADD COLUMN IF NOT EXISTS skill_name TEXT, + ADD COLUMN IF NOT EXISTS status TEXT, + ADD COLUMN IF NOT EXISTS input_hash TEXT, + ADD COLUMN IF NOT EXISTS result_hash TEXT;