# property-assessor

Decision-grade residential property assessment skill for OpenClaw, with official public-record enrichment and fixed-template PDF report rendering.

## Overview

`property-assessor` is for evaluating a condo, townhouse, house, or similar residential property from an address or listing URL and ending with a practical recommendation such as `buy`, `pass`, or `only below X`.

If the subject property has an apartment / unit / suite number, include it. Discovery is now unit-aware for Zillow and HAR when unit data is present, while still supporting plain single-family addresses that have no unit.

The skill is intended to:

- normalize the property across listing sources
- review listing photos before making condition claims
- incorporate official public-record / appraisal-district context when available
- compare the property against comps and carrying costs
- produce a fixed-format PDF report, not just ad hoc chat prose

## Standalone helper usage

This skill now ships with a small TypeScript helper package for three tasks:

- assembling an address-first preliminary assessment payload
- locating official public-record jurisdiction from an address
- rendering a fixed-template PDF report

From `skills/property-assessor/`:

```bash
npm install
scripts/property-assessor --help
```

The wrapper script uses the skill-local Node dependencies under `node_modules/`.

Delivery rule:

- If the user explicitly says to email or send the finished PDF to a stated target address, that counts as delivery authorization once the report is ready.
- The agent should not ask for a second `send it` confirmation unless the user changed the destination or showed uncertainty.
- Final property-assessor delivery should be sent as Luke via the Luke Google Workspace wrapper, while the destination remains whatever address the user specified.

## Commands

```bash
scripts/property-assessor assess --address "4141 Whiteley Dr, Corpus Christi, TX 78418" --assessment-purpose "investment property"
scripts/property-assessor assess --address "4141 Whiteley Dr, Corpus Christi, TX 78418" --assessment-purpose "investment property" --recipient-email "buyer@example.com"
scripts/property-assessor assess --address "4141 Whiteley Dr, Corpus Christi, TX 78418" --assessment-purpose "investment property" --recipient-email "buyer@example.com" --output /tmp/property-assessment.pdf

scripts/property-assessor locate-public-records --address "4141 Whiteley Dr, Corpus Christi, TX 78418"
scripts/property-assessor locate-public-records --address "4141 Whiteley Dr, Corpus Christi, TX 78418" --parcel-id "14069438"
scripts/property-assessor locate-public-records --address "4141 Whiteley Dr, Corpus Christi, TX 78418" --listing-geo-id "233290"

scripts/property-assessor render-report --input examples/report-payload.example.json --output /tmp/property-assessment.pdf
```

## Core workflow

Default operating sequence:

1. Normalize the address and property type.
2. Resolve public-record jurisdiction from the address.
3. Discover accessible listing sources for the same property.
4. Build a baseline fact set.
5. Review listing photos before making condition claims.
6. Pull same-building or nearby comps.
7. Underwrite carry costs and risk factors.
8. Render the final report as a fixed-template PDF.

Operational rule:

- The helper returning a preliminary payload is not the end of the job.
- For a user request that clearly asks for a full assessment or PDF delivery, the agent is expected to continue the missing analysis after the helper returns.
- Preliminary helper output should be treated as structured scaffolding for the remaining work, not as a reason to stop and wait for another user nudge.
- In chat/messaging runs, do not waste the turn on `npm install` or `npm ci` when the local skill dependencies are already present.
- If the user asks `update?` or `and?` mid-run, treat that as a status request and continue the same assessment rather than restarting or stopping at the last checkpoint.
- In WhatsApp or similar messaging runs, keep the core analysis on `web-automation` plus `web_fetch`. Treat `web_search` as a narrow fallback for alternate-URL discovery only, not the default path for Zillow/HAR/CAD work.
- Do not start Zillow/HAR property discovery or photo review from Brave-backed `web_search` when `web-automation` can open the candidate listing source directly.
- For CAD/public-record lookup, prefer official assessor/CAD pages via `web_fetch` first and `web-automation` second if the site needs rendered interaction.
- In Texas runs, do not use `https://www.texas.gov/propertytaxes/search/` as the CAD lookup path; use the address-first CAD/helper path or the discovered county CAD pages directly.
- In those messaging runs, reserve subprocess use for a single final `render-report` attempt after the verdict and fair-value range are complete.
- In those messaging runs, do not start Gmail/email-send skill discovery or delivery tooling until the report content is complete and the PDF is ready to render or already rendered.
- Property-assessor delivery emails should be sent as Luke from Luke's Google Workspace account, while still delivering to the user-specified destination.
- Use `zsh ~/.openclaw/workspace/bin/gog-luke gmail send --to "<target>" --subject "<subject>" --body "<body>" --attach "<pdf-path>"` for final PDF delivery.
- Do not route property-assessor delivery through generic `gog` or the Stefano helper `node ~/.openclaw/workspace/integrations/google-workspace/gw.js`.
- If the agent needs to confirm Luke auth before sending, use `zsh ~/.openclaw/workspace/bin/gog-luke auth list --check --plain`.
- Treat a silent helper as a failed helper in messaging runs. If a helper produces no useful output within a short bound, abandon it and continue with the chat-native path instead of repeatedly polling it.
- If Zillow photo extraction fails, immediately continue with HAR photo fallback or the next available rendered listing/photo source rather than stopping the assessment.
- After a Zillow/HAR photo miss, continue the comp and CAD/public-record work in the same run. A photo-source miss is a fallback event, not a terminal state.
- If the original request already authorized sending the finished PDF to a stated email address, do not pause for a redundant send-confirmation prompt after rendering.
- If final PDF render/send fails, return the completed decision-grade report in chat and report delivery failure separately rather than restarting the whole assessment.

### `assess`

```bash
scripts/property-assessor assess --address "<street-address>" --assessment-purpose "<purpose>"
scripts/property-assessor assess --address "<street-address>" --assessment-purpose "<purpose>" --recipient-email "<target@example.com>"
```

Current behavior:

- starts from the address
- requires an assessment purpose for decision-grade analysis
- does not assume the assessment purpose from prior thread context unless the user explicitly says the purpose is unchanged
- automatically runs public-record / appraisal-district lookup
- keeps CAD-site selection address-driven and jurisdiction-specific; it does not hardcode one county's CAD as the global source
- when a supported official CAD detail host is found, captures direct property facts such as property ID/account, owner, legal description, assessed value, exemptions, and the official property-detail URL
- automatically tries to discover Zillow and HAR listing URLs from the address when no listing URL is provided
- starts Zillow and HAR listing discovery in parallel so HAR can already be in flight if Zillow misses or stalls
- runs Zillow photo extraction first, then HAR as fallback when available
- gives Zillow a longer source-specific discovery/photo window than the generic fallback path, because some exact-unit Zillow pages resolve more slowly than HAR or public-record lookups
- reuses the OpenClaw web-automation logic in-process instead of spawning nested helper commands
- fails fast when Zillow/HAR discovery or photo extraction stalls instead of hanging indefinitely
- returns a structured preliminary report payload
- 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:

- this implementation now wires the address-first intake, purpose-aware framing, public-record lookup, listing discovery, and photo-source extraction
- it still does not perform full comp analysis, pricing judgment, or completed carry underwriting inside the helper itself
- those deeper decision steps are still governed by the skill workflow after the helper assembles the enriched payload

## Source priority

Unless the user says otherwise, preferred listing/source order is:

1. Zillow
2. Redfin
3. Realtor.com
4. HAR / Homes.com / brokerage mirrors
5. county or appraisal pages

Public-record / assessor data should be linked in the final result when available.

## Public-record enrichment

The skill should not rely on listing-site geo IDs as if they were assessor record identifiers.

Correct approach:

1. start from the street address
2. resolve the address to state/county/FIPS/GEOID
3. identify the official public-record jurisdiction
4. use parcel/APN/account identifiers when available
5. link the official jurisdiction page and any direct property page used

### `locate-public-records`

```bash
scripts/property-assessor locate-public-records --address "<street-address>"
```

Current behavior:

- uses the official Census geocoder
- when Census address matching misses, falls back to an external address geocoder and then resolves official Census geographies from coordinates
- retries fallback geocoding without the unit suffix when a condo/unit-qualified address is too specific for the fallback provider
- returns matched address, county/state/FIPS, and block GEOID context
- for Texas, returns:
  - Texas Comptroller county directory page
  - appraisal district contact/site details
  - tax assessor/collector contact/site details
  - official CAD property-detail facts when a supported county adapter can retrieve them from the discovered CAD site

Important rules:

- Zillow/Redfin/HAR geo IDs are hints only
- parcel/APN/account IDs are stronger search keys than listing geo IDs
- if Zillow exposes a parcel/APN/account number on the listing, capture it and use that identifier in CAD lookup before falling back to address-only matching
- official jurisdiction pages should be linked in the final report
- if a direct property detail page is accessible, its data should be labeled as official public-record evidence

