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

#AssumptionEvidenceStatus
1Self-hosted Baserow at baserow.solanasis.com, DB ID: 54Confirmed in migrate_to_baserow.py load_env() and BASE_URL
2JWT auth for schema ops, DB token for row CRUDConfirmed in BaserowClient: _jwt_headers() for create_table/field, _token_headers() for batch_create_rows
3httpx for HTTP clientimport httpx + httpx.Client() confirmed in existing script
4.env has BASEROW_BASE_URL, DB_TOKEN, EMAIL, PASSWORD, DATABASE_IDConfirmed from code references (not reading actual credentials)
5Fireflies YAML frontmatter has fireflies_id, date, participants, typeConfirmed from 2026-03-24 - Jonathan Dekle-summary.md
6Existing —plan/—run flag patternConfirmed in migrate_to_baserow.py argparse
7run-fireflies-sync.py architectureConfirmed: calls claude -p, logs to solanasis-docs/logs/, straightforward to extend
8Table row counts (Tag:51, Org:66, People:179, Meeting Notes:37)From plan writing time — not re-verified via API. Non-blocking.⚠️ Unverified
9Formula syntax (isblank, field, date_diff)Plan’s syntax notes section (lines 563-568) is correct⚠️ See Issue #4
10link_row field creation via APIGeneric 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 auth

Critical execution order (already documented in TS migration, must replicate in Python):

  1. Create ALL tables first (need table IDs for link_row references)
  2. Create non-link fields on each table
  3. 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 ApproachRight ApproachWhy
Create tables/fields manually in UIScript everything (idempotent)Repeatable, survives mistakes, documents schema
Hardcode table IDs in scriptsDiscover via list_tables() dynamicallyIDs change between environments
Create link_row fields alongside other fieldsCreate ALL tables first, then link_row fieldslink_row needs target table to already exist
Scan only meeting-transcripts/ for FirefliesScan 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 expectationsPlan for ~19 summary filesActual 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)

DecisionAnswer
Self-hosted migrationCOMPLETE — working with self-hosted instance
Premium licenseNO — free tier only
n8n automationDEFERRED — separate project later
Apollo.io importDEFERRED — separate project later
Guild CRM sharingVISION ONLY — deferred indefinitely, do not design for it now
Community pluginsOnly 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 TypeFree?CRM Use
GridYESPrimary working view. Group by field = poor man’s kanban. Filter + sort = dashboards.
FormYESData entry for contacts, interactions, deals, tasks
GalleryYESContact cards with photos/avatars
KanbanPREMIUMNot available — use grouped grids instead
CalendarPREMIUMNot available — use date-sorted grids instead
DashboardPREMIUMNot available — use filtered grids + formula fields instead
TimelinePREMIUMNot available

Fields (The Real Power — ALL Free)

Field TypeFree?CRM Use
link_rowYESTHE key feature — relations between tables
lookupYESPull data from linked records (read-only)
rollupYESAggregate linked data (sum, avg, count, etc.)
countYESCount linked rows
formulaYES100+ functions — date math, conditionals, text, aggregations
single_select / multiple_selectYESStatus, stage, type dropdowns
All other typesYEStext, long_text, number, boolean, date, url, email, phone, file, rating, autonumber, created_on, last_modified, uuid, password, created_by, last_modified_by
AI promptPREMIUMNot available — use external Python + AI API instead

Infrastructure (All Free on Self-Hosted)

CapabilityLimit
RowsUnlimited (server resources only)
TablesUnlimited
DatabasesUnlimited
StorageUnlimited
API requestsUnlimited, no rate limit
WebhooksUnlimited (row created/updated/deleted triggers)
Plugin supportYes (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)

TableRowsKey FieldsIssues
Tag51Name, NotesFine as-is
Location33Name, State, NotesFine as-is
Organization66Name, Tags→Tag, Location→Location, Website, LinkedIn, Summary, NotesMissing: Industry, Size Band, Account Status
People179Name, Tags→Tag, Location→Location, Title, Organization→Org, Phone, Email, LinkedIn, social URLs, Notes, Connected From, Referral SourceMissing: Status, Lead Score, Last Contacted, Next Touch Date
Meeting Notes37Person→People, LinkedIn, Date, Follow-up Date, NotesTo be migrated into new Interactions table

Pipeline Tables (flat, isolated — NOT linked to People/Org)

