Architecture

How the packages fit together and why they are organized this way

Core Principle: Layered Independence

Montage Concord is structured so that each layer only knows about the layers below it. No package ever imports from a package at the same level or above. This makes each package independently testable and replaceable.

graph TD
  subgraph "Layer 4 — Interaction"
    SERVER["concord-server\nFastAPI + browser dashboard"]
  end
  subgraph "Layer 3 — Visualization"
    VIZ["concord-viz\nTimeseries, spectrogram, PSD, metric prep"]
  end
  subgraph "Layer 2 — Analysis & Simulation"
    MU["metrics-\nunivariate"]
    MS["metrics-\nspectral"]
    MC["metrics-\nconnectivity"]
    MJR["model-\njansen-rit"]
  end
  subgraph " "
    MUTILS["metrics-utils\nWindowing, filtering, analytic signal"]
    MODUTILS["models-utils\nIntegrators, noise, sigmoids"]
  end
  subgraph "Layer 1 — I/O"
    IO["concord-io\nEDF reader, BIDS scanner, re-referencing"]
  end
  subgraph "Layer 0 — Foundation"
    CORE["concord-core\nRecording, MetricResult, ParameterVector,\nModelOutput, Metric & Model ABCs"]
  end

  SERVER --> VIZ
  VIZ --> MU & MS & MC
  MU & MS & MC --> MUTILS
  MJR --> MODUTILS
  MUTILS --> IO
  MODUTILS --> IO
  IO --> CORE

The Core Types

Data flows through core container types and ABCs defined in concord-core. Every other package speaks this common language.

TypeWhat it holdsWho creates itWho reads it
Recording Raw or re-referenced EEG signal: 2D array (channels × samples), sampling rate, channel names, montage, events, metadata concord-io metrics, viz, server
MetricResult Output of a metric computation: N-D array + channel labels + optional frequency/time axes + units Metric subclasses viz, server, fit
ParameterVector Named model parameter values + lower/upper bounds for optimization Model.default_parameters() Model.simulate(), fit
ModelOutput Simulated signal: 2D array (nodes × samples) + all state variables + parameters used Model subclasses metrics, viz, server, fit
Metric (ABC) Interface contract: name, requirements, compute() concord-core defines it all metric packages implement it; fit discovers them
Model (ABC) Interface contract: name, default_parameters(), simulate() concord-core defines it all model packages implement it; fit discovers them

Design Rules

No Cross-Imports at the Same Level

Metric packages (metrics-univariate, metrics-spectral, etc.) must not import each other. If they need shared signal-processing code, it goes in metrics-utils. Similarly, model packages must not import each other — shared math goes in models-utils.

Immutable Data Containers

Recording and MetricResult are Python dataclasses. Functions that transform data (re-referencing, filtering, slicing) always return a new object — they never modify in place. This makes the code easier to reason about and test.

Stateless, Pickle-Safe Functions

Metric classes carry only their configuration parameters (e.g., window size). They do not store results. This means you can pickle a metric, send it to a worker process, call compute(), and get a result back — critical for parallel fitting later.

Self-Registration via Entry Points

Metric and model packages register themselves in their pyproject.toml under [project.entry-points]. The concord-fit package (and others) can discover all installed metrics without any hard import list. See Python Entry Points for how this works.

Package Size Limit

Each sub-package stays under ~1500 lines of implementation code. If a package exceeds this, it is a signal to split it further.

Package Roles at a Glance

PackageLayerRoleKey rule
concord-core0 — FoundationDefines the shared language (types + interfaces)Only numpy. No logic.
concord-io1 — I/OReads files; produces RecordingsOnly package that knows file formats
concord-metrics-utils2 — Analysis supportShared signal primitives (windowing, filtering)No ABC knowledge; no cross-metric imports
concord-metrics-*2 — AnalysisEach implements Metric ABCNo cross-imports between metric packages
concord-models-utils2 — Simulation supportIntegrators, noise, sigmoidsNo ABC knowledge; no cross-model imports
concord-model-*2 — SimulationEach implements Model ABC (future)No cross-imports between model packages
concord-viz3 — VisualizationPure: container → JSON dict for PlotlyNever modifies data; never computes metrics
concord-server4 — InteractionHTTP API + static frontendManages session state; routes to viz functions
concord-fit4 — Optimization (future)Discovers models/metrics; runs optimizationNo hard imports of specific models/metrics
concord-connectome2 — Connectivity (future)Structural connectivity, parcellationSeparate from functional metrics

Python Conventions

Development Workflow

Each sub-package is designed to be developed in an isolated Claude Code session. The workflow: read this architecture page, read the target package's own CLAUDE.md, read the core ABCs, then implement.

Tests for a specific package:

pytest concord-{name}/tests/

When a new dependency is added to a pyproject.toml, re-install:

pip install -e ./concord-{name}