### Texas support

Texas is the first-class public-record path in this implementation.

For Texas addresses, the helper resolves:

- the official Census geocoder link
- the official Texas Comptroller county directory page
- the appraisal district website
- the tax assessor/collector website
- the official CAD property-detail page when a supported adapter can identify and retrieve the subject record

That output should be used by the skill to:

- identify the correct CAD
- attempt address / parcel / account lookup on the discovered CAD site for that county
- prefer Zillow-exposed parcel/APN/account identifiers over address-only search when the listing provides them
- capture official owner / legal / assessed-value evidence when a public detail page is available
- treat county-specific CAD detail retrieval as an adapter layer on top of generic county/jurisdiction resolution

Nueces-specific note:

- when using Nueces CAD `By ID` / `Geographic ID`, insert a dash after the first 4 digits and again after the first 8 digits, for example `123456789012` -> `1234-5678-9012`

Recommended fields to capture from official records when accessible:

- account number
- owner name
- land value
- improvement value
- assessed total
- exemptions
- official property-detail URL

## Minimum data to capture

For the subject property, capture when available:

- address
- list price or last known list price
- property type
- beds / baths
- square footage
- lot size if relevant
- year built
- HOA fee and included services
- taxes
- days on market
- price history
- parking
- waterfront / flood clues
- subdivision or building name
- same-building or nearby active inventory
- listing photos and visible condition cues
- public-record jurisdiction and linked official source
- account / parcel / tax ID if confirmed
- official assessed values and exemptions if confirmed

## Photo-review rules

Photo review is mandatory when photos are exposed by a listing source.

Do not make strong condition claims from structured text alone if photos are available.

Preferred photo-access order:

1. Zillow extractor
2. HAR extractor
3. Realtor.com photo page
4. brokerage mirror or other accessible listing mirror

Use the dedicated `web-automation` extractors first:

```bash
cd ~/.openclaw/workspace/skills/web-automation/scripts
node zillow-photos.js "<zillow-listing-url>"
node har-photos.js "<har-listing-url>"
```

When those extractors return `imageUrls`, that returned set is the photo-review set.

## Approval-safe command shape

For local/manual runs, prefer file-based commands.

For chat-driven messaging runs, prefer native `web_search`, `web_fetch`, and bounded browser actions first. Treat these commands as local/manual helpers or non-chat fallbacks, not as the default core-analysis path.

Good:

- `scripts/property-assessor assess --address "..." --assessment-purpose "..."`
- `node check-install.js`
- `node zillow-discover.js "<street-address>"`
- `node har-discover.js "<street-address>"`
- `node zillow-photos.js "<url>"`
- `node har-photos.js "<url>"`
- `scripts/property-assessor locate-public-records --address "..."`
- `scripts/property-assessor render-report --input ... --output ...`
- `web_fetch` to read an official CAD / assessor page when the TypeScript helper needs a plain page fetch

Messaging default:

- `web_search` to discover listing and public-record URLs
- `web_fetch` for official CAD / assessor pages and accessible listing pages
- bounded `web-automation` actions for rendered listing/photo views
- one final `scripts/property-assessor render-report ...` attempt only after the decision-grade report is complete

Avoid when possible:

- `node -e "..."`
- `node --input-type=module -e "..."`
- `python3 - <<'PY' ... PY`
- `python -c "..."`
- raw `bash -lc '...'` or `zsh -lc '...'` probes for CAD / public-record lookup

Reason:

- OpenClaw exec approvals are path-based, and inline shell / interpreter forms are treated conservatively in allowlist mode.
- For `property-assessor`, CAD and public-record lookup should stay on the skill’s file-based TypeScript helper path or use `web_fetch`.
- If the workflow drifts into an ad hoc shell snippet, that is not the approved skill path and can still trigger Control UI approval prompts.

## PDF report template

The final deliverable should be a fixed-template PDF, not a one-off layout.

Template reference:

- `skills/property-assessor/references/report-template.md`

Current renderer:

```bash
scripts/property-assessor assess --address "<street-address>" --recipient-email "<target@example.com>"
scripts/property-assessor render-report --input "<report-payload-json>" --output "<output-pdf>"
```

The fixed template includes:

1. Report header
2. Verdict panel
3. Subject-property summary table
4. Snapshot
5. What I Like
6. What I Do Not Like
7. Comp View
8. Underwriting / Carry View
9. Risks and Diligence Items
10. Photo Review
11. Public Records
12. Source Links
13. Notes page

### Recipient email gate

The report must not be rendered or sent unless target recipient email address(es) are known.

This requirement applies when the operator is actually rendering or sending the PDF.
It should not interrupt a normal analysis-only `assess` run.

If the prompt does not include recipient email(s), the skill should:

- stop
- ask for target recipient email address(es)
- not finalize the PDF workflow yet

The renderer enforces this. If `recipientEmails` is missing or empty, it fails with:

`Missing target email. Stop and ask the user for target email address(es) before generating or sending the property assessment PDF.`

## Example payload

Sample payload:

- `skills/property-assessor/examples/report-payload.example.json`

This is the easiest way to test the renderer without building a report payload from scratch.

## Output contract

The assessment itself should remain concise but decision-grade.

Recommended narrative structure:

1. Snapshot
2. What I like
3. What I do not like
4. Comp view
5. Underwriting / carry view
6. Risks and diligence items
7. Verdict with fair-value range and offer guidance

It must also explicitly include:

- `Photo source attempts: ...`
- `Photo review: completed via <source>` or `Photo review: not completed`
- public-record / CAD evidence and links when available

## Validation flow

### 1. Install the helper package locally

```bash
cd ~/.openclaw/workspace/skills/property-assessor
npm install
npm test
```

### 2. Run address-first assess without recipient email

```bash
cd ~/.openclaw/workspace/skills/property-assessor
scripts/property-assessor assess --address "4141 Whiteley Dr, Corpus Christi, TX 78418" --assessment-purpose "investment property"
```

Expected shape:

- `needsAssessmentPurpose: false`
- `needsRecipientEmails: false`
- public-record / CAD jurisdiction included in the returned payload
- `photoReview.imageUrls` populated when Zillow or HAR extraction succeeds
- no PDF generated yet
- explicit message saying the payload is ready and email is only needed when rendering or sending the PDF

### 3. Run public-record lookup directly

```bash
cd ~/.openclaw/workspace/skills/property-assessor
scripts/property-assessor locate-public-records --address "4141 Whiteley Dr, Corpus Christi, TX 78418"
```

Expected shape:

- state/county/FIPS/GEOID present
- official Census geocoder link present
- for Texas: Comptroller county directory link present
- for Texas: appraisal district and tax assessor/collector contacts present

### 4. Run assess with recipient email and render the PDF

```bash
cd ~/.openclaw/workspace/skills/property-assessor
scripts/property-assessor assess --address "4141 Whiteley Dr, Corpus Christi, TX 78418" --assessment-purpose "investment property" --recipient-email "buyer@example.com" --output /tmp/property-assessment.pdf
```

Expected result:

- `needsRecipientEmails: false`
- JSON success payload with `outputPath`
- a non-empty PDF written to `/tmp/property-assessment.pdf`

### 5. Run PDF render with the sample payload

```bash
cd ~/.openclaw/workspace/skills/property-assessor
scripts/property-assessor render-report --input examples/report-payload.example.json --output /tmp/property-assessment.pdf
```

Expected result:

- JSON success payload with `outputPath`
- a non-empty PDF written to `/tmp/property-assessment.pdf`

### 6. Verify the email gate

Run the renderer with a payload that omits `recipientEmails`.

Expected result:

- non-zero exit
- explicit message telling the operator to stop and ask for target recipient email(s)

### 7. Verify the end-to-end skill behavior

When testing `property-assessor` itself, confirm the assessment:

- starts from the address when available
- uses Zillow first for photo extraction, HAR as fallback
- frames the analysis around the stated assessment purpose
- uses official public-record jurisdiction links when available
- does not treat listing geo IDs as assessor keys
- asks for assessment purpose if it was not provided
- asks for recipient email(s) if they were not provided
- renders the final report through the fixed PDF template once recipient email(s) are known

## Related files

- skill instructions:
  - `skills/property-assessor/SKILL.md`
- underwriting heuristics:
  - `skills/property-assessor/references/underwriting-rules.md`
- PDF template rules:
  - `skills/property-assessor/references/report-template.md`
- sample report payload:
  - `skills/property-assessor/examples/report-payload.example.json`
- photo extraction docs:
  - `docs/web-automation.md`
