You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
DM32 and many commercial DMR radios use per-channel scan lists (Scan.csv + channel Scan List FK) rather than “zone = scan” (OpenGD77) or a single global list (CHIRP). Operators routinely:
Mark which channels in a zone should be scanned vs zone-only (operational grouping).
Add a scan carrier dummy channel (e.g. GB7GL Scan) as the first zone member — a throwaway simplex slot the radio sits on while scanning; real traffic is heard on scanned channels.
Configure scan so TX goes to the last activated (heard) channel, not the carrier.
Today we defer all scan semantics (#125 — manual ScanList entity). Operators build this layout by hand in CPS. We want zone-driven scan automation at export that derives scan lists from zone membership without requiring operators to maintain a parallel scan-list CRUD surface first.
This is complementary to, not a replacement for, #125 — see comment on #125 for the split.
Intended outcome
Export-time (and optional internal model) scan policy derived from zones, format-aware at the adapter boundary.
1. Per zone membership: include in scan list
Each zone↔channel membership carries an opt-in flag (vendor-neutral), e.g. includeInScanList: boolean (default true when unset — preserve today’s behaviour).
Export target
How the flag is honoured
OpenGD77
Zone is the scan list — flag ignored; zone member order = scan order
DM32
Builds / updates Scan.csv member list from flagged members in that zone’s scan group
CHIRP
Single zone / single scan list — flagged members contribute to the one scan sequence
Member filter (all targets that honour scan lists):
Respect Channel.scanSkip — channels with scanSkip: true are excluded from generated scan lists.
Do not map OpenGD77 Zone Skip (opengd77Extras['Zone Skip']) — that vendor field stays in opengd77Extras only; scan inclusion is modelled via includeInScanList + scanSkip, not wire stash.
2. Per zone: generate scan carrier channel
Zone-level boolean, e.g. generateScanCarrier: boolean (default false).
When enabled on export to targets with separate scan lists (primarily DM32):
Emit a scan list named after the zone (or deterministic derivative — finalise collision rules).
Emit a carrier channel (dummy RF — default 145.500 MHz simplex analogue unless operator overrides in zone settings; used only for scanning, not real traffic).
Wire carrier channel Scan List → emitted scan list.
Prepend carrier as first zone member in exported zone row (export-time injection — not necessarily stored in memberChannelIds until export resolves).
Scan list members = zone channels with includeInScanList and not scanSkip, in zone member order (excluding carrier from scanned members; carrier holds the scan-list FK only).
OpenGD77: carrier generation not applicable (no separate scan list / carrier pattern). Flag ignored or hidden in CRUD when export target is zone-as-scan.
CHIRP: evaluate whether carrier pattern applies — likely N/A; document in tier-3 reference.
3. Scan TX mode: last activated channel
For generated scan lists (DM32 Scan.csv), set:
scanTxMode = last-activated-channel (TX on last heard channel during scan, not on the carrier).
Other scan-list wire fields (hang time, priority channels, digital scan mode, …) use documented defaults at export unless zone-level overrides are added in a follow-up.
Internal model (proposed)
interfaceZoneMemberEntry{channelId: string;/** When false, channel stays in zone but is omitted from derived scan lists. Default true. */includeInScanList?: boolean;}interfaceZone{id: string;name: string;/** Migrate from string[] or parallel metadata map — design in PR. */members: ZoneMemberEntry[];/** Export-time: emit scan carrier + derived scan list (DM32-style targets). */generateScanCarrier?: boolean;/** Optional override for carrier RF; export default 145.500 MHz simplex. */scanCarrierFrequencyHz?: number|null;meta?: EntityMeta;}
Migration: existing memberChannelIds → members with includeInScanList default true.
Vendor boundary: caps (DM32 ≤16 scan members) apply at export with warnings — not in CRUD validation.
Zone/membership flags; carrier/list may be export-only ephemera initially
First-class ScanList[] in codeplug
Coexistence
Not mutually exclusive — manual lists from #125 can coexist; export merge rules TBD (manual wins? union? warn on conflict?)
Ship order flexible: this ticket can land with export-only synthesis before #125 model exists (write Scan.csv without persisting ScanList), then integrate when #125 lands.
Affected
src/models/codeplug.ts — zone membership shape + generateScanCarrier; schema migration
Problem
DM32 and many commercial DMR radios use per-channel scan lists (
Scan.csv+ channelScan ListFK) rather than “zone = scan” (OpenGD77) or a single global list (CHIRP). Operators routinely:GB7GL Scan) as the first zone member — a throwaway simplex slot the radio sits on while scanning; real traffic is heard on scanned channels.Today we defer all scan semantics (#125 — manual
ScanListentity). Operators build this layout by hand in CPS. We want zone-driven scan automation at export that derives scan lists from zone membership without requiring operators to maintain a parallel scan-list CRUD surface first.This is complementary to, not a replacement for, #125 — see comment on #125 for the split.
Intended outcome
Export-time (and optional internal model) scan policy derived from zones, format-aware at the adapter boundary.
1. Per zone membership: include in scan list
Each zone↔channel membership carries an opt-in flag (vendor-neutral), e.g.
includeInScanList: boolean(default true when unset — preserve today’s behaviour).Scan.csvmember list from flagged members in that zone’s scan groupMember filter (all targets that honour scan lists):
Channel.scanSkip— channels withscanSkip: trueare excluded from generated scan lists.Zone Skip(opengd77Extras['Zone Skip']) — that vendor field stays inopengd77Extrasonly; scan inclusion is modelled viaincludeInScanList+scanSkip, not wire stash.2. Per zone: generate scan carrier channel
Zone-level boolean, e.g.
generateScanCarrier: boolean(default false).When enabled on export to targets with separate scan lists (primarily DM32):
Scan List→ emitted scan list.memberChannelIdsuntil export resolves).includeInScanListand notscanSkip, in zone member order (excluding carrier from scanned members; carrier holds the scan-list FK only).OpenGD77: carrier generation not applicable (no separate scan list / carrier pattern). Flag ignored or hidden in CRUD when export target is zone-as-scan.
CHIRP: evaluate whether carrier pattern applies — likely N/A; document in tier-3 reference.
3. Scan TX mode: last activated channel
For generated scan lists (DM32
Scan.csv), set:scanTxMode=last-activated-channel(TX on last heard channel during scan, not on the carrier).Other scan-list wire fields (hang time, priority channels, digital scan mode, …) use documented defaults at export unless zone-level overrides are added in a follow-up.
Internal model (proposed)
Migration: existing
memberChannelIds→memberswithincludeInScanListdefault true.Vendor boundary: caps (DM32 ≤16 scan members) apply at export with warnings — not in CRUD validation.
Relationship to #125 (manual ScanList)
channel.scanListIdScanList[]in codeplugShip order flexible: this ticket can land with export-only synthesis before #125 model exists (write
Scan.csvwithout persistingScanList), then integrate when #125 lands.Affected
src/models/codeplug.ts— zone membership shape +generateScanCarrier; schema migrationsrc/lib/export/dm32/—Scan.csvsynthesis + channelScan Listcolumn + carrier channel rowsrc/lib/export/opengd77/— confirm zone-as-scan unchanged; membership flag ignoredsrc/lib/export/chirp/— single-list member selection from flagssrc/lib/channelExpansion/— zone member expansion (multi-mode / multi-TG) must fan out scan membership consistentlydocs/reference/multi-talkgroup-expansion.mdor newdocs/reference/scan-policy.md(tier-2 domain doc)docs/reference/dm32/scan-lists.md— generated list + carrier semanticsRelated
ScanListentity +Scan.csvround-trip (complementary)Open design questions
ScanListwhen feat: ScanList entity + DM32 Scan.csv import/export #125 lands — precedence rules.{zoneName} Scanvs{callsign} Scantemplate.generateScanCarrierbut zero scan-eligible members — warn and skip?Out of scope
Scan.csvcolumns beyond TX mode default (follow-up unless trivial)Zone Skipwire column modellingManual verify
includeInScanListflags → DM32Scan.csvcontains only opted-in, non-scanSkipmembers.generateScanCarrieron → carrier channel first in zone export; carrier references scan list; scanned members exclude carrier.scanTxMode= last activated channel on DM32 wire.scanSkip: truenever appears in derived scan list even ifincludeInScanListtrue.opengd77Extras['Zone Skip']not read for scan inclusion decisions.Workflow note
Branch from
origin/main, atomic conventional commits (model → export synthesis + tests → CRUD → docs). PR linkingCloses #. Coordinate with #125 when both are in flight — avoid conflictingScanListshapes.