Client Portal · Cultivate + Curate
BASE TIER · past single-home buyer · with us since 2024-03
· tag [Ashworth-Nakagawa/Ashland] (BUYER-side)
Nothing of ours is in flight for you right now — which is the right answer. So here is your home and your street instead.
| Your home | |
|---|---|
| Address | 1400 block of Ashland St |
| Neighborhood | Houston Heights |
| Beds / baths | 3 / 2 |
| Size | 1,980 sq ft |
| Yours since | 2024-03 |
| Current value | MISSINGNo AVM / home-valuation feed exists anywhere on this box. No tool, no credential, no source. (Requirements §10 item 1.) A number here would be invented. |
🎯 Two more introductions reaches the top ten.
Private by default. You see your own standing and your own gap. You never see another client’s name, rank or count — and they never see yours.
| Resource | Kind | Content ladder tier |
|---|---|---|
| What your first year of ownership actually costs | How-to | Evergreen Baselines |
| Protest your HCAD appraisal — the 20-minute version | How-to | Timeless Insights |
| Storm-ready: the Houston homeowner checklist sample link target — public TPJG site | Helpful tips | Anchor Content |
| Category | Preferred partner | When you’d need them | |
|---|---|---|---|
| Handyman | Fitzroy & Blunt Home Services FICTIONAL | after you close | Introduce me |
| Landscaper | Bayou Bend Grounds Co. FICTIONAL | after you close · while under contract | Introduce me |
| Pest Control | Cicada Pest Solutions FICTIONAL | — | Introduce me |
Priya — the Heights kept moving this year, and your block moved with it. Nothing here needs anything from you. If the pecan out front finally loses the argument with a storm, the tree crew below is the one we call ourselves. And if anyone you know starts asking the questions you were asking in early 2024, send them our way — we will take it from there.
— Joseph & Keri · The Property Joes Group
Joseph: “Depending upon the complexity and the sophistication of their portfolio will determine how deep and wide their portal will be.”
Both sample portals come out of one generator and one code path. The only thing that differs is the client record. Width = which blocks exist at all. Depth = how many layers each block goes (1 headline → 2 breakdown → 3 history → 4 method/source).
| Input | Value |
|---|---|
| Transactions on file | 1 |
| Roles held | buyer |
| Individual or entity | individual |
| Properties owned | 1 |
| Referrals given | 1 |
| Manual override by Joseph | MISSING |
| Resolved tier | BASE |
| Rule applied | 1 transaction, no entity, <2 referrals -> BASE (PROPOSED, not ruled) |
Q1 in the requirements is open: Joseph has defined no tiers and no thresholds, and there is no portfolio-complexity score anywhere in the doc or on our box. The rule above is the proposed derivation (DEPTH-3), rendered here so it can be argued with rather than buried in code. The intended design is machine derives → Joseph overrides → the override always wins — our own calibration across 140 rulings says his judgement outranks our filter (his sponsor sheet 100%, our usage heuristic 28%).
| Block | id | Depth here (BASE) | Depth on the DEEP sample |
|---|---|---|---|
| What’s happening now | now | 1 / 4 | 3 / 4 |
| Your home | home | 1 / 4 | 3 / 4 |
| Your neighborhood | neighborhood | 1 / 4 | 4 / 4 |
| Your referral impact | referral | 1 / 4 | 3 / 4 |
| Resources that don’t expire | resources | 1 / 4 | 2 / 4 |
| Our preferred people | vendors | 1 / 4 | 3 / 4 |
| A note from us | note | 1 / 4 | 1 / 4 |
Blocks the DEEP record earns that this one does not: local, entity, documents. They are not hidden by CSS — they are never generated, because this record does not list them.
| BASE sample | DEEP sample | |
|---|---|---|
| Blocks rendered (width) | 7 | 10 |
| Total layers (depth) | 7 | 25 |
| Deepest single block | 1 of 4 | 4 of 4 |
| Properties rendered | 1 | 4 |
| Neighborhood datasets | 1 | 3 |
| Live deal | no | yes |
| Generator | client_portal_sample_build.py | client_portal_sample_build.py |
| Code path | identical | identical |
The point of the exercise. If those two columns had come out the same, the architecture would be wrong — bespoke-ness would have to be hand-built per client, which is the one thing the rule forbids (DEPTH-1: one generator; bespoke-ness is data, never a second generator). Far better to find that out on a fictional client.
| Held about this client | Visible to them? | Why |
|---|---|---|
| Their transactions, documents, home | yes | theirs |
| Their own referral count and private rank | yes | this is the return engine |
| Another client’s name, rank or count | never | private by default (TO-6) |
| Our frequency / vendor scores | never | our commercial intelligence |
| Our internal classification and notes | never | our commercial intelligence |
| The persona profile we hold on them | never | Q6 and Q7, both unresolved |
Q6 is open: what a client is allowed to learn about how we rank them is a relationship judgement, not an engineering one. The split above is the recommendation, not the ruling.
It is classified internal — the page type for creator-only pages — so it carries the library navigation furniture (the “Library” and “Full Index” pills, bottom left). A real client portal must never carry that furniture. It is a live route into the private library and a map of the whole estate.
We typed it internal on purpose, because that is honestly what it is — a review artifact for Joseph — and because the correct type does not exist yet. config/page-types.json has exactly three types (internal, grouped, public) and fails closed. A fourth — client-facing: private, no library furniture, not public, not indexed — must be added before a single real portal page can deploy (TREE-6). Pretending otherwise would have been the easier lie.
| Piece | Where it comes from |
|---|---|
| Generator | tools/client_portal_sample_build.py — one file, one code path, both samples |
| This client record | data/client-portal-samples/base.json |
| Tables | tools/lib_table_standard.py — the ONE shared component (sortable, filterable, row-moveable, first column frozen). Not re-implemented here. |
| Market figures | <slug>-value-trends/data.json, built by tools/value_trends_build.py from MLS exports |
| Local insights + map | local_interest_layer.py — keyless, Leaflet 1.9.4 + OpenStreetMap. No new mapping dependency was added. |
| Vendor categories + phases | data/vendors/directory.json (902 vendors) — taxonomy and counts only |
| Brand | tools/brand_gate.py — client-facing is The Curator, locked by Joseph 2026-05-15 |
| Page furniture | Canopy / Understory / Root Level tabs, already shipped on /digital-avatar/ and /voice-digitization/ |
| Publishing gate | tools/page_type_gate.py + config/page-types.json |
| Change record | tools/lib_change_log.py → /changes/ |
| On this page | Status | What a real build would need |
|---|---|---|
| The household, the names, the client tag | INVENTED | a real client record — and Q2 (mask polarity) and Q3 (auth) resolved first |
| Transaction history, deal status, milestone dates | INVENTED | config/transaction-parties/*.json plus the title company’s iCal invite as the source of truth for dates — never a hand-typed snapshot |
| Addresses (block-level only, never a parcel) | INVENTED | the real property record. Block-level here on purpose, so the sample points at no real household |
| Referral counts, private rank, next-rung gap | INVENTED | a referral graph we do not currently have — RM’s surface is a “Referrals Generated YTD” counter that resets every Jan 1 |
| The preferred-vendor businesses | INVENTED | the 902-row directory plus the RM “Preferred Partners” group (94) |
| Neighborhood medians, $/sq ft, days on market, yield | REAL | already real — MLS-derived, on this box, dated |
| Vendor categories, phase tags, directory counts | REAL | already real |
| Local places, coordinates and the map | REAL | already real — curated pins + OpenStreetMap |
| Every “current value” cell | MISSING | an AVM / valuation feed. None exists anywhere on this box — no tool, no credential, no source. That is a procurement decision, not an engineering one. |
| # | Blocker | Why it stops a real build |
|---|---|---|
| Q3 | No client-facing authentication exists. | Our own live /clients/ page says it plainly: “They are not password-protected — the URL is the credential.” Fine for a page only Joseph opens. Not fine for a page carrying a client’s contracts, settlement sheets and referral rank. That is why this sample client is fictional. |
| Q2 | Mask polarity inverts. | /clients/<slug>/ exists and is built FOR JOSEPH — which is exactly why one live file shows 11 contacts and masks 17. A client portal is built FOR THE CLIENT: same data, opposite audience. assert_no_leak() has to run once per audience. |
| TREE-6 | The client-facing page type does not exist. | The publishing gate has three types and fails closed. No portal page can legally deploy until a fourth is added. |
| TI-7 | The notification transport has no consumer. | /api/tour-email enqueues into KV and names tools/tour_email_poller.py, which does not exist on this box. Vendor/agent auto-notification cannot be built on a promise. |
| §10 | No AVM feed. | The highest-return-frequency block in the inspiration material, and we hold zero data for it. |
Full requirements, the reuse inventory and all 13 open questions: exports/briefs/client-portal-requirements.md. Q1, Q2 and Q3 block any real build.
| Data | As of | Source |
|---|---|---|
| Houston Heights market data | 2026-07-11 | heights-value-trends/data.json |
| Vendor directory | 2026-08-09 | data/vendors/directory.json |
| Local-insight pins | 2026-06-10 | local_interest_layer.HEIGHTS_PINS |
| This page | 2026-08-09 15:17 CDT | this build |
Every block on the Canopy tab carries its own as-of stamp. Nothing unknown is guessed — it renders MISSING with the reason.