Model Atlas is a clone-and-run app for turning heterogeneous forensic data exports into one spatio-temporal integration model.
The core is CLI-first and source-agnostic. It reads CSV files, Excel workbooks, and SQLite databases, matches them to YAML presets, maps source fields into a flat spatio-temporal assertion model, and writes CSV output plus warning and traceability sidecars. A PySide6 GUI is available as an optional front-end.
- Python 3.11 to 3.14
pandaspyyamlopenpyxl
The GUI dependency is optional and installed through the gui extra.
From a clone of this folder:
python matlas.py process \
--input ./evidence \
--presets ./presets \
--output ./out/merged.csv \
--linked-entity "Case Subject"--linked-entity is required: it names the case subject every output row is
attributed to. --entity is optional. When supplied, these arguments override
any entity/linked_entity a preset maps; the preset value is the default used
only when the argument is absent.
Useful options:
python matlas.py process --help
python matlas.py --log-level DEBUG process --input ./evidence --presets ./presets --output ./out/merged.csv --linked-entity Subject
python matlas.py process --input ./evidence --presets ./presets --output ./out/merged.csv --linked-entity Subject --entity iPhone
python matlas.py process --input ./evidence --presets ./presets --output ./out/merged.csv --linked-entity Subject --traceability-format prov
python matlas.py process --input ./evidence --presets ./presets --output ./out/by-preset --no-merge --linked-entity Subject
python matlas.py process --input ./evidence --presets ./presets --output ./out/merged.csv --linked-entity Subject --no-source-columns
python matlas.py process --input ./evidence --profile ./profiles/ios-locations.mATLAS-profile --output ./out/merged.csv --linked-entity SubjectBy default every original source column is appended verbatim to the output, prefixed
orig_ (e.g. orig_Latitude), after the canonical columns. Use --no-source-columns
to emit the canonical columns only. See docs/integration_model.md.
When --input is a single file that resolves to exactly one source element and
--presets is a single YAML, mATLAS runs in force-preset mode: it applies that
preset without selector matching, which is handy for testing a new preset against
one file.
Merge-mode outputs:
merged.csvmerged.matlas.traceability.jsonmerged.matlas.warnings.json
In split mode (--no-merge), --output is a folder and mATLAS writes one CSV
per matched preset with sidecars next to each CSV.
--log-level INFO is the default and records the main processing phases.
--log-level DEBUG records precise discovery, preset matching, extraction,
mapping, timestamp conversion, untangle, and export steps. Logs are written to
stderr and to --log-file when supplied.
Install the GUI extra, then launch with no arguments or with gui:
python -m pip install '.[gui]'
python matlas.py
python matlas.py guiFor CLI help when launching with no arguments, use:
python matlas.py --helpThe GUI exposes source selection, preset folder/YAML selection, auto matching, manual preset selection, profile load/save, merge/split output mode, log level, traceability format, and a log window. GUI files do not implement core preset, source, transform, export, or traceability logic; they call the same package API as the CLI.
Presets are YAML files loaded recursively from the folder passed with
--presets, or loaded directly when --presets points to one YAML file. A v3
preset has:
- a
presetheader (id, name, os/tool, version, os_version, tier); - a
matchblock identifying the source (CSV, Excel, or SQLite, direct or in a ZIP); commonrow-level fields plusassertions(each aposition+time+links);- explicit value references (
field(),header(),filename(),param(),const()), named timestamp codecs (epoch:), declarativeunit:conversion, andpipechains for the procedural cases.
Example presets live under presets/axiom/ (AXIOM CSV exports) and presets/ios/
— direct SQLite presets read from a full-filesystem ZIP, e.g.
ios_routined_cached_locations.yaml (com.apple.routined/Cache.sqlite CLLocation
points) and ios_routined_significant_visits.yaml
(com.apple.routined/Local.sqlite significant-location dwell intervals).
The canonical model is documented in docs/integration_model.md; preset YAML
logic is documented in docs/preset_schema.md and
docs/preset_authoring_quickstart.md.
Set-once preferences (not per-run options) live in an optional matlas_config.toml in the
directory you run from. Copy matlas_config.toml.example to start. It is optional —
when absent, built-in defaults are used; a present-but-invalid value (bad IANA zone, unknown
key) is a hard error.
The most important key is the local timezone, used to record utc_offset_hours:
[timezone]
local_zone = "Europe/Paris" # IANA name; unset => unknown => utc_offset_hours = nullFor an absolute-UTC source (e.g. a Cocoa-epoch Cache.sqlite), mATLAS records that zone's
DST-aware offset at each row's instant (1.0 in winter, 2.0 in summer for Paris) in
utc_offset_hours. The canonical time_*_unix_utc_us columns stay UTC regardless, so
local = time_*_unix_utc_us + utc_offset_hours. Other sections set logging
(level/format), discovery depths, and the orig_ passthrough prefix. Security safeguards
(zip-bomb guards) are deliberately not configurable here.
Profiles are .mATLAS-profile JSON files that store selected preset YAML paths.
They are a CLI/GUI convenience for curated preset sets.
python matlas.py process \
--input ./evidence \
--profile ./profiles/ios-locations.mATLAS-profile \
--output ./out/merged.csv- No adapter parses the original file in place: every non-archive source is copied to a temporary directory and read from the copy, and the original's hash is recorded before and after the run. (SQLite stages its WAL/SHM/journal siblings too.)
- Unmatched source elements are warned and skipped.
- Expected-field drift is warned.
- Missing mapped fields resolve to
Noneunless a later pipe raises. - Duplicate explicit
source_row_idvalues across different source rows are hard errors. - Source file, original path, parser, preset, warning, and execution context are recorded in sidecar files.
Developer and implementation documentation lives in docs/.
Generative AI was used in this project mainly to assist during the coding phase. The original ideas and the overall structure are the owner's, and all core logic has been reviewed. Even so, mistakes or bugs may have slipped past proof-reading
- please report anything unexpected.