Data sources and database
Which data the tools read, how sources are switched on and off, and what the app's own database stores.
The tools never call an upstream system directly. They read marts through the app's data service, which picks the source, applies the source policy, syncs upstream snapshots into the app's own database and falls back to synthetic data when a source is excluded, unconfigured or still syncing.
Sources
DATA_SOURCE | What it is | Basis | History |
|---|---|---|---|
synthetic (default) | Seeded generator, invented areas and values, labelled "SYNTHETIC – not Dubai data" | Transactions | 5 years, monthly |
marts + MARTS_DIR | Any marts directory in the same schema, for example a build from licensed data (none ships with this repository) | Transactions | Monthly |
listings-api | Asking-price aggregates from a listings API that implements the documented contract (off unless configured) | Asking prices | None: a dated snapshot |
Every figure carries provenance.source and provenance.basis. The wording follows the basis: asking-price figures read Asking prices from public listings, as of YYYY-MM-DD, never "sales", "transactions" or "DLD". Tools that need history (area_price_trend) or off-plan counts refuse a snapshot source rather than guess.
Source policy
SOURCE_POLICY is one switch for every surface (chat, REST, MCP):
allow-all(default)exclude-listings: never serve the listings source; fall back to the default dataexclude:<id>,<id>: exclude sources by id
GET /api/status shows which data is being served, whether a sync is running, and which sources the policy excludes.
A superuser can also set the source, the source policy and every listings setting below in Admin › Data; a value set there overrides its environment variable until "Use environment default" is chosen. The listings token can be stored there only when SETTINGS_ENCRYPTION_KEY is set, and LISTINGS_API_TOKEN from the environment is used only while LISTINGS_API_URL also comes from the environment: pointing the URL elsewhere in the panel requires entering the token for that address. Changes to this section need a login from the last 15 minutes.
Listings API
Set, in next/.env.local:
DATA_SOURCE=listings-api
LISTINGS_API_URL=... # base URL ending in /v1
LISTINGS_API_TOKEN=... # Bearer token; never commit itOn the first request the app starts a background sync (about three requests per area, paced 250 ms apart by default) and serves the default data until it finishes; afterwards it serves the stored snapshot and refreshes it every LISTINGS_SYNC_TTL_HOURS (default 24). The contract (endpoints, parameters, response fields) and the mapping into the marts are documented in docs/upstream-api.md in the repository; any service that implements it can be configured. The configured provider's terms apply, and this repository ships no listings data, provider URL or credentials.
A local mock with invented values runs with pnpm --filter @alghafat-market/cli mock-listings-api and serves http://127.0.0.1:3071/v1 (token dev-token).
The app's database
The app has its own database:
- Locally: an embedded Postgres (PGlite) under
.data/pglite. Nothing to install; created and migrated on first use. - Hosted: any Postgres via
DATABASE_URL=postgres://...(for example Neon). Same schema and migrations (Drizzle ORM).
It stores:
| Table | Contents |
|---|---|
datasets, dataset_shards | Marts and their manifest: synthetic, licensed builds, synced snapshots |
upstream_cache | Raw upstream API responses with a time to live |
watches | Saved queries a user re-runs in one click (alerts come later) |
shortlists | A user's shortlisted areas |
conversations | Signed-in users' chats (not temporary ones), with a title and a text copy for search; deleted after the retention period |
handoffs | Briefs the user approved for sending (the local test inbox); deleted after the longer of 30 days and the retention period |
users, sessions, accounts, verifications | Accounts (better-auth): profile, role and suspension, sign-in sessions with IP and browser, password hashes and Google links |
user_prefs | A user's units and the "Save new chats to history" switch |
settings | Overrides made in the admin panel, one row per section; stored keys are encrypted |
prompt_versions | Every composed system prompt a chat has used, kept unchanged so older chats keep theirs |
audit_log | Admin and account changes: who, when, what and from which IP; kept for a year |
rate_limits | Request counters for the account endpoints (sign-in, sign-up and others); cleared after a day |
eval_runs | Eval scorecards |
Watches, shortlists and briefs belong either to an account or, for guests, to a random id kept in the browser; when a guest logs in, that browser's data moves into the account. Guest chats are never stored. "Delete my data" removes what belongs to the account (or the browser), and deleting an account removes everything stored with it. See Accounts and admin for retention and the hourly clean-up.
The local database is single-process: while the web app runs, use the app itself (it syncs and imports on its own); stop it before running CLI commands that open the database:
pnpm --filter @alghafat-market/cli listings-sync
pnpm --filter @alghafat-market/cli db-status
pnpm --filter @alghafat-market/cli superuser --email you@example.comWith DATABASE_URL set, the CLI uses that database and can run while the app is up. The superuser command reads next/.env.local and next/.env (without overriding variables already set), so it targets the same database as the web app, and prints the target before it writes.
Tool reference
Generated from the zod schemas that every surface validates with.
Methodology
How every figure is computed, what it rests on, and what it is not.
Code: Apache-2.0. Original docs text and diagrams: CC BY 4.0. Excluded from both: data, figures and charts derived from DLD or Data.Dubai (governed by DATA_LICENSE.md and any DLD grant); EIBOR values (CBUAE terms); third-party names and logos; and the product and operator names and logos (no trademark licence).
The default data is SYNTHETIC – not Dubai data. Nothing here is financial, legal or investment advice.