Semarize

CRM & Data

Modeling Semarize Outputs in Your Database or Warehouse

This is the vendor-neutral reference for how to store, version, and query structured conversation signals — regardless of whether you use Snowflake, BigQuery, PostgreSQL, or any other platform. Every platform-specific guide links back here for the shared modeling concepts.

What this guide covers

  • The recommended 3-layer storage approach for conversation intelligence data
  • Versioning strategies so you never lose the ability to compare scoring frameworks
  • Knowledge grounding lineage fields to persist for traceability
  • Retention guidance for raw transcripts, evidence excerpts, and structured signals
  • Anti-patterns that cause pain at scale

Storage architecture

The 3-Layer Storage Approach

Conversation intelligence data flows through three distinct layers. Each layer serves a different purpose and has different retention, mutability, and query characteristics.

Layer 1: Raw Run Log (append-only)

Every evaluation run produces a raw output. Store this as-is, append-only, and never modify it. This layer is your insurance policy - if your scoring logic changes, you can always re-derive signals from the raw output. In practice this means one row per run, with the full JSON response, transcript reference, timestamps, and run configuration.

  • One row per evaluation run (not per signal)
  • Full JSON response from the evaluation engine
  • Transcript reference (inline text or object storage pointer)
  • Run configuration: which kit, which bricks, any custom parameters
  • Append-only - never update or delete rows in this layer

Layer 2: Signal / Event Table (row-per-signal, versioned)

Extract individual signals from the raw output and store them as structured, typed rows. Each row represents one signal from one evaluation of one conversation. This is where most queries and joins happen. Always include the scoring version so you can filter and compare across framework iterations.

  • One row per signal per evaluation (e.g., overall_score, risk_flag, competitor_mention)
  • Typed columns: signal_name, signal_value, signal_type, confidence
  • Versioned: scoring_version or kit_version identifies the framework that produced it
  • Linked to raw layer via run_id or call_id + evaluated_at
  • This is your primary query and join layer

Layer 3: Consumption Views (latest snapshot, optional wide marts)

Create views or materialized tables that present the latest signal values per conversation. These power dashboards, CRM syncs, and downstream applications. They're derived from Layer 2 and can be rebuilt at any time.

  • Latest-value views: one row per conversation with the most recent score for each signal
  • Wide marts: pivot signals into columns for BI tools that prefer wide tables
  • CRM sync views: filtered subsets ready to push back to Salesforce, HubSpot, etc.
  • Trend views: time-series aggregations for tracking scoring changes over time
  • These are derived - rebuild them whenever the underlying data changes
The 3-layer model applies to every platform. Your platform-specific guide covers the native implementation details (VARIANT vs JSONB vs SUPER, partitioning vs clustering, Delta vs Parquet, etc.).

Versioning

Versioning Best Practice

Scoring frameworks evolve. New bricks get added, evaluation criteria change, weights get adjusted. Without versioning, you lose the ability to compare results across time or explain why a score changed.

Fields to always persist

kit_versionThe version identifier of the kit (evaluation framework) that produced the run. Enables filtering reports by framework iteration.
brick_versionIndividual brick versions if your kit composes multiple bricks. Useful when one brick updates independently.
scoring_versionA composite version string if you don't track kit/brick separately. The minimum viable version field.
evaluated_atTimestamp of when the evaluation ran. Combined with version fields, this tells you exactly what logic produced each signal.

Never overwrite historical signal rows. Insert new rows with the updated version instead.

Use version columns in WHERE clauses to compare frameworks side-by-side.

When a new version is deployed, backfill a sample of conversations to validate the change before full reprocessing.

Store the version in both the raw run log and the signal table for easy cross-referencing.

Traceability

Knowledge Grounding Lineage

When evaluation logic uses a knowledge base for grounding — product docs, competitive intel, compliance rules — the identity and version of that knowledge base should be persisted alongside the signals it influenced.

Fields to persist (when available)

knowledge_base_idIdentifier of the knowledge base used during evaluation. Links the signal back to the source material.
knowledge_base_versionVersion or content hash of the knowledge base at evaluation time. Critical for reproducibility.
grounding_modeHow the knowledge base was used - strict retrieval, augmented generation, hybrid, etc.
retrieval_configRetrieval parameters if applicable - chunk size, top-k, similarity threshold. Helps debug unexpected results.
Why this matters: If a knowledge base updates (new product features, revised pricing, updated compliance rules), signals produced before and after the update are not directly comparable. Lineage fields let you partition analysis by knowledge base version and identify when a signal shift is caused by content changes rather than conversation quality changes.

Data lifecycle

Retention Guidance

Different data types have different retention profiles. Storing everything forever is expensive and often unnecessary. Here's a practical framework.

Raw transcripts

12-24 months in your database, then archive to object storage (S3, GCS, Azure Blob). Keep an object storage pointer in the database so you can retrieve on demand. Compliance requirements may dictate shorter or longer periods.

Evidence excerpts

Keep alongside signal rows indefinitely - they're small (a few sentences each) and critical for explaining why a signal was produced. These are the most-queried part of raw evaluation output.

Structured signals

Keep indefinitely. Signal rows are compact (a few hundred bytes each) and form the basis of all trend analysis, cohort comparison, and framework evaluation. Storage cost is negligible relative to their analytical value.

Raw run logs (full JSON)

Keep for 6-12 months in the database for debugging and reprocessing. After that, archive to object storage. You'll rarely query these directly, but they're invaluable when you need to debug a specific evaluation or reprocess historical data.

Compliance note: GDPR, CCPA, and other regulations may require you to delete conversation data on request. Design your schema so that deletion of a specific conversation cascades cleanly across all three layers. Use foreign keys or a shared conversation_id that enables targeted purging.

What to avoid

Anti-Patterns

These patterns seem convenient at first but create real problems as your conversation intelligence system grows.

Dumping one big JSON blob only

Problem: Storing the entire evaluation response as a single JSON column with no extraction. Queries require JSON parsing on every read, BI tools can't access individual signals without custom transforms, and storage/compute costs balloon because you scan the full blob for every query.

Fix: Extract signals into typed columns in Layer 2. Keep the raw JSON in Layer 1 for reference, but don't make it your query layer.

Wide table as source of truth

Problem: One table with a column for every possible signal (overall_score, risk_flag, competitor_mentioned, next_steps_agreed, ...). Adding a new signal means a schema migration. Sparse columns waste storage. Historical comparisons are impossible because the column didn't exist when older conversations were evaluated.

Fix: Use the row-per-signal model in Layer 2. New signals appear as new rows, not new columns. Create wide views in Layer 3 for BI tools that prefer that shape.

Overwriting scores without version tracking

Problem: Updating signal values in place when the scoring framework changes. You lose all historical context - you can't tell whether a score changed because the conversation was different or because the scoring logic changed. Trend analysis becomes meaningless.

Fix: Never overwrite. Insert new rows with the updated scoring_version. Use consumption views to show the latest value while preserving the full history.

Skipping the raw layer

Problem: Only storing extracted signals without keeping the raw evaluation output. When your scoring logic improves (and it will), you have no way to re-derive signals from historical conversations without re-fetching and re-evaluating every transcript.

Fix: Always persist the raw run log. Storage is cheap. The ability to reprocess historical data is invaluable.

Mixing CRM sync data with analytical data

Problem: Using the same table for both CRM sync (latest values) and historical analysis. CRM syncs need the most recent score; analysis needs the full history. Trying to serve both from one table leads to either stale CRM data or lost history.

Fix: Layer 2 holds the full history. Layer 3 consumption views serve the latest values for CRM sync. Each layer serves its purpose cleanly.

Implementation

Platform-Specific Guides

The concepts above apply to every platform. For native implementation details — data types, loading patterns, performance tuning, and operational playbooks — see your platform's dedicated guide.

CRM guides: For pushing signals back to your CRM, see the HubSpot or Salesforce guides for field mapping and sync patterns.

FAQ

Frequently Asked Questions

Explore

Explore Semarize