Improve gallery photo-review guidance for property assessment

This commit is contained in:
2026-03-27 12:02:47 -05:00
parent b13f272e48
commit 90635bf8f2
3 changed files with 54 additions and 4 deletions

View File

@@ -63,6 +63,25 @@ For the target property, capture when available:
Always look at the listing photos when they are available. Do not rate a property only from structured text. Always look at the listing photos when they are available. Do not rate a property only from structured text.
### Required gallery workflow
When the source site has a photo gallery or modal lightbox, you must explicitly open it and verify that you are seeing distinct images, not just a gallery preview tile or repeated screenshot of the first image.
Use `web-automation` for this. Preferred process:
1. Open the listing page.
2. Click the gallery entry point such as `See all photos`, `See all 29 photos`, or the main hero image.
3. Confirm the gallery/lightbox is actually open.
4. Capture the first image.
5. Advance through the gallery using the actual next-arrow control inside the gallery, not a blind keyboard press unless you have confirmed it changes the image.
6. After each advance, confirm the image changed before treating it as reviewed.
7. Review enough images to cover the key rooms and exterior, and for smaller listings aim to review all photos when practical.
8. If navigation fails, say so explicitly and do not claim that you reviewed all photos.
Minimum honesty rule: never say you "looked at all photos" unless you successfully inspected distinct gallery images one by one.
A gallery landing page, collage view, repeated first image, or a single screenshot of the listing page does **not** count as full photo review.
### What to inspect in the photos
At minimum, note: At minimum, note:
- overall finish level: dated, average, lightly updated, fully updated - overall finish level: dated, average, lightly updated, fully updated
- kitchen condition: cabinets, counters, backsplash, appliance quality - kitchen condition: cabinets, counters, backsplash, appliance quality
@@ -72,8 +91,16 @@ At minimum, note:
- obvious make-ready issues: paint, damaged trim, old fixtures, mismatched finishes, worn surfaces - obvious make-ready issues: paint, damaged trim, old fixtures, mismatched finishes, worn surfaces
- visible missing items: refrigerator, washer/dryer, range hood, dishwasher, etc. - visible missing items: refrigerator, washer/dryer, range hood, dishwasher, etc.
- any signs of deferred maintenance or water intrusion visible in photos - any signs of deferred maintenance or water intrusion visible in photos
- exterior/common-area condition when visible
- balconies, decks, sliders, windows, and waterfront-facing elements for condos/townhomes near water
If photos are weak or incomplete, say so explicitly and lower confidence. ### If photo review is incomplete
If photos are weak, incomplete, blocked, or the gallery automation fails:
- say so explicitly
- lower confidence
- avoid strong condition claims
- do not infer turnkey condition from marketing text alone
## Normalization / make-ready adjustment ## Normalization / make-ready adjustment

View File

@@ -127,6 +127,21 @@ Example:
npx tsx flow.ts --instruction 'go to https://search.fiorinis.com then type "pippo" then press enter then wait 2s' npx tsx flow.ts --instruction 'go to https://search.fiorinis.com then type "pippo" then press enter then wait 2s'
``` ```
### Gallery/lightbox workflows
For real-estate listings and other image-heavy pages, prefer opening the gallery/lightbox explicitly and verifying that each navigation step produces a distinct image.
Practical rules:
- Do not treat a listing page hero image, gallery collage, or modal landing view as full photo review.
- After opening the gallery, capture the first image, then use the actual in-gallery next control.
- Confirm the image changed before counting the next screenshot as reviewed.
- If a generic `Next` control exits the gallery or returns to the listing shell, stop and adjust the selector/interaction; do not claim the photos were reviewed.
- Blind `ArrowRight` presses are not reliable enough unless you have already verified that they advance the gallery on that site.
- For smaller listings, review all photos when practical; otherwise review enough distinct photos to cover kitchen, baths, living areas, bedrooms, exterior, and any waterfront/balcony/deck elements.
- If automation cannot reliably advance the gallery, say so explicitly in the final answer.
Where possible, prefer specific controls over vague ones, for example a gallery-specific next-arrow button rather than a page-level `Next` button.
## Compatibility Aliases ## Compatibility Aliases
- `CAMOUFOX_PROFILE_PATH` still works as a legacy alias for `CLOAKBROWSER_PROFILE_PATH` - `CAMOUFOX_PROFILE_PATH` still works as a legacy alias for `CLOAKBROWSER_PROFILE_PATH`

View File

@@ -6,7 +6,7 @@ import { launchBrowser } from './browse';
type Step = type Step =
| { action: 'goto'; url: string } | { action: 'goto'; url: string }
| { action: 'click'; selector?: string; text?: string } | { action: 'click'; selector?: string; text?: string; role?: string; name?: string }
| { action: 'type'; selector?: string; text: string } | { action: 'type'; selector?: string; text: string }
| { action: 'press'; key: string; selector?: string } | { action: 'press'; key: string; selector?: string }
| { action: 'wait'; ms: number } | { action: 'wait'; ms: number }
@@ -41,7 +41,13 @@ function parseInstruction(instruction: string): Step[] {
continue; continue;
} }
// click on "text" or click #selector // click on "text" or click #selector or click button "name"
const clickRole = p.match(/^click\s+(button|link|textbox|img|image|tab)\s+"([^"]+)"$/i);
if (clickRole) {
const role = clickRole[1].toLowerCase() === 'image' ? 'img' : clickRole[1].toLowerCase();
steps.push({ action: 'click', role, name: clickRole[2] });
continue;
}
const clickText = p.match(/^click(?: on)?\s+"([^"]+)"/i); const clickText = p.match(/^click(?: on)?\s+"([^"]+)"/i);
if (clickText) { if (clickText) {
steps.push({ action: 'click', text: clickText[1] }); steps.push({ action: 'click', text: clickText[1] });
@@ -203,6 +209,8 @@ async function runSteps(page: Page, steps: Step[]) {
case 'click': case 'click':
if (step.selector) { if (step.selector) {
await page.locator(step.selector).first().click({ timeout: 15000 }); await page.locator(step.selector).first().click({ timeout: 15000 });
} else if (step.role && step.name) {
await page.getByRole(step.role as any, { name: new RegExp(escapeRegExp(step.name), 'i') }).first().click({ timeout: 15000 });
} else if (step.text) { } else if (step.text) {
const clicked = await clickByText(page, step.text); const clicked = await clickByText(page, step.text);
if (!clicked) { if (!clicked) {
@@ -212,7 +220,7 @@ async function runSteps(page: Page, steps: Step[]) {
} }
} }
} else { } else {
throw new Error('click step missing selector/text'); throw new Error('click step missing selector/text/role');
} }
try { try {
await page.waitForLoadState('domcontentloaded', { timeout: 10000 }); await page.waitForLoadState('domcontentloaded', { timeout: 10000 });