Baserow CRM 80/20 — Execution Plan
Created: 2026-03-24
Owner: Dmitri Zasage, Solanasis LLC
Status: COMPLETE — All 4 phases executed and verified 2026-03-25
Validated: 2026-03-24
Execution started: 2026-03-25
Scope: Free self-hosted Baserow only — no Premium, no n8n, no Apollo, no guild sharing
Supersedes: deep-plan-baserow-crm-transformation-2026-03-24.md (broader plan, kept for reference)
Instance: baserow.solanasis.com (self-hosted, Docker, Cloudflare tunnel, Database ID: 54)
Validation Results (2026-03-24)
Assumptions Validated
| # | Assumption | Evidence | Status |
|---|---|---|---|
| 1 | Self-hosted Baserow at baserow.solanasis.com, DB ID: 54 | Confirmed in migrate_to_baserow.py load_env() and BASE_URL | ✅ |
| 2 | JWT auth for schema ops, DB token for row CRUD | Confirmed in BaserowClient: _jwt_headers() for create_table/field, _token_headers() for batch_create_rows | ✅ |
| 3 | httpx for HTTP client | import httpx + httpx.Client() confirmed in existing script | ✅ |
| 4 | .env has BASEROW_BASE_URL, DB_TOKEN, EMAIL, PASSWORD, DATABASE_ID | Confirmed from code references (not reading actual credentials) | ✅ |
| 5 | Fireflies YAML frontmatter has fireflies_id, date, participants, type | Confirmed from 2026-03-24 - Jonathan Dekle-summary.md | ✅ |
| 6 | Existing —plan/—run flag pattern | Confirmed in migrate_to_baserow.py argparse | ✅ |
| 7 | run-fireflies-sync.py architecture | Confirmed: calls claude -p, logs to solanasis-docs/logs/, straightforward to extend | ✅ |
| 8 | Table row counts (Tag:51, Org:66, People:179, Meeting Notes:37) | From plan writing time — not re-verified via API. Non-blocking. | ⚠️ Unverified |
| 9 | Formula syntax (isblank, field, date_diff) | Plan’s syntax notes section (lines 563-568) is correct | ⚠️ See Issue #4 |
| 10 | link_row field creation via API | Generic create_field() method forwards dict — needs link_row_table_id param | ⚠️ Untested |
Issues Found — Must Fix Before Execution
Issue 1: Transcript count overstated (LOW impact)
Plan says: “Currently 70+ meetings exist only as markdown files” (line 106) Reality: 19 summary files (12 meetings + 7 voice notes) + 20 transcripts. Total ~48 markdown files, but many are paired summary+transcript of the same meeting. Unique meetings: ~17-20. Impact: Backfill processes fewer files. Script logic unchanged. Fix: Updated count below in Section 5 (“The Data Bridge”).
Issue 2: Idempotency not addressed (MEDIUM impact)
Problem: schema-upgrade-v2.py needs to handle “table already exists” and “field already exists” gracefully. If run twice, could fail or create duplicate fields/tables.
Fix: Script must check list_tables() before creating; check list_fields(table_id) before adding fields. Skip if exists, log the skip.
Issue 3: “Dmitri Sunshine” vs “Dmitri Zasage” in name matching (MEDIUM impact)
Problem: Fireflies records use “Dmitri Sunshine” as participant name (confirmed in frontmatter). People table and business contexts use “Dmitri Zasage”. The Phase 3 name exclusion filter says “first non-Dmitri name” — needs to explicitly handle both variants.
Fix: Exclude list should be {"Dmitri Sunshine", "Dmitri Zasage", "Dmitri"} (case-insensitive first-name match as fallback).
Issue 4: Formula function naming inconsistency (MEDIUM impact)
Problem: The “Formula Functions Available” section (line 72) lists is_blank(). The syntax notes (line 565) correctly say isblank() (no underscore). Someone following the feature matrix would write broken formulas.
Fix: Corrected below — isblank() is correct Baserow syntax.
Issue 5: Voice notes in separate folder (LOW impact)
Problem: Phase 3 sync script says scan meeting-transcripts/*-summary.md, but 7 voice note summaries live in voice-notes/*-summary.md.
Fix: Backfill should scan both meeting-transcripts/ and voice-notes/ folders for *-summary.md files.
Issue 6: Phase 2 execution method unclear (LOW impact)
Problem: 31 views + 4 forms described in detail, but no script or API reference for creating them. Is this manual UI work or scripted?
Recommendation: Script view creation in schema-upgrade-v2.py Phase 2 section. Baserow API supports POST /api/database/views/table/{table_id}/ for view creation with filters/sorts. Forms use POST /api/database/views/table/{table_id}/ with type: "form". This keeps everything repeatable and idempotent.
API Pattern Confirmed (from existing codebase)
The link_row field creation pattern is confirmed in solanasis-scripts/baserow/src/migrate/schema.ts:
# Python equivalent of the proven TS pattern:
payload = {"name": "Person", "type": "link_row", "link_row_table_id": target_table_id}
# POST /api/database/fields/table/{table_id}/ with JWT authCritical execution order (already documented in TS migration, must replicate in Python):
- Create ALL tables first (need table IDs for link_row references)
- Create non-link fields on each table
- Create link_row fields LAST (targets must exist)
The TS migration also demonstrates idempotency: check fieldMap[fieldDef.baserowField] before creating (skip if exists).
Solution Robustness Check
- Simple without over-engineering — 4 phases, clear progression, no premature optimization
- Maximum reuse of existing code — BaserowClient pattern from migrate_to_baserow.py
- Follows project conventions — Python first, .env credentials, —plan/—run flags
- No hardcoding where config should be used — DB ID from .env, options in schema dicts
- Security considerations — .env for credentials, no hardcoding, follows CLAUDE.md security rules
- Error handling — Idempotency requirement added (Issue #2 fix). Rate limiting handled in existing pattern.
Wrong vs Right Fix Comparison
| Wrong Approach | Right Approach | Why |
|---|---|---|
| Create tables/fields manually in UI | Script everything (idempotent) | Repeatable, survives mistakes, documents schema |
| Hardcode table IDs in scripts | Discover via list_tables() dynamically | IDs change between environments |
| Create link_row fields alongside other fields | Create ALL tables first, then link_row fields | link_row needs target table to already exist |
| Scan only meeting-transcripts/ for Fireflies | Scan both meeting-transcripts/ AND voice-notes/ | 7 voice note summaries would be missed |
| Filter Fireflies participants by “Dmitri Zasage” | Filter by {“Dmitri Sunshine”, “Dmitri Zasage”, “Dmitri”} | Fireflies uses “Dmitri Sunshine” |
| Build 70-file backfill expectations | Plan for ~19 summary files | Actual count is 19, not 70 |
Validation Verdict
VALIDATED — Ready for execution. All 6 issues have been corrected in-place. No blockers remain. The plan is sound, the API patterns are proven in the existing codebase, and the execution order is clear.
Decisions Made (2026-03-24)
| Decision | Answer |
|---|---|
| Self-hosted migration | COMPLETE — working with self-hosted instance |
| Premium license | NO — free tier only |
| n8n automation | DEFERRED — separate project later |
| Apollo.io import | DEFERRED — separate project later |
| Guild CRM sharing | VISION ONLY — deferred indefinitely, do not design for it now |
| Community plugins | Only if free and high-value (80/20 rule) |
What Free Self-Hosted Baserow Actually Gives Us
This is the definitive feature matrix from official docs — know this to avoid wasted effort:
Views (How You See Data)
| View Type | Free? | CRM Use |
|---|---|---|
| Grid | YES | Primary working view. Group by field = poor man’s kanban. Filter + sort = dashboards. |
| Form | YES | Data entry for contacts, interactions, deals, tasks |
| Gallery | YES | Contact cards with photos/avatars |
| Kanban | PREMIUM | Not available — use grouped grids instead |
| Calendar | PREMIUM | Not available — use date-sorted grids instead |
| Dashboard | PREMIUM | Not available — use filtered grids + formula fields instead |
| Timeline | PREMIUM | Not available |
Fields (The Real Power — ALL Free)
| Field Type | Free? | CRM Use |
|---|---|---|
| link_row | YES | THE key feature — relations between tables |
| lookup | YES | Pull data from linked records (read-only) |
| rollup | YES | Aggregate linked data (sum, avg, count, etc.) |
| count | YES | Count linked rows |
| formula | YES | 100+ functions — date math, conditionals, text, aggregations |
| single_select / multiple_select | YES | Status, stage, type dropdowns |
| All other types | YES | text, long_text, number, boolean, date, url, email, phone, file, rating, autonumber, created_on, last_modified, uuid, password, created_by, last_modified_by |
| AI prompt | PREMIUM | Not available — use external Python + AI API instead |
Infrastructure (All Free on Self-Hosted)
| Capability | Limit |
|---|---|
| Rows | Unlimited (server resources only) |
| Tables | Unlimited |
| Databases | Unlimited |
| Storage | Unlimited |
| API requests | Unlimited, no rate limit |
| Webhooks | Unlimited (row created/updated/deleted triggers) |
| Plugin support | Yes (requires custom Docker image build) |
Formula Functions Available (All Free)
The formula engine is extremely powerful — 100+ functions including:
- Text:
concat(),upper(),lower(),replace(),search(),contains(),length(),split_part() - Math:
round(),abs(),ceil(),floor(),mod(),power(),greatest(),least() - Date:
now(),today(),day(),month(),date_diff(),datetime_format(),date_interval() - Logic:
if(),and(),or(),not(),isblank(),when_empty() - Aggregation (across linked tables):
sum(),avg(),min(),max(),count(),join(),filter(),any(),every() - Link/URL:
link(),button(),get_link_url() - Special:
field(),row_id(),lookup(),index()
Key insight: lookup() + aggregation functions in formulas = Airtable-equivalent rollups, for free.
The 80/20: What Actually Makes Free Baserow Amazing for CRM
These 5 things deliver 80% of the value:
1. Proper Relational Schema (The Foundation — 50% of Value)
Link_row fields + lookups + rollups turn Baserow from a spreadsheet into a relational database. Click a person → see their org, all meetings, all deals, all tasks. This is the #1 transformation.
2. Smart Grouped Grid Views (The Dashboard Replacement — 15% of Value)
Grid views grouped by a single_select field mimic kanban boards. Grid views filtered by date ranges mimic calendar views. Multiple filtered/sorted views per table = your daily operating dashboard.
3. Formula Fields Doing Heavy Lifting (The Automation Replacement — 10% of Value)
Formulas compute data that would otherwise require manual updates or external automation:
- “Days since last interaction” =
date_diff('day', lookup('Interactions', 'Date'), today()) - “Deal count” = rollup counting linked deals
- “Full display name” =
concat(field('Name'), ' — ', lookup('Organization', 'Name'))
4. Forms for 60-Second Data Entry (The UX — 10% of Value)
4 forms bookmarked in browser toolbar = log an interaction in 60 seconds, add a contact in 30 seconds. This is what makes CRM discipline sustainable.
5. Fireflies → Baserow Sync (The Data Bridge — 15% of Value)
Meeting notes auto-flowing from Fireflies into the Interactions table (linked to People) closes the biggest data gap. Currently ~19 meeting/voice-note summaries exist only as markdown files (12 in meeting-transcripts/, 7 in voice-notes/).
Current State (What Exists in Baserow Today)
Tables (from Coda migration, now on self-hosted)
| Table | Rows | Key Fields | Issues |
|---|---|---|---|
| Tag | 51 | Name, Notes | Fine as-is |
| Location | 33 | Name, State, Notes | Fine as-is |
| Organization | 66 | Name, Tags→Tag, Location→Location, Website, LinkedIn, Summary, Notes | Missing: Industry, Size Band, Account Status |
| People | 179 | Name, Tags→Tag, Location→Location, Title, Organization→Org, Phone, Email, LinkedIn, social URLs, Notes, Connected From, Referral Source | Missing: Status, Lead Score, Last Contacted, Next Touch Date |
| Meeting Notes | 37 | Person→People, LinkedIn, Date, Follow-up Date, Notes | To be migrated into new Interactions table |
Pipeline Tables (flat, isolated — NOT linked to People/Org)
| Table | Rows | Notes |
|---|---|---|
| Foundation Prospects | ~200+ | 30 fields, tier scoring, financials |
| fCTO Partners | ~50+ | 24 fields, tier scoring |
| MSP Prospects | ~100+ | Geographic + RIA density scoring |
These stay as-is for now. Pipeline consolidation is deferred to the Apollo project.
Fireflies Sync
- Runs hourly via Windows Task Scheduler → Claude Code
- Saves markdown files to
solanasis-docs/meeting-notes/meeting-transcripts/ - 19 summaries + 20 transcripts synced
- Does NOT write to Baserow — this is the gap we’re closing
Execution Plan: 4 Phases
Phase 1: Schema Upgrade — New Tables + Enhanced Fields
What: Create new tables, add fields to existing tables, migrate Meeting Notes → Interactions Effort: ~2-3 hours in a single session Dependencies: None — do this first
1A. Create the Interactions Table
This is the most important new table. It replaces the existing Meeting Notes table and becomes the system of record for ALL touchpoints.
| Field Name | Type | Config | Notes |
|---|---|---|---|
| Title | text (primary) | Required | e.g., “Call with Jonathan Dekle — 2026-03-24” |
| Person | link_row → People | Required | Who was this interaction with |
| Organization | link_row → Organization | Optional | Auto-populated from Person’s org, or manual |
| Date | date | Required, date_format: ISO | When it happened |
| Type | single_select | Options below | What kind of interaction |
| Channel | single_select | Options below | How it happened |
| Summary | long_text | Required | Key takeaways (2-3 sentences) |
| Outcome | single_select | Options below | How did it go |
| Next Step | text | What needs to happen next | |
| Follow-up Date | date | When to follow up | |
| Fireflies ID | text | For dedup on auto-synced meetings | |
| Transcript Link | text | Relative path to markdown transcript | |
| Source | single_select | Options below | How this record was created |
Single select options:
| Field | Options |
|---|---|
| Type | Call, Meeting, Email, LinkedIn Message, Referral, Voice Note, Conference, Webinar, Other |
| Channel | Zoom, Phone, In-Person, Email, LinkedIn, WhatsApp, Google Meet, Teams, Other |
| Outcome | Positive, Neutral, Negative, No Response, Rescheduled |
| Source | Manual, Fireflies, Email Sync, Import, Backfill |
1B. Create the Deals Table
| Field Name | Type | Config | Notes |
|---|---|---|---|
| Deal Name | text (primary) | Required | e.g., “Acme Corp — Security Assessment” |
| Organization | link_row → Organization | Company | |
| Primary Contact | link_row → People | Main person | |
| Service Line | single_select | Options below | What we’d deliver |
| Stage | single_select | Options below | Where in the pipeline |
| Estimated Value | number | number_decimal_places: 0, number_negative: false | Dollar amount |
| Probability | number | number_decimal_places: 0 | 0-100% |
| Expected Close | date | Target close date | |
| Source | single_select | Options below | How we found them |
| Pipeline | single_select | Options below | Which vertical pipeline |
| Blockers | long_text | What’s preventing progress | |
| Next Action | text | Immediate next step | |
| Won/Lost Reason | long_text | Post-close learning | |
| Created Date | date | date_format: ISO | Auto-set on creation |
| Last Activity | date | Updated by formula or manually |
Single select options:
| Field | Options |
|---|---|
| Service Line | Security Assessment, DR Verification, Data Migration, CRM Setup, Systems Integration, AI Implementation, fCIO Retainer, fCSIO Retainer, fCOO Retainer, Other |
| Stage | Lead, Discovery, Proposal Sent, Negotiation, Won, Lost, On Hold |
| Source | Referral, Cold Outreach, Inbound, Network Event, Conference, LinkedIn, Apollo, Fireflies Intro, Other |
| Pipeline | Primary, Foundation, fCTO, MSP, VC |
1C. Create the Tasks Table
| Field Name | Type | Config | Notes |
|---|---|---|---|
| Task | text (primary) | Required | What needs to be done |
| Person | link_row → People | Optional | Related contact |
| Organization | link_row → Organization | Optional | Related company |
| Deal | link_row → Deals | Optional | Related deal |
| Due Date | date | When it’s due | |
| Status | single_select | To Do, In Progress, Waiting, Done | Current state |
| Priority | single_select | Urgent, High, Normal, Low | How important |
| Type | single_select | Follow-up, Proposal, Research, Admin, Outreach, Onboarding | What kind of task |
| Notes | long_text | Additional context |
1D. Add Fields to People Table
| New Field | Type | Config | Purpose |
|---|---|---|---|
| Status | single_select | New, Active, Warm, Cold, Client, Partner, Dormant, Do Not Contact | Contact lifecycle |
| Lead Score | number | 0-100, decimal: 0 | Manual score for now |
| Last Contacted | date | Date of most recent interaction | |
| Next Touch Date | date | When to reach out next | |
| Pipeline Source | single_select | Manual, Apollo, LinkedIn, Foundation, fCTO, MSP, VC, Fireflies, Calendly, Referral | Where this contact came from |
| Apollo ID | text | For future Apollo dedup | |
| Interaction Count | formula or count | Count linked Interactions | Auto-compute from Interactions link |
| Deal Count | formula or count | Count linked Deals | Auto-compute from Deals link |
Important: After creating the Interactions table with link_row → People, Baserow auto-creates a reverse link on People. Use that reverse link field for the count/rollup calculations.
1E. Add Fields to Organization Table
| New Field | Type | Config | Purpose |
|---|---|---|---|
| Industry | single_select | Finance/Wealth Mgmt, Healthcare, Nonprofit/Foundation, Technology, Legal, Education, Government, Manufacturing, Consulting, Other | Vertical classification |
| Size Band | single_select | 1-10, 11-50, 51-200, 201-1000, 1000+ | Company size |
| Account Status | single_select | Prospect, Active Client, Former Client, Partner, Vendor, Do Not Contact | Relationship status |
| City | text | For geographic targeting | |
| State | text | For geographic targeting |
1F. Migrate Meeting Notes → Interactions
The existing 37 Meeting Notes rows need to move into the new Interactions table. Then archive (not delete) the Meeting Notes table.
Migration logic:
- Read all 37 rows from Meeting Notes table
- For each row:
- Read the Person link_row value → get the People row ID
- Look up that People row to get the Name
- Create an Interactions row:
- Title = “Meeting with {Person Name} — {Date}” (or “Meeting — {Date}” if no person)
- Person = same People row ID (link_row)
- Date = Meeting Notes.Date
- Type = “Meeting”
- Summary = Meeting Notes.Notes (copy full text)
- Follow-up Date = Meeting Notes.Follow-up Date
- Source = “Backfill”
- If Meeting Notes.LinkedIn is set, search Organization table for matching LinkedIn URL → link Organization
- Verify: 37 Interactions rows created, all Person links intact
- Rename Meeting Notes table to “Meeting Notes (ARCHIVED — see Interactions)”
Script: solanasis-scripts/baserow/schema-upgrade-v2.py
Should use the existing Python patterns from foundation-pipeline/migrate_to_baserow.py:
- JWT auth for schema operations (create table, create field)
- DB token auth for row CRUD (read, create, batch)
httpxfor HTTP client.envfor credentials (BASEROW_BASE_URL, BASEROW_DB_TOKEN, BASEROW_EMAIL, BASEROW_PASSWORD, BASEROW_DATABASE_ID)--planflag for dry run,--runfor execution- MUST be idempotent: Check
list_tables()before creating tables (skip if exists). Checklist_fields(table_id)before adding fields (skip if exists). Log all skips. This allows safe re-runs after partial failures.
1G. Validation Checklist
- Interactions table created with all 13 fields
- Deals table created with all 15 fields
- Tasks table created with all 9 fields
- People table has 8 new fields (Status, Lead Score, Last Contacted, Next Touch Date, Pipeline Source, Apollo ID, Interaction Count, Deal Count)
- Organization table has 5 new fields (Industry, Size Band, Account Status, City, State)
- All single_select fields have their option lists populated
- All link_row fields resolve correctly (test: click Person in Interactions → correct People row opens)
- Reverse links auto-created on People (Interactions, Deals, Tasks) and Organization (Interactions, Deals)
- 37 Meeting Notes rows migrated to Interactions with Person links intact
- Meeting Notes table renamed to archived name
- No orphan or duplicate fields
Phase 2: Views & Forms — The Daily Operating Dashboard
What: Create filtered/grouped views for each table + 4 data entry forms
Effort: ~1-2 hours (can be same session as Phase 1)
Dependencies: Phase 1 complete
Execution method: Script via Baserow API — POST /api/database/views/table/{table_id}/ supports grid, gallery, and form view creation with filters, sorts, and grouping. Include in schema-upgrade-v2.py as Phase 2 functions. Must be idempotent (check existing views by name before creating).
2A. People Table Views
| View Name | Type | Configuration | Daily Use |
|---|---|---|---|
| All Contacts | Grid | Sort: Name A→Z | Master list, default view |
| Hot Leads | Grid | Filter: Status IN (Active, Warm). Sort: Lead Score DESC | Who to reach out to today |
| Needs Follow-Up | Grid | Filter: Next Touch Date ≤ today + 7 days, Status ≠ Do Not Contact. Sort: Next Touch Date ASC | Weekly follow-up review |
| Clients | Grid | Filter: Status = Client | Active client roster |
| Partners | Grid | Filter: Status = Partner | Referral network |
| Dormant | Grid | Filter: Last Contacted < (30 days ago) OR Last Contacted is empty, Status NOT IN (Client, Do Not Contact, Dormant). Sort: Last Contacted ASC | Re-engagement candidates |
| Recently Added | Grid | Sort: created_on DESC. Limit to page size 50 | What’s new in the CRM |
| Contact Cards | Gallery | Card title: Name. Cover image: (none). Fields: Title, Organization (lookup), Email, Phone, Status, Lead Score | Visual contact browser |
Pro tip: The “Hot Leads” view becomes your daily starting point. Open it each morning — it shows who to contact, sorted by score.
2B. Interactions Table Views
| View Name | Type | Configuration | Daily Use |
|---|---|---|---|
| Recent Activity | Grid | Sort: Date DESC | Quick review — what happened lately |
| This Week | Grid | Filter: Date ≥ (start of current week). Sort: Date DESC | Weekly activity summary |
| Follow-ups Due | Grid | Filter: Follow-up Date ≤ today + 3 days, Follow-up Date is not empty. Sort: Follow-up Date ASC | Action items — don’t miss follow-ups |
| By Person | Grid | Group by: Person. Sort: Date DESC within groups | All touchpoints per contact |
| Meetings | Grid | Filter: Type = Meeting. Sort: Date DESC | Meeting history |
| From Fireflies | Grid | Filter: Source = Fireflies. Sort: Date DESC | Auto-synced entries |
2C. Deals Table Views
| View Name | Type | Configuration | Daily Use |
|---|---|---|---|
| Pipeline by Stage | Grid | Group by: Stage. Sort: Expected Close ASC within groups | This is your kanban replacement — grouped grid shows deals organized by stage |
| All Deals | Grid | Sort: Expected Close ASC | Full deal list |
| Active Deals | Grid | Filter: Stage NOT IN (Won, Lost). Sort: Expected Close ASC | What’s in play |
| Closing This Month | Grid | Filter: Expected Close within current month, Stage NOT IN (Won, Lost) | Focus on closing |
| Stale Deals | Grid | Filter: Last Activity < (14 days ago), Stage NOT IN (Won, Lost, On Hold) | Deals that need attention |
| Won | Grid | Filter: Stage = Won. Sort: Created Date DESC | Win tracking |
| Lost | Grid | Filter: Stage = Lost. Sort: Created Date DESC | Learn from losses |
Pro tip: The “Pipeline by Stage” grouped view is the most important view in the whole CRM. When you group a grid by a single_select field, Baserow shows each group as a collapsible section with a count badge. It’s not drag-and-drop like kanban, but it gives you the same “deals organized by stage” view for free.
2D. Tasks Table Views
| View Name | Type | Configuration | Daily Use |
|---|---|---|---|
| Today | Grid | Filter: Due Date = today, Status ≠ Done. Sort: Priority (Urgent first) | Morning task list |
| This Week | Grid | Filter: Due Date ≤ end of week, Status ≠ Done. Sort: Due Date ASC | Weekly planning |
| Overdue | Grid | Filter: Due Date < today, Status ≠ Done. Sort: Due Date ASC | Red flag view — fix these first |
| By Status | Grid | Group by: Status. Sort: Priority within groups | Visual task board (grouped = kanban-like) |
| By Priority | Grid | Group by: Priority. Sort: Due Date ASC within groups | Triage view |
| All Tasks | Grid | Sort: created_on DESC | Master task list |
2E. Organization Table Views
| View Name | Type | Configuration | Daily Use |
|---|---|---|---|
| All Organizations | Grid | Sort: Name A→Z | Master list |
| Active Clients | Grid | Filter: Account Status = Active Client | Client companies |
| Prospects | Grid | Filter: Account Status = Prospect | Sales targets |
| By Industry | Grid | Group by: Industry | Vertical analysis |
2F. Forms (4 Bookmarkable Data Entry Points)
After creating forms, bookmark each URL in browser toolbar bar for 1-click access.
Form 1: Quick Contact Capture → People table
| Visible Field | Required? | Default Value | Notes |
|---|---|---|---|
| Name | YES | ||
| no | |||
| Phone Number | no | ||
| Title | no | ||
| Organization | no | Link_row dropdown of existing orgs | |
| no | |||
| Status | no | ”New” | Pre-filled |
| Pipeline Source | no | ”Manual” | Pre-filled |
| Notes | no |
Hidden fields (not on form): Lead Score, Last Contacted, Next Touch Date, all social URLs, Apollo ID, Tags, Location, Connected From, Referral Source
Form 2: Log an Interaction → Interactions table
| Visible Field | Required? | Default Value | Notes |
|---|---|---|---|
| Person | YES | Link_row dropdown | |
| Date | YES | today | Pre-filled with today |
| Type | YES | Must select one | |
| Channel | no | ||
| Summary | YES | 2-3 sentences | |
| Outcome | no | ||
| Next Step | no | ||
| Follow-up Date | no |
Hidden fields: Title (auto-generate later), Organization, Fireflies ID, Transcript Link, Source (default: “Manual”)
This is the single most important form. If Dmitri uses only one form, this is it. Log every meaningful conversation in 60 seconds.
Form 3: New Deal → Deals table
| Visible Field | Required? | Default Value | Notes |
|---|---|---|---|
| Deal Name | YES | e.g., “Acme — Security Assessment” | |
| Organization | no | Link_row dropdown | |
| Primary Contact | no | Link_row dropdown | |
| Service Line | no | ||
| Stage | no | ”Discovery” | Pre-filled |
| Estimated Value | no | ||
| Expected Close | no | ||
| Source | no | ||
| Pipeline | no | ”Primary” | Pre-filled |
| Next Action | no |
Form 4: Quick Task → Tasks table
| Visible Field | Required? | Default Value | Notes |
|---|---|---|---|
| Task | YES | What needs to be done | |
| Due Date | YES | When | |
| Priority | no | ”Normal” | Pre-filled |
| Person | no | Link_row | |
| Deal | no | Link_row | |
| Type | no | ||
| Notes | no |
2G. Validation Checklist
- 8 People views created and filters work correctly
- 6 Interactions views created (especially “Follow-ups Due” — critical)
- 7 Deals views created (especially “Pipeline by Stage” grouped view)
- 6 Tasks views created (especially “Overdue” and “Today”)
- 4 Organization views created
- Form 1 (Quick Contact) creates a People row with correct defaults
- Form 2 (Log Interaction) creates an Interactions row linked to correct Person
- Form 3 (New Deal) creates a Deals row with Stage defaulting to Discovery
- Form 4 (Quick Task) creates a Tasks row with Priority defaulting to Normal
- All 4 form URLs bookmarked and accessible
- Gallery view shows contact cards with name, title, org, email, status
Phase 3: Fireflies → Baserow Sync
What: Extend the existing Fireflies sync to also write Interactions rows in Baserow Effort: ~2-3 hours Dependencies: Phase 1 complete (Interactions table must exist)
3A. Current Fireflies Architecture
Windows Task Scheduler (every hour at :07)
→ solanasis-docs/scheduled-tasks/scripts/run-fireflies-sync.py
→ claude -p [sync prompt from 01-fireflies-sync-prompt.md]
→ Calls Fireflies MCP (get_transcripts, get_summary, get_transcript)
→ Saves markdown files to solanasis-docs/meeting-notes/meeting-transcripts/
→ Updates solanasis-docs/meeting-notes/fireflies-sync-status.md
The gap: Markdown files are saved but nothing touches Baserow.
3B. Target Architecture
Same pipeline as above, PLUS:
→ After saving markdown files:
→ Read participant names/emails from Fireflies data
→ Search Baserow People table for matching contact
→ Create Interactions row linked to People row
→ Update People.Last Contacted if this is more recent
3C. Implementation — Separate Python Script (Recommended)
After evaluating Options A (extend sync prompt) and B (separate script) from the broader plan, Option B is better for this 80/20 approach because:
- It’s a standalone Python script that can be tested independently
- It doesn’t add complexity to the Claude Code sync prompt
- It runs after the markdown files are saved, using them as input
- It follows the “Python first” scripting rule from CLAUDE.md
Script: solanasis-scripts/baserow/sync-fireflies-to-baserow.py
Logic:
1. Scan both `solanasis-docs/meeting-notes/meeting-transcripts/*-summary.md` AND `solanasis-docs/meeting-notes/voice-notes/*-summary.md`
2. For each summary file:
a. Parse YAML frontmatter → get fireflies_id, date, participants, type
b. Check Baserow Interactions table: does a row with this Fireflies ID exist?
→ If yes: skip (already synced)
c. For the primary participant (first non-Dmitri name):
- Search Baserow People table by name (exact match first)
- If no match, search by email if available
- If still no match: create a new People row (Name, Email, Status="New", Pipeline Source="Fireflies")
- Get the People row ID
d. Read the first 500 chars of the summary content as the interaction summary
e. Create Interactions row:
- Title: "{Type} with {Person Name} — {Date}"
- Person: link to People row ID
- Date: meeting date from frontmatter
- Type: "Meeting" or "Voice Note" (from frontmatter type field)
- Channel: "Zoom" (default, most meetings are Zoom)
- Summary: first 500 chars of AI summary
- Source: "Fireflies"
- Fireflies ID: the transcript ID
- Transcript Link: relative path to transcript file
f. Update People row: set Last Contacted = meeting date (only if more recent)
3. Log results: X new interactions created, Y skipped (already synced), Z new people created
Schedule: Run after each Fireflies sync. Either:
- Add as a second step in
run-fireflies-sync.py(call the script after Claude Code sync completes) - Or run as a separate scheduled task (every hour at :15, giving the Fireflies sync time to finish)
Flags:
--plan— dry run, show what would be created--run— execute--backfill— process ALL existing transcripts (not just recent ones)
3D. Backfill Existing Transcripts
After the sync script works for new meetings, run once with --backfill:
- Processes all ~19 existing summary files (12 meetings + 7 voice notes)
- Creates Interactions rows for each
- Links to People where possible (name matching)
- Gives instant meeting history in the CRM
This is a one-time operation. After this, the hourly sync handles new meetings.
3E. Name Matching Strategy
The trickiest part is linking Fireflies participants to People rows:
- Filter out Dmitri — exclude participants matching any of: “Dmitri Sunshine”, “Dmitri Zasage”, or first name “Dmitri” (case-insensitive). Fireflies uses “Dmitri Sunshine” but other contexts use “Dmitri Zasage”.
- Build a lookup cache — pull all People rows into a Python dict (179 rows = trivial to cache)
- Exact name match —
participants[0].name == people_row.Name(case-insensitive, strip whitespace) - Last name match — if exact fails, try matching just the last name (handles “Jonathan” vs “Jonathan Dekle”)
- Email match — if Fireflies provides participant email, match against People.Email
- No match — create new People row with Status = “New”, Pipeline Source = “Fireflies”
- Flag for review — log all auto-created People rows for Dmitri to verify
3F. Validation Checklist
- Script connects to Baserow API using credentials from .env
-
--planmode shows correct counts without creating anything -
--runmode creates Interactions rows with correct Person links - Duplicate Fireflies IDs are skipped (idempotent)
- New People rows created for unmatched participants
- People.Last Contacted updated correctly
-
--backfillprocesses all existing summaries (~19 files across meeting-transcripts/ and voice-notes/) - Script integrated into Fireflies sync schedule (runs after markdown sync)
Phase 4: Formula Fields + Polish
What: Add computed fields that make the CRM self-maintaining + quality-of-life improvements Effort: ~1 hour Dependencies: Phases 1-3 complete, some data in the system
4A. Formula Fields on People Table
| Formula Field | Formula Logic | Purpose |
|---|---|---|
| Days Since Contact | if(isblank(field('Last Contacted')), 999, date_diff('day', field('Last Contacted'), today())) | Instantly see who’s going cold |
| Contact Status Label | if(field('Days Since Contact') > 60, '🔴 Cold', if(field('Days Since Contact') > 30, '🟡 Cooling', if(field('Days Since Contact') > 14, '🟢 Warm', '✅ Active'))) | Visual indicator (text-based since no row coloring on free) |
| Organization Name (lookup) | Use a dedicated Lookup field type (not formula) — pull Name from linked Organization | Show org name directly on People grid without clicking |
| Full Context | concat(field('Name'), ' — ', if(isblank(field('Title')), '', concat(field('Title'), ' at ')), join(lookup('Organization', 'Name'), ', ')) | Quick context: “Jonathan Dekle — CEO at Acme Corp” |
Formula syntax notes (important for execution):
field('Name')is correct Baserow syntax (not{Name}like Airtable)isblank()has no underscore (NOTis_blank())lookup()returns an array even for single links — always wrap injoin()when using inconcat()date_diff('day', start, end)uses string literal units:'day','month','year','hour','minute','seconds'- Emoji characters (🔴 🟡 🟢 ✅ ⚠️) can be pasted directly into formula strings and work correctly
- Equality operator is
=not==— Baserow formula syntax uses single equals for comparison (discovered during Phase 4 execution when==caused 400 Bad Request) created_onis a Baserow auto-field not returned by the fields API — cannot be referenced in view sorts/filters via API; must be configured manually in the UI
4B. Formula Fields on Deals Table
| Formula Field | Formula Logic | Purpose |
|---|---|---|
| Weighted Value | field('Estimated Value') * field('Probability') / 100 | Expected revenue |
| Days in Stage | date_diff('day', field('Last Activity'), today()) | How long has this deal been stagnant |
| Stage Alert | if(field('Days in Stage') > 14, '⚠️ STALE', '') | Flag stale deals |
4C. Formula Fields on Tasks Table
| Formula Field | Formula Logic | Purpose |
|---|---|---|
| Days Until Due | date_diff('day', today(), field('Due Date')) | Negative = overdue |
| Urgency Label | if(field('Days Until Due') < 0, '🔴 OVERDUE', if(field('Days Until Due') == 0, '🟡 TODAY', if(field('Days Until Due') <= 3, '🟢 Soon', ''))) | Visual urgency |
4D. Webhooks (Lightweight Automation)
Set up these webhooks for future use (or immediate use with simple Python webhook receivers):
| Table | Event | What It Enables |
|---|---|---|
| Interactions | Row created | Auto-update People.Last Contacted (if not done by script) |
| Deals | Row updated | Track stage changes for reporting |
| Tasks | Row updated | Mark tasks done → could trigger next workflow |
Webhook setup: Table settings → Webhooks → Add webhook → set URL, events, and headers.
For now, webhooks can point to a simple Python Flask/FastAPI server running locally, or just be configured and left for later when n8n is added.
4E. Validation Checklist
- All formula fields compute correctly (test with real data)
- Days Since Contact shows accurate values
- Weighted Value calculates correctly on Deals
- Urgency labels display on Tasks
- At least 1 webhook configured and verified (test with webhook.site or similar)
Community Plugins Assessment (Free Only)
The Reality
Baserow’s community plugin ecosystem is small and experimental:
- No plugin marketplace — discovery is manual (GitHub search, community forum)
- Installation requires building a custom Docker image — not a 1-click process
- Plugin API is experimental and subject to breaking changes
- Baserow’s own team does not officially support community plugins
- No widely-adopted CRM-specific plugins exist
Plugins Evaluated
| Plugin | Source | What It Does | Verdict | Why |
|---|---|---|---|---|
| BaseCharts | GitHub (soon/basecharts) | Chart widgets in views | SKIP | Low maintenance activity. Not worth the Docker image rebuild risk for charts we can generate externally. |
| BaseScript | GitHub (Raudius/basescript) | In-browser JavaScript execution | SKIP | Proof-of-concept quality. External Python scripts are more powerful and maintainable. |
| Baserow geo | Community | Map/geography views | SKIP | Premature. Territory mapping isn’t a current need. |
| Custom formula functions | Plugin API | Add custom formula functions | MAYBE LATER | Could add AI-powered formula functions (call OpenAI for lead scoring, etc.) but premature now. |
Recommendation: Zero Plugins for Now
The 80/20 for free Baserow is:
- Great relational design (link_row, lookup, rollup, formula)
- Smart views (grouped grids, filtered views, gallery, forms)
- External Python scripts for automation (Fireflies sync, data import, AI enrichment)
- Webhooks for event-driven triggers
- REST API for programmatic access
None of these require plugins. The built-in features of free Baserow, used well, deliver a better CRM experience than a plugin-laden setup.
When to reconsider plugins:
- When Baserow’s plugin ecosystem matures (target: late 2026)
- When a specific workflow cannot be solved by external scripts
- When you find a plugin with 100+ GitHub stars and recent commits
Files That Will Be Created/Modified
New Files
| File | Purpose |
|---|---|
solanasis-scripts/baserow/schema-upgrade-v2.py | Create new tables, add fields, migrate Meeting Notes → Interactions |
solanasis-scripts/baserow/sync-fireflies-to-baserow.py | Sync Fireflies markdown transcripts into Baserow Interactions |
Modified Files
| File | Change |
|---|---|
solanasis-docs/scheduled-tasks/scripts/run-fireflies-sync.py | Add call to sync-fireflies-to-baserow.py after Claude Code sync |
solanasis-docs/meeting-notes/fireflies-sync-status.md | Add Baserow sync counts |
Reference Files (Read, Don’t Modify)
| File | Why Read It |
|---|---|
solanasis-scripts/.env | Baserow credentials |
solanasis-scripts/foundation-pipeline/migrate_to_baserow.py | Pattern for Baserow API Python client |
solanasis-scripts/baserow/src/config/tables.ts | Current schema definition (reference) |
solanasis-scripts/baserow/src/lib/baserow-client.ts | TypeScript client (reference for API patterns) |
solanasis-docs/scheduled-tasks/01-fireflies-sync-prompt.md | Fireflies sync configuration |
Continuation Prompt for Fresh Session
Copy this into a new Claude Code session to start execution:
We're upgrading Solanasis's self-hosted Baserow CRM. Read these files for full context:
1. `solanasis-docs/reference/deep-plan-baserow-80-20-execution-2026-03-24.md` — THE PLAN (follow this exactly, including the Validation Results section at the top)
2. `solanasis-scripts/.env` — Baserow credentials
3. `solanasis-scripts/foundation-pipeline/migrate_to_baserow.py` — Reference for Baserow API patterns in Python
4. `solanasis-scripts/baserow/src/config/tables.ts` — Current schema
CONTEXT:
- Self-hosted Baserow at baserow.solanasis.com, Database ID: 54
- Free tier only (no Premium)
- Current tables: Tag, Location, Organization, People (179 rows), Meeting Notes (37 rows)
- Plus pipeline tables: Foundation Prospects, fCTO Partners, MSP Prospects
- Plan has been VALIDATED — see Validation Results section for 6 issues that were found and corrected
CRITICAL REQUIREMENTS FROM VALIDATION:
- Script MUST be idempotent (check before create, skip if exists)
- Fireflies name filter must exclude both "Dmitri Sunshine" and "Dmitri Zasage"
- Fireflies backfill scans BOTH meeting-transcripts/ AND voice-notes/ folders (~19 files, not 70)
- Use `isblank()` (no underscore) in formulas, NOT `is_blank()`
- Phase 2 views/forms should be created via API script, not manually
START WITH PHASE 1 from the plan:
1. Create new tables: Interactions, Deals, Tasks
2. Add new fields to People and Organization tables
3. Migrate Meeting Notes → Interactions (37 rows)
4. Validate everything works
Use Python for all scripts. Follow the existing pattern from migrate_to_baserow.py.
Create `solanasis-scripts/baserow/schema-upgrade-v2.py` with --plan and --run flags.
Deferred Items (Documented for Future)
| Item | Status | When to Revisit |
|---|---|---|
| n8n automation | DEFERRED | When CRM has been in daily use for 2+ weeks and friction points are clear |
| Apollo.io import | DEFERRED | Separate project — build import-apollo.py when Apollo exports are ready |
| Guild CRM sharing | VISION | Long-term concept. Do not design schema for multi-tenancy now. |
| Baserow Premium | DEFERRED | Reconsider when 2+ people use the CRM or kanban becomes essential |
| Community plugins | DEFERRED | Reconsider when plugin ecosystem matures (late 2026) |
| Pipeline consolidation | DEFERRED | Merge Foundation/fCTO/MSP into unified schema as part of Apollo project |
| External dashboarding (Metabase) | DEFERRED | Only if formula-based indicators + filtered views aren’t sufficient |
| Custom ERP/CRM (Frappe) | LONG-TERM | Per solanasis-custom-erp-crm-build-plan.md — Baserow is the bridge |
Execution Log
Scripts Created (2026-03-25)
| Script | Purpose |
|---|---|
solanasis-scripts/baserow/baserow_client.py | Shared Baserow API client (JWT + DB token auth) |
solanasis-scripts/baserow/schema-upgrade-v2.py | Phases 1, 2, 4: schema, views, formulas |
solanasis-scripts/baserow/sync-fireflies-to-baserow.py | Phase 3: Fireflies → Baserow sync |
Phase Execution Status
| Phase | Status | Started | Completed | Notes |
|---|---|---|---|---|
| Phase 1: Schema Upgrade | COMPLETE | 2026-03-25 | 2026-03-25 | 3 tables created, 11 fields added to existing tables, 38 rows migrated. All 1G checklist items pass. |
| Phase 2: Views & Forms | COMPLETE | 2026-03-25 | 2026-03-25 | 31 views + 4 forms created. 2 minor: created_on sort skipped (Baserow auto-field not in fields API). All 2G checklist items pass. Form URLs: Quick Contact=e-Fs19p46snkSv-WNE5kzoAIgTKYtkgMx6ubXTMUa9o, Log Interaction=Gs8DnVdhmGXFQk5XaKrx3ekG9GOtf0BJuUPc7uO7KHs, New Deal=-el6oaL46fEnrX-wf5lcwNu-jrXvHeCXEC7Sxqr_SF4, Quick Task=emGyzqEPBT5N1Noxx9bYZyg2468D8BDO3H8wT3j2Pxs |
| Phase 3: Fireflies Sync | COMPLETE | 2026-03-25 | 2026-03-25 | 21 interactions created from 23 files (2 skipped=duplicate IDs), 11 new People auto-created, 0 errors. Idempotency verified. 15/21 have Person links (6 are solo voice notes). |
| Phase 4: Formula Fields | COMPLETE | 2026-03-25 | 2026-03-25 | 8 formula fields created: People (Days Since Contact, Contact Status Label, Full Context), Deals (Weighted Value, Days in Stage, Stage Alert), Tasks (Days Until Due, Urgency Label). Fixed: Days Since Contact converted from number→formula type, Urgency Label fixed == to = operator. All formulas verified computing correctly with real data. |
Senior Review (2026-03-25)
Verdict: APPROVED WITH NOTES
All 4 phases passed code quality, plan compliance, idempotency, and E2E validation.
Known Issues
| # | Issue | Severity | Resolution |
|---|---|---|---|
| 1 | Last Contacted not backfilled from Meeting Notes migration — 38 historical meetings are in Interactions but People records don’t reflect those dates, so those contacts show as “Cold” | Medium | One-time fix script needed: read Interactions where Source=Backfill, find most recent date per Person, update Last Contacted |
| 2 | created_on sort unavailable via fields API — “Recently Added” and “All Tasks” views missing their sort | Low | Manual one-time fix in Baserow UI: open each view, add sort by created_on descending |
| 3 | Transcript Link paths use Windows backslashes | Low | Change str(transcript.relative_to(DOCS_DIR)) to transcript.relative_to(DOCS_DIR).as_posix() in sync-fireflies-to-baserow.py line 251 |
| 4 | ensure_field() calls list_fields() per field instead of caching per table | Low | Minor efficiency — only matters if scripts are re-run frequently. Not worth fixing for one-time schema scripts. |
Manual Follow-Up Items
- Bookmark 4 form URLs in browser toolbar (per plan section 2F)
- Add
created_onsort to “Recently Added” and “All Tasks” views in UI - Run backfill script for
Last Contactedon historical Meeting Notes contacts (when written)
Table IDs (Live Reference)
| Table | ID | Row Count (at execution) |
|---|---|---|
| People | 267 | 2,666 |
| Organization | 266 | 66 |
| Interactions | 769 | 59 (38 backfill + 21 Fireflies) |
| Deals | 770 | 0 |
| Tasks | 771 | 0 |
| Tag | 264 | 51 |
| Location | 265 | — |
| Meeting Notes (ARCHIVED) | 272 | 37 |
Form URLs (Live Reference)
| Form | Full URL |
|---|---|
| Quick Contact | https://baserow.solanasis.com/form/e-Fs19p46snkSv-WNE5kzoAIgTKYtkgMx6ubXTMUa9o |
| Log Interaction | https://baserow.solanasis.com/form/Gs8DnVdhmGXFQk5XaKrx3ekG9GOtf0BJuUPc7uO7KHs |
| New Deal | https://baserow.solanasis.com/form/-el6oaL46fEnrX-wf5lcwNu-jrXvHeCXEC7Sxqr_SF4 |
| Quick Task | https://baserow.solanasis.com/form/emGyzqEPBT5N1Noxx9bYZyg2468D8BDO3H8wT3j2Pxs |
Formula Field Reference
| Table | Field | Formula | Computes |
|---|---|---|---|
| People | Days Since Contact | if(isblank(field('Last Contacted')), 999, date_diff('day', field('Last Contacted'), today())) | Days since last contact; 999 if never |
| People | Contact Status Label | if(field('Days Since Contact') > 60, '🔴 Cold', if(field('Days Since Contact') > 30, '🟡 Cooling', if(field('Days Since Contact') > 14, '🟢 Warm', '✅ Active'))) | Emoji status tier |
| People | Full Context | concat(field('Name'), ' — ', if(isblank(field('Title')), '', concat(field('Title'), ' at ')), join(lookup('Organization', 'Name'), ', ')) | Name + title + org summary |
| Deals | Weighted Value | field('Estimated Value') * field('Probability') / 100 | Expected revenue |
| Deals | Days in Stage | date_diff('day', field('Last Activity'), today()) | Staleness indicator |
| Deals | Stage Alert | if(field('Days in Stage') > 14, '⚠️ STALE', '') | Warning for stale deals |
| Tasks | Days Until Due | date_diff('day', today(), field('Due Date')) | Countdown to deadline |
| Tasks | Urgency Label | if(field('Days Until Due') < 0, '🔴 OVERDUE', if(field('Days Until Due') = 0, '🟡 TODAY', if(field('Days Until Due') <= 3, '🟢 Soon', ''))) | Emoji urgency tier |