Prismatic DD

Prismatic DD

1561 files Intelligence

Abstract

Prismatic DD is the entity-centric due diligence investigation platform within the Prismatic ecosystem. It provides a structured environment for creating, managing, and exploring investigable subjects -- persons, companies, domains, assets, and documents -- along with the directional relationships that connect them. The platform implements a graph-based data model with breadth-first traversal for multi-level relationship exploration, a case management system with full lifecycle tracking (draft through archived), and dual-layer persistence combining ETS in-memory caching with file-backed JSON storage for durability without external database dependencies. The architecture is intentionally minimal: three supervised GenServer processes, zero external service requirements, and a clean facade API that exposes all operations through a single module. Five Phoenix LiveView dashboards provide real-time interactive interfaces for case management, entity exploration, graph visualization, and investigation coordination. With 1,561 lines of production Elixir code, 16 relationship types, 5 entity categories, and comprehensive test coverage, Prismatic DD delivers the investigation infrastructure required for corporate due diligence, compliance screening, and intelligence analysis workflows.

1. Introduction

1.1 Problem Statement

Due diligence investigations require analysts to track complex webs of relationships between entities -- persons who direct companies, companies that own subsidiaries, investors who fund ventures, and the documents and domains that connect them. Traditional investigation tools either force analysts into flat spreadsheets that lose structural context, or require heavyweight database deployments that create operational friction for time-sensitive investigations.

The core challenge is graph complexity. A single corporate investigation can involve dozens of entities connected by hundreds of relationships spanning ownership, directorship, shareholding, employment, investment, and family ties. Analysts need to traverse these relationship graphs at variable depth, scope investigations to specific cases, and attach analysis results and documents to individual entities -- all while maintaining a clear audit trail of what was investigated and when.

Commercial due diligence platforms address this with SaaS offerings that require network connectivity, data residency compromises, and per-seat licensing that scales poorly for organizations running frequent investigations. Prismatic DD takes a different approach: an embedded OTP application that runs within the BEAM runtime, stores data locally, and provides sub-millisecond graph traversal through ETS-backed caching.

1.2 Design Goals

  1. Entity-centric data model -- model all investigable subjects as typed entities with identifiers, attributes, documents, and analysis results, connected by directional relationships.
  2. Graph-first exploration -- provide breadth-first graph traversal with configurable depth, enabling analysts to explore relationship networks at any scale.
  3. Zero external dependencies -- run entirely within the BEAM runtime using ETS for speed and file-backed JSON for persistence, requiring no database server.
  4. Case-scoped investigations -- group entities into named cases with lifecycle tracking, risk assessment, and investigation notes.
  5. Real-time dashboards -- deliver interactive LiveView interfaces for all investigation workflows without JavaScript framework complexity.
  6. Clean API surface -- expose all operations through a single facade module with consistent {:ok, result} / {:error, reason} return patterns.

1.3 Scope

Prismatic DD covers entity management, relationship modeling, graph traversal, and case lifecycle tracking. OSINT data collection is handled by upstream applications (prismatic_osint_sources, prismatic_hawkeye) which feed investigation results into DD entities via the analysis API. Sanctions screening, risk scoring algorithms, and compliance assessment are provided by the broader Prismatic platform -- DD provides the structural foundation on which these capabilities operate.

2. Architecture

2.1 System Design

Prismatic DD follows a three-store architecture where each domain concern -- entities, relationships, and cases -- is managed by an independent GenServer with its own ETS table and file-backed persistence:

PrismaticDd.Application (Supervisor, :one_for_one)
|
+-- PrismaticDd.Entity.Store (GenServer)
|   +-- ETS: :prismatic_dd_entities
|   +-- Files: priv/data/entities/{type}/{id}.json
|
+-- PrismaticDd.Graph.Store (GenServer)
|   +-- ETS: :prismatic_dd_relationships
|   +-- Index: {entity_id, relationship_id} pairs
|   +-- Files: priv/data/relationships/{id}.json
|   +-- Aggregate: priv/data/graph.json
|
+-- PrismaticDd.Case.Store (GenServer)
    +-- ETS: :prismatic_dd_cases
    +-- Files: priv/data/cases/{id}/case.json
    +-- Markdown: README.md, AGENTS.md, CLAUDE.md per case

The :one_for_one supervision strategy ensures that a failure in one store (e.g., a file I/O error in the Case Store) does not cascade to other stores. Each GenServer loads its data from disk into ETS on startup and writes through to both ETS and disk on every mutation, providing crash recovery without transaction logs.

2.2 Core Components

ModuleLOCResponsibility
PrismaticDd429Public facade: entity CRUD, relationships, graph traversal, case management
PrismaticDd.Entity205Entity schema: 5 types, identifiers, attributes, documents, analysis
PrismaticDd.Entity.Store211GenServer: ETS cache + file persistence for entities
PrismaticDd.Case216Case schema: lifecycle states, risk levels, entity scoping
PrismaticDd.Case.Store361GenServer: case persistence + markdown document management
PrismaticDd.Graph.Relationship218Relationship schema: 16 directional types with inverse mapping
PrismaticDd.Graph.Store310GenServer: relationship storage + BFS graph traversal engine

2.3 Data Model

Entity Types

TypePurposeCommon Identifiers
:personIndividual under investigationbirth_date, ssn, passport, ico
:companyCorporate entityico, vat_id, trade_registry_id
:domainInternet domain or IPdomain, ip_address, asn
:assetPhysical or digital assetserial_number, mac_address, vin
:documentReference document or evidencedoc_type, file_hash, isbn

Relationship Types (16 total)

RelationshipInverseUse Case
:owns:owned_byOwnership chains
:director_of:has_directorCorporate governance
:shareholder_of:has_shareholderEquity holdings (with percentage)
:partner_with(bidirectional)Business partnerships
:employee_of:employsEmployment relationships
:invested_in:has_investorInvestment flows
:founded:founded_byCompany founding
:family_of(bidirectional)Family connections
:related_to(generic)Uncategorized associations
:associated_with(generic)Loose associations

Case Lifecycle

:draft --> :active --> :review --> :closed --> :archived

Risk Levels

:unknown < :low < :medium < :high < :critical

3. Implementation

3.1 Facade API

All operations are accessed through the PrismaticDd facade module. The API follows consistent patterns: creation functions return {:ok, struct} or {:error, reason}, queries return lists or {:ok, struct} / {:error, :not_found}.

# Create entities
{:ok, person} = PrismaticDd.create_person("Jan Novak", %{role: "CEO"})
{:ok, company} = PrismaticDd.create_company("Acme s.r.o.")

# Add identifiers
{:ok, _} = PrismaticDd.add_identifier(company.id, "ico", "12345678")
{:ok, _} = PrismaticDd.add_identifier(person.id, "birth_date", "1975-03-15")

# Create directional relationships
{:ok, _} = PrismaticDd.create_relationship(person.id, company.id, :director_of)
{:ok, _} = PrismaticDd.create_relationship(person.id, company.id, :shareholder_of,
  %{"percentage" => 51.0})

# Attach analysis results from OSINT agents
{:ok, _} = PrismaticDd.add_analysis(person.id, "sanctions_screening", %{
  result: :clear,
  sources: ["OFAC SDN", "EU Consolidated"],
  checked_at: DateTime.utc_now()
})

3.2 Graph Traversal

The Graph Store implements breadth-first search (BFS) for relationship exploration. Starting from any entity, the traversal expands outward level by level, returning a map of discovered entities with their distance from the origin:

# Traverse from person, 3 levels deep
traversal = PrismaticDd.traverse(person.id, depth: 3)
# => %{"person-jan-novak-1234" => 0, "company-acme-5678" => 1,
#       "person-eva-novakova-9012" => 2, "company-beta-3456" => 3}

# Full exploration with entity data and relationships
result = PrismaticDd.explore(person.id, depth: 2)
# => %{
#   center: %Entity{name: "Jan Novak", type: :person, ...},
#   related: [
#     %{entity: %Entity{name: "Acme s.r.o.", type: :company}, level: 1},
#     %{entity: %Entity{name: "Eva Novakova", type: :person}, level: 2}
#   ],
#   relationships: [%Relationship{type: :director_of, ...}, ...]
# }

3.3 Case Management

Cases provide investigation scoping -- grouping entities and their relationships into named investigations with lifecycle tracking:

# Create investigation case
{:ok, investigation} = PrismaticDd.create_case("Acme Corporate Structure", %{
  description: "Full corporate structure investigation for Acme group",
  tags: ["corporate", "czech-registry"]
})

# Add entities to case
:ok = PrismaticDd.add_entity_to_case(investigation.id, person.id)
:ok = PrismaticDd.add_entity_to_case(investigation.id, company.id)

# Update case status and risk assessment
{:ok, _} = PrismaticDd.set_case_status(investigation.id, :active)
{:ok, _} = PrismaticDd.set_case_risk(investigation.id, :medium)

# Get case-scoped graph (only entities within this case)
graph = PrismaticDd.case_graph(investigation.id)

# Manage investigation documentation
:ok = PrismaticDd.put_case_readme(investigation.id, "# Acme Investigation\n\n...")
:ok = PrismaticDd.put_case_agents(investigation.id, "# Agent Notes\n\n...")

3.4 Dual-Layer Persistence

Every mutation writes to both ETS (for microsecond read performance) and the filesystem (for crash recovery):

# Entity storage path pattern:
# priv/data/entities/person/person-jan-novak-1234.json
# priv/data/entities/company/company-acme-5678.json

# Case storage with markdown documents:
# priv/data/cases/2026-02-06-acme-investigation/
#   case.json      - Case metadata and entity IDs
#   README.md      - Investigation notes
#   AGENTS.md      - Agent coordination notes
#   CLAUDE.md      - Session context

On GenServer startup, all JSON files are read from disk and loaded into ETS tables. This eliminates the need for a database server while providing sub-millisecond read latency for graph traversal operations.

4. LiveView Integration

Prismatic DD exposes five LiveView dashboards through the Prismatic Web application, all accessible under the /dd route prefix:

4.1 Route Map

RouteModulePurpose
/ddDD.DashboardLiveOverview with stats grid and quick actions
/dd/casesDD.CasesLiveNewCase listing with status filtering
/dd/cases/newDD.CasesLiveNewCase creation form
/dd/cases/:idDD.CasesLiveNewCase detail with tabs (overview, entities, graph, notes)
/dd/entitiesDD.EntitiesLiveEntity listing with type filtering and search
/dd/entities/newDD.EntitiesLiveEntity creation with type selection
/dd/entities/:idDD.EntitiesLiveEntity detail with identifiers, documents, analysis
/dd/graphDD.GraphLiveInteractive graph exploration with depth control

4.2 Dashboard Components

The Dashboard (/dd) displays a stats grid showing total cases, active cases, entity counts by type, and relationship totals. Quick action cards link to case creation, entity management, and graph exploration.

The Cases interface provides a tabbed detail view with five tabs: Overview (metadata, status, risk badges), Entities (scoped entity list), Graph (case-scoped relationship visualization), Notes (README.md editor), and Agents (AGENTS.md editor). Case creation includes name, description, risk level, and tag assignment.

The Graph explorer (/dd/graph) offers entity search, configurable traversal depth (1-5 levels), and interactive relationship visualization showing entity nodes connected by typed relationship edges.

5. Performance

5.1 Storage Characteristics

OperationLatencyBackend
Entity lookup by ID~1 microsecondETS :set table
Entity search by name~10-100 microsecondsETS scan with filter
Relationship lookup~1 microsecondETS with indexed lookups
Graph traversal (depth 1)~10 microsecondsETS BFS
Graph traversal (depth 3)~100-500 microsecondsETS BFS, depends on graph density
Entity save (write-through)~1-5 millisecondsETS insert + JSON file write
Case save with markdown~2-10 millisecondsETS insert + multiple file writes

5.2 Scalability

The current architecture is optimized for investigation-scale datasets (hundreds to low thousands of entities). ETS provides O(1) key lookups and O(n) scans, making entity and relationship operations efficient for datasets up to approximately 100,000 records. File-backed persistence creates one JSON file per entity/relationship, which works well for datasets under 50,000 files but would benefit from aggregated storage for larger deployments.

Graph traversal performance depends on graph density. A BFS traversal at depth 3 in a sparsely connected graph (average 2-3 connections per entity) completes in under 100 microseconds. Dense graphs (10+ connections per entity) may take 1-5 milliseconds at depth 3.

5.3 Resource Requirements

  • Memory: ~50KB base (3 GenServers) + ~200 bytes per entity in ETS + ~150 bytes per relationship
  • Disk: ~500 bytes per entity JSON file + ~300 bytes per relationship JSON file
  • CPU: Negligible for CRUD operations; BFS traversal is CPU-bound but microsecond-scale

6. Testing

6.1 Test Coverage

Test ModuleTestsFocus
prismatic_dd_test.exsFacade APIEntity CRUD, relationships, graph traversal, case operations
entity/entity_test.exsSchemaEntity creation, identifiers, attributes, serialization
entity/store_test.exsPersistenceETS cache, file I/O, search, concurrent access
case/case_test.exsSchemaCase lifecycle, risk levels, entity scoping, tags
case/store_test.exsPersistenceCase persistence, markdown documents, status filtering
graph/relationship_test.exsSchemaRelationship types, inverse mapping, serialization
graph/store_test.exsPersistenceGraph storage, traversal correctness, index consistency

6.2 LiveView Tests

The web interface is tested through 17 LiveView test cases covering:

  • Empty state rendering and initial page load
  • Case list rendering with filtering
  • Case creation modal and form submission
  • Case detail view with tab switching (Overview, Entities, Graph, Notes, Agents)
  • Breadcrumb navigation and 404 handling
  • Status and risk badge display
  • Tag rendering and case deletion

6.3 Running Tests

# Unit tests for DD domain logic
mix test apps/prismatic_dd/test/

# LiveView integration tests
mix test apps/prismatic_web/test/prismatic_web/live/dd/

7. Security

7.1 Investigation Isolation

Cases provide logical isolation for investigations. Entities can belong to multiple cases, but case-scoped queries (case_entities/1, case_graph/1) only return data within the investigation boundary. This prevents cross-contamination between unrelated investigations.

7.2 Data Residency

All investigation data is stored locally on the filesystem -- no external API calls, no cloud storage, no third-party data processors. This is by design for investigations involving sensitive PII, corporate intelligence, or regulatory compliance data where data residency requirements prohibit external transmission.

7.3 Access Control

DD inherits the platform's RBAC system through the Prismatic Web authentication layer. All LiveView routes under /dd/* require authenticated sessions. API-level access control is enforced through the platform's plug pipeline.

8. Operational Considerations

8.1 Deployment

Prismatic DD deploys as part of the Prismatic umbrella application. No additional infrastructure is required -- the application uses only ETS (in-memory) and the local filesystem. The priv/data/ directory must be writable and should be included in backup procedures.

8.2 Monitoring

Supervision tree health is monitored through standard OTP mechanisms. Each GenServer emits telemetry events for CRUD operations, enabling dashboards to track entity creation rates, relationship density, and case lifecycle transitions.

8.3 Troubleshooting

SymptomCauseResolution
Empty entity list after restartpriv/data/entities/ directory missing or unreadableVerify directory permissions; check GenServer logs for file I/O errors
Graph traversal returns emptyEntity has no relationshipsVerify relationships exist with PrismaticDd.relationships_for(entity_id)
Case entities not showingEntities not added to caseUse PrismaticDd.add_entity_to_case/2 to associate entities
Slow search operationsLarge entity count with full-text scanConsider adding Meilisearch integration for text search offloading

9. Future Work

Planned enhancements include Meilisearch integration for full-text entity search across large datasets, graph visualization using D3.js force-directed layouts in the LiveView graph explorer, integration with the OSINT pipeline for automated entity enrichment, and timeline views showing investigation progress and entity modification history. The storage layer may be extended with optional PostgreSQL backing for multi-node deployments requiring shared state.

10. References

  • Competitor Researcher -- Leverages due diligence investigation capabilities for competitive intelligence gathering and corporate structure analysis
  • Evidence Enforcement Agent -- Ensures investigation entities carry proper provenance, analysis attribution, and evidence chain integrity
  • Alert Management Specialist -- Configures alerting for case lifecycle transitions, risk level escalations, and investigation deadline management
  • Intelligence Synthesis -- Combines multi-source entity analysis from OSINT agents into comprehensive due diligence investigation profiles
  • NABLA Axioms -- Enforces signal plurality and contradiction preservation for investigation findings across multiple intelligence sources
  • Trinity Gate -- Verifies structural and logical consistency of relationship graphs and entity attribution claims in investigations

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL