Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Import / export

How CPS export files enter the app, become internal codeplug models, and leave again as vendor formats.

Tracking: codeplug-tool#7 (import foundation), #38 (OpenGD77 full I/O), #58 (active import), #84 (doc collate), #103 (CHIRP)

Operator workflow (create → edit → persist → export to multiple formats): operator lifecycle.

Problem

Import was originally hard-wired to OpenGD77 CSV inside the channel map. The app now has a format registry, OpenGD77 as the first adapter pair, and a central store that resolves vendor names to internal ids. Export serialises the internal models back to a format the vendor CPS accepts.

The internal model is format- and radio-agnostic. Format specifics — column mapping, cardinality caps, skipped files — apply at the import/export boundary only.

Formats vs variants. OpenGD77 CSV is one import/export format, sibling to Baofeng DM32 CSV, qDMR YAML, native YAML, and analogue-only formats like CHIRP (DM32 and CHIRP are unrelated to OpenGD77). Within the OpenGD77 format there are per-radio variants (1701, MD9600, GD-77, …). Variant-specific limits are documented in OpenGD77 radio profiles and are intended to be applied when the operator picks a target OpenGD77 radio at export time (#72 — OpenGD77-only; not cross-format work).

Implementation status

Area Status Notes
Internal models Shipped src/models/codeplug.ts — schema v18
Adapter interface contracts Shipped src/lib/import-export/ImportAdapter, ExportAdapter
Export format registry Shipped src/lib/export/
OpenGD77 import Shipped Channels, Zones, Contacts, TG_Lists (#38)
OpenGD77 export Shipped Per-file + ZIP; DTMF/APRS header-only
Multi-file + directory import UI Shipped ImportDropzone on home
Active project import Shipped ImportIntoActivePanel on Import & export (#58)
Merge / overwrite modes Shipped importMerge.ts — idempotent merge by vendor name
Name → id resolution Shipped Store + src/lib/codeplug.ts
Export page (/export) Shipped Nav link when a project is active
Delivery-aware export UI Shipped Registry dispatch + CHIRP profile picker (#103)
LocalStorage persistence Shipped #9persistence/
Multi-project import Shipped Home creates project; Import & export merges into active — codeplug-project/
OpenGD77 radio-variant picker Planned Apply per-radio (1701, MD9600, …) limits within OpenGD77 export — #72; OpenGD77-only, not cross-format
qDMR YAML Deferred #37 — UI placeholder
Native YAML Shipped #10native-yaml/
Baofeng DM32 CPS Shipped #67dm32/; expandModes: false, RX list fan-out
Multi-talkgroup expansion (shared lib) Shipped #36channelExpansion/; OpenGD77 export unchanged
CHIRP CSV (analogue FM/AM) Shipped #103chirp/
Channel wire name split + export composition Shipped #54channel-name-parsing.md
Export name shortening Shipped #130, #150name-shortening.md
Zone-derived scan lists + scratch (DM32) Shipped #164, #163zone-derived-scan-lists-progress.md

Documentation map

Doc Contents
import-export-fidelity-contract.md Authoritative — what fidelity adapters and tests guarantee, and what we deliberately let slip
channel-name-parsing.md CPS wire name → callsign + name split (#54)
name-shortening.md Export-time channel/zone name shortening (#130, #150)
name-shortening-progress.md Execution log for #130 and #150
name-shortening-outstanding.md Debt discovered during name-shortening work
data-model/README.md Entity definitions (canonical, vendor-neutral)
opengd77/README.md OpenGD77 adapter behaviour; columns in reference/opengd77/
adding-a-new-vendor.md Contributor checklist for new formats
format-taxonomy.md Formats vs variants mental model + data-model findings (planning input)
outstanding.md Collated open debt
opengd77/progress.md OpenGD77 execution log
dm32/README.md DM32 adapter behaviour (#67)
zone-derived-scan-lists-progress.md Zone-derived scan + scratch execution log (#164, #163)
zone-derived-scan-lists-outstanding.md Debt from zone-derived scan work
../../reference/zone-derived-scan-lists.md Tier-2 policy — gating, format matrix, scratch/carrier semantics
chirp/README.md CHIRP adapter behaviour (#103)
operator-lifecycle.md Multi-format operator workflow
Testing strategy Format fidelity, layers, CI
persistence/README.md LocalStorage envelope
codeplug-project/README.md Project wrapper + CRUD

Architecture

flowchart TD
  HomeUI["ImportDropzone (home)"] --> importFiles
  ExportUI["ImportIntoActivePanel (import & export page)"] --> importFiles
  importFiles --> detect["detectImportAdapter / getImportAdapter"]
  detect --> Adapter["format adapter (OpenGD77, CHIRP, …)"]
  Adapter --> Raw["ImportResult"]
  Raw --> Merge["importMerge — merge or overwrite"]
  Merge --> Store["codeplugStore — active project"]
  Store --> Resolve["resolveZoneMembers"]
  Resolve --> Codeplug["Codeplug"]
  Codeplug --> Serialise["getExportAdapter — multi-file or single-file"]
  Serialise --> Download["per-file CSV, ZIP, or single CHIRP CSV"]
Loading

All vendor formats convert through the radio-agnostic internal model. Import adapters parse vendor files into entities; export adapters serialise entities back to vendor columns. Feature code (map, CRUD, store) works on the internal model only.

Channel expansion (multi-mode / multi-talkgroup)

Shared logic in src/lib/channelExpansion/ — adapters call expandAllChannelsForExport before serialising wire rows:

Axis When to enable OpenGD77
Multi-mode Format has no native dual-mode row Always (separate Analogue/Digital rows)
Multi-talkgroup Format has no native RX group lists Never — lean export with TG List

Pass ExportOptions.expandRxGroupLists and expandRxGroupListMembers through expandOptionsFromExport() (exportOptions.ts). Zone export uses expandZoneMemberWireNames with the same flags.

Domain rules: multi-talkgroup-expansion.md. DM32 (#67) will enable TG expansion on export.

Import modes (#58)

Only entity types present in the import batch are touched.

Mode Behaviour
Merge (default) Match by vendor name (case-sensitive). Update rows only when imported fields differ; append new names; preserve internal ids and app-only fields (hideFromMap). Re-importing an unchanged file is a no-op.
Overwrite Replace the entire array for each imported file type (e.g. all channels when Channels.csv is included).

Merge matching keys

Entity Match key
Channel Channel Name
Zone Zone Name
Contact / talk group Contact Name
RX group list TG List Name

After apply, all zones' memberChannelIds are re-resolved from meta.imported.memberWireNames. Unresolved member names appear in the confirm modal and import report.

OpenGD77 export

  • Per-file download: Channels.csv, Zones.csv, Contacts.csv, TG_Lists.csv
  • ZIP: all six CPS files; DTMF.csv and APRS.csv are header-only (not modelled)
  • Route: /#/export when a codeplug project is active

OpenGD77 CPS CSV is one interchange format shared by many radios. Import/export adapters are format-level; radio-specific limits are profile-level at export time.

Code: src/lib/export/opengd77/, UI src/routes/ImportExport.tsx

Wire format reference

CPS file Reference
All files — cross-cutting rules file-format.md
Channels.csv channels.md
Zones.csv zones.md
Contacts.csv contacts.md
TG_Lists.csv tg-lists.md
DTMF.csv / APRS.csv dtmf-aprs.md

Authoritative column and conversion reference: reference/opengd77/. Per-radio limits: radio profiles.

Code anchors

Symbol File Role
importFiles src/lib/import/index.ts Read files, route by adapter, classify, parse
getImportAdapter / getExportAdapter src/lib/import-export/registry.ts Format registry
previewImportMerge / applyImportToCodeplug src/lib/importMerge.ts Merge/overwrite + stats
channelsImportEqual src/lib/importEntityCompare.ts Idempotent field compare
opengd77Adapter src/lib/import/opengd77/adapter.ts detectKind, delegates to parse
parseChannels / parseZones src/lib/import/opengd77/parse.ts CSV → models / raw zones
exportCodeplug src/lib/export/index.ts Serialise to vendor format
CodeplugProvider src/state/codeplugStore.tsx Central state + applyImportToActive
runActiveImportWorkflow src/test/system/importWorkflow.ts System test harness

Import UI behaviour

  • Home: ImportDropzone creates a new codeplug project (importNewProject).
  • Import & export (/export): ImportIntoActivePanel merges into the active project with confirm modal (applyImportToActive).
  • Drop target: multiple .csv files or a whole folder.
  • New project naming (Home import only): folder selection → leaf directory name; loose files (one or many) → {adapter projectNameLabel} YYYY-MM-DD (ISO date). Each import adapter sets projectNameLabel — see per-format docs (e.g. OpenGD77). See also codeplug-project.
  • Recognised (OpenGD77): Channels.csv, Zones.csv, Contacts.csv, TG_Lists.csv
  • Skipped (OpenGD77): DTMF.csv, APRS.csv, other unknown CSVs when OpenGD77 files are present
  • CHIRP: single memory CSV with standard 21-column header fingerprint

Automated tests

npm run test              # unit tests including importMerge, round-trip
npm run test:system       # workflow harness + ImportIntoActivePanel UI flow

Synthetic CSV bundles: src/test/opengd77/bundles.ts.

Format fidelity strategy: format-fidelity.md. Authoritative tier promises: import-export-fidelity-contract.md.

Manual verify

Merge workflow

  1. npm run dev → Home → import a supported CPS export (folder or loose files) → Summary opens with new project named from the folder leaf or {adapter projectNameLabel} YYYY-MM-DD.
  2. Import & export → Merge → import Zones.csv → confirm shows zones added → zones resolve on /zones.
  3. Re-import identical Channels.csv → confirm shows all unchanged.
  4. Re-import modified Channels.csv → only changed rows updated; zone links intact.
  5. Import Contacts.csv / TG_Lists.csv alone → other entities unchanged.

Overwrite workflow

  1. With a populated codeplug, Import & export → Overwrite → import smaller Channels.csv → confirm warns removed count.
  2. Overwrite Zones.csv only → channels/contacts unchanged.

Export workflow

  1. With a populated codeplug, /#/export → download per-file CSVs or ZIP.
  2. Re-import exported files → merge shows unchanged (same-format round-trip).

Regression

  1. Home → import second codeplug → still creates new project.
  2. Hard refresh → data persists from LocalStorage.

History

  • #7 — Genericise import: internal models, format registry, central store, home import UI.
  • #38 — OpenGD77 full import/export: extended channel model, Contacts/TG lists, export serialisers, round-trip test.
  • #58 — Active project import: merge/overwrite modes, ImportIntoActivePanel, system test harness.

Related