TableRowsNotes
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 NameTypeConfigNotes
Titletext (primary)Requirede.g., “Call with Jonathan Dekle — 2026-03-24”
Personlink_row → PeopleRequiredWho was this interaction with
Organizationlink_row → OrganizationOptionalAuto-populated from Person’s org, or manual
DatedateRequired, date_format: ISOWhen it happened
Typesingle_selectOptions belowWhat kind of interaction
Channelsingle_selectOptions belowHow it happened
Summarylong_textRequiredKey takeaways (2-3 sentences)
Outcomesingle_selectOptions belowHow did it go
Next SteptextWhat needs to happen next
Follow-up DatedateWhen to follow up
Fireflies IDtextFor dedup on auto-synced meetings
Transcript LinktextRelative path to markdown transcript
Sourcesingle_selectOptions belowHow this record was created

Single select options:

FieldOptions
TypeCall, Meeting, Email, LinkedIn Message, Referral, Voice Note, Conference, Webinar, Other
ChannelZoom, Phone, In-Person, Email, LinkedIn, WhatsApp, Google Meet, Teams, Other
OutcomePositive, Neutral, Negative, No Response, Rescheduled
SourceManual, Fireflies, Email Sync, Import, Backfill

1B. Create the Deals Table

Field NameTypeConfigNotes
Deal Nametext (primary)Requirede.g., “Acme Corp — Security Assessment”
Organizationlink_row → OrganizationCompany
Primary Contactlink_row → PeopleMain person
Service Linesingle_selectOptions belowWhat we’d deliver
Stagesingle_selectOptions belowWhere in the pipeline
Estimated Valuenumbernumber_decimal_places: 0, number_negative: falseDollar amount
Probabilitynumbernumber_decimal_places: 00-100%
Expected ClosedateTarget close date
Sourcesingle_selectOptions belowHow we found them
Pipelinesingle_selectOptions belowWhich vertical pipeline
Blockerslong_textWhat’s preventing progress
Next ActiontextImmediate next step
Won/Lost Reasonlong_textPost-close learning
Created Datedatedate_format: ISOAuto-set on creation
Last ActivitydateUpdated by formula or manually

Single select options:

FieldOptions
Service LineSecurity Assessment, DR Verification, Data Migration, CRM Setup, Systems Integration, AI Implementation, fCIO Retainer, fCSIO Retainer, fCOO Retainer, Other
StageLead, Discovery, Proposal Sent, Negotiation, Won, Lost, On Hold
SourceReferral, Cold Outreach, Inbound, Network Event, Conference, LinkedIn, Apollo, Fireflies Intro, Other
PipelinePrimary, Foundation, fCTO, MSP, VC

1C. Create the Tasks Table

Field NameTypeConfigNotes
Tasktext (primary)RequiredWhat needs to be done
Personlink_row → PeopleOptionalRelated contact
Organizationlink_row → OrganizationOptionalRelated company
Deallink_row → DealsOptionalRelated deal
Due DatedateWhen it’s due
Statussingle_selectTo Do, In Progress, Waiting, DoneCurrent state
Prioritysingle_selectUrgent, High, Normal, LowHow important
Typesingle_selectFollow-up, Proposal, Research, Admin, Outreach, OnboardingWhat kind of task
Noteslong_textAdditional context

1D. Add Fields to People Table

New FieldTypeConfigPurpose
Statussingle_selectNew, Active, Warm, Cold, Client, Partner, Dormant, Do Not ContactContact lifecycle
Lead Scorenumber0-100, decimal: 0Manual score for now
Last ContacteddateDate of most recent interaction
Next Touch DatedateWhen to reach out next
Pipeline Sourcesingle_selectManual, Apollo, LinkedIn, Foundation, fCTO, MSP, VC, Fireflies, Calendly, ReferralWhere this contact came from
Apollo IDtextFor future Apollo dedup
Interaction Countformula or countCount linked InteractionsAuto-compute from Interactions link
Deal Countformula or countCount linked DealsAuto-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 FieldTypeConfigPurpose
Industrysingle_selectFinance/Wealth Mgmt, Healthcare, Nonprofit/Foundation, Technology, Legal, Education, Government, Manufacturing, Consulting, OtherVertical classification
Size Bandsingle_select1-10, 11-50, 51-200, 201-1000, 1000+Company size
Account Statussingle_selectProspect, Active Client, Former Client, Partner, Vendor, Do Not ContactRelationship status
CitytextFor geographic targeting
StatetextFor 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:

  1. Read all 37 rows from Meeting Notes table
  2. 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
  3. Verify: 37 Interactions rows created, all Person links intact
  4. 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)
  • httpx for HTTP client
  • .env for credentials (BASEROW_BASE_URL, BASEROW_DB_TOKEN, BASEROW_EMAIL, BASEROW_PASSWORD, BASEROW_DATABASE_ID)
  • --plan flag for dry run, --run for execution
  • MUST be idempotent: Check list_tables() before creating tables (skip if exists). Check list_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 NameTypeConfigurationDaily Use
