From b1722a04fad9ba4f269136458cbfdc002f6a5072 Mon Sep 17 00:00:00 2001 From: Stefano Fiorini Date: Sat, 28 Mar 2026 01:43:34 -0500 Subject: [PATCH] fix: require completed photo review before pdf render --- docs/property-assessor.md | 2 ++ skills/property-assessor/SKILL.md | 2 ++ skills/property-assessor/src/report-pdf.ts | 4 +++- .../tests/report-pdf.test.ts | 20 +++++++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/property-assessor.md b/docs/property-assessor.md index 66104de..57a734e 100644 --- a/docs/property-assessor.md +++ b/docs/property-assessor.md @@ -100,12 +100,14 @@ Current behavior: - does not require recipient email(s) for the analysis-only run - asks for recipient email(s) only when PDF rendering is explicitly requested - does not render/send the PDF from a preliminary helper payload with `decision: pending` +- does not render/send the PDF when `photoReview.status` is not `completed` - only renders the fixed-template PDF after a decision-grade verdict and fair-value range are actually present Expected agent behavior: - if the user asked for the full assessment, continue beyond the preliminary helper output - fill the remaining gaps with listing facts, comp work, condition interpretation, and valuation logic +- require completed subject-unit photo review before treating the report as decision-grade enough for PDF delivery - only stop early when there is a real blocker, not merely because the helper stopped at a checkpoint Important limitation: diff --git a/skills/property-assessor/SKILL.md b/skills/property-assessor/SKILL.md index 388e382..2beddac 100644 --- a/skills/property-assessor/SKILL.md +++ b/skills/property-assessor/SKILL.md @@ -120,12 +120,14 @@ scripts/property-assessor render-report --input "" --output - only stop and ask for recipient email(s) when the user is explicitly rendering or sending the PDF - render the PDF only after recipient email(s) are known - do **not** render or send a PDF from the helper's preliminary payload while verdict is still `pending` or fair value is not established +- do **not** render or send a decision-grade PDF while `photoReview.status` is anything other than `completed` - if comps, valuation, or decision-grade condition interpretation are still incomplete, return the preliminary payload and say that the PDF/send step must wait Agent follow-through rule: - When the user asked for a full property assessment or asked for the PDF/email result, do not stop at the helper output. - After `assess` returns a preliminary payload, continue with the remaining manual/model-driven steps needed to reach a decision-grade report. - Only after the verdict and fair-value range are established should you render/send the PDF. +- A verdict and fair-value range are still not enough by themselves; the subject-unit photo review must also be completed before the PDF/send step. - If the analysis still cannot be completed, explain the first real blocker, not just that the helper was preliminary. - If the user sends `update?`, `and?`, or similar mid-run, answer with status and keep the original assessment going. Do not treat that message as a reset or a cue to stop at the last helper checkpoint. - In WhatsApp or similar messaging runs, do **not** start a background `assess` helper and then wait on repeated zero-output polls. That counts as a failed path; abandon it and continue with native search/fetch/browser work. diff --git a/skills/property-assessor/src/report-pdf.ts b/skills/property-assessor/src/report-pdf.ts index 5573beb..6ce50bc 100644 --- a/skills/property-assessor/src/report-pdf.ts +++ b/skills/property-assessor/src/report-pdf.ts @@ -28,8 +28,10 @@ export interface ReportPayload { export function isDecisionGradeReportPayload(payload: ReportPayload): boolean { const decision = String(payload.verdict?.decision || "").trim().toLowerCase(); const fairValueRange = String(payload.verdict?.fairValueRange || "").trim().toLowerCase(); + const photoReviewStatus = String(payload.photoReview?.status || "").trim().toLowerCase(); if (!decision || decision === "pending") return false; if (!fairValueRange || fairValueRange === "not established") return false; + if (photoReviewStatus !== "completed") return false; return true; } @@ -79,7 +81,7 @@ export function validateReportPayload(payload: ReportPayload): string[] { } if (!isDecisionGradeReportPayload(payload)) { throw new ReportValidationError( - "The report payload is still preliminary. Stop and complete the decision-grade analysis before generating or sending the property assessment PDF." + "The report payload is still preliminary. Stop and complete the decision-grade analysis, including subject-unit photo review, before generating or sending the property assessment PDF." ); } return recipients; diff --git a/skills/property-assessor/tests/report-pdf.test.ts b/skills/property-assessor/tests/report-pdf.test.ts index 45d3b13..f06249d 100644 --- a/skills/property-assessor/tests/report-pdf.test.ts +++ b/skills/property-assessor/tests/report-pdf.test.ts @@ -87,3 +87,23 @@ test("renderReportPdf rejects a preliminary report with pending verdict", async /decision-grade|preliminary|pending/i ); }); + +test("renderReportPdf rejects a report when subject-unit photo review is not completed", async () => { + const outputPath = path.join(os.tmpdir(), `property-assessor-missing-photos-${Date.now()}.pdf`); + await assert.rejects( + () => + renderReportPdf( + { + ...samplePayload, + photoReview: { + status: "not completed", + source: "accessible listing-photo source not reliably exposed for unit 235", + attempts: ["Zillow and HAR photo review did not complete."], + summary: "Condition review is incomplete." + } + }, + outputPath + ), + /photo review|decision-grade|incomplete/i + ); +});