# WestrockAttom — Development Handoff (Mukesh / Inteligenz)

**Date:** 2026-09-07  
**Project:** `C:\Users\ahuja\Westrock\WestrockAttom`  
**Remote:** `https://github.com/Inteligenz-codebase/WestrockAttom.git`  
**Branch:** `main` (synced with `origin/main`)  
**Reference:** `Mukesh_Handoff_Step1_Development_Guide_20260906.md`

---

## 1. Executive summary

This session completed **Track A** (three urgent code fixes from the Step 1 handoff guide), investigated and reported **Track B** (five information items for S. Romer), set up the local dev environment, and pushed two commits to GitHub.

The ATTOM middleware is now more secure (auth-locked API routes, TLS verification restored) and handles hyphenated range addresses (e.g. `13-27 Race Street`). Track B answers are ready to send back so the Step 1 implementation spec can proceed — with two items still blocked pending ATTOM contract portal access (Ts&Cs §4.3) and collaborative API design (B.1).

---

## 2. Environment setup (completed)

| Item | Status |
|------|--------|
| Git pull | `main` was already up to date with `origin/main` |
| PHP 8.3.32 | OK |
| Composer | `composer.phar` downloaded locally (not in global PATH) |
| `composer install` | OK |
| `npm install` + `npm run build` | OK |
| SQLite database + migrations | All 5 migrations ran |
| Admin user | `admin@admin.com` (admin role) |
| Dev server | `http://127.0.0.1:8001` |

**Note:** `.env` has a duplicate empty `ATTOM_API_KEY` on line 7; the populated key on line 76 is the one in use.

---

## 3. Track A — Development (DONE)

### A.1 — Lock down unauthenticated API endpoints ✅

**File:** `routes/api.php`

**Change:** Wrapped both routes in `auth` middleware:

```php
Route::middleware(['auth'])->group(function () {
    Route::get('/getAddressdata', [AttomApiController::class, 'getSalesComparables']);
    Route::get('/salescomparables', [AttomApiController::class, 'salescomparables']);
});
```

**Caller check:** No internal or external references to `/api/getAddressdata` or `/api/salescomparables` found in the codebase. Safe to use session auth.

**Verified:** Unauthenticated requests return **302 → `/login`**.

**Fix path used:** Auth middleware (not held for B.1 token design).

**Report back:** Both endpoints are closed from an unauthenticated session.

---

### A.2 — Re-enable TLS certificate verification ✅

**Changes:**

1. Removed all `->withOptions(['verify' => false])` from `AttomSearchFallbackService.php` (including `fetchPropertyByAddressParts()`).
2. Added `composer/ca-bundle` package.
3. Configured global HTTP verification in `app/Providers/AppServiceProvider.php`:

```php
Http::globalOptions([
    'verify' => CaBundle::getSystemCaRootBundlePath(),
]);
```

**CA bundle fix:** Local PHP had no `curl.cainfo` / `openssl.cafile`. Composer CA bundle resolves this for the app.

**Verified:** Live ATTOM call succeeds with verification on:

- `11235 S STEWART AVE, Chicago, IL 60628` → Status 200, Code 0, AttomId 20823815

**Follow-up for a later pass:** ~48 other `verify => false` occurrences remain in `PropertySearchController`, `ComparablesController`, `BatchLookupService`, console commands, etc. Those override the global CA bundle where present and should be cleaned up in a follow-up.

**Report back:** TLS verification is on; CA bundle fix used = `composer/ca-bundle` via `AppServiceProvider` global options.

---

### A.3 — AddressNormalizer integration ✅

**New files:**

| File | Purpose |
|------|---------|
| `app/Support/AddressNormalizer.php` | Pure static class for house-number variants |
| `tests/Unit/AddressNormalizerTest.php` | 11 tests, 18 assertions, all passing |

**Integration in `AttomSearchFallbackService.php` (4 edits):**

1. `buildAddress1Variants(string $address1, string $address2 = '')` — signature updated
2. Seeds variants from `AddressNormalizer::variants($address1, $address2)`
3. Passed `$address2` at all 3 call sites (~lines 270, 423, 1041)

**Expected output for `13-27 Race Street` (verified in integration):**