All ContactsGridSort: Name A→ZMaster list, default view
Hot LeadsGridFilter: Status IN (Active, Warm). Sort: Lead Score DESCWho to reach out to today
Needs Follow-UpGridFilter: Next Touch Date ≤ today + 7 days, Status ≠ Do Not Contact. Sort: Next Touch Date ASCWeekly follow-up review
ClientsGridFilter: Status = ClientActive client roster
PartnersGridFilter: Status = PartnerReferral network
DormantGridFilter: Last Contacted < (30 days ago) OR Last Contacted is empty, Status NOT IN (Client, Do Not Contact, Dormant). Sort: Last Contacted ASCRe-engagement candidates
Recently AddedGridSort: created_on DESC. Limit to page size 50What’s new in the CRM
Contact CardsGalleryCard title: Name. Cover image: (none). Fields: Title, Organization (lookup), Email, Phone, Status, Lead ScoreVisual 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 NameTypeConfigurationDaily Use
Recent ActivityGridSort: Date DESCQuick review — what happened lately
This WeekGridFilter: Date ≥ (start of current week). Sort: Date DESCWeekly activity summary
Follow-ups DueGridFilter: Follow-up Date ≤ today + 3 days, Follow-up Date is not empty. Sort: Follow-up Date ASCAction items — don’t miss follow-ups
By PersonGridGroup by: Person. Sort: Date DESC within groupsAll touchpoints per contact
MeetingsGridFilter: Type = Meeting. Sort: Date DESCMeeting history
From FirefliesGridFilter: Source = Fireflies. Sort: Date DESCAuto-synced entries

2C. Deals Table Views

View NameTypeConfigurationDaily Use
Pipeline by StageGridGroup by: Stage. Sort: Expected Close ASC within groupsThis is your kanban replacement — grouped grid shows deals organized by stage
All DealsGridSort: Expected Close ASCFull deal list
Active DealsGridFilter: Stage NOT IN (Won, Lost). Sort: Expected Close ASCWhat’s in play
Closing This MonthGridFilter: Expected Close within current month, Stage NOT IN (Won, Lost)Focus on closing
Stale DealsGridFilter: Last Activity < (14 days ago), Stage NOT IN (Won, Lost, On Hold)Deals that need attention
WonGridFilter: Stage = Won. Sort: Created Date DESCWin tracking
LostGridFilter: Stage = Lost. Sort: Created Date DESCLearn 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 NameTypeConfigurationDaily Use
TodayGridFilter: Due Date = today, Status ≠ Done. Sort: Priority (Urgent first)Morning task list
This WeekGridFilter: Due Date ≤ end of week, Status ≠ Done. Sort: Due Date ASCWeekly planning
OverdueGridFilter: Due Date < today, Status ≠ Done. Sort: Due Date ASCRed flag view — fix these first
By StatusGridGroup by: Status. Sort: Priority within groupsVisual task board (grouped = kanban-like)
By PriorityGridGroup by: Priority. Sort: Due Date ASC within groupsTriage view
All TasksGridSort: created_on DESCMaster task list

2E. Organization Table Views

