---
name: vpa360-import
description: Import Valbridge appraisal workbooks into VPA360 via the Import Tool's MCP server. Use when the user asks to upload, validate, preprocess, import, or submit an xlsx of appraisal/property data, to check the status of a previously-uploaded import, or to certify the source of imported data.
---

# VPA360 Import Tool

This skill drives the **Valbridge VPA360 Import Tool** through its MCP server.
The tool ingests `.xlsx` workbooks, preprocesses them into VPA360's
intermediate format, reports per-cell errors, records a source-of-data
certification, and submits the result to the VPA360 backend. A clean import
auto-submits; imports with warnings stop for review. Every session also
exposes downloadable artifacts (the intermediate JSON and an
Excel-with-comments report).

## Available tools

Each tool's own description carries its full contract — arguments, return
shape, and when to call it. This is the map:

| Tool | Use it to |
|---|---|
| `get_upload_token` | Start every upload. Returns a URL + token you POST the file to **from code**. |
| `get_import_status` | Poll a session until terminal. Carries the certification receipt and, when you declared capabilities, the feedback document inline. |
| `submit_to_vpa` | Send a session held back at `READY_FOR_VPA` for warnings, or retry `SUBMIT_FAILED`. Confirm with the user first. |
| `get_import_report` | Structured per-cell findings for a terminal session. **Finalizes** the session. |
| `get_extraction_feedback` | Only when you extracted the workbook yourself: what to fix and whose problem each issue is. Does not finalize. |
| `get_download_urls` | One-time URLs to fetch artifacts as files — the intermediate JSON, or the marked-up Excel report to hand a human. |

Two rules that hold across all of them: the workbook bytes never pass through
your context — upload and download from code — and if your runtime cannot
execute code, refuse the upload rather than working around it.

## Source certification

Every import carries a **source-of-data certification**: an attestation of
where the property data came from and that the uploader is authorized to
import it. The mechanism is driven by a source-origin column in the
workbook — a **`Data Source`** column, one value per property.

**On the MCP channel, certification is file-driven — there is
no interactive consent step and you must never invent a source.** The
outcome depends on that column:

| `Data Source` column | Outcome |
|---|---|
| **Fully populated** (every property has a source) | The server materializes per-source certification records and **writes the certification act automatically** on the file's behalf (server identity, server timestamp, `channel: mcp`, `mode: attribute`, a versioned statement). The session proceeds to `READY_FOR_VPA` and auto-submits if the gate is clean. |
| **Partially filled** (some properties blank) | `PREPROCESS_FAILED` with `source_certification_missing`. Tell the user to fill in the source for **every** property, then re-upload. |
| **Absent** (no such column) | `PREPROCESS_FAILED` with `source_certification_missing`. Tell the user to add a `Data Source` column and populate it, then re-upload. **MCP has no bulk-consent path — do not manufacture or guess a source to get past this.** |

**Surface the attestation.** Uploading a workbook with a populated
`Data Source` column *is* the user certifying that the data originates from
those sources and that they are authorized to import it. Make them aware of
this, and report the `certification` receipt (`certified_by`, `certified_at`,
`mode`, `statement_version`, `data_digest`, `sources`) from
`get_import_status` / `get_import_report`. The statement's legal wording is
server-owned — don't paraphrase it as binding text; point at
`statement_version`.

## Canonical workflow

For "import this workbook" tasks, the default sequence is:

1. **Confirm the file is reachable from your code-execution side** — typically
   a path on disk. If the user attached the xlsx through the chat UI, the
   runtime exposes it as a file; do not read its bytes directly.
2. **Call `get_upload_token`** → returns `{upload_url, upload_token, …}`.
3. **Run code** to POST the file with that token — Bash `curl`, Python
   `requests`, whatever your runtime offers. The HTTP response gives you
   `{tracking_id, state}`. Capture `tracking_id`. Tell the user "uploaded;
   checking…".
4. **Poll `get_import_status`** every ~2 s until terminal. Most workbooks
   finish in <5 s. A clean import auto-certifies (file-driven) and
   auto-submits — it can reach `COMPLETED` without further action.
5. **Act on the state:**
   - `COMPLETED` / `SUBMITTED` — the data reached VPA. Report the VPA run
     and the certification receipt.
   - `READY_FOR_VPA` with warnings — surface them; call `submit_to_vpa` only
     after the user agrees.
   - `PREPROCESS_FAILED` — list errors by cell address (`Sheet!B7: …`).
   - `SUBMIT_FAILED` — transient; retry with `submit_to_vpa`.
   - `VPA_FAILED` / `REJECTED` — terminal; report and stop. Exception:
     `REJECTED` + `unreadable_workbook` = damaged transfer, re-send the same
     file.

   See **Error handling** for what each failure code means.
6. **`get_import_report`** (or `get_download_urls`) for details. Use
   `get_download_urls` to fetch the intermediate JSON or the marked-up Excel
   report as files rather than inlining them.
7. **Summarize for the user**: bottom line first (submitted / needs review /
   failed), then posit / warning / error counts, the certification receipt,
   and where to find artifacts. Suggest the **Excel-with-comments report**
   (`report_xlsx`) if they want to fix errors in place — it round-trips their
   original workbook with a first-sheet "Issues" index and per-cell comments.

## Error handling

| Symptom | What it means | What to do |
|---|---|---|
| 413 on upload | File over 25 MB | Tell the user; do not retry |
| `PREPROCESS_FAILED` with `unsafe-upload` | Zip-bomb / oversized member | Tell the user the file looks malformed |
| `PREPROCESS_FAILED` with `unknown-feed` | Workbook shape isn't recognised | Report that the shape matched no supported feed; the report's `rejection.scores` names how close each candidate came |
| `REJECTED` with `unreadable_workbook` | The upload didn't arrive intact — no sheet was ever opened | **Re-send the same file; do not re-extract or restructure it.** Nothing is wrong with its contents as far as anyone knows. The report's `rejection.diagnostics` says what was damaged |
| `400` on upload mentioning `sha256` / `size_bytes` | The bytes that arrived differ from what you said you sent | Re-send the file (this is the check working — it caught the damage at the door). Don't strip the digest to make the error go away |
| `PREPROCESS_FAILED` with `source_certification_missing` | `Data Source` column absent or partially filled | Tell the user to add/complete a `Data Source` column (one source per property) and re-upload — **do not fabricate a source** |
| `error_count > 0` but `feed != "unknown"` | Real data errors (e.g. missing PID) | Surface cell addresses; suggest the Excel report for editing |
| `READY_FOR_VPA` with `warning_count > 0` | Auto-submit withheld pending review | Surface warnings; call `submit_to_vpa` only after the user agrees |
| `submit_policy_blocked` from `submit_to_vpa` | Gate has blocking findings | Fix the input/policy; blocks (unlike warnings) can't ride along |
| `SUBMIT_FAILED` | Transient network/auth/4xx/5xx on submit | Retry once with `submit_to_vpa` |
| `VPA_FAILED` | VPA returned a terminal failure | Report; retry won't help (idempotent) |
| Code-side POST/GET to the URL fails with DNS/connect/TLS error | Your sandbox blocks egress to the host | Walk the user through allowlisting — see below |

### Network egress (code-side upload/download blocked)

`get_upload_token` and `get_download_urls` hand you a URL that **your**
code-execution side has to reach. If your sandbox blocks egress, the transfer
step fails even though the tool call succeeded. Symptoms, against the host in
the URL you just received:

- `curl: (6) Could not resolve host …`
- `curl: (7) Failed to connect to … : Connection refused / network unreachable`
- Python `requests.exceptions.ConnectionError` / `socket.gaierror`
- Any "blocked", "egress", "policy" wording from the runtime

**Do not retry** — the sandbox is the issue, not the deployment. The fix is
the user's egress allowlist: enable code execution and network egress under
**Settings → Capabilities**, then add the host from the URL. On managed/Team
accounts this lives under **Organization settings** and is admin-only, so a
non-owner has to ask their workspace admin. Afterwards the user should send
any short message in the conversation so the sandbox picks up the new policy.

Then re-run **only** the transfer step — **do not mint a fresh token** unless
the original expired (10 min TTL); a new token doesn't help when the sandbox
is the blocker. If the identical call fails identically after the user
confirms the change, the policy didn't take effect — say so, don't loop.

If allowlisting is impossible (corporate policy), stop and tell the user the
upload cannot proceed from this runtime — do not work around it by pulling the
workbook through your context.

## Output style

- Lead with the bottom line: *submitted / needs review / failed* and the
  posit / error / warning counts.
- For errors, list cell addresses (`Sheet!Cell — message`) — never paraphrase
  away the location.
- For warnings, group by column when there are many of the same kind
  ("12 unmapped MVS Class values like 'Class D - Wood Frame'").
- Report the **certification receipt** (who certified, when, which sources,
  statement version) whenever a session carries one.
- Never paste the full intermediate JSON into chat. Offer a download
  (`get_download_urls`) or summary instead.
- Workbooks carry owner names, addresses, and financial data. Don't quote
  large blocks of cell values back; summarize.
