- Created agent/evals/conftest.py: autouse fixture patches teleport_api._fetch_from_teleport
and search_city_slug to bypass all live HTTP calls during tests
- Tests now use HARDCODED_FALLBACK data for all cities (deterministic, instant)
- Created agent/pytest.ini with asyncio_mode=strict and testpaths=evals
- All 126 tests collected and passing: 0 failures, 0 skips
Made-with: Cursor
Remove 'total net worth' and 'everything i own' from wealth_net_worth_kws
so they fall through to property_net_worth_kws which runs property tracker
and shows the complete financial picture template.
Add 'show my total net worth' and related phrases to property_net_worth_kws.
Made-with: Cursor
Replace market data impact story with personal property tracking story.
Update data sources to be accurate (remove detailed MLS tables,
add clear descriptions of what is featured vs background).
Update suggestion chips to match new UI.
Update test count to 100 deterministic passing tests.
Add new tool and eval files to the file table.
Made-with: Cursor
Build pre-formatted financial picture template in property_net_worth executor.
Shows investment portfolio, real estate equity per property, total net worth,
and portfolio breakdown percentages. If no properties, prompts user to add one.
Template is passed as tool result so LLM presents it cleanly.
Made-with: Cursor
When user says 'add my home' without property details, return a warm
structured prompt asking for address, purchase price, current value,
mortgage balance, and monthly rent instead of calling add_property
with empty/zero values.
Detection: message lacks a price pattern (\$[\d,]+, \d+k, \d{5,}).
If message already contains a price, proceed directly to add_property.
Made-with: Cursor
Row 2: Add my home to track equity / Show my total net worth / What are my equity options?
Row 3: Can my portfolio buy a house? / I have a job offer — is it worth it? / How long until I'm stable if I move?
Row 4: Am I ahead or behind financially? / Can I afford to have kids? / What if I buy a house every 2 years?
Removes Austin MLS and Tokyo cost-of-living chips from featured UI.
Market data stays in codebase as background capability.
Made-with: Cursor
Add _extract_strategy_params() helper to graph.py that parses
appreciation rate, buy interval, total years, home price, rent yield,
and annual income from natural language messages.
Update life_decision branch to detect strategy queries and route them
to simulate_real_estate_strategy() with extracted params instead of
calling the general life_decision_advisor. Supports conservative /
moderate / optimistic presets. Falls back to life_decision_advisor
for non-strategy queries.
Made-with: Cursor
Create realestate_strategy.py with simulate_real_estate_strategy().
All rate parameters (appreciation, rent_yield, mortgage_rate,
market_return) default to None — sensible fallbacks applied inside
the function body, clearly labeled as starting points not predictions.
Adds disclaimer, how_to_adjust, and user_provided flag in assumptions.
Adds test_realestate_strategy.py with 7 passing tests.
Made-with: Cursor
- Expand classify_node keyword lists for relocation_runway, wealth_gap,
equity_unlock, and family_planner to cover more natural phrasings
- Add realestate_strategy_kws block that routes multi-property strategy
queries ("buy a house every N years", "rental portfolio strategy") to
the life_decision executor (home_purchase decision type)
- Expand real_estate_kws with MLS/market-data terms and add "show me"
to _location_intent_kws so city + intent triggers correctly
- Update _route_after_classify with explicit routing map comment showing
all 9 non-write categories → "tools" (only existing node names used)
- Add tool categories block to SYSTEM_PROMPT so LLM knows to use
non-portfolio tool results instead of defaulting to portfolio framing
Made-with: Cursor
property_tracker.py:
- Full SQLite backing at agent/data/properties.db (PROPERTIES_DB_PATH for tests)
- :memory: support: module-level _MEMORY_CONN so data persists across calls in tests
- add_property(), get_properties(), list_properties() (alias), update_property(),
remove_property() (soft-delete), get_real_estate_equity(), get_total_net_worth()
- _row_to_dict() computes equity/appreciation and backward-compat added_at alias
- property_store_clear() does DELETE FROM (test reset)
test_wealth_bridge.py (8 new tests, total now 89):
- test_down_payment_austin_portfolio_94k: $94k covers Caldwell/Hays counties
- test_down_payment_small_portfolio: $20k cannot afford safe 20% down anywhere
- test_job_offer_seattle_not_real_raise: $180k Seattle < $120k Austin purchasing power
- test_job_offer_sf_genuine_raise: $250k SF > $80k Austin purchasing power
- test_job_offer_global_city_london: required fields present for any global city
- test_property_crud_full_cycle: CREATE→READ→UPDATE→DELETE all verified
- test_net_worth_combines_portfolio_and_property: equity + portfolio = correct total
- test_teleport_fallback_works_when_api_unavailable: always returns usable data
Made-with: Cursor
- Add Row 3 wealth bridge chips inside enableRealEstate guard (same as Row 2)
- Three new chips with amber/gold theme (distinct from green RE chips):
* 💰 "Can my portfolio buy a house?" → triggers wealth_down_payment
* ✈️ "Is my job offer a real raise?" → pre-fills Seattle/Austin $180k/$120k example
* 🌍 "Cost of living in Tokyo" → triggers Teleport API global city lookup
- New ai-suggestion-chip--wealth CSS modifier with amber tint (light + dark theme)
- New ai-suggestions__row--wealth spacing rule
- Row 3 only visible when enableRealEstate=true (same feature flag as Row 2)
Made-with: Cursor
- New agent/tools/wealth_bridge.py with 3 registered agent tools:
* calculate_down_payment_power(): maps portfolio value to 7 Austin markets
(or any cities) with can_afford_full/conservative/safe + monthly payment estimates
* calculate_job_offer_affordability(): COL-adjusted salary comparison for any
two cities worldwide using ACTRIS COL index (Austin) + Teleport API (global)
* get_portfolio_real_estate_summary(): reads live Ghostfolio portfolio + runs
down payment analysis in one call
- graph.py: add wealth_bridge imports + 4 new query_type routes:
wealth_down_payment, wealth_job_offer, wealth_global_city, wealth_portfolio_summary
- graph.py: add _extract_salary, _extract_offer_city, _extract_current_city helpers
- Mortgage formula: 30yr @ 6.95%, 20% down, ×1.25 for PITI
Made-with: Cursor
- New agent/tools/teleport_api.py with search_city_slug() and get_city_housing_data()
- Live calls to api.teleport.org /scores/ and /details/ endpoints (no auth required)
- In-memory slug cache with 60+ pre-seeded city→slug mappings
- Normalizes Teleport data to unified schema (ListPrice, MedianRentMonthly,
AffordabilityScore, col_index, teleport_scores) compatible with ACTRIS structure
- HARDCODED_FALLBACK for 23 major cities when API is unreachable — never crashes
- Austin TX area detection routes callers back to real_estate.py (ACTRIS data)
- col_index derived from Teleport COL score for wealth_bridge calculations
Made-with: Cursor
- Use real data_source label from _MOCK_SNAPSHOTS (ACTRIS/Unlock MLS) in response envelope
- Append 📊 ACTRIS/Unlock MLS footer to all Texas market_summary responses
- Expose months_of_inventory, pending_sales_yoy, close_to_list_ratio, median_rent_monthly
- Fix compare_neighborhoods data_source attribution for mixed TX/non-TX comparisons
- All 7 counties already had real Jan 2026 MLS figures; this surfaces them properly
Made-with: Cursor
- ngOnInit restores conversation from sessionStorage on mount
- saveHistory() persists every message exchange automatically
- clearHistory() resets to welcome message and wipes stored history
- Clear button added to panel header (small, subtle, matches dark theme)
- welcomeMessage getter adapts copy based on enableRealEstate flag
Made-with: Cursor
- test_search_listings_bedroom_filter: min_beds=3 returns only 3+ bed
listings and records the filter in result.filters_applied.
- test_search_listings_price_filter: max_price=400000 excludes listings
above threshold and records filter in result.filters_applied.
- test_structured_error_code: all error paths return nested
{code, message} dict with a REAL_ESTATE_* code.
- Updated test_feature_flag_disabled: assert nested error dict with
REAL_ESTATE_FEATURE_DISABLED code.
- Updated test_unknown_location_graceful_error: assert nested error
dict with REAL_ESTATE_PROVIDER_UNAVAILABLE code.
All 8 tests pass in < 1s.
Made-with: Cursor
- main.py: add GET /real-estate/log endpoint (feature-flag gated).
Returns total_invocations, success_count, failure_count, and
last 50 log entries from real_estate._invocation_log.
Returns 404 when ENABLE_REAL_ESTATE is not true.
Made-with: Cursor
- search_listings(): add min_beds and max_price optional filter params
with per-field filtering before max_results cap.
filters_applied dict included in result for transparency.
Cache key incorporates filter values.
- All error returns now use nested {code, message} format:
REAL_ESTATE_FEATURE_DISABLED, REAL_ESTATE_PROVIDER_UNAVAILABLE.
- Add _invocation_log + _log_invocation() module-level observability:
records timestamp, function, query (80-char truncated), duration_ms,
success. Bounded to 500 entries. Exposed via get_invocation_log().
- _log_invocation() called in all 4 public functions (both success
and failure paths, including cache hits).
Made-with: Cursor
- graph.py: import real_estate functions (get_neighborhood_snapshot,
search_listings, compare_neighborhoods, is_real_estate_enabled).
- classify_node: detect real estate keywords → route to
real_estate_snapshot | real_estate_search | real_estate_compare.
Detection is fully guarded by is_real_estate_enabled() — when flag
is off, this block is never entered and existing routing is unchanged.
- tools_node: 3 new elif branches for real estate query types.
All append-only; no existing branches modified.
- Added _extract_real_estate_location() and _extract_two_locations()
helpers (new functions, no changes to existing helpers).
Made-with: Cursor
- New tools/real_estate.py: MockProvider with realistic 2024 data for
10 US cities. TTL in-memory cache (5-min). Feature flag:
ENABLE_REAL_ESTATE=true/false — false = zero behavior change.
- tools/__init__.py: 3 new TOOL_REGISTRY entries (append-only).
- README.md: Real Estate demo section with 2-minute run instructions.
Made-with: Cursor
- eval runner: add retry logic (2 attempts) for transient connection drops
- gs-001: accept 'percent' as well as '%' (LLM formatting variance)
- gs-002: use must_contain_one_of for ticker/company name variance
- gs-008/sc-014: fix expected_tools for conditionally-triggered compliance
- graph.py: route 'health check'/'full report' queries to compliance path
so compliance_check always runs for full portfolio report requests
Co-authored-by: Cursor <cursoragent@cursor.com>
Source tags [tool_result_id] were appearing after every individual figure,
making responses unreadable. Rules 1 and 10 in SYSTEM_PROMPT and the
format_node user prompt now enforce one citation per sentence placed at
the end, not inline after each value.
Co-authored-by: Cursor <cursoragent@cursor.com>
Raised bottom from 1.5rem to 5.5rem and reduced size slightly so the
Ask AI button no longer overlaps the Add Account / Add Transaction FABs
that Ghostfolio renders at the bottom-right of the screen.
Co-authored-by: Cursor <cursoragent@cursor.com>
When the agent returns an empty-portfolio response (e.g. after a new
Google OAuth sign-in), a blue banner appears offering to load 18 demo
activities. Clicking 'Load demo data' calls POST /agent/seed with the
user's own bearer token so the seeded data belongs to their account.
Also adds the /seed URL construction in the component constructor.
Co-authored-by: Cursor <cursoragent@cursor.com>
Injects TokenStorageService and passes the active session token in
every /agent/chat request body as bearer_token. The agent now serves
each user's own portfolio data. Unauthenticated visitors still get the
shared demo data via the agent's env-var fallback.
Co-authored-by: Cursor <cursoragent@cursor.com>