```
1. 13 Race Street # 27
2. 13 Race Street
3. 27 Race Street
4. 15, 17, 19, 21, 23, 25 Race Street
5. 13-27 Race Street (original retained last)
```

**End-to-end verified:**

| Test case | Result |
|-----------|--------|
| `13-27 Race Street, Bloomfield, NJ` | Resolves as **`13 RACE ST # 27, BLOOMFIELD, NJ 07003`** |
| `72-10 41st Ave, Woodside, NY 11377` | Still resolves — **no Queens regression** |

**Post-deploy action:** Report success rate over the next 50 lookups (per handoff).

---

## 4. Track B — Information for S. Romer

### B.1 — Programmatic API surface (BLOCKING) ⚠️

**Answer:** **No machine-facing API exists today.**

| Question | Finding |
|----------|---------|
| Authenticated API already built? | **No.** Only 2 routes in `routes/api.php` — thin ATTOM sales-comparables proxies, now session-auth protected. |
| Sanctum / Passport / API-key tables? | **None.** Single `web` session guard only. |
| Dashboard "API Keys" panel? | **Placeholder UI only** — button in `dashboard.blade.php` with no route, controller, or backend. |
| What are "API keys" in the codebase? | **Outbound credentials only** — `ATTOM_API_KEY`, `ELASTIC_API_KEY`, `GOOGLE_MAPS_API_KEY` in `.env`. |

**JSON-capable web routes** (session + CSRF, browser-only):

- `/property/autocomplete`
- `/area/counties/{state}`
- `/area/lookups`
- `/property/{attomId}/comparables/attom-sales-columns`

**Recommendation for Step 1:** Design a collaborative `/api/v1/*` token layer (Sanctum PATs or dedicated `api_keys` table with scopes + rate limits). Expose existing service logic — do not add a second ATTOM client elsewhere. Extend `PropertySearchLogService` for API audit/cost control.

**Additional finding:** `BatchLookupController` is referenced in `routes/web.php` but the class file is **missing** — batch routes are broken.

---

### B.2 — Comparable population (BLOCKING) ✅ Answered

**Answer:** This middleware produces **reduced, user-selected exports** — not full valuation populations.

| Output | Source | Rows | Full or reduced? |
|--------|--------|------|------------------|
| `AttomSales_{attomId}_{timestamp}.xlsx` | `ComparablesController::downloadAttomSales` | Checkbox-selected comps only | **Reduced selection** |
| `AttomSP_{attomId}_{timestamp}.xlsx` | `ComparablesController::downloadAttomSalesSp` | Exactly 1 row (subject) | **Subject only** |

**Not in this repo:**

- `CostarSales*.xlsx`, `CapRateMaster*.xlsx`, `Rentals Leased/Market Master*.xlsx`
- CoStar export flow
- Any cap-rate or rental comp exports

Those files in job folders (e.g. Race Street) come from **outside this middleware** (Main365 / CoStar / manual workflow).

**Search population caps (before export):**

```
ATTOM salescomparables API  → maxComps = 500 per call
ATTOM sale/snapshot buckets → pageSize 50–100, page 1 only (no pagination loop)
Initial seed load           → maxComps = 25
        ↓
Post-fetch UI filters (distance, date, price, class, style, location)
        ↓
Session: comparables_export.{attomId}
        ↓
Export = checkbox-selected subset only
```

**Typical row counts today:**

- UI display set: **0–500+** comps (after filters; not full market)
- `AttomSales` export: **however many the user checks** (often a small shortlist)
- `AttomSP` export: **1 row**

**Doctrine implication:** Step 1 Oversight can verify ATTOM sales comps by **inspection only**, not independent re-derivation from a full ~100+ comp population.

---

### B.3 — ATTOM Ts&Cs PDF (BLOCKING) ⚠️

**Status:** **Could not retrieve.**

URL `https://dla.attomdata.com/v2501.pdf` redirects to ATTOM Contract Center (email verification gate). Direct download returns HTML (~8 KB), not the PDF.

