Real Estate Dev: The Whole Field
Build a validated concept graph and deal-triage checklist for real estate development.
Introduction
30 Second Summary
Real estate development feels impossibly scattered. The knowledge lives across thousand-page textbooks, practitioner war stories, and acronym-heavy courses that never show you how the pieces connect. You can study for months and still feel like you are looking at fog.
In this project, you will compress the entire field of real estate development into one connected concept graph, validate it computationally, then turn it into a working deal-screening tool with concrete go/no-go thresholds you can run on any deal cold.
What You'll Build
You hand your one-page checklist to a friend, they walk through a hypothetical 200-unit multifamily deal, and within five minutes they have flagged issues across every risk category without needing any prior real estate knowledge.
By the end of this project, you'll have:
- A validated concept graph you can open in Obsidian's graph view and navigate visually, showing how every lifecycle stage, player, risk type, and funding layer connects to everything else, verified by networkx integrity checks with zero orphans and full weak connectivity.
- A one-page deal-triage checklist with concrete numeric and categorical thresholds that screens any deal in five minutes, covering all five standard development risk categories (market, entitlement, construction, financing, operations).
- A proven sample-deal run on a hypothetical 200-unit multifamily ground-up development that flags at least one issue per risk category, plus a teach-back instrument and spaced-repetition deck so the structure stays learned and can be demonstrated cold.
- Secret Mission: Write a plain-language documentation guide and a two-audience lessons-learned presentation that makes the entire system teachable to someone who was not in the room.
Are there any prerequisites?
No prior real estate knowledge is needed. This project IS the foundations build.
You do need a Windows machine with internet access, a Cursor Pro subscription (or equivalent flat-rate AI client) already active, and a free Linear account at linear.app for project tracking. You will install Python 3.11+ and Obsidian in Step 1 if you do not already have them.
Before We Start
Before We Start...
Before we dive in, let's lock in what you're about to build and why the pieces connect the way they do.
Set Up Your Toolchain and Project Board
Without confirmed tools, a preflight that proves your AI client runs within plan limits, and a delivery board that makes the work visible, the build cannot start.
This step removes those blockers. You will install and verify every tool, prove that Cursor responds within your subscription limits, and create the Linear project board that tracks each build phase as a ticket.
In this step, get ready to:
- Install and verify Cursor, Obsidian, Python, networkx, and Git.
- Create the Linear project board with 5 planned tickets.
- Scaffold the project directory, create the Obsidian vault, run the AI-client preflight, and push to GitHub.
Install and verify the toolchain
Every tool you need lives on your Windows machine by the end of this substep. You will install them one at a time and confirm each one responds before moving on.
- Press the Windows key to open your search bar.
- Type Cursor and press Enter to check if Cursor is already installed.
✔️ Cursor opens
You already have Cursor installed. Move on to the next instruction.
ⓧ Cursor is not installed
- Open your browser and go to cursor.com/download.
- Download the Windows installer and run it.
- Follow the on-screen prompts to complete installation.
- Once installed, press the Windows key, type Cursor, and press Enter to open it.
You should see the Cursor editor window with a welcome tab or an empty workspace.
What is Cursor?
Cursor is an IDE that combines a code editor, integrated terminal, Git integration, markdown preview, Mermaid rendering, and an AI assistant in one window. You will work inside Cursor for the entire build.
- Download and install Obsidian 1.12.7 from obsidian.md/download (select the Windows Universal installer).
- Run the installer and follow the on-screen prompts.
- When the installer finishes, leave Obsidian closed for now. You will create a vault in the final substep.
Now confirm that Python 3.11 or higher is available. This project uses networkx 3.6.1, which requires Python 3.11, 3.12, 3.13, or 3.14.
- In Cursor, open the integrated terminal by pressing Ctrl+`.
- Check your Python version by running:
python --version
✔️ I see version 3.11 or higher
Python is ready. You will see something like Python 3.11.x or Python 3.12.x. Move on to the next instruction.
ⓧ I see an older version
Your system Python is below 3.11. Windows often ships with an older Python or none at all.
- Go to python.org and download the latest Python 3.11+ installer for Windows.
- During installation, check the box that says Add python.exe to PATH.
- After installation, close and reopen Cursor's terminal (Ctrl+` twice) to reload the PATH.
- Run python --version again and confirm it now shows 3.11 or higher.
ⓧ Command not found
Python is not installed on this machine.
- Go to python.org and download the latest Python 3.11+ installer for Windows.
- During installation, check the box that says Add python.exe to PATH.
- After installation, close and reopen Cursor's terminal (Ctrl+` twice) to reload the PATH.
- Run python --version again and confirm it now shows 3.11 or higher.
- Install the networkx graph library by running:
pip install networkx==3.6.1
- Verify the installation by running:
python -c "import networkx; print(networkx.__version__)"
You should see 3.6.1 printed in the terminal.
Seeing a permissions error on pip install?
Try running the command with the --user flag: pip install networkx==3.6.1 --user. If you still see errors, confirm that the python command resolves to 3.11+ (not an older system Python).
Help me troubleshoot my pip install failing on Windows.
- Initialize your project repository by moving to your Desktop and creating the project folder:
cd ~/Desktop
git init field-on-one-page
cd field-on-one-page
What does this command do?
`cd ~/Desktop` moves your terminal to the Desktop so the project folder is easy to find. `git init field-on-one-page` creates a new folder called field-on-one-page and initializes it as a Git repository. The final cd moves you inside that folder.
- Confirm you are inside the project folder by running:
pwd
You should see a path ending in field-on-one-page.
Create the Linear project and planned tickets
Linear is a project-tracking tool. You will use it as the delivery spine for this build. Each of the five remaining build steps maps to one ticket that you work on a feature branch, merge via pull request, and auto-close when the PR merges.
- Open your browser and go to linear.app.
- Sign up for a free account (or sign in if you already have one).
- Linear will ask you to create a workspace and a team during onboarding. Complete these steps with any names you like.
What is Linear?
Linear is a project management tool built for software teams. On the free tier, you get unlimited issues, projects, and integrations. You will use it as a lightweight delivery board with 5 tickets.
- From your team's Projects page, click the + button to create a new project.
- In the project name field, type Field on One Page.
- Click Create.
You should now see the empty project page for Field on One Page.
- With the project open, press C to open the issue creation modal.
- In the title field, type Agent architecture and topology.
- Click Create (or press Ctrl+Enter) to save the issue.
- Repeat to create four more issues with these titles:
- Build concept graph.
- Encode deal-triage checklist.
- Run sample deal and validate.
- Documentation and lessons learned.
These correspond to tickets FIELD-1 through FIELD-5. Linear auto-assigns sequential IDs.
- Click on the first issue (Agent architecture and topology).
- Change its status from Backlog to In Progress by clicking the status badge and selecting In Progress.
Your Linear board now shows FIELD-1 in In Progress and FIELD-2 through FIELD-5 in Backlog.
Why a delivery board for a learning project?
Each ticket maps to a feature branch. When you open a pull request whose body contains the ticket ID, Linear auto-closes the ticket on merge. This gives you a visible trail of completed work, not just code on a hard drive.
Scaffold the project, create the Obsidian vault, and run the AI-client preflight
You now have all tools installed and a delivery board ready. This substep builds the directory structure, wires up the Obsidian vault, proves your Cursor AI chat works within plan limits, and pushes the scaffold to GitHub.
- In Cursor's terminal (which should still be inside field-on-one-page), create the project subdirectories by running:
mkdir agents data outputs docs tests
- Confirm the folders exist by running:
ls
You should see agents, data, docs, outputs, and tests listed.
- Now open Obsidian. Press the Windows key, type Obsidian, and press Enter.
- In the Obsidian startup screen, find Open folder as vault and click Open.
- In the file browser, navigate to Desktop > field-on-one-page > outputs.
- Select the outputs folder and click Open.
Why point the vault at the outputs folder?
Obsidian treats whatever folder you open as the vault root. By pointing it at outputs/, every markdown file the agents export will automatically appear in the vault's graph view without any manual copying.
- In the left sidebar of Obsidian, click the Graph view icon (or press Ctrl+G).
You should see an empty graph canvas. The vault name shown in the bottom-left corner is outputs. This confirms Obsidian is wired to the correct folder.
- Switch back to Cursor's terminal. Create the requirements.txt file by running:
echo networkx==3.6.1 > requirements.txt
- Now run the AI-client preflight. Open Cursor's AI chat by pressing Ctrl+L.
- Set the model to Auto (the dropdown at the top of the chat panel).
- Type this prompt and press Enter:
Confirm you can respond. Reply with only the word READY.
You should see a response containing the word READY within a few seconds. Confirm there is no overage warning or billing alert in the chat panel.
What does the preflight prove?
Your Cursor Pro plan includes $20 of API usage per month at published model rates, plus unlimited Auto mode. This single-word exchange confirms the chat is active and operating within plan limits. If you saw a billing warning, you would need to check your usage before continuing.
- Create the preflight decision record by running:
echo # ADR-000: Preflight > docs/ADR-000-preflight.md
echo Date: [[TODAY_DATE="today's date in YYYY-MM-DD format"]] >> docs/ADR-000-preflight.md
echo AI client: Cursor Auto >> docs/ADR-000-preflight.md
echo Plan tier: Pro >> docs/ADR-000-preflight.md
echo Preflight result: READY response received within plan limits, no overage warning. >> docs/ADR-000-preflight.md
What is an ADR?
ADR stands for Architecture Decision Record. It captures a decision and its context so future-you (or a collaborator) can understand why something was chosen. This first ADR simply records that the AI client is confirmed working.
- Create a private repository on GitHub. Open github.com/new in your browser.
- Name the repository field-on-one-page.
- Set visibility to Private.
- Click Create repository (do not initialize with a README).
- Copy the repository URL that GitHub shows you.
- Back in Cursor's terminal, push your scaffold to GitHub by running these commands:
git remote add origin [[REPO_URL="your GitHub repo URL, e.g. https://github.com/username/field-on-one-page.git"]]
git add -A
git commit -m "chore: scaffold and preflight"
git push -u origin main
What do these commands do?
- `git remote add origin` links your local repository to the GitHub remote.
- `git add -A` stages all new files for commit.
- `git commit -m` saves the staged files with a message.
- `git push -u origin main` uploads your commit to GitHub and sets main as the default remote branch.
Before you run the push, what do you expect to see if everything is wired correctly?
You should see output ending with a line like Branch 'main' set up to track remote branch 'main' from 'origin'. If Git prompts you to authenticate, follow the browser flow GitHub presents.
Push failed or authentication error?
Make sure you copied the correct repository URL (it should end in .git). If Git asks for credentials and you have not set up a personal access token or SSH key, GitHub will open a browser window for device authentication. Follow the prompts.
Help me fix my git push to GitHub failing on Windows.
Your toolchain is installed, the delivery board is live, and the scaffold is on GitHub. Next up, you will design the agent architecture that orchestrates the entire build.
Design the Agent Architecture
Your tools are confirmed, your board is live, and FIELD-1 is already In Progress.
Before any Python runs, the build needs an orchestration plan. Each agent module needs a defined goal, a clear input contract, and a clear output contract so that modules never collide or duplicate work. Without this blueprint, the graph builder might expect data the extractor never produces, or the checklist encoder might read a format the deal runner cannot consume. This step creates the blueprint.
In this step, get ready to:
- Define the 9-agent roster with goal, input, and output contracts in an Architecture Decision Record.
- Write the build plan with validation gates and the branch/PR/ticket workflow.
- Draw the Mermaid topology diagram showing all agents and their data flows.
Define the agent roster and build plan
Every agent in this system is a single-purpose Python module. You will define all nine of them in one decision record, then capture the build order and delivery workflow in a plan document.
- Create a feature branch for this ticket by running this command in Cursor's integrated terminal:
git checkout -b feat/FIELD-1-agent-architecture
- Create a new file called docs/ADR-001-agent-architecture.md and paste the following content, which defines agents 1 through 5 (the data pipeline and validation modules):
# ADR-001: Agent Architecture
## Decision
Each agent is a single-purpose Python module with one goal, one input contract, and one output contract.
## Agent Roster
### 1. node_extractor
- **Goal**: Read the ULI framework and extract all concept nodes.
- **Input**: ULI eight-stage model reference (manual research).
- **Output**: `data/nodes.json` — array of node objects with name, category, role.
### 2. edge_typer
- **Goal**: Define typed relationships between all nodes.
- **Input**: `data/nodes.json`.
- **Output**: `data/edges.json` — array of edge objects with source, target, type.
### 3. graph_builder
- **Goal**: Assemble the directed graph from nodes and edges.
- **Input**: `data/nodes.json`, `data/edges.json`.
- **Output**: In-memory `networkx.DiGraph` object.
### 4. integrity_checker
- **Goal**: Validate graph has no orphans, is weakly connected, and covers all required categories.
- **Input**: `networkx.DiGraph` from graph_builder.
- **Output**: Pass/fail report with specific failures listed.
### 5. checklist_encoder
- **Goal**: Convert deal-relevant nodes into screening questions with go/no-go thresholds.
- **Input**: `data/nodes.json` (filtered to deal_relevant role).
- **Output**: `outputs/deal_triage_checklist.md`, `outputs/deal_triage_checklist.csv`.
What does this structure do?
Each agent entry locks in three things: what it does (goal), what it consumes (input contract), and what it produces (output contract). This is the handoff spec. When you build each module later, you only need to satisfy its contract.
Agents 1-3 form the data pipeline: extract nodes, type the edges, assemble the graph. Agents 4-5 validate and transform: the integrity_checker gates quality, and the checklist_encoder turns the map into a usable screening tool.
- Now add agents 6 through 9 (execution and output modules) to the same file, directly below agent 5:
### 6. deal_runner
- **Goal**: Execute the checklist against a sample deal and score each question.
- **Input**: `outputs/deal_triage_checklist.md`, `data/sample_deal.json`.
- **Output**: Scored result with GO/CAUTION/NO-GO per question, summary by risk category.
### 7. teachback_scorer
- **Goal**: Generate a teach-back instrument with rubric and tasks.
- **Input**: `networkx.DiGraph`, `outputs/deal_triage_checklist.md`.
- **Output**: `outputs/teachback_instrument.md`.
### 8. deck_exporter
- **Goal**: Produce spaced-repetition flashcards from the graph.
- **Input**: `networkx.DiGraph`, checklist questions.
- **Output**: `outputs/spaced_rep_deck.md`.
### 9. doc_writer
- **Goal**: Produce a plain-language guide to the system.
- **Input**: All outputs from previous agents.
- **Output**: `outputs/guide.md`.
What do agents 6 through 9 cover?
Agent 6 (deal_runner) proves the checklist works by scoring a real deal. Agents 7-8 (teachback_scorer and deck_exporter) lock the knowledge in for retention. Agent 9 (doc_writer) makes the whole system teachable to someone who was not in the room.
- Save docs/ADR-001-agent-architecture.md.
- Confirm the file exists by running:
ls docs/
You should see both ADR-000-preflight.md and ADR-001-agent-architecture.md listed.
- Now create the build plan. Create a new file called docs/PLAN.md and paste:
# Build Plan
## Build Order
1. **Design** — Agent architecture, topology diagram, decision records (FIELD-1).
2. **Map** — Concept graph: nodes, edges, integrity checks (FIELD-2).
3. **Encode** — Deal-triage checklist with thresholds (FIELD-3).
4. **Run** — Sample deal, spaced-rep deck, teach-back instrument (FIELD-4).
5. **Document** — Plain-language guide and lessons learned (FIELD-5).
## Validation Gates
- After Map: integrity_checker must pass (zero isolates, weak connectivity, coverage).
- After Encode: checklist_encoder --validate must confirm all risk categories covered.
- After Run: deal_runner must flag at least one issue per risk category.
## Branch/PR/Ticket Workflow
- Each ticket maps to a feature branch: `feat/FIELD-N-description`.
- Merged via pull request with ticket ID in PR body: "Closes FIELD-N".
- Linear auto-closes the ticket when the PR merges.
- After merge: `git checkout main && git pull`.
Why validation gates between steps?
Each gate is a hard pass/fail check that must succeed before the next phase starts. If the graph has orphan nodes, encoding it into a checklist would produce gaps. If the checklist has missing risk categories, running a deal against it would give false confidence. Gates prevent errors from compounding downstream.
- Save docs/PLAN.md.
- Confirm it was created by running:
ls docs/
You should now see three files: ADR-000-preflight.md, ADR-001-agent-architecture.md, and PLAN.md.
Only seeing two files?
Make sure you saved the new file before running ls. Check you created PLAN.md inside the docs/ folder, not in the project root.
Help me check why PLAN.md is not showing in my docs/ folder.
Draw the Mermaid topology diagram
The topology diagram is the visual proof that the architecture holds together. It shows the orchestrator calling each agent in sequence, with data files as intermediate nodes and parallel lanes where agents can run concurrently.
- Create a new file called outputs/topology.mmd and paste this Mermaid flowchart:
flowchart TD
orchestrator[Orchestrator] --> node_extractor
node_extractor --> nodes_json[data/nodes.json]
nodes_json -.-> edge_typer
edge_typer --> edges_json[data/edges.json]
nodes_json -.-> graph_builder
edges_json -.-> graph_builder
graph_builder --> digraph[DiGraph]
digraph -.-> integrity_checker
integrity_checker --> validated[Validated Graph]
validated -.-> checklist_encoder
checklist_encoder --> checklist[deal_triage_checklist.md]
checklist -.-> deal_runner
deal_runner --> scored[Scored Results]
scored -.-> deck_exporter
scored -.-> teachback_scorer
deck_exporter --> deck[spaced_rep_deck.md]
teachback_scorer --> teachback[teachback_instrument.md]
scored -.-> doc_writer
doc_writer --> guide[guide.md]
How to read this diagram
Solid arrows (-->) represent sequential flow: one agent must finish before the next starts. Dotted arrows (-.->) represent data dependencies: the target reads from the source file.
Notice that deck_exporter, teachback_scorer, and doc_writer all read from the scored results. They can run in parallel once the deal_runner completes.
- Save outputs/topology.mmd.
- Before you verify, predict: when you paste this into a Mermaid renderer, do you expect to see any disconnected nodes?
Every node connects to at least one other node through either a solid or dotted arrow. You should see a single connected flowchart with no floating boxes.
- Verify the diagram renders correctly by opening https://mermaid.live in your browser.
- Clear the default code in the left panel, then paste the entire contents of outputs/topology.mmd.
You should see a rendered top-down flowchart showing the Orchestrator at the top, all 9 agent modules, and the data files flowing between them. No disconnected nodes.
Seeing a syntax error in mermaid.live?
Check that the first line is exactly flowchart TD with no extra characters before it. Make sure every arrow uses either --> or -.-> with no spaces inside the arrow syntax.
Help me debug a Mermaid syntax error in my topology diagram.
Commit via PR and close the ticket
The design artifacts are complete. Time to ship them through the delivery workflow you just documented in PLAN.md: commit on the feature branch, push, open a pull request referencing the ticket, merge, and pull main.
- Stage and commit all files by running:
git add -A && git commit -m "feat: agent architecture ADR, plan, and topology"
- Push the branch to GitHub by running:
git push -u origin feat/FIELD-1-agent-architecture
- Open your private repository on GitHub in a browser.
- GitHub will show a banner offering to create a pull request from the newly pushed branch. Click Compare & pull request.
- In the PR description body, type:
Closes FIELD-1
Why this exact text?
When a PR body contains "Closes FIELD-N", Linear's GitHub integration automatically moves the referenced ticket to Done when the PR merges. This is the delivery spine you defined in PLAN.md.
- Click Create pull request.
- On the PR page, click Merge pull request, then Confirm merge.
- Return to Cursor's terminal and pull main by running:
git checkout main && git pull
- Open your Linear project board in a browser. Predict: what status should FIELD-1 show now?
FIELD-1 should show as Done (or Closed). The GitHub integration picked up the "Closes FIELD-1" text in the merged PR and moved it automatically.
FIELD-1 still showing In Progress?
The Linear-GitHub integration needs to be connected. In Linear, go to Settings then Integrations and confirm GitHub is linked to your repository. If it was not linked before the merge, you can manually drag FIELD-1 to Done.
Help me connect the Linear-GitHub integration so tickets auto-close on merge.
✔️ Awesome, I've got everything!
Great. Confirm you have saved all three files: docs/ADR-001-agent-architecture.md, docs/PLAN.md, and outputs/topology.mmd. All three are merged to main via your PR.
ⓧ I'd like to double check the full code
Scroll up and confirm your files match these three chunks, in order:
- docs/ADR-001-agent-architecture.md — header, decision statement, and all 9 agents (chunks 1 and 2 from above).
- docs/PLAN.md — build order (5 items), validation gates (3 items), branch/PR/ticket workflow (4 items).
- outputs/topology.mmd — Mermaid flowchart starting with flowchart TD, 19 lines of node and edge definitions.
Your blueprint is locked in and shipped. The orchestrator, the contracts, the validation gates, and the delivery workflow are all documented and merged. Next up, you will build the concept graph itself: extracting every node from the ULI framework, typing the edges, and proving the map holds together with zero orphans.
Build the Concept Graph
Your agent architecture is documented, your topology diagram renders, and FIELD-1 is closed. Now it is time to build the core artifact: the concept graph that maps the entire field of real estate development into a single connected structure.
A graph without integrity checks is just a pretty picture. Orphan nodes and missing connections hide in plain sight until you try to use the map for something real. This step builds AND checks in one pass so the artifact is validated the moment it exists.
In this step, get ready to:
- Create the node and edge data files covering all six categories (lifecycle, capital stack, asset class, player, route, risk type).
- Build the directed graph and run integrity checks confirming zero orphans and full weak connectivity.
- Export the graph to an Obsidian vault and verify the connected web in graph view.
Create the node and edge data files
Every node in the graph represents one concept in real estate development. Every edge represents a typed relationship between two concepts. Together they form the map.
- Create a new feature branch by running this command:
git checkout -b feat/FIELD-2-concept-graph
Each node needs four fields: a machine-readable id, a human-readable label, a category tag, and a role (either deal_relevant or context).
- Create data/nodes.json following this schema pattern:
[
{"id": "idea_inception", "label": "Idea Inception", "category": "lifecycle", "role": "deal_relevant"},
{"id": "senior_debt", "label": "Senior Debt", "category": "capital_stack", "role": "deal_relevant"},
{"id": "multifamily", "label": "Multifamily", "category": "asset_class", "role": "deal_relevant"},
{"id": "developer_sponsor_gp", "label": "Developer/Sponsor (GP)", "category": "player", "role": "deal_relevant"},
{"id": "academic_doctorate", "label": "Academic Doctorate", "category": "route", "role": "context"},
{"id": "market_risk", "label": "Market Risk", "category": "risk_type", "role": "deal_relevant"}
]
What does each field do?
- id is the machine-readable key used in edges and code (snake_case, no spaces).
- category groups nodes by domain: lifecycle, capital_stack, asset_class, player, route, or risk_type.
- role marks whether the node feeds into the deal-triage checklist (deal_relevant) or provides context only (context).
Your file needs every node from the following categories. Use Cursor's AI chat (Ctrl+L) to generate the complete file, giving it the schema above and the full list below.
- Lifecycle (8 nodes, all deal_relevant): Idea Inception, Idea Refinement, Feasibility Study, Contract Negotiation, Formal Commitment, Construction, Completion and Formal Opening, Property Asset and Portfolio Management.
- Capital stack (4 nodes, all deal_relevant): Senior Debt, Mezzanine Debt, Preferred Equity, Common Equity.
- Asset classes (6 nodes, all deal_relevant): Multifamily, Office, Industrial, Retail, Hospitality, Mixed-Use.
- Players (9 nodes, all deal_relevant): Developer/Sponsor (GP), Limited Partners (LPs), Senior Lender, Mezzanine Lender, Architect, General Contractor, Broker, Property Manager, Municipal Authority.
- Routes (3 nodes, all context): Academic Doctorate, Practitioner MRE + CCIM + Deals, Pure Experience.
- Risk types (5 nodes, all deal_relevant): Market Risk, Entitlement Risk, Construction Risk, Financing Risk, Operations Risk.
- Confirm you have 35 nodes by running this quick check:
python -c "import json; nodes=json.load(open('data/nodes.json')); print(f'{len(nodes)} nodes loaded')"
You should see 35 nodes loaded in the output.
Seeing a FileNotFoundError?
- Make sure you are running the command from the project root (field-on-one-page folder), not from inside data/.
- Confirm the file is named exactly nodes.json inside the data/ folder.
help me debug a FileNotFoundError when loading data/nodes.json
Now create the edge data. Each edge connects two node IDs with a typed relationship.
- Create data/edges.json following this schema pattern:
[
{"source": "idea_inception", "target": "idea_refinement", "type": "precedes"},
{"source": "senior_debt", "target": "construction", "type": "funds"},
{"source": "developer_sponsor_gp", "target": "market_risk", "type": "bears_the_risk"},
{"source": "multifamily", "target": "office", "type": "is_a_kind_of"},
{"source": "feasibility_study", "target": "architect", "type": "requires"},
{"source": "entitlement_risk", "target": "contract_negotiation", "type": "threatens"},
{"source": "academic_doctorate", "target": "developer_sponsor_gp", "type": "informs"}
]
What do the edge types mean?
- precedes connects lifecycle stages in sequence (Idea Inception precedes Idea Refinement, etc.).
- funds connects a capital-stack layer to the stages it finances.
- bears_the_risk connects a player or capital layer to the risk type it carries.
- is_a_kind_of establishes the asset class taxonomy.
- requires connects a stage to the players needed during that stage.
- threatens connects a risk type to the stages it endangers.
- informs connects a mastery route to the players it trains you to become.
Use Cursor's AI chat to generate the complete edge list. Tell it to create at least 50 edges covering all seven types, ensuring every node has at least one connection.
- Confirm you have 50+ edges by running:
python -c "import json; edges=json.load(open('data/edges.json')); print(f'{len(edges)} edges loaded')"
You should see 50 or more edges loaded.
Fewer than 50 edges?
Go back to Cursor's chat and ask it to add more edges. Common gaps: missing requires edges from later lifecycle stages to players, or missing threatens edges from risk types to multiple stages.
help me add more typed edges to reach 50 in my concept graph
Build the graph and run integrity checks
The data files are raw JSON. The graph builder assembles them into a networkx DiGraph that the rest of the pipeline can query, traverse, and validate.
- Create agents/graph_builder.py with the following code:
import json
import os
import networkx as nx
# Resolve paths relative to project root
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def build_graph():
"""Read node and edge JSON files, return a constructed DiGraph."""
nodes_path = os.path.join(PROJECT_ROOT, "data", "nodes.json")
edges_path = os.path.join(PROJECT_ROOT, "data", "edges.json")
with open(nodes_path, "r") as f:
nodes = json.load(f)
with open(edges_path, "r") as f:
edges = json.load(f)
# Construct directed graph with node and edge attributes
G = nx.DiGraph()
for node in nodes:
G.add_node(node["id"], **node)
for edge in edges:
G.add_edge(edge["source"], edge["target"], type=edge["type"])
return G
if __name__ == "__main__":
G = build_graph()
print(f"Graph built: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
What does this code do?
- PROJECT_ROOT resolves to the field-on-one-page folder regardless of where you run the script from.
- build_graph() loads both JSON files and adds every node and edge with their attributes to a DiGraph.
- The **node syntax unpacks each node dict as keyword attributes, so you can later query G.nodes['idea_inception']['category'].
- Save agents/graph_builder.py and run it to confirm the graph assembles:
python agents/graph_builder.py
You should see output like Graph built: 35 nodes, 52 edges (your edge count may differ).
Seeing a KeyError or JSONDecodeError?
- A KeyError means a node in your JSON is missing one of the required fields (id, label, category, role).
- A JSONDecodeError means a trailing comma or missing bracket in your JSON file.
help me fix a JSON parsing error in my nodes or edges file
The graph assembles, but you have no proof it is correct. The integrity checker validates five properties that together guarantee the map has no gaps.
- Create agents/integrity_checker.py and add the imports and canonical node lists at the top:
import networkx as nx
from graph_builder import build_graph
# Canonical node lists the checker validates against
REQUIRED = {
"lifecycle": ["idea_inception", "idea_refinement", "feasibility_study",
"contract_negotiation", "formal_commitment", "construction",
"completion_and_formal_opening", "property_asset_and_portfolio_management"],
"capital_stack": ["senior_debt", "mezzanine_debt", "preferred_equity", "common_equity"],
"asset_class": ["multifamily", "office", "industrial", "retail", "hospitality", "mixed_use"]
}
What is the REQUIRED dict?
This dictionary holds the canonical node IDs for the three most critical categories. The checker compares your graph against this list to confirm nothing was missed. If a node ID in REQUIRED does not exist in the graph, the check fails and names the gap.
- Below the REQUIRED dict, add the check function and entry point:
def check_integrity():
G = build_graph()
# Check for orphan nodes (no connections at all)
isolates = list(nx.isolates(G))
print(f"[{'PASS' if not isolates else 'FAIL'}] Zero isolates {isolates if isolates else ''}")
# Check weak connectivity (all nodes reachable ignoring direction)
connected = nx.is_weakly_connected(G)
print(f"[{'PASS' if connected else 'FAIL'}] is_weakly_connected: {connected}")
# Check required node coverage by category
node_ids = set(G.nodes())
for cat, nodes in REQUIRED.items():
missing = [n for n in nodes if n not in node_ids]
status = 'PASS' if not missing else 'FAIL'
print(f"[{status}] All {cat} present{f' Missing: {missing}' if missing else ''}")
# Check minimum counts
n, e = G.number_of_nodes(), G.number_of_edges()
print(f"[{'PASS' if n >= 30 else 'FAIL'}] Node count >= 30 (count: {n})")
print(f"[{'PASS' if e >= 50 else 'FAIL'}] Edge count >= 50 (count: {e})")
if __name__ == "__main__":
check_integrity()
What does each check prove?
- nx.isolates(G) finds nodes with zero in-degree AND zero out-degree. An isolate means a concept that connects to nothing, which is a gap in the map.
- nx.is_weakly_connected(G) checks that every node is reachable from every other node when you ignore edge direction. If this fails, the graph has disconnected islands.
- The coverage check confirms the ULI eight-stage lifecycle, all four capital-stack layers, and all six asset classes are present.
- Save agents/integrity_checker.py.
Before you run this, what do you think will happen? Will all checks pass on the first try, or will the checker find gaps in your data?
- Run the integrity checker:
python agents/integrity_checker.py
You should see output confirming all checks pass:
[PASS] Zero isolates
[PASS] is_weakly_connected: True
[PASS] All lifecycle present
[PASS] All capital_stack present
[PASS] All asset_class present
[PASS] Node count >= 30 (count: 35)
[PASS] Edge count >= 50 (count: 52)
Seeing FAIL on one or more checks?
This is normal on a first run. The checker is doing its job by finding exactly what to fix.
- Isolates FAIL: find the listed node IDs in data/edges.json and add at least one edge connecting each orphan.
- is_weakly_connected FAIL: you have disconnected islands. Add edges bridging isolated clusters to the rest of the graph.
- Coverage FAIL: the checker names the missing node IDs. Add them to data/nodes.json with the correct category.
help me fix integrity check failures in my concept graph
Export to Obsidian, PR, and close the ticket
The graph is validated in code. Now you will export it to Obsidian so you can see the connected web visually and navigate between concepts using wikilinks.
- Create agents/obsidian_exporter.py with the following code:
import os
from graph_builder import build_graph
def export_to_obsidian():
"""Generate one markdown file per node with YAML frontmatter and wikilinks."""
G = build_graph()
output_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "outputs", "vault")
os.makedirs(output_dir, exist_ok=True)
for node_id, attrs in G.nodes(data=True):
successors = list(G.successors(node_id))
predecessors = list(G.predecessors(node_id))
# Write markdown with YAML frontmatter and wikilinks
filepath = os.path.join(output_dir, f"{node_id}.md")
with open(filepath, "w") as f:
f.write("---\n")
f.write(f"category: {attrs.get('category', '')}\n")
f.write(f"role: {attrs.get('role', '')}\n")
f.write(f"edges_out: {successors}\n")
f.write(f"edges_in: {predecessors}\n")
f.write("---\n\n")
f.write(f"# {attrs.get('label', node_id)}\n\n## Connected Nodes\n\n")
for conn in successors + predecessors:
f.write(f"- [[{conn}]]\n")
if __name__ == "__main__":
export_to_obsidian()
print("Vault exported to outputs/vault/")
What does this code do?
- For each node in the graph, it creates a .md file named after the node ID.
- YAML frontmatter stores the category, role, and edge lists so Obsidian can filter by metadata.
- Wikilinks ([[node_id]]) let you click between connected concepts in Obsidian's editor.
- Obsidian's graph view reads these links and renders the entire web visually.
- Save agents/obsidian_exporter.py and run it:
python agents/obsidian_exporter.py
You should see Vault exported to outputs/vault/ in the terminal.
- Confirm the files were created by running:
ls outputs/vault/ | head -10
You should see .md files named after your node IDs (like idea_inception.md, senior_debt.md, etc.).
- Switch to Obsidian. Your vault is already pointed at outputs/ from Step 1.
- Open the graph view by pressing Ctrl+G.
You should see a connected web of nodes with lines between them. No nodes should be floating alone with zero connections.
Seeing floating nodes in Obsidian's graph?
- A floating node means it has no wikilinks pointing to or from it. Run python agents/integrity_checker.py again to confirm which nodes are isolates.
- If the checker passes but Obsidian shows floaters, the vault folder may contain stale files from a previous run. Delete everything in outputs/vault/ and re-run the exporter.
help me fix disconnected nodes showing in Obsidian's graph view
The graph is built, validated, and visible. Time to ship it.
- Commit and push the branch by running these commands:
git add -A
git commit -m "feat: concept graph passes integrity"
git push -u origin feat/FIELD-2-concept-graph
- Open GitHub in your browser and create a pull request from feat/FIELD-2-concept-graph to main.
- In the PR description, type Closes FIELD-2 so Linear auto-closes the ticket on merge.
- Merge the pull request.
- Pull main locally by running:
git checkout main
git pull
Check Linear. FIELD-2 should now show as closed.
FIELD-2 still open in Linear?
- Confirm your PR description contains the exact text Closes FIELD-2 (case-sensitive).
- Check that the GitHub integration is connected in Linear's settings under Integrations.
help me connect Linear's GitHub integration to auto-close tickets
✔️ Awesome, I've got everything!
Great. Double check you have saved all three agent files and that your main branch is up to date.
ⓧ I'd like to double check the full code
Scroll up and confirm each file is present and saved:
- agents/graph_builder.py reads both JSON files and constructs a DiGraph.
- agents/integrity_checker.py validates zero isolates, weak connectivity, node coverage, 30+ nodes, 50+ edges.
- agents/obsidian_exporter.py generates one markdown file per node with YAML frontmatter and wikilinks.
- data/nodes.json contains 35 nodes across all six categories.
- data/edges.json contains 50+ typed edges across all seven types.
Run python agents/integrity_checker.py one more time. All checks should print PASS.
Your concept graph is validated, visible, and shipped. Next up, you will turn every deal-relevant node into a screening question with a concrete go/no-go threshold, transforming the map into a working decision tool.
Encode the Deal-Triage Checklist
Your concept graph is a validated map of the entire field. It passes integrity checks, renders in Obsidian, and connects every node. But a map you cannot act on is not yet mastery.
This step turns every deal-relevant node into a screening question with a concrete go/no-go threshold. The result is a one-page checklist that decides rather than describes. Without this encoding, the concept graph is decoration.
In this step, get ready to:
- Create the checklist encoder that maps deal-relevant nodes to screening questions with numeric or categorical thresholds.
- Validate that all five risk categories have coverage and every deal-relevant node maps to exactly one question.
- Render the one-page checklist as both markdown and CSV, then ship it via pull request.
Create the checklist encoder
The encoder reads your existing data/nodes.json, separates deal-relevant nodes from context nodes, and generates a screening question with a go/no-go threshold for each deal-relevant node. Context nodes get a reference prompt instead.
- Move FIELD-3 to In Progress in your Linear project board.
- Create the feature branch by running this command:
git checkout -b feat/FIELD-3-checklist
- Create agents/checklist_encoder.py with the following imports and helper functions:
import json
import sys
import csv
import argparse
from pathlib import Path
# Risk categories the checklist must cover
RISK_CATEGORIES = [
"market", "entitlement", "construction",
"financing", "operations"
]
def load_nodes():
"""Load all nodes from data/nodes.json."""
with open("data/nodes.json", "r") as f:
return json.load(f)
def get_deal_relevant(nodes):
"""Filter nodes tagged as deal_relevant."""
return [n for n in nodes if n.get("role") == "deal_relevant"]
def get_context_nodes(nodes):
"""Filter nodes tagged as context."""
return [n for n in nodes if n.get("role") == "context"]
What does this code do?
The script sets up the foundation for the encoder. RISK_CATEGORIES defines the five standard development risk categories every checklist must cover.
The two filter functions separate nodes by their role tag. Deal-relevant nodes become screening questions. Context nodes (like the three mastery routes) get reference prompts instead of go/no-go gates.
The core of the encoder is the ENCODINGS dictionary. Each deal-relevant node maps to a risk category, a screening question, and concrete go/no-go thresholds.
- Add the ENCODINGS dictionary below the helper functions, starting with these examples that cover multiple risk categories:
# Each deal-relevant node encodes to a screening question
# with a concrete go/no-go threshold
ENCODINGS = {
"Feasibility Study": {
"risk_category": "financing",
"question": "Does the project achieve minimum 150bp spread between yield-on-cost and market cap rate?",
"go": ">= 150bp spread",
"no_go": "< 100bp spread"
},
"Senior Debt": {
"risk_category": "financing",
"question": "Can the project secure senior debt at <= 60% LTC with DSCR >= 1.25x?",
"go": "LOI in hand at feasible terms",
"no_go": "No lender interest below 65% LTC"
},
"Entitlement Risk": {
"risk_category": "entitlement",
"question": "Is the required entitlement path achievable within 12 months with precedent approvals in the jurisdiction?",
"go": "By-right or minor variance",
"no_go": "Rezoning required with no political precedent"
},
}
What is node-to-question encoding?
Each entry takes an abstract concept (a node in your graph) and turns it into something actionable. The question is what you ask when screening a deal. The thresholds are the decision gate.
Notice the thresholds are concrete. ">=150bp spread" is a number you can calculate. "By-right or minor variance" is a category you can verify against municipal records. Vague criteria like "seems reasonable" would defeat the purpose.
You need encodings for every deal-relevant node in your graph, covering all five risk categories. Use Cursor to help generate the remaining encodings.
- Open Cursor's AI chat with Ctrl+L.
- Prompt Cursor to complete the ENCODINGS dictionary by referencing your data/nodes.json file. Ask it to generate an encoding for every node with role: deal_relevant, ensuring at least one entry per risk category (market, entitlement, construction, financing, operations).
- Review each generated encoding. Confirm every question has a concrete numeric or categorical threshold, not a vague judgment call.
Match your node names exactly
The keys in ENCODINGS must match the name field in data/nodes.json character-for-character. If your graph has "Market Risk" and your encoding says "market_risk", the validator will flag it as a gap.
- Below the ENCODINGS dictionary, add the REFERENCE_PROMPTS dictionary and the main encoding function:
# Context nodes get reference prompts, not go/no-go gates
REFERENCE_PROMPTS = {
"Academic Doctorate": "What academic programs feed this deal's market research?",
"Practitioner MRE + CCIM + Deals": "Which practitioner credentials validate this team?",
"Pure Experience": "What adjacent-role experience does the sponsor bring?",
}
def encode_checklist(nodes):
"""Build the full checklist from node data."""
deal_nodes = get_deal_relevant(nodes)
context_nodes = get_context_nodes(nodes)
checklist = []
for node in deal_nodes:
name = node["name"]
if name in ENCODINGS:
entry = ENCODINGS[name].copy()
entry["node"] = name
checklist.append(entry)
# Attach reference prompts to context nodes
references = []
for node in context_nodes:
name = node["name"]
if name in REFERENCE_PROMPTS:
references.append({
"node": name,
"reference_prompt": REFERENCE_PROMPTS[name]
})
return checklist, references
What does encode_checklist do?
It walks every deal-relevant node, looks up its encoding, and builds the checklist as a list of dictionaries. Context nodes get a separate list of reference prompts. The function returns both lists so downstream code can render or validate them independently.
- Confirm the encoder loads correctly by running this smoke test:
python -c "from agents.checklist_encoder import load_nodes, encode_checklist; nodes = load_nodes(); c, r = encode_checklist(nodes); print(f'{len(c)} questions, {len(r)} reference prompts')"
You should see a count showing your deal-relevant questions and reference prompts. If you see an import error, confirm agents/checklist_encoder.py is saved.
Seeing a ModuleNotFoundError?
Make sure you are running the command from the field-on-one-page project root directory, not from inside the agents/ folder.
help me debug a ModuleNotFoundError when running my checklist encoder
Validate coverage against the risk rubric
The encoder needs a validation mode that proves completeness. The --validate flag checks two things: every deal-relevant node has exactly one question, and all five risk categories have at least one question.
- Add the validation function and the main execution block at the bottom of agents/checklist_encoder.py:
def validate(checklist, nodes):
"""Check coverage: all risk categories and all deal-relevant nodes."""
deal_nodes = get_deal_relevant(nodes)
errors = []
# Check every deal-relevant node has an encoding
encoded_names = {entry["node"] for entry in checklist}
for node in deal_nodes:
if node["name"] not in encoded_names:
errors.append(f"MISSING: {node['name']} has no encoding")
# Check all 5 risk categories are covered
covered = {entry["risk_category"] for entry in checklist}
for cat in RISK_CATEGORIES:
if cat not in covered:
errors.append(f"GAP: risk category '{cat}' has no question")
# Check no duplicates
names = [entry["node"] for entry in checklist]
dupes = [n for n in names if names.count(n) > 1]
if dupes:
errors.append(f"DUPLICATE: {set(dupes)}")
return errors
What does validate check?
- Every deal-relevant node from nodes.json maps to exactly one question (no gaps, no duplicates).
- All five risk categories (market, entitlement, construction, financing, operations) have at least one question that would flag them.
- Context nodes are excluded from this check. They have reference prompts, not screening gates.
- Add the argument-parsing main block below the validate function:
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Encode deal-triage checklist from concept graph"
)
parser.add_argument(
"--validate", action="store_true",
help="Run coverage validation checks"
)
args = parser.parse_args()
nodes = load_nodes()
checklist, references = encode_checklist(nodes)
if args.validate:
errors = validate(checklist, nodes)
if errors:
print("VALIDATION FAILED:")
for e in errors:
print(f" {e}")
sys.exit(1)
print("VALIDATION PASSED:")
print(f" Deal-relevant questions: {len(checklist)}")
print(f" Risk categories covered: 5/5")
print(f" Context reference prompts: {len(references)}")
print(f" Duplicates: 0")
What does the --validate flag do?
The argparse module handles the flag with action="store_true". When the learner passes --validate, the script runs the coverage checks and exits with code 1 if any fail. This makes it usable as a CI gate later.
Before you run validation, what do you expect to see? If your ENCODINGS dictionary covers every deal-relevant node and spans all five risk categories, it should pass cleanly.
- Save agents/checklist_encoder.py and run the validator:
python agents/checklist_encoder.py --validate
You should see output confirming all checks pass, with zero gaps and zero duplicates.
Seeing VALIDATION FAILED?
This is expected if your ENCODINGS dictionary is incomplete. The error messages tell you exactly which nodes are missing or which risk categories have no coverage.
- If you see a MISSING error, add an encoding for that node with a question and threshold.
- If you see a GAP error for a risk category, check that at least one encoding has that category in its risk_category field.
- If you see a DUPLICATE error, remove the repeated entry from ENCODINGS.
help me fix my checklist encoder validation failures
Render the one-page checklist, commit, and close the ticket
With validation passing, the encoder needs to produce two output files: a markdown checklist organized by risk category, and a CSV version for spreadsheet use.
- Add the output rendering functions above the if __name__ block in agents/checklist_encoder.py:
def render_markdown(checklist, references):
"""Output the one-page checklist as markdown."""
out = Path("outputs/deal_triage_checklist.md")
lines = ["# Deal-Triage Checklist\n"]
# Group questions by risk category
for cat in RISK_CATEGORIES:
lines.append(f"\n## {cat.title()} Risk\n")
for entry in checklist:
if entry["risk_category"] == cat:
lines.append(f"- **{entry['node']}**: {entry['question']}")
lines.append(f" - GO: {entry['go']}")
lines.append(f" - NO-GO: {entry['no_go']}\n")
# Append reference prompts
lines.append("\n## Reference Prompts (Context Nodes)\n")
for ref in references:
lines.append(f"- **{ref['node']}**: {ref['reference_prompt']}")
out.write_text("\n".join(lines))
print(f"Written: {out}")
def render_csv(checklist):
"""Output the checklist as CSV for spreadsheet use."""
out = Path("outputs/deal_triage_checklist.csv")
with open(out, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["Node", "Risk Category", "Question", "Go", "No-Go"])
for entry in checklist:
writer.writerow([
entry["node"],
entry["risk_category"],
entry["question"],
entry["go"],
entry["no_go"]
])
print(f"Written: {out}")
Why two output formats?
The markdown file is your one-page checklist. It is readable in any text editor, renders in GitHub, and prints cleanly. The CSV is for anyone who wants to open it in a spreadsheet and sort or filter by risk category.
- Update the if __name__ block to call the render functions when not in validate-only mode. Find the end of the if args.validate: block and add an else branch:
else:
# Render output files
render_markdown(checklist, references)
render_csv(checklist)
print(f"Checklist encoded: {len(checklist)} questions across {len(RISK_CATEGORIES)} risk categories")
- Save agents/checklist_encoder.py and run it without the --validate flag to generate the output files:
python agents/checklist_encoder.py
You should see two "Written:" confirmations and a summary count.
- Confirm both output files exist by running:
ls outputs/deal_triage_checklist.*
You should see deal_triage_checklist.md and deal_triage_checklist.csv listed.
- Open outputs/deal_triage_checklist.md in Cursor's preview. Confirm it has sections for each risk category with questions and thresholds on each line.
Seeing an empty or malformed checklist?
Check that your ENCODINGS keys match the name field in data/nodes.json exactly. A mismatch means encode_checklist skips the node silently.
Run python agents/checklist_encoder.py --validate again. Any nodes that are missing from the output will show as MISSING errors.
help me debug why my checklist output is incomplete
Run the final validation pass to confirm everything is clean before committing:
python agents/checklist_encoder.py --validate
Confirm you see VALIDATION PASSED with all five risk categories covered and zero duplicates.
- Commit and push the branch by running these commands:
git add -A && git commit -m "feat: checklist encoded, coverage validated"
git push -u origin feat/FIELD-3-checklist
- Open a pull request on GitHub with the body: Closes FIELD-3.
- Merge the pull request.
- Confirm FIELD-3 is now closed in your Linear project board.
- Pull main locally by running:
git checkout main && git pull
✔️ Awesome, I've got everything!
Great. Confirm you have saved agents/checklist_encoder.py and that your main branch is up to date.
ⓧ I'd like to double check the full code
Your agents/checklist_encoder.py file is longer than 30 lines, so scroll through and confirm each section is present in this order:
- Imports: json, sys, csv, argparse, Path.
- RISK_CATEGORIES list with all five categories.
- load_nodes, get_deal_relevant, and get_context_nodes helper functions.
- ENCODINGS dictionary with one entry per deal-relevant node, covering all five risk categories.
- REFERENCE_PROMPTS dictionary for context nodes.
- encode_checklist function returning checklist and references.
- validate function checking coverage and duplicates.
- render_markdown and render_csv output functions.
- if __name__ == "__main__" block with argparse, validate path, and else render path.
Your concept graph now has teeth. Every deal-relevant node is an actionable screening question with a line in the sand. Next up, you will run this checklist against a real (hypothetical) deal and prove it catches problems across all five risk categories.
Run the Sample Deal and Validate
Your checklist has questions and thresholds for every deal-relevant node. It covers all five risk categories. But it has never screened a real scenario.
In this step, you will create a hypothetical 200-unit multifamily development designed to trigger flags across all five risk categories, run the checklist against it, and produce the spaced-repetition deck and teach-back instrument that complete the validated learning system.
In this step, get ready to:
- Create the sample deal and run the checklist against it.
- Export the spaced-repetition deck and teach-back instrument.
- Run the full pipeline, tag the release, and close the ticket.
Create the sample deal and run the checklist
The checklist is only proven when it screens a deal and raises flags. You will construct a hypothetical deal with parameters deliberately chosen to trigger at least one flag in each of the five risk categories: market, entitlement, construction, financing, and operations.
- Move FIELD-4 to In Progress in your Linear project board.
- Create the feature branch by running this command:
git checkout -b feat/FIELD-4-sample-deal
- Create data/sample_deal.json with the following deal parameters:
{
"name": "Greenfield Commons",
"type": "Class A Multifamily Ground-Up",
"units": 200,
"total_cost_usd": 45000000,
"capital_stack": {
"senior_debt_ltc_pct": 55,
"preferred_equity_pct": 15,
"lp_equity_pct": 25,
"gp_equity_pct": 5
},
"returns": {
"yield_on_cost_pct": 7.0,
"market_cap_rate_pct": 5.5
},
"zoning": "agricultural",
"rezoning_required": true,
"political_precedent": false,
"construction_months": 18,
"lease_up_months": 12,
"stabilized_occupancy_target_pct": 93,
"absorption_units_12mo": 200,
"trailing_submarket_absorption_12mo": 150,
"gc_bonded": true,
"gmp_contract": true,
"contingency_pct": 5,
"lp_equity_fully_committed": false,
"sponsor_prior_max_units": 120,
"climate_zone_weather_delay_risk": "high"
}
Why is this deal designed to fail?
Each parameter targets a specific risk category. Absorbing 200 units in 12 months exceeds the trailing submarket average by 33%, triggering market risk. The agricultural zoning with no political precedent triggers entitlement risk. The 18-month construction in a high weather-delay climate zone triggers construction risk. LP equity not fully committed with a borderline yield-on-cost spread triggers financing risk. The sponsor's prior max of 120 units at a new 200-unit scale triggers operations risk.
A deal that passes every check teaches nothing. A deal that triggers every category proves the checklist catches what it should.
- Use Cursor's AI chat (Ctrl+L) to create agents/deal_runner.py with these requirements:
- Loads outputs/deal_triage_checklist.md (or the CSV) and data/sample_deal.json.
- Evaluates each checklist question against the deal's parameters.
- Outputs a GO, CAUTION, or NO-GO verdict for each question.
- Prints a summary grouped by risk category (market, entitlement, construction, financing, operations).
- Prints a final recommendation based on the aggregate results.
- Records the learner's own analysis alongside each flag (your reasoning, not a framework diagnosis).
Give Cursor the full context
Paste the contents of outputs/deal_triage_checklist.md and data/sample_deal.json directly into the chat alongside your requirements. The more context Cursor has, the more accurate the generated code will be.
- Save agents/deal_runner.py and review the generated code to confirm it reads both files and produces per-question verdicts.
Before you run the deal runner, predict: do you think the deal will pass cleanly, or will it raise flags? In which risk categories?
- Run the deal runner by executing this command:
python agents/deal_runner.py
You should see output showing a verdict (GO, CAUTION, or NO-GO) for each checklist question, grouped by risk category. At least one RED flag (CAUTION or NO-GO) should appear in each of the five categories.
What should I see in the output?
The entitlement risk flag fires on the rezoning requirement (agricultural zoning with no political precedent). The market risk flag fires on the aggressive 200-unit absorption in 12 months. Construction risk flags on weather-delay exposure. Financing risk flags on uncommitted LP equity. Operations risk flags on sponsor scale mismatch.
If any risk category shows zero flags, revisit the thresholds in your checklist or the parameters in sample_deal.json and adjust until all five categories are covered.
Missing flags in one or more categories?
Check that deal_runner.py is reading the correct threshold values from your checklist. A common issue is that the comparison logic inverts the threshold direction (checking greater-than when it should check less-than).
Also confirm sample_deal.json has the exact values above. If climate_zone_weather_delay_risk is missing, the construction category will not flag.
Ask Cursor for help debugging.
Export the spaced-repetition deck and teach-back instrument
The deal runner proves the checklist works. Now you need two more artifacts that make the knowledge stick and transferable: a flashcard deck for long-term retention and a teach-back rubric for cold demonstration.
- Use Cursor's AI chat to create agents/deck_exporter.py with these requirements:
- Reads the concept graph (via agents/graph_builder.py or directly from data/nodes.json and data/edges.json).
- Generates one flashcard per node (30+ cards total).
- Card front: node name.
- Card back: definition, its edges (connected nodes and edge types), and its screening question if the node is deal-relevant.
- Outputs outputs/spaced_rep_deck.md in a format importable to Anki or similar tools.
- Save agents/deck_exporter.py and run it:
python agents/deck_exporter.py
- Open outputs/spaced_rep_deck.md and confirm it contains at least 30 cards, one per node.
Fewer than 30 cards in the deck?
Confirm that deck_exporter.py reads from data/nodes.json which should contain 30+ nodes. If the exporter is filtering by category or role, it may be excluding context nodes. Every node (deal-relevant and context) should produce a card.
Ask Cursor for help troubleshooting.
- Use Cursor's AI chat to create agents/teachback_scorer.py with these requirements:
- Generates outputs/teachback_instrument.md containing a three-task rubric.
- Task (a): explain any randomly-selected node cold to a novice. Must name its category, edges, and why it matters.
- Task (b): redraw the map from memory onto a blank page. Scored against the integrity checker's canonical node list for completeness.
- Task (c): walk the checklist through the sample deal in five minutes. Must state the verdict for each risk category and justify each flag with the learner's own analysis.
- Pass criteria: all three tasks completed within the time limit. Missing a lifecycle stage or capital-stack layer is critical. Missing a secondary player is not.
- Save agents/teachback_scorer.py and run it:
python agents/teachback_scorer.py
- Open outputs/teachback_instrument.md and confirm it contains all three tasks with clear pass/fail criteria.
What is the teach-back instrument for?
The teach-back is how you prove the map lives in your head, not just in a file. If you can explain a random node cold, redraw the structure from memory, and walk the deal in five minutes, the field is yours. If you stumble, the instrument tells you exactly which node to revisit.
Run the full pipeline and tag the release
Every agent has run individually. Now you run the entire pipeline end-to-end to confirm nothing breaks when they execute in sequence.
Before you run the full pipeline, predict: will all six commands pass without error, or will one break?
- Run the full pipeline in sequence by executing this command:
python agents/graph_builder.py && python agents/integrity_checker.py && python agents/checklist_encoder.py --validate && python agents/deal_runner.py && python agents/deck_exporter.py && python agents/teachback_scorer.py
All six commands should complete without errors. The integrity checker confirms zero isolates and weak connectivity. The checklist encoder confirms full risk-category coverage. The deal runner confirms flags in all five categories.
Pipeline fails partway through?
The && operator stops at the first failure. Check which command failed by reading the error message. The most common cause is a file path mismatch: one agent writes to a path that the next agent does not read from.
Ask Cursor for help debugging.
- Commit all changes by running:
git add -A && git commit -m "feat: sample deal run, deck, teachback all pass"
- Push the branch and open a pull request on GitHub with the body "Closes FIELD-4".
- Merge the pull request. Linear auto-closes FIELD-4.
- Tag the release on main by running these commands:
git checkout main && git pull
git tag v1.0.0 -m "The Field on One Page: validated learning system" && git push --tags
- Confirm the tag exists on GitHub by checking the Releases section of your repository. You should see v1.0.0 listed with the annotation.
Why tag a release?
A tagged release marks a stable, known-good state. Anyone (including future-you) can check out v1.0.0 and know it passes every integrity check and screens a deal successfully. It also creates a clear before/after if you extend the system later.
Your checklist is no longer theoretical. It screened a deal, raised the right flags, and survived a full pipeline run. Next up: the Secret Mission, where you write the documentation that makes this system teachable and transferable to someone who was not in the room.
Secret mission
Documentation Guide and Lessons-Learned Presentation
Your validated learning system works. But a system without documentation dies with its builder. Can you make the entire build teachable and transferable to someone who was never in the room?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on local files, a private GitHub repository, and free-tier services. There are no ongoing costs beyond your existing Cursor Pro subscription.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Local project folder field-on-one-page on your Desktop (Python scripts, data files, outputs).
- Obsidian vault field-on-one-page-vault inside the outputs/ folder.
- Private GitHub repository (field-on-one-page with tagged release v1.0.0).
- Linear project board ("Field on One Page" with 5 closed tickets).
Keep everything running
No action needed. Choose this if you plan to continue working with the concept graph, run the checklist on new deals, or extend the agent architecture.
- The local folder persists on your Desktop at no cost.
- The Obsidian vault is local-only. No Obsidian Sync subscription is required.
- The GitHub repository stays on your account at no cost (private repos are free).
- The Linear project stays on the free tier with no usage limits for your 5 tickets.
Pause - I'll come back to this later
There are no running processes or paid resources to pause. Everything is either local or on free-tier services that persist without action.
- Your local files remain on disk until you delete them.
- GitHub and Linear retain your data indefinitely on their free tiers.
- When you return, open the field-on-one-page folder in Cursor and pick up where you left off.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below to clean up completely.
- Delete the local project folder. Open your terminal and run:
- On macOS/Linux:
cd ~/Desktop
rm -rf field-on-one-page
- On Windows (PowerShell):
cd ~\Desktop
Remove-Item -Recurse -Force field-on-one-page
- Delete the GitHub repository. Open your repository page on GitHub, click Settings, scroll down to the Danger Zone section, and click Delete this repository. Confirm by clicking I want to delete this repository, then I have read and understand these effects, type the repository name, and click Delete this repository.
- Delete the Linear project. Open your "Field on One Page" project in Linear. Click the three-dot menu next to the project name (beside the Overview and Issues tabs) and select Delete. Confirm the deletion. The project moves to Recently Deleted and is permanently removed after 30 days.
- Uninstall the networkx package if you no longer need it:
pip uninstall networkx
- Confirm with y when prompted.
That's a Wrap!
That's a Wrap!
Nice work. You just built a validated learning system that compresses the entire field of real estate development into one connected picture and turns it into a working screening tool.
You've learned how to:
- Built a validated concept graph of real estate development with 30+ nodes and 50+ typed edges, grounded in the ULI eight-stage lifecycle, passing graph-integrity checks with zero orphans and full weak connectivity.
- Encoded every deal-relevant node into a one-page deal-triage checklist with concrete go/no-go thresholds covering all five standard development risk categories, then proved it works by running a sample 200-unit multifamily deal that flags at least one issue per risk category.
- Shipped the build through a Linear project of planned tickets, each worked on a feature branch and merged via pull request, ending in a tagged v1.0.0 release on GitHub with a spaced-repetition deck and teach-back instrument ready to use cold.
- Secret Mission: Wrote a plain-language documentation guide and a two-audience lessons-learned presentation making the system teachable and transferable to someone who was not in the room.
Ready to quiz yourself?