455 lines
17 KiB
Markdown
455 lines
17 KiB
Markdown
# 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.
|
||
|
||
## 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 native `web_search`, `web_fetch`, and bounded browser actions. Do not make helper subprocesses the default path for discovery, CAD lookup, or photo extraction.
|
||
- 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.
|
||
- 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 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
|
||
- automatically tries to discover Zillow and HAR listing URLs from the address when no listing URL is provided
|
||
- runs Zillow photo extraction first, then HAR as fallback when available
|
||
- 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
|
||
|
||
Important rules:
|
||
|
||
- Zillow/Redfin/HAR geo IDs are hints only
|
||
- parcel/APN/account IDs are stronger search keys than listing geo IDs
|
||
- 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
|
||
|
||
That output should be used by the skill to:
|
||
|
||
- identify the correct CAD
|
||
- attempt address / parcel / account lookup on the CAD site
|
||
- capture official assessed values and exemptions when a public detail page is available
|
||
|
||
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`
|