Skip to content

Property Management System Review and Bug fixs - #154

Open
MichaelGetu-git wants to merge 4 commits into
Base360-AI:mainfrom
MichaelGetu-git:fixes
Open

Property Management System Review and Bug fixs#154
MichaelGetu-git wants to merge 4 commits into
Base360-AI:mainfrom
MichaelGetu-git:fixes

Conversation

@MichaelGetu-git

Copy link
Copy Markdown

Summary

Investigated the two client reports and the finance team's rounding complaint.
Found four bugs. Three of them were compounding, and one was masking the other
two, so the dashboard looked healthy while serving fabricated numbers.

The bugs

1. Revenue queries never reached the database

DatabasePool.initialize() built its connection string from settings.supabase_db_user,
supabase_db_password, supabase_db_host, supabase_db_port and supabase_db_name.
None of those exist on the Settings object. The setting that does exist is
database_url, already pointing at the right Postgres instance.

The pool therefore threw on every initialization, and calculate_total_revenue
caught the exception and returned a hardcoded dictionary of mock revenue. No error
reached the client, nothing was logged that operations would see, and the numbers
looked plausible. The dashboard had never read the database.

Fixed by building the async URL from database_url with the asyncpg driver.

2. Cache key was not scoped by tenant

property_id is only unique within a tenant. The schema makes this explicit with a
composite primary key of (id, tenant_id), and the seed data has prop-001 as
Beach House Alpha in Paris for tenant-a and Mountain Lodge Beta in New York for
tenant-b. The revenue cache keyed on revenue:{property_id} alone, so whichever
client requested a shared ID first wrote their totals into a slot the other client
then read for the next five minutes.

This is deterministic rather than a race, and it is what Ocean Rentals reported.

Fixed by scoping the key to revenue:{tenant_id}:{property_id}.

3. Month boundaries ignored the property timezone

properties.timezone exists and is populated (Europe/Paris, America/New_York) but
nothing read it. calculate_monthly_revenue built naive datetimes for the month
boundaries, which compare as UTC against a TIMESTAMP WITH TIME ZONE column.

Reservation res-tz-1 checks in at 2024-02-29 23:30+00. Beach House Alpha is in
Paris, so locally that is 2024-03-01 00:30, a March booking worth 1250.00. UTC
boundaries filed it under February and dropped it, which is the March discrepancy
Sunset Properties reported.

Fixed by constructing the boundaries in the property's own timezone.

4. Money was cast to a float

Amounts are stored as NUMERIC(10, 3). The dashboard endpoint ran
float(revenue_data['total']) before serialising, which rounds to the nearest
binary double. The seed data exposes this directly: 333.333 + 333.333 + 333.334
is exactly 1000.000 as a Decimal and 999.9999999999999 as a float.

Fixed by quantizing to two decimal places and returning an exact decimal string.
The frontend type and display were updated to match.

Verification

Checked both client logins against the values in Postgres.

Request Before After Database
Sunset, prop-001 1000.0 / 3 bookings "2250.00" / 4 2250.000 / 4
Ocean, prop-001 1000.0 / 3 (Sunset's data) "0.00" / 0 0 / 0
Ocean, prop-004 1776.5 / 4 "1776.50" / 4 1776.500 / 4

Redis now holds revenue:tenant-a:prop-001 and revenue:tenant-b:prop-001 as
separate entries. Backend logs show no pool initialization failures after restart.

Notes

The mock fallback in calculate_total_revenue is still in place. It is harmless
while the database connects, but it is the reason a total outage presented as
healthy data. I would remove it so that a database failure surfaces as an error
rather than as convincing fiction, but that changes error handling behaviour beyond
the scope of a debugging fix, so I left it for review.

The pool built its URL from supabase_db_* settings that do not exist in
config, and passed QueuePool to an asyncio engine. Both raised, so every
request silently fell back to hardcoded mock totals: prop-001 reported
1000.00 across 3 bookings where the database holds 2250.000 across 4.
get_session was also a coroutine, so 'async with' could not use it.
prop-001 exists for both tenant-a and tenant-b as different properties.
Keying the cache on property_id alone meant whichever tenant requested
first served their totals to the other for the full 5 minute TTL.
Ocean Rentals saw 2250.00 across 4 bookings belonging to Sunset
Properties, where their own prop-001 has no reservations at all.
Amounts are NUMERIC(10,3) and the API converted them with float(), which
snaps to the nearest binary double. Values like 333.333 have no exact
binary form, so totals drifted by fractions of a cent. The value is now
quantized to 2dp with ROUND_HALF_UP and serialized as a string.
Month boundaries were built as naive datetimes and compared against
timestamptz check_in dates, so every month ran midnight to midnight UTC
regardless of where the property is. Beach House Alpha is Europe/Paris,
where a booking checking in 2024-03-01 00:30 local is 2024-02-29 23:30
UTC, so March dropped it and reported 1000.000 instead of 2250.000.

The function also referenced tenant_id without accepting it and returned
a hardcoded zero.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant