Your data in a database you own and can question.

For owners paying monthly to reach their records: a database designed around your work, backed by 27 numbered, repeatable changes across two databases. [Postgres]

Database console, a query replayed /work/database-design/demo/ Captured

One console over PostgreSQL, DuckDB and SQLite. The builds behind it hold 27 repeatable migrations across two PostgreSQL databases of 18 tables each.

Sound familiar

Your Monday export lands in a spreadsheet. You still need another report to answer your question. Back to the platform: change the filters, download another file. You spend your time assembling records instead of using them.

We plan a database around your records and the questions you need answered. Start with the report you keep rebuilding. Show which fields it needs and what the current export leaves out.

Out of the platform

Your customer records, invoices or membership history determine what needs to move. We assess exports from a customer management tool, a business operations system or an association management system. [CRM, ERP, AMS]

The work follows these steps: collect your records, reshape them, upload them, add custom fields and connect your apps. Collection uses permitted access through the platform's app connection, export files or your own logged-in screens. [API]

Some platforms make leaving hard, so we check first that the access reaches the whole database. Anything with an API or a CSV upload is a candidate to move, with bulk reshaping before the upload. The review compares another platform's features and ongoing cost.

We also add a browser console for exploring the database. It lists tables with row counts, shows how tables link, then runs a question and shows the result. Customer records, large-file analysis and research each use database software suited to the job. Bring an export file or connection details to the migration discussion.

The right engine per job

Customer records use one database engine, the phone app another, and the call desk a third. [Postgres, SQLite, DuckDB]

We map your records and their relationships, write numbered changes, then add queries for search and analysis. The work includes backups and a restore check before release. Each change describes what happens to the stored data.

The research database lets you search written content. [SQLite FTS5] The landlord pipeline also checks whether a location falls inside an area. [DuckDB spatial point-in-polygon]

Stored secrets use encryption, and a column supports searches for similar records. [AES-256-GCM, pgvector]

DuckDB console /work/duckdb-console/demo/ Captured

Replay

DuckDB console: tables, SQL, saved queries, CSV export

A real run, replayed: the browser console checks each query and accepts only a single request to read data. [parsed SELECT] Its server carries 14 security tests and 11 tests for simultaneous requests.

Open

The database console lists tables and row counts, saves questions and exports results to a spreadsheet file. [CSV]

Your console walkthrough starts with records for individual pieces of land, their data types and a map of the tables, then covers calculations across groups of records and text search. See the database design example for the engines behind each job.

Schema map: agent_service, 18 tables, 22 relationships, rows as recorded

tenants 8 rows15 tables carry a tenant key, so each business's records stay its own

conversations 64

  • conversation_events 261
  • leads 39
  • conversation_attributes 189

contacts 47

  • consent_records 94
  • leads 39

leads 39

  • outbound_log 90

integrations 13

  • webhook_deliveries 30

Settings and logs

  • chatwoot_accounts 8
  • llm_usage_counters 120
  • processed_events 261
  • secrets 4
  • suppression_list 5
  • tenant_chunks 24
  • tenant_client_controls 2
  • tenant_config_versions 21

Rows recorded from the console.Recorded

Ask a question

3 of the console's 33 prepared queries, replayed

Pick a question

Every tenant with its live config version, conversation count and lead count in one pass. PostgreSQL 14.24 · agent_service

agent_service: Tenants at a glance, lines 1 to 11
SELECT t.slug,
       t.lifecycle_state            AS state,
       v.version                    AS live_version,
       count(DISTINCT c.id)         AS conversations,
       count(DISTINCT l.id)         AS leads
FROM tenants t
LEFT JOIN tenant_config_versions v ON v.tenant_id = t.id AND v.status = 'active'
LEFT JOIN conversations c          ON c.tenant_id = t.id
LEFT JOIN leads l                  ON l.tenant_id = t.id
GROUP BY t.slug, t.lifecycle_state, v.version
ORDER BY leads DESC, t.slug;
slugstatelive_versionconversationsleads
osgood-water-systemsactive3107
confluence-water-recoveryactive396
prairie-line-radonactive196
sand-creek-basement-recoveryactive385
uncompahgre-foundation-worksactive385
red-rock-lower-level-designactive164
poudre-gate-ironworkspaused173
ten-mile-heating-airactive373

8 rows · 0.91 msRecorded

Group by a value inside the captured JSON and count the urgent ones with FILTER. PostgreSQL 14.24 · agent_service

agent_service: What leads ask for, lines 1 to 8
SELECT capture_snapshot ->> 'service'  AS service,
       count(*)                          AS leads,
       count(*) FILTER (WHERE capture_snapshot ->> 'urgency' = 'emergency') AS emergencies,
       min(created_at)::date             AS first_seen
FROM leads
GROUP BY 1
ORDER BY leads DESC, service
LIMIT 8;
serviceleadsemergenciesfirst_seen
basement finishing302026-09-26
helical pier underpinning322026-09-21
sewer line lining312026-09-21
class 4 water damage212026-09-27
multi suction point radon212026-09-30
sub slab depressurization202026-09-16
sump pump installation212026-10-02
wet insulation removal202026-10-07

8 rows · 0.13 msRecorded

Postgres full-text search over each tenant's knowledge chunks, ranked by relevance. PostgreSQL 14.24 · agent_service

agent_service: Search the knowledge base, lines 1 to 10
SELECT t.slug,
       c.source_ref,
       round(ts_rank(to_tsvector('english', c.content), q)::numeric, 3) AS rank,
       left(c.content, 78) AS excerpt
FROM tenant_chunks c
JOIN tenants t ON t.id = c.tenant_id,
     websearch_to_tsquery('english', 'insurance OR warranty OR permit') AS q
WHERE to_tsvector('english', c.content) @@ q
ORDER BY rank DESC, t.slug
LIMIT 8;
slugsource_refrankexcerpt
red-rock-lower-level-designfaq.md0.025Q: Do you handle permits? A: Yes. Design drawings, permit applications and ins
confluence-water-recoveryfaq.md0.02Q: Do you work with insurance? A: Yes. We photograph the loss, keep daily mois
confluence-water-recoveryservices.md0.02Confluence Water Recovery handles class 3 and class 4 water losses with moistu
poudre-gate-ironworksfaq.md0.02Q: Can an existing gate be automated? A: Often yes; we check hinges, posts and
prairie-line-radonfaq.md0.02Q: How do I know it worked? A: We retest after installation and the system car
sand-creek-basement-recoveryfaq.md0.02Q: Can you install a sump pump? A: Yes. We size the pump to the pit, add a bat
ten-mile-heating-airfaq.md0.02Q: How long does a furnace swap take? A: Most replacements finish in one day a
uncompahgre-foundation-worksfaq.md0.02Q: Do I need an engineer? A: Our plans are engineered for each house. Every pi

8 rows · 0.65 msRecorded

At scale

County records show how a large collection became records for individual pieces of land. In all, 3,361,791 raw county records became 1,391,184 records from nine county sources.

The call-list process marks duplicates without deleting them, so separate locations of a chain stay callable. Records missing both phone and email stay out of the call-ready list. The process records why it excludes each record.

Rental lead manager /work/rental-lead-manager/demo/ Captured

Replay

Rental lead manager: ranked call desk

A real run, replayed: a call desk ranks records in one HTML file of about 700 KB. Keys 1 to 8 log outcomes and move to the next record.

Open

The call desk shows properties and roof ages behind each name. You can undo an outcome entered incorrectly.

We connect data collection to a business app built around the decisions you need to make with those records.

A small CRM in SQLite

A public feed supplies records for this working example of a small tool for keeping leads and customers. [CRM, SQLite] Pick a feed of public software activity, encyclopedia edits or new stories. [GitHub, Wikipedia, Hacker News]

The server fetches the records, puts them in a common format, removes repeats and saves them, here from public feeds.

A separate panel receives messages from another app. It accepts correctly signed messages and rejects forged signatures. It ignores an event identifier it has already received. [Webhook]

Your database starts with one specific task: receive a record, check it and make it available to answer a useful question. We define that work before adding other connections or building a larger interface.

Done for someone else

We delivered this kind of work for clients. The console and pipeline above show how we work.

Tell us what you would change

Name the platform holding your records and the question you cannot answer. We reply with the proposed scope of the database work.

Start a project