**Action needed:** Someone with ATTOM contract portal access (Catherine Berlin / WestRock contracts channel) must download and place in `Claude_Private_Steve\`. **§4.3 caching design remains blocked.**

---

### B.4 — Residential-only license + unlicensed packages ✅ Reported

**Residential scope:** Cannot confirm from Ts&Cs (B.3 blocked). Schedule §2.1/§2.2 language ("residential real property") is a valid concern for a commercial appraisal firm — **escalate to Catherine Berlin at ATTOM (`catherine.berlin@attomdata.com`) and S. Romer**. Not a build-side decision.

| Package | In codebase? | Production status |
|---------|--------------|-------------------|
| **Building Permits** | `property:building-permits` artisan command only | **Not in web UI.** Dev tooling only; requires missing CSV. Dead for production. |
| **Transportation Noise** | Console `attom:transport-noise` (401) + UI via `PropertySearchController::fetchSoundscoreData()` | **Partially active in UI** — may fail silently (401/unlicensed) |
| **Parcel Tiles** | **Not found** in repo | N/A |

**Recommendation:** Disable or guard `fetchSoundscoreData()` until licensed.

---

### B.5 — Housekeeping ✅ Partially done

| Item | Status |
|------|--------|
| **`nohup.out`** | Rotated (~59 MB → `nohup.out.20260907.bak`); added to `.gitignore`; removed from git tracking (commit `a9efc7e`) |
| **`.env` in ShareSync** | **Decision pending** — contains ATTOM key, DB creds, Elasticsearch, Google Maps key. Recommend moving outside synced folder or restricting ShareSync permissions. Not changed automatically. |

---

## 5. Git history (pushed to origin/main)

| Commit | Description |
|--------|-------------|
| `fb5347e` | Secure ATTOM middleware and fix hyphenated address lookups (Track A) |
| `a9efc7e` | Exclude nohup.out from version control (Track B.5) |

**Files changed in Track A commit:**

- `routes/api.php`
- `app/Support/AddressNormalizer.php` (new)
- `tests/Unit/AddressNormalizerTest.php` (new)
- `app/Services/AttomSearchFallbackService.php`
- `app/Providers/AppServiceProvider.php`
- `composer.json` / `composer.lock`

**Not committed (local WIP, separate from this handoff):**

- `app/Http/Controllers/ComparablesController.php`
- `app/Http/Controllers/PropertySearchController.php`
- `composer.phar`, `package-lock.json`

---

## 6. Definition of done — this round

| Item | Status |
|------|--------|
| A.1 — API endpoints require auth | ✅ Done + verified |
| A.2 — TLS verification re-enabled | ✅ Done + live ATTOM call verified |
| A.3 — AddressNormalizer integrated | ✅ Done + tests + E2E verified |
| B.1 — Programmatic API answer | ✅ Reported — needs collaborative design |
| B.2 — Comparable population inventory | ✅ Reported |
| B.3 — ATTOM Ts&Cs PDF | ⚠️ Blocked — needs contract portal access |
| B.4 — Residential license + dead code | ✅ Reported — escalate to ATTOM/S. Romer |
| B.5 — `.env` / `nohup.out` | ✅ nohup done; `.env` decision pending |

---

## 7. Recommended next steps

1. **Deploy** dev → stage → prod (Track A changes).
2. **Post-deploy verification:** Confirm API routes blocked from incognito; re-test Race Street and Queens addresses in prod.
3. **Report ATTOM success rate** over next 50 lookups.
4. **Send this document** (or Track B sections) to S. Romer via `Claude\` or direct channel.
5. **Retrieve ATTOM Ts&Cs** (B.3) via contract portal.
6. **Collaborative spec** for Step 1 programmatic API (B.1).
7. **Follow-up cleanup:** Remove remaining `verify => false` in other controllers; fix/remove broken `BatchLookupController` routes; disable unlicensed `fetchSoundscoreData()` until licensed.

---

## 8. How to run locally

```powershell
cd C:\Users\ahuja\Westrock\WestrockAttom
php artisan serve --host=127.0.0.1 --port=8001
```

Login: `http://127.0.0.1:8001/login` — `admin@admin.com`

Run tests:

```powershell
php artisan test --filter AddressNormalizerTest
```

---

*Prepared by Inteligenz / Mukesh Ahuja — 2026-09-07*
