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
- 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>
* fix(lib): resolve typescript errors
* feat(lib): migrate to takeUntilDestroyed
* fix(lib): resolve type errors
* feat(lib): implement output signal
* feat(lib): clean up variables
* fix(lib): resolve input is deprecated
* feat(lib): implement input signal on placeholder
* feat(lib): implement input signal on isLoading
* fix(lib): add type annotations for date adapter
* fix(lib): handle form value possibly null
* fix(lint): use arrow fn for validators
* fix(lib): accounts table typings
* fix(lib): remove unsubscribeSubject due to lack of observable
* fix(lib): remove validators variable
* feat(lib): implement inject functions
* feat(lib): make locale an input signal
* feat(lib): make showActions an input signal
* feat(lib): make accountId an input signal
* feat(lib): make accountCurrency an input signal
* feat(lib): make accountBalances an input signal
* feat(lib): make sort a viewChild signal
* feat(lib): implement isNil