View NameTypeConfigurationDaily Use
All OrganizationsGridSort: Name A→ZMaster list
Active ClientsGridFilter: Account Status = Active ClientClient companies
ProspectsGridFilter: Account Status = ProspectSales targets
By IndustryGridGroup by: IndustryVertical 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 FieldRequired?Default ValueNotes
NameYES
Emailno
Phone Numberno
Titleno
OrganizationnoLink_row dropdown of existing orgs
LinkedInno
Statusno”New”Pre-filled
Pipeline Sourceno”Manual”Pre-filled
Notesno

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 FieldRequired?Default ValueNotes
PersonYESLink_row dropdown
DateYEStodayPre-filled with today
TypeYESMust select one
Channelno
SummaryYES2-3 sentences
Outcomeno
Next Stepno
Follow-up Dateno

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 FieldRequired?Default ValueNotes
Deal NameYESe.g., “Acme — Security Assessment”
OrganizationnoLink_row dropdown
Primary ContactnoLink_row dropdown
Service Lineno
Stageno”Discovery”Pre-filled
Estimated Valueno
Expected Closeno
Sourceno
Pipelineno”Primary”Pre-filled
Next Actionno
Form 4: Quick Task → Tasks table
Visible FieldRequired?Default ValueNotes
TaskYESWhat needs to be done
Due DateYESWhen
Priorityno”Normal”Pre-filled
PersonnoLink_row
DealnoLink_row
Typeno
Notesno

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

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:

  1. Processes all ~19 existing summary files (12 meetings + 7 voice notes)
  2. Creates Interactions rows for each
  3. Links to People where possible (name matching)
  4. 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:

  1. 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”.
  2. Build a lookup cache — pull all People rows into a Python dict (179 rows = trivial to cache)
  3. Exact name matchparticipants[0].name == people_row.Name (case-insensitive, strip whitespace)
  4. Last name match — if exact fails, try matching just the last name (handles “Jonathan” vs “Jonathan Dekle”)
  5. Email match — if Fireflies provides participant email, match against People.Email
  6. No match — create new People row with Status = “New”, Pipeline Source = “Fireflies”
  7. 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
  • --plan mode shows correct counts without creating anything
  • --run mode 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
  • --backfill processes 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 FieldFormula LogicPurpose
Days Since Contactif(isblank(field('Last Contacted')), 999, date_diff('day', field('Last Contacted'), today()))Instantly see who’s going cold
Contact Status Labelif(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 OrganizationShow org name directly on People grid without clicking
Full Contextconcat(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 (NOT is_blank())
  • lookup() returns an array even for single links — always wrap in join() when using in concat()
  • 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_on is 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 FieldFormula LogicPurpose
Weighted Valuefield('Estimated Value') * field('Probability') / 100Expected revenue
Days in Stagedate_diff('day', field('Last Activity'), today())How long has this deal been stagnant
Stage Alertif(field('Days in Stage') > 14, '⚠️ STALE', '')Flag stale deals

4C. Formula Fields on Tasks Table

Formula FieldFormula LogicPurpose
Days Until Duedate_diff('day', today(), field('Due Date'))Negative = overdue
Urgency Labelif(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):

TableEventWhat It Enables
InteractionsRow createdAuto-update People.Last Contacted (if not done by script)
DealsRow updatedTrack stage changes for reporting
TasksRow updatedMark 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

PluginSourceWhat It DoesVerdictWhy
BaseChartsGitHub (soon/basecharts)Chart widgets in viewsSKIPLow maintenance activity. Not worth the Docker image rebuild risk for charts we can generate externally.
BaseScriptGitHub (Raudius/basescript)In-browser JavaScript executionSKIPProof-of-concept quality. External Python scripts are more powerful and maintainable.
Baserow geoCommunityMap/geography viewsSKIPPremature. Territory mapping isn’t a current need.
Custom formula functionsPlugin APIAdd custom formula functionsMAYBE LATERCould 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:

  1. Great relational design (link_row, lookup, rollup, formula)
  2. Smart views (grouped grids, filtered views, gallery, forms)
  3. External Python scripts for automation (Fireflies sync, data import, AI enrichment)
  4. Webhooks for event-driven triggers
  5. 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

FilePurpose
solanasis-scripts/baserow/schema-upgrade-v2.pyCreate new tables, add fields, migrate Meeting Notes → Interactions
solanasis-scripts/baserow/sync-fireflies-to-baserow.pySync Fireflies markdown transcripts into Baserow Interactions

Modified Files

FileChange
solanasis-docs/scheduled-tasks/scripts/run-fireflies-sync.pyAdd call to sync-fireflies-to-baserow.py after Claude Code sync
solanasis-docs/meeting-notes/fireflies-sync-status.mdAdd Baserow sync counts

Reference Files (Read, Don’t Modify)

FileWhy Read It
solanasis-scripts/.envBaserow credentials
solanasis-scripts/foundation-pipeline/migrate_to_baserow.pyPattern for Baserow API Python client
solanasis-scripts/baserow/src/config/tables.tsCurrent schema definition (reference)
solanasis-scripts/baserow/src/lib/baserow-client.tsTypeScript client (reference for API patterns)
solanasis-docs/scheduled-tasks/01-fireflies-sync-prompt.mdFireflies 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)

ItemStatusWhen to Revisit
n8n automationDEFERREDWhen CRM has been in daily use for 2+ weeks and friction points are clear
Apollo.io importDEFERREDSeparate project — build import-apollo.py when Apollo exports are ready
Guild CRM sharingVISIONLong-term concept. Do not design schema for multi-tenancy now.
Baserow PremiumDEFERREDReconsider when 2+ people use the CRM or kanban becomes essential
Community pluginsDEFERREDReconsider when plugin ecosystem matures (late 2026)
Pipeline consolidationDEFERREDMerge Foundation/fCTO/MSP into unified schema as part of Apollo project
External dashboarding (Metabase)DEFERREDOnly if formula-based indicators + filtered views aren’t sufficient
Custom ERP/CRM (Frappe)LONG-TERMPer solanasis-custom-erp-crm-build-plan.md — Baserow is the bridge

Execution Log

Scripts Created (2026-03-25)

ScriptPurpose
solanasis-scripts/baserow/baserow_client.pyShared Baserow API client (JWT + DB token auth)
solanasis-scripts/baserow/schema-upgrade-v2.pyPhases 1, 2, 4: schema, views, formulas
solanasis-scripts/baserow/sync-fireflies-to-baserow.pyPhase 3: Fireflies → Baserow sync

Phase Execution Status

PhaseStatusStartedCompletedNotes
Phase 1: Schema UpgradeCOMPLETE2026-03-252026-03-253 tables created, 11 fields added to existing tables, 38 rows migrated. All 1G checklist items pass.
Phase 2: Views & FormsCOMPLETE2026-03-252026-03-2531 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 SyncCOMPLETE2026-03-252026-03-2521 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 FieldsCOMPLETE2026-03-252026-03-258 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

#IssueSeverityResolution
1Last 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”MediumOne-time fix script needed: read Interactions where Source=Backfill, find most recent date per Person, update Last Contacted
2created_on sort unavailable via fields API — “Recently Added” and “All Tasks” views missing their sortLowManual one-time fix in Baserow UI: open each view, add sort by created_on descending
3Transcript Link paths use Windows backslashesLowChange str(transcript.relative_to(DOCS_DIR)) to transcript.relative_to(DOCS_DIR).as_posix() in sync-fireflies-to-baserow.py line 251
4ensure_field() calls list_fields() per field instead of caching per tableLowMinor 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_on sort to “Recently Added” and “All Tasks” views in UI
  • Run backfill script for Last Contacted on historical Meeting Notes contacts (when written)

Table IDs (Live Reference)

TableIDRow Count (at execution)
People2672,666
Organization26666
Interactions76959 (38 backfill + 21 Fireflies)
Deals7700
Tasks7710
Tag26451
Location265
Meeting Notes (ARCHIVED)27237

Form URLs (Live Reference)

FormFull URL
Quick Contacthttps://baserow.solanasis.com/form/e-Fs19p46snkSv-WNE5kzoAIgTKYtkgMx6ubXTMUa9o
Log Interactionhttps://baserow.solanasis.com/form/Gs8DnVdhmGXFQk5XaKrx3ekG9GOtf0BJuUPc7uO7KHs
New Dealhttps://baserow.solanasis.com/form/-el6oaL46fEnrX-wf5lcwNu-jrXvHeCXEC7Sxqr_SF4
Quick Taskhttps://baserow.solanasis.com/form/emGyzqEPBT5N1Noxx9bYZyg2468D8BDO3H8wT3j2Pxs

Formula Field Reference

TableFieldFormulaComputes
PeopleDays Since Contactif(isblank(field('Last Contacted')), 999, date_diff('day', field('Last Contacted'), today()))Days since last contact; 999 if never
PeopleContact Status Labelif(field('Days Since Contact') > 60, '🔴 Cold', if(field('Days Since Contact') > 30, '🟡 Cooling', if(field('Days Since Contact') > 14, '🟢 Warm', '✅ Active')))Emoji status tier
PeopleFull Contextconcat(field('Name'), ' — ', if(isblank(field('Title')), '', concat(field('Title'), ' at ')), join(lookup('Organization', 'Name'), ', '))Name + title + org summary
DealsWeighted Valuefield('Estimated Value') * field('Probability') / 100Expected revenue
DealsDays in Stagedate_diff('day', field('Last Activity'), today())Staleness indicator
DealsStage Alertif(field('Days in Stage') > 14, '⚠️ STALE', '')Warning for stale deals
TasksDays Until Duedate_diff('day', today(), field('Due Date'))Countdown to deadline
TasksUrgency Labelif(field('Days Until Due') < 0, '🔴 OVERDUE', if(field('Days Until Due') = 0, '🟡 TODAY', if(field('Days Until Due') <= 3, '🟢 Soon', '')))Emoji urgency tier