diff --git a/pkg/alerting/alert/alert.go b/pkg/alerting/alert/alert.go index f57de08..060948b 100644 --- a/pkg/alerting/alert/alert.go +++ b/pkg/alerting/alert/alert.go @@ -8,6 +8,7 @@ import ( "dynatron.me/x/stillbox/internal/trending" "dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "github.com/jackc/pgx/v5/pgtype" ) @@ -44,7 +45,8 @@ func (a *Alert) ToAddAlertParams() database.AddAlertParams { } // Make creates an alert for later rendering or storage. -func Make(ctx context.Context, store talkgroups.Store, score trending.Score[talkgroups.ID], origScore float64) (Alert, error) { +func Make(ctx context.Context, score trending.Score[talkgroups.ID], origScore float64) (Alert, error) { + store := tgstore.FromCtx(ctx) d := Alert{ Score: score, Timestamp: time.Now(), diff --git a/pkg/alerting/alerting.go b/pkg/alerting/alerting.go index 8249ff2..259d767 100644 --- a/pkg/alerting/alerting.go +++ b/pkg/alerting/alerting.go @@ -15,7 +15,8 @@ import ( "dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/notify" "dynatron.me/x/stillbox/pkg/sinks" - talkgroups "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "dynatron.me/x/stillbox/internal/timeseries" "dynatron.me/x/stillbox/internal/trending" @@ -51,7 +52,7 @@ type alerter struct { alertCache map[talkgroups.ID]alert.Alert renotify time.Duration notifier notify.Notifier - tgCache talkgroups.Store + tgCache tgstore.Store } type offsetClock time.Duration @@ -86,7 +87,7 @@ func WithNotifier(n notify.Notifier) AlertOption { } // New creates a new Alerter using the provided configuration. -func New(cfg config.Alerting, tgCache talkgroups.Store, opts ...AlertOption) Alerter { +func New(cfg config.Alerting, tgCache tgstore.Store, opts ...AlertOption) Alerter { if !cfg.Enable { return &noopAlerter{} } @@ -169,7 +170,7 @@ func (as *alerter) eval(ctx context.Context, now time.Time, testMode bool) ([]al if s.Score > as.cfg.AlertThreshold || testMode { if old, inCache := as.alertCache[s.ID]; !inCache || now.Sub(old.Timestamp) > as.renotify { s.Score *= as.tgCache.Weight(ctx, s.ID, now) - a, err := alert.Make(ctx, as.tgCache, s, origScore) + a, err := alert.Make(ctx, s, origScore) if err != nil { return nil, fmt.Errorf("makeAlert: %w", err) } @@ -202,7 +203,7 @@ func (as *alerter) testNotifyHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() ridx := rand.Intn(len(as.scores)) - a, err := alert.Make(ctx, talkgroups.StoreFrom(ctx), as.scores[ridx], 1.0) + a, err := alert.Make(ctx, as.scores[ridx], 1.0) if err != nil { log.Error().Err(err).Msg("test notify make alert fail") http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/pkg/alerting/simulate.go b/pkg/alerting/simulate.go index bfe69b7..641ab28 100644 --- a/pkg/alerting/simulate.go +++ b/pkg/alerting/simulate.go @@ -13,6 +13,7 @@ import ( "dynatron.me/x/stillbox/internal/trending" "dynatron.me/x/stillbox/pkg/config" "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "github.com/rs/zerolog/log" ) @@ -59,7 +60,7 @@ func (s *Simulation) stepClock(t time.Time) { // Simulate begins the simulation using the DB handle from ctx. It returns final scores. func (s *Simulation) Simulate(ctx context.Context) (trending.Scores[talkgroups.ID], error) { now := time.Now() - tgc := talkgroups.NewCache() + tgc := tgstore.NewCache() s.Enable = true s.alerter = New(s.Alerting, tgc, WithClock(&s.clock)).(*alerter) diff --git a/pkg/auth/jwt.go b/pkg/auth/jwt.go index c915c29..561716d 100644 --- a/pkg/auth/jwt.go +++ b/pkg/auth/jwt.go @@ -40,6 +40,23 @@ type jwtAuth interface { type claims map[string]interface{} +func UIDFrom(ctx context.Context) *int32 { + tok, _, err := jwtauth.FromContext(ctx) + if err != nil { + return nil + } + + uidStr := tok.Subject() + uidInt, err := strconv.Atoi(uidStr) + if err != nil { + return nil + } + + uid := int32(uidInt) + + return &uid +} + func (a *Auth) Authenticated(r *http.Request) (claims, bool) { // TODO: check IP against ACL, or conf.Public, and against map of routes tok, cl, err := jwtauth.FromContext(r.Context()) diff --git a/pkg/calls/call.go b/pkg/calls/call.go index d76b530..6359940 100644 --- a/pkg/calls/call.go +++ b/pkg/calls/call.go @@ -1,13 +1,11 @@ package calls import ( - "context" "fmt" "time" "dynatron.me/x/stillbox/internal/audio" "dynatron.me/x/stillbox/pkg/auth" - "dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/pb" "dynatron.me/x/stillbox/pkg/talkgroups" @@ -113,21 +111,6 @@ func (c *Call) ToPB() *pb.Call { } } -func (c *Call) LearnTG(ctx context.Context, db database.Store) (learnedId int, err error) { - err = db.AddTalkgroupWithLearnedFlag(ctx, int32(c.System), int32(c.Talkgroup)) - if err != nil { - return 0, fmt.Errorf("addTalkgroupWithLearnedFlag: %w", err) - } - - return db.AddLearnedTalkgroup(ctx, database.AddLearnedTalkgroupParams{ - SystemID: c.System, - TGID: c.Talkgroup, - Name: c.TalkgroupLabel, - AlphaTag: c.TGAlphaTag, - TGGroup: c.TalkgroupGroup, - }) -} - func (c *Call) computeLength() (err error) { var td time.Duration diff --git a/pkg/database/batch.go b/pkg/database/batch.go index 31b3fa8..d614580 100644 --- a/pkg/database/batch.go +++ b/pkg/database/batch.go @@ -18,6 +18,83 @@ var ( ErrBatchAlreadyClosed = errors.New("batch already closed") ) +const storeTGVersion = `-- name: StoreTGVersion :batchexec +INSERT INTO talkgroup_versions(time, created_by, + system_id, + tgid, + name, + alpha_tag, + tg_group, + frequency, + metadata, + tags, + alert, + alert_config, + weight, + learned +) SELECT NOW(), $1, + tg.system_id, + tg.tgid, + tg.name, + tg.alpha_tag, + tg.tg_group, + tg.frequency, + tg.metadata, + tg.tags, + tg.alert, + tg.alert_config, + tg.weight, + tg.learned +FROM talkgroups tg WHERE tg.system_id = $2 AND tg.tgid = $3 +` + +type StoreTGVersionBatchResults struct { + br pgx.BatchResults + tot int + closed bool +} + +type StoreTGVersionParams struct { + Submitter *int32 `json:"submitter"` + SystemID int32 `json:"system_id"` + TGID int32 `json:"tgid"` +} + +func (q *Queries) StoreTGVersion(ctx context.Context, arg []StoreTGVersionParams) *StoreTGVersionBatchResults { + batch := &pgx.Batch{} + for _, a := range arg { + vals := []interface{}{ + a.Submitter, + a.SystemID, + a.TGID, + } + batch.Queue(storeTGVersion, vals...) + } + br := q.db.SendBatch(ctx, batch) + return &StoreTGVersionBatchResults{br, len(arg), false} +} + +func (b *StoreTGVersionBatchResults) Exec(f func(int, error)) { + defer b.br.Close() + for t := 0; t < b.tot; t++ { + if b.closed { + if f != nil { + f(t, ErrBatchAlreadyClosed) + } + continue + } + _, err := b.br.Exec() + if f != nil { + f(t, err) + } + } +} + +func (b *StoreTGVersionBatchResults) Close() error { + b.closed = true + return b.br.Close() +} + const upsertTalkgroup = `-- name: UpsertTalkgroup :batchone INSERT INTO talkgroups AS tg ( system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned @@ -47,7 +124,7 @@ SET alert_config = COALESCE($10, tg.alert_config), weight = COALESCE($11, tg.weight), learned = COALESCE($12, tg.learned) -RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned +RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored ` type UpsertTalkgroupBatchResults struct { @@ -119,6 +196,7 @@ func (b *UpsertTalkgroupBatchResults) QueryRow(f func(int, Talkgroup, error)) { &i.AlertConfig, &i.Weight, &i.Learned, + &i.Ignored, ) if f != nil { f(t, i, err) diff --git a/pkg/database/mocks/Store.go b/pkg/database/mocks/Store.go index b7a94f9..6d78e98 100644 --- a/pkg/database/mocks/Store.go +++ b/pkg/database/mocks/Store.go @@ -123,22 +123,22 @@ func (_c *Store_AddCall_Call) RunAndReturn(run func(context.Context, database.Ad } // AddLearnedTalkgroup provides a mock function with given fields: ctx, arg -func (_m *Store) AddLearnedTalkgroup(ctx context.Context, arg database.AddLearnedTalkgroupParams) (int, error) { +func (_m *Store) AddLearnedTalkgroup(ctx context.Context, arg database.AddLearnedTalkgroupParams) (database.Talkgroup, error) { ret := _m.Called(ctx, arg) if len(ret) == 0 { panic("no return value specified for AddLearnedTalkgroup") } - var r0 int + var r0 database.Talkgroup var r1 error - if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) (int, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) (database.Talkgroup, error)); ok { return rf(ctx, arg) } - if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) int); ok { + if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) database.Talkgroup); ok { r0 = rf(ctx, arg) } else { - r0 = ret.Get(0).(int) + r0 = ret.Get(0).(database.Talkgroup) } if rf, ok := ret.Get(1).(func(context.Context, database.AddLearnedTalkgroupParams) error); ok { @@ -169,60 +169,12 @@ func (_c *Store_AddLearnedTalkgroup_Call) Run(run func(ctx context.Context, arg return _c } -func (_c *Store_AddLearnedTalkgroup_Call) Return(_a0 int, _a1 error) *Store_AddLearnedTalkgroup_Call { +func (_c *Store_AddLearnedTalkgroup_Call) Return(_a0 database.Talkgroup, _a1 error) *Store_AddLearnedTalkgroup_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *Store_AddLearnedTalkgroup_Call) RunAndReturn(run func(context.Context, database.AddLearnedTalkgroupParams) (int, error)) *Store_AddLearnedTalkgroup_Call { - _c.Call.Return(run) - return _c -} - -// AddTalkgroupWithLearnedFlag provides a mock function with given fields: ctx, systemID, tGID -func (_m *Store) AddTalkgroupWithLearnedFlag(ctx context.Context, systemID int32, tGID int32) error { - ret := _m.Called(ctx, systemID, tGID) - - if len(ret) == 0 { - panic("no return value specified for AddTalkgroupWithLearnedFlag") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int32, int32) error); ok { - r0 = rf(ctx, systemID, tGID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Store_AddTalkgroupWithLearnedFlag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddTalkgroupWithLearnedFlag' -type Store_AddTalkgroupWithLearnedFlag_Call struct { - *mock.Call -} - -// AddTalkgroupWithLearnedFlag is a helper method to define mock.On call -// - ctx context.Context -// - systemID int32 -// - tGID int32 -func (_e *Store_Expecter) AddTalkgroupWithLearnedFlag(ctx interface{}, systemID interface{}, tGID interface{}) *Store_AddTalkgroupWithLearnedFlag_Call { - return &Store_AddTalkgroupWithLearnedFlag_Call{Call: _e.mock.On("AddTalkgroupWithLearnedFlag", ctx, systemID, tGID)} -} - -func (_c *Store_AddTalkgroupWithLearnedFlag_Call) Run(run func(ctx context.Context, systemID int32, tGID int32)) *Store_AddTalkgroupWithLearnedFlag_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int32), args[2].(int32)) - }) - return _c -} - -func (_c *Store_AddTalkgroupWithLearnedFlag_Call) Return(_a0 error) *Store_AddTalkgroupWithLearnedFlag_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Store_AddTalkgroupWithLearnedFlag_Call) RunAndReturn(run func(context.Context, int32, int32) error) *Store_AddTalkgroupWithLearnedFlag_Call { +func (_c *Store_AddLearnedTalkgroup_Call) RunAndReturn(run func(context.Context, database.AddLearnedTalkgroupParams) (database.Talkgroup, error)) *Store_AddLearnedTalkgroup_Call { _c.Call.Return(run) return _c } @@ -1569,6 +1521,63 @@ func (_c *Store_InTx_Call) RunAndReturn(run func(context.Context, func(database. return _c } +// RestoreTalkgroupVersion provides a mock function with given fields: ctx, versionIds +func (_m *Store) RestoreTalkgroupVersion(ctx context.Context, versionIds int) (database.Talkgroup, error) { + ret := _m.Called(ctx, versionIds) + + if len(ret) == 0 { + panic("no return value specified for RestoreTalkgroupVersion") + } + + var r0 database.Talkgroup + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) (database.Talkgroup, error)); ok { + return rf(ctx, versionIds) + } + if rf, ok := ret.Get(0).(func(context.Context, int) database.Talkgroup); ok { + r0 = rf(ctx, versionIds) + } else { + r0 = ret.Get(0).(database.Talkgroup) + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(ctx, versionIds) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Store_RestoreTalkgroupVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestoreTalkgroupVersion' +type Store_RestoreTalkgroupVersion_Call struct { + *mock.Call +} + +// RestoreTalkgroupVersion is a helper method to define mock.On call +// - ctx context.Context +// - versionIds int +func (_e *Store_Expecter) RestoreTalkgroupVersion(ctx interface{}, versionIds interface{}) *Store_RestoreTalkgroupVersion_Call { + return &Store_RestoreTalkgroupVersion_Call{Call: _e.mock.On("RestoreTalkgroupVersion", ctx, versionIds)} +} + +func (_c *Store_RestoreTalkgroupVersion_Call) Run(run func(ctx context.Context, versionIds int)) *Store_RestoreTalkgroupVersion_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int)) + }) + return _c +} + +func (_c *Store_RestoreTalkgroupVersion_Call) Return(_a0 database.Talkgroup, _a1 error) *Store_RestoreTalkgroupVersion_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Store_RestoreTalkgroupVersion_Call) RunAndReturn(run func(context.Context, int) (database.Talkgroup, error)) *Store_RestoreTalkgroupVersion_Call { + _c.Call.Return(run) + return _c +} + // SetCallTranscript provides a mock function with given fields: ctx, iD, transcript func (_m *Store) SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error { ret := _m.Called(ctx, iD, transcript) @@ -1666,6 +1675,55 @@ func (_c *Store_SetTalkgroupTags_Call) RunAndReturn(run func(context.Context, [] return _c } +// StoreTGVersion provides a mock function with given fields: ctx, arg +func (_m *Store) StoreTGVersion(ctx context.Context, arg []database.StoreTGVersionParams) *database.StoreTGVersionBatchResults { + ret := _m.Called(ctx, arg) + + if len(ret) == 0 { + panic("no return value specified for StoreTGVersion") + } + + var r0 *database.StoreTGVersionBatchResults + if rf, ok := ret.Get(0).(func(context.Context, []database.StoreTGVersionParams) *database.StoreTGVersionBatchResults); ok { + r0 = rf(ctx, arg) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*database.StoreTGVersionBatchResults) + } + } + + return r0 +} + +// Store_StoreTGVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StoreTGVersion' +type Store_StoreTGVersion_Call struct { + *mock.Call +} + +// StoreTGVersion is a helper method to define mock.On call +// - ctx context.Context +// - arg []database.StoreTGVersionParams +func (_e *Store_Expecter) StoreTGVersion(ctx interface{}, arg interface{}) *Store_StoreTGVersion_Call { + return &Store_StoreTGVersion_Call{Call: _e.mock.On("StoreTGVersion", ctx, arg)} +} + +func (_c *Store_StoreTGVersion_Call) Run(run func(ctx context.Context, arg []database.StoreTGVersionParams)) *Store_StoreTGVersion_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]database.StoreTGVersionParams)) + }) + return _c +} + +func (_c *Store_StoreTGVersion_Call) Return(_a0 *database.StoreTGVersionBatchResults) *Store_StoreTGVersion_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Store_StoreTGVersion_Call) RunAndReturn(run func(context.Context, []database.StoreTGVersionParams) *database.StoreTGVersionBatchResults) *Store_StoreTGVersion_Call { + _c.Call.Return(run) + return _c +} + // UpdatePassword provides a mock function with given fields: ctx, username, password func (_m *Store) UpdatePassword(ctx context.Context, username string, password string) error { ret := _m.Called(ctx, username, password) diff --git a/pkg/database/models.go b/pkg/database/models.go index f756897..cc46a39 100644 --- a/pkg/database/models.go +++ b/pkg/database/models.go @@ -96,16 +96,26 @@ type Talkgroup struct { AlertConfig rules.AlertRules `json:"alert_config"` Weight float32 `json:"weight"` Learned bool `json:"learned"` + Ignored bool `json:"ignored"` } -type TalkgroupsLearned struct { - ID int `json:"id"` - SystemID int `json:"system_id"` - TGID int `json:"tgid"` - Name string `json:"name"` - AlphaTag *string `json:"alpha_tag"` - TGGroup *string `json:"tg_group"` - Ignored *bool `json:"ignored"` +type TalkgroupVersion struct { + ID int `json:"id"` + Time pgtype.Timestamptz `json:"time"` + CreatedBy *int32 `json:"created_by"` + SystemID *int32 `json:"system_id"` + TGID *int32 `json:"tgid"` + Name *string `json:"name"` + AlphaTag *string `json:"alpha_tag"` + TGGroup *string `json:"tg_group"` + Frequency *int32 `json:"frequency"` + Metadata []byte `json:"metadata"` + Tags []string `json:"tags"` + Alert *bool `json:"alert"` + AlertConfig []byte `json:"alert_config"` + Weight *float32 `json:"weight"` + Learned *bool `json:"learned"` + Ignored *bool `json:"ignored"` } type User struct { diff --git a/pkg/database/querier.go b/pkg/database/querier.go index 43849db..c9c531b 100644 --- a/pkg/database/querier.go +++ b/pkg/database/querier.go @@ -14,8 +14,7 @@ import ( type Querier interface { AddAlert(ctx context.Context, arg AddAlertParams) error AddCall(ctx context.Context, arg AddCallParams) error - AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (int, error) - AddTalkgroupWithLearnedFlag(ctx context.Context, systemID int32, tGID int32) error + AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error) CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) DeleteAPIKey(ctx context.Context, apiKey string) error @@ -35,8 +34,10 @@ type Querier interface { GetUserByUID(ctx context.Context, id int) (User, error) GetUserByUsername(ctx context.Context, username string) (User, error) GetUsers(ctx context.Context) ([]User, error) + RestoreTalkgroupVersion(ctx context.Context, versionIds int) (Talkgroup, error) SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error SetTalkgroupTags(ctx context.Context, tags []string, systemID int32, tGID int32) error + StoreTGVersion(ctx context.Context, arg []StoreTGVersionParams) *StoreTGVersionBatchResults UpdatePassword(ctx context.Context, username string, password string) error UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error) UpsertTalkgroup(ctx context.Context, arg []UpsertTalkgroupParams) *UpsertTalkgroupBatchResults diff --git a/pkg/database/talkgroups.go b/pkg/database/talkgroups.go index ef8f8a6..0773abb 100644 --- a/pkg/database/talkgroups.go +++ b/pkg/database/talkgroups.go @@ -41,20 +41,11 @@ func (t *TGTuples) Append(sys, tg uint32) { // Below queries are here because sqlc refuses to parse unnest(x, y) const getTalkgroupsWithLearnedBySysTGID = `SELECT -tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name, tg.learned +tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name, tg.learned, tg.ignored FROM talkgroups tg JOIN systems sys ON tg.system_id = sys.id JOIN UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) ON (tg.system_id = tgt.sys AND tg.tgid = tgt.tg) -WHERE tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, sys.id, sys.name, TRUE learned -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id -JOIN UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) ON (tgl.system_id = tgt.sys AND tgl.tgid = tgt.tg);` +WHERE tg.learned IS NOT TRUE;` type GetTalkgroupsRow struct { Talkgroup Talkgroup `json:"talkgroup"` @@ -86,6 +77,7 @@ func (q *Queries) GetTalkgroupsWithLearnedBySysTGID(ctx context.Context, ids TGT &i.System.ID, &i.System.Name, &i.Talkgroup.Learned, + &i.Talkgroup.Ignored, ); err != nil { return nil, err } @@ -97,7 +89,7 @@ func (q *Queries) GetTalkgroupsWithLearnedBySysTGID(ctx context.Context, ids TGT return items, nil } -const getTalkgroupsBySysTGID = `SELECT tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name FROM talkgroups tg +const getTalkgroupsBySysTGID = `SELECT tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name FROM talkgroups tg JOIN systems sys ON tg.system_id = sys.id JOIN UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) ON (tg.system_id = tgt.sys AND tg.tgid = tgt.tg) WHERE tg.learned IS NOT TRUE;` @@ -124,6 +116,8 @@ func (q *Queries) GetTalkgroupsBySysTGID(ctx context.Context, ids TGTuples) ([]G &i.Talkgroup.Alert, &i.Talkgroup.AlertConfig, &i.Talkgroup.Weight, + &i.Talkgroup.Learned, + &i.Talkgroup.Ignored, &i.System.ID, &i.System.Name, ); err != nil { diff --git a/pkg/database/talkgroups.sql.go b/pkg/database/talkgroups.sql.go index c620d38..ff41080 100644 --- a/pkg/database/talkgroups.sql.go +++ b/pkg/database/talkgroups.sql.go @@ -13,30 +13,32 @@ import ( ) const addLearnedTalkgroup = `-- name: AddLearnedTalkgroup :one -INSERT INTO talkgroups_learned( +INSERT INTO talkgroups( system_id, tgid, + learned, name, alpha_tag, tg_group ) VALUES ( $1, $2, + TRUE, $3, $4, $5 -) RETURNING id +) RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored ` type AddLearnedTalkgroupParams struct { - SystemID int `json:"system_id"` - TGID int `json:"tgid"` + SystemID int32 `json:"system_id"` + TGID int32 `json:"tgid"` Name *string `json:"name"` AlphaTag *string `json:"alpha_tag"` TGGroup *string `json:"tg_group"` } -func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (int, error) { +func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error) { row := q.db.QueryRow(ctx, addLearnedTalkgroup, arg.SystemID, arg.TGID, @@ -44,26 +46,24 @@ func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgro arg.AlphaTag, arg.TGGroup, ) - var id int - err := row.Scan(&id) - return id, err -} - -const addTalkgroupWithLearnedFlag = `-- name: AddTalkgroupWithLearnedFlag :exec -INSERT INTO talkgroups ( - system_id, - tgid, - learned -) VALUES( - $1, - $2, - TRUE -) -` - -func (q *Queries) AddTalkgroupWithLearnedFlag(ctx context.Context, systemID int32, tGID int32) error { - _, err := q.db.Exec(ctx, addTalkgroupWithLearnedFlag, systemID, tGID) - return err + var i Talkgroup + err := row.Scan( + &i.ID, + &i.SystemID, + &i.TGID, + &i.Name, + &i.AlphaTag, + &i.TGGroup, + &i.Frequency, + &i.Metadata, + &i.Tags, + &i.Alert, + &i.AlertConfig, + &i.Weight, + &i.Learned, + &i.Ignored, + ) + return i, err } const getSystemName = `-- name: GetSystemName :one @@ -78,7 +78,7 @@ func (q *Queries) GetSystemName(ctx context.Context, systemID int) (string, erro } const getTalkgroup = `-- name: GetTalkgroup :one -SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned FROM talkgroups +SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned, talkgroups.ignored FROM talkgroups WHERE (system_id, tgid) = ($1, $2) ` @@ -103,6 +103,7 @@ func (q *Queries) GetTalkgroup(ctx context.Context, systemID int32, tGID int32) &i.Talkgroup.AlertConfig, &i.Talkgroup.Weight, &i.Talkgroup.Learned, + &i.Talkgroup.Ignored, ) return i, err } @@ -153,19 +154,10 @@ func (q *Queries) GetTalkgroupTags(ctx context.Context, systemID int32, tGID int const getTalkgroupWithLearned = `-- name: GetTalkgroupWithLearned :one SELECT -tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name +tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name FROM talkgroups tg JOIN systems sys ON tg.system_id = sys.id -WHERE (tg.system_id, tg.tgid) = ($1, $2) AND tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id -WHERE tgl.system_id = $1 AND tgl.tgid = $2 AND ignored IS NOT TRUE +WHERE (tg.system_id, tg.tgid) = ($1, $2) ` type GetTalkgroupWithLearnedRow struct { @@ -190,6 +182,7 @@ func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int32, t &i.Talkgroup.AlertConfig, &i.Talkgroup.Weight, &i.Talkgroup.Learned, + &i.Talkgroup.Ignored, &i.System.ID, &i.System.Name, ) @@ -197,7 +190,7 @@ func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int32, t } const getTalkgroupsWithAllTags = `-- name: GetTalkgroupsWithAllTags :many -SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned FROM talkgroups +SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned, talkgroups.ignored FROM talkgroups WHERE tags && ARRAY[$1] ` @@ -228,6 +221,7 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) ( &i.Talkgroup.AlertConfig, &i.Talkgroup.Weight, &i.Talkgroup.Learned, + &i.Talkgroup.Ignored, ); err != nil { return nil, err } @@ -240,7 +234,7 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) ( } const getTalkgroupsWithAnyTags = `-- name: GetTalkgroupsWithAnyTags :many -SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned FROM talkgroups +SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned, talkgroups.ignored FROM talkgroups WHERE tags @> ARRAY[$1] ` @@ -271,6 +265,7 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) ( &i.Talkgroup.AlertConfig, &i.Talkgroup.Weight, &i.Talkgroup.Learned, + &i.Talkgroup.Ignored, ); err != nil { return nil, err } @@ -284,18 +279,9 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) ( const getTalkgroupsWithLearned = `-- name: GetTalkgroupsWithLearned :many SELECT -tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name +tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name FROM talkgroups tg JOIN systems sys ON tg.system_id = sys.id -WHERE tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id WHERE ignored IS NOT TRUE ` @@ -327,6 +313,7 @@ func (q *Queries) GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroups &i.Talkgroup.AlertConfig, &i.Talkgroup.Weight, &i.Talkgroup.Learned, + &i.Talkgroup.Ignored, &i.System.ID, &i.System.Name, ); err != nil { @@ -342,19 +329,10 @@ func (q *Queries) GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroups const getTalkgroupsWithLearnedBySystem = `-- name: GetTalkgroupsWithLearnedBySystem :many SELECT -tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name +tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name FROM talkgroups tg JOIN systems sys ON tg.system_id = sys.id -WHERE tg.system_id = $1 AND tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id -WHERE tgl.system_id = $1 AND ignored IS NOT TRUE +WHERE tg.system_id = $1 ` type GetTalkgroupsWithLearnedBySystemRow struct { @@ -385,6 +363,7 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i &i.Talkgroup.AlertConfig, &i.Talkgroup.Weight, &i.Talkgroup.Learned, + &i.Talkgroup.Ignored, &i.System.ID, &i.System.Name, ); err != nil { @@ -398,6 +377,73 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i return items, nil } +const restoreTalkgroupVersion = `-- name: RestoreTalkgroupVersion :one +INSERT INTO talkgroups( + system_id, + tgid, + name, + alpha_tag, + tg_group, + frequency, + metadata, + tags, + alert, + alert_config, + weight, + learned, + ignored +) +SELECT + system_id, + tgid, + name, + alpha_tag, + tg_group, + frequency, + metadata, + tags, + alert, + alert_config, + weight, + learned, + ignored +FROM talkgroup_versions tgv ON CONFLICT (system_id, tgid) DO UPDATE SET + name = excluded.name, + alpha_tag = excluded.alpha_tag, + tg_group = excluded.tg_group, + metadata = excluded.metadata, + tags = excluded.tags, + alert = excluded.alert, + alert_config = excluded.alert_config, + weight = excluded.weight, + learned = excluded.learner, + ignored = excluded.ignored +WHERE tgv.id = ANY($1) +RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored +` + +func (q *Queries) RestoreTalkgroupVersion(ctx context.Context, versionIds int) (Talkgroup, error) { + row := q.db.QueryRow(ctx, restoreTalkgroupVersion, versionIds) + var i Talkgroup + err := row.Scan( + &i.ID, + &i.SystemID, + &i.TGID, + &i.Name, + &i.AlphaTag, + &i.TGGroup, + &i.Frequency, + &i.Metadata, + &i.Tags, + &i.Alert, + &i.AlertConfig, + &i.Weight, + &i.Learned, + &i.Ignored, + ) + return i, err +} + const setTalkgroupTags = `-- name: SetTalkgroupTags :exec UPDATE talkgroups SET tags = $1 WHERE system_id = $2 AND tgid = $3 @@ -422,7 +468,7 @@ SET weight = COALESCE($9, weight), learned = COALESCE($10, learned) WHERE id = $11 OR (system_id = $12 AND tgid = $13) -RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned +RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored ` type UpdateTalkgroupParams struct { @@ -472,6 +518,7 @@ func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams &i.AlertConfig, &i.Weight, &i.Learned, + &i.Ignored, ) return i, err } diff --git a/pkg/database/talkgroups.sql_test.go b/pkg/database/talkgroups.sql_test.go deleted file mode 100644 index a15f07e..0000000 --- a/pkg/database/talkgroups.sql_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package database - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -const getTalkgroupWithLearnedTest = `-- name: GetTalkgroupWithLearned :one -SELECT -tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name -FROM talkgroups tg -JOIN systems sys ON tg.system_id = sys.id -WHERE (tg.system_id, tg.tgid) = ($1, $2) AND tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id -WHERE tgl.system_id = $1 AND tgl.tgid = $2 AND ignored IS NOT TRUE -` - -const getTalkgroupsWithLearnedBySystemTest = `-- name: GetTalkgroupsWithLearnedBySystem :many -SELECT -tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name -FROM talkgroups tg -JOIN systems sys ON tg.system_id = sys.id -WHERE tg.system_id = $1 AND tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id -WHERE tgl.system_id = $1 AND ignored IS NOT TRUE -` - -const getTalkgroupsWithLearnedTest = `-- name: GetTalkgroupsWithLearned :many -SELECT -tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name -FROM talkgroups tg -JOIN systems sys ON tg.system_id = sys.id -WHERE tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id -WHERE ignored IS NOT TRUE -` - -func TestQueryColumnsMatch(t *testing.T) { - assert.Equal(t, getTalkgroupWithLearnedTest, getTalkgroupWithLearned) - assert.Equal(t, getTalkgroupsWithLearnedBySystemTest, getTalkgroupsWithLearnedBySystem) - assert.Equal(t, getTalkgroupsWithLearnedTest, getTalkgroupsWithLearned) -} diff --git a/pkg/nexus/commands.go b/pkg/nexus/commands.go index 4c4bcaa..37e06a8 100644 --- a/pkg/nexus/commands.go +++ b/pkg/nexus/commands.go @@ -6,6 +6,7 @@ import ( "dynatron.me/x/stillbox/pkg/calls" "dynatron.me/x/stillbox/pkg/pb" "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "github.com/rs/zerolog/log" "google.golang.org/protobuf/types/known/structpb" @@ -59,9 +60,9 @@ func (c *client) SendError(cmd *pb.Command, err error) { } func (c *client) Talkgroup(ctx context.Context, tg *pb.Talkgroup) error { - tgi, err := talkgroups.StoreFrom(ctx).TG(ctx, talkgroups.TG(tg.System, tg.Talkgroup)) + tgi, err := tgstore.FromCtx(ctx).TG(ctx, talkgroups.TG(tg.System, tg.Talkgroup)) if err != nil { - if err != talkgroups.ErrNotFound { + if err != tgstore.ErrNotFound { log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("get talkgroup fail") } return err diff --git a/pkg/rest/api.go b/pkg/rest/api.go index 3ed9adc..a55afdf 100644 --- a/pkg/rest/api.go +++ b/pkg/rest/api.go @@ -4,7 +4,7 @@ import ( "errors" "net/http" - "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "github.com/go-chi/chi/v5" "github.com/go-chi/render" @@ -67,6 +67,15 @@ func recordNotFound(err error) render.Renderer { } } +func errTextNotFound(err error) render.Renderer { + return &errResponse{ + Err: err, + Code: http.StatusNotFound, + Error: "Record not found: " + err.Error(), + } +} + + func internalError(err error) render.Renderer { return &errResponse{ Err: err, @@ -78,8 +87,8 @@ func internalError(err error) render.Renderer { type errResponder func(error) render.Renderer var statusMapping = map[error]errResponder{ - talkgroups.ErrNoSuchSystem: recordNotFound, - talkgroups.ErrNotFound: recordNotFound, + tgstore.ErrNoSuchSystem: errTextNotFound, + tgstore.ErrNotFound: errTextNotFound, pgx.ErrNoRows: recordNotFound, } diff --git a/pkg/rest/talkgroups.go b/pkg/rest/talkgroups.go index 403f523..e9c4fc5 100644 --- a/pkg/rest/talkgroups.go +++ b/pkg/rest/talkgroups.go @@ -6,6 +6,7 @@ import ( "dynatron.me/x/stillbox/internal/forms" "dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "dynatron.me/x/stillbox/pkg/talkgroups/importer" "github.com/go-chi/chi/v5" @@ -53,7 +54,7 @@ func (t tgParams) ToID() talkgroups.ID { func (tga *talkgroupAPI) get(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - tgs := talkgroups.StoreFrom(ctx) + tgs := tgstore.FromCtx(ctx) var p tgParams @@ -91,7 +92,7 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - tgs := talkgroups.StoreFrom(ctx) + tgs := tgstore.FromCtx(ctx) input := database.UpdateTalkgroupParams{} @@ -137,12 +138,12 @@ func (tga *talkgroupAPI) putTalkgroups(w http.ResponseWriter, r *http.Request) { } if id.System == nil { // don't think this would ever happen - wErr(w, r, badRequest(talkgroups.ErrNoSuchSystem)) + wErr(w, r, badRequest(tgstore.ErrNoSuchSystem)) return } ctx := r.Context() - tgs := talkgroups.StoreFrom(ctx) + tgs := tgstore.FromCtx(ctx) var input []database.UpsertTalkgroupParams diff --git a/pkg/server/routes.go b/pkg/server/routes.go index b296d6b..76ba310 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -9,7 +9,7 @@ import ( "dynatron.me/x/stillbox/internal/version" "dynatron.me/x/stillbox/pkg/config" "dynatron.me/x/stillbox/pkg/database" - "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/httprate" @@ -28,7 +28,7 @@ func (s *Server) setupRoutes() { r := s.r r.Use(middleware.WithValue(database.DBCtxKey, s.db)) - r.Use(middleware.WithValue(talkgroups.StoreCtxKey, s.tgs)) + r.Use(middleware.WithValue(tgstore.StoreCtxKey, s.tgs)) s.installPprof() diff --git a/pkg/server/server.go b/pkg/server/server.go index bc55ae3..b14778f 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -15,7 +15,8 @@ import ( "dynatron.me/x/stillbox/pkg/rest" "dynatron.me/x/stillbox/pkg/sinks" "dynatron.me/x/stillbox/pkg/sources" - "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" + "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" @@ -37,7 +38,7 @@ type Server struct { alerter alerting.Alerter notifier notify.Notifier hup chan os.Signal - tgs talkgroups.Store + tgs tgstore.Store rest rest.API } @@ -61,7 +62,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) { return nil, err } - tgCache := talkgroups.NewCache() + tgCache := tgstore.NewCache() api := rest.New() srv := &Server{ @@ -78,7 +79,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) { rest: api, } - srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db), true) + srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db, tgCache), true) srv.sinks.Register("nexus", sinks.NewNexusSink(srv.nex), false) if srv.alerter.Enabled() { @@ -117,7 +118,7 @@ func (s *Server) Go(ctx context.Context) error { s.installHupHandler() ctx = database.CtxWithDB(ctx, s.db) - ctx = talkgroups.CtxWithStore(ctx, s.tgs) + ctx = tgstore.CtxWithStore(ctx, s.tgs) httpSrv := &http.Server{ Addr: s.conf.Listen, diff --git a/pkg/sinks/database.go b/pkg/sinks/database.go index 9ced102..98ea90c 100644 --- a/pkg/sinks/database.go +++ b/pkg/sinks/database.go @@ -7,6 +7,7 @@ import ( "dynatron.me/x/stillbox/internal/common" "dynatron.me/x/stillbox/pkg/calls" "dynatron.me/x/stillbox/pkg/database" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -15,10 +16,11 @@ import ( type DatabaseSink struct { db database.Store + tgs tgstore.Store } -func NewDatabaseSink(store database.Store) *DatabaseSink { - return &DatabaseSink{store} +func NewDatabaseSink(store database.Store, tgs tgstore.Store) *DatabaseSink { + return &DatabaseSink{store, tgs} } func (s *DatabaseSink) Call(ctx context.Context, call *calls.Call) error { @@ -43,14 +45,14 @@ func (s *DatabaseSink) Call(ctx context.Context, call *calls.Call) error { if err != nil && database.IsTGConstraintViolation(err) { return s.db.InTx(ctx, func(tx database.Store) error { - _, err := call.LearnTG(ctx, tx) + _, err := s.tgs.LearnTG(ctx, call) if err != nil { - return fmt.Errorf("add call: learn tg: %w", err) + return fmt.Errorf("learn tg: %w", err) } err = tx.AddCall(ctx, params) if err != nil { - return fmt.Errorf("add call: retry: %w", err) + return fmt.Errorf("learn tg retry: %w", err) } return nil diff --git a/pkg/sources/http.go b/pkg/sources/http.go index 7c30347..6255a03 100644 --- a/pkg/sources/http.go +++ b/pkg/sources/http.go @@ -131,7 +131,7 @@ func (h *RdioHTTP) routeCallUpload(w http.ResponseWriter, r *http.Request) { return } - log.Info().Int("system", cur.System).Int("tgid", cur.Talkgroup).Msg("ingested") + log.Info().Int("system", cur.System).Int("tgid", cur.Talkgroup).Str("duration", call.Duration.Duration().String()).Msg("ingested") written, err := w.Write([]byte("Call imported successfully.")) if err != nil { diff --git a/pkg/talkgroups/importer/import.go b/pkg/talkgroups/importer/import.go index c765b78..347c25b 100644 --- a/pkg/talkgroups/importer/import.go +++ b/pkg/talkgroups/importer/import.go @@ -13,6 +13,7 @@ import ( "dynatron.me/x/stillbox/internal/jsontypes" "dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/talkgroups" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" ) type ImportSource string @@ -66,9 +67,9 @@ var rrRE = regexp.MustCompile(`DEC\s+HEX\s+Mode\s+Alpha Tag\s+Description\s+Tag` func (rr *radioReferenceImporter) importTalkgroups(ctx context.Context, sys int, r io.Reader) ([]talkgroups.Talkgroup, error) { sc := bufio.NewScanner(r) tgs := make([]talkgroups.Talkgroup, 0, 8) - sysn, has := talkgroups.StoreFrom(ctx).SystemName(ctx, sys) + sysn, has := tgstore.FromCtx(ctx).SystemName(ctx, sys) if !has { - return nil, talkgroups.ErrNoSuchSystem + return nil, tgstore.ErrNoSuchSystem } var groupName string diff --git a/pkg/talkgroups/importer/import_test.go b/pkg/talkgroups/importer/import_test.go index 980d5e9..b82bed9 100644 --- a/pkg/talkgroups/importer/import_test.go +++ b/pkg/talkgroups/importer/import_test.go @@ -16,6 +16,7 @@ import ( "dynatron.me/x/stillbox/pkg/database/mocks" "dynatron.me/x/stillbox/pkg/talkgroups" "dynatron.me/x/stillbox/pkg/talkgroups/importer" + "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" ) func getFixture(fixture string) []byte { @@ -62,7 +63,7 @@ func TestImport(t *testing.T) { dbMock.EXPECT().GetSystemName(mock.AnythingOfType("*context.valueCtx"), tc.sysID).Return(tc.sysName, nil) } ctx := database.CtxWithDB(context.Background(), dbMock) - ctx = talkgroups.CtxWithStore(ctx, talkgroups.NewCache()) + ctx = tgstore.CtxWithStore(ctx, tgstore.NewCache()) ij := &importer.ImportJob{ Type: importer.ImportSource(tc.impType), SystemID: tc.sysID, diff --git a/pkg/talkgroups/importer/testdata/riscon.json b/pkg/talkgroups/importer/testdata/riscon.json index 1131908..9651b05 100644 --- a/pkg/talkgroups/importer/testdata/riscon.json +++ b/pkg/talkgroups/importer/testdata/riscon.json @@ -1 +1 @@ -[{"id":0,"system_id":197,"tgid":2,"name":"Intercity Fire","alpha_tag":"Intercity FD","tg_group":"Statewide Mutual Aid/Intersystem","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":1,"system_id":197,"tgid":3,"name":"Intercity Police","alpha_tag":"Intercity PD","tg_group":"Statewide Mutual Aid/Intersystem","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":2,"system_id":197,"tgid":21,"name":"North Dispatch ","alpha_tag":"RISP N Disp","tg_group":"State Police - District A (North)","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":3,"system_id":197,"tgid":22,"name":"North Car-to-Car/Information","alpha_tag":"RISP N Car","tg_group":"State Police - District A (North)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":4,"system_id":197,"tgid":24,"name":"North Tactical Ops 1","alpha_tag":"RISP N Tac 1","tg_group":"State Police - District A (North)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":5,"system_id":197,"tgid":23,"name":"North Tactical Ops 2","alpha_tag":"RISP N Tac 2","tg_group":"State Police - District A (North)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":6,"system_id":197,"tgid":25,"name":"South Dispatch ","alpha_tag":"RISP S Disp","tg_group":"State Police - District B (South)","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":7,"system_id":197,"tgid":27,"name":"South Car-to-Car/Information","alpha_tag":"RISP S Car","tg_group":"State Police - District B (South)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":8,"system_id":197,"tgid":16,"name":"State Fire Marshall","alpha_tag":"State FMO","tg_group":"Statewide Fire","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":9,"system_id":197,"tgid":1038,"name":"Northern Rhode Island Fire Chiefs","alpha_tag":"NRI Fire Chi","tg_group":"Statewide Fire","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":10,"system_id":197,"tgid":1041,"name":"Southern Rhode Island Fire Chiefs","alpha_tag":"SRI Fire Chi","tg_group":"Statewide Fire","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":11,"system_id":197,"tgid":1314,"name":"Tanker Taskforce 1","alpha_tag":"Tanker TF 1","tg_group":"Statewide Fire","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":12,"system_id":197,"tgid":194,"name":"Lifepact Ambulance (Statewide)","alpha_tag":"Lifepact Amb","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":13,"system_id":197,"tgid":212,"name":"Fatima St Josephs","alpha_tag":"Fatima-St Joes","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["Business"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":14,"system_id":197,"tgid":220,"name":"Health 1","alpha_tag":"Health 1","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":{"encrypted":true},"tags":["EMS-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":15,"system_id":197,"tgid":221,"name":"Health 2","alpha_tag":"Health 2","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":{"encrypted":true},"tags":["EMS-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":16,"system_id":197,"tgid":222,"name":"Department of Health - Statewide","alpha_tag":"Dept of HealthSW","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["EMS-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":17,"system_id":197,"tgid":228,"name":"DMAT South","alpha_tag":"DMAT South","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":{"encrypted":true},"tags":["Emergency Ops"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":18,"system_id":197,"tgid":232,"name":"Life Span Net 1","alpha_tag":"Life Span 1","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["EMS-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":19,"system_id":197,"tgid":234,"name":"RI Hospital Operations","alpha_tag":"RI Hosp Ops","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["Business"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":20,"system_id":197,"tgid":120,"name":"Law Enforcement Operations","alpha_tag":"DEM PD Ops","tg_group":"Department of Environmental Management","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":21,"system_id":197,"tgid":122,"name":"Law Enforcement Police","alpha_tag":"DEM Police","tg_group":"Department of Environmental Management","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":22,"system_id":197,"tgid":10,"name":"Emergency Management Agency 1","alpha_tag":"EMA-1","tg_group":"Emergency Management Agency","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":23,"system_id":197,"tgid":20,"name":"Emergency Management Agency","alpha_tag":"EMA","tg_group":"Emergency Management Agency","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":24,"system_id":197,"tgid":4,"name":"Wide Area 3","alpha_tag":"Wide Area 3","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":25,"system_id":197,"tgid":5,"name":"Wide Area 4","alpha_tag":"Wide Area 4","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":26,"system_id":197,"tgid":6,"name":"Wide Area 5","alpha_tag":"Wide Area 5","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":27,"system_id":197,"tgid":7,"name":"Wide Area 6","alpha_tag":"Wide Area 6","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":28,"system_id":197,"tgid":1018,"name":"Southwide CH-1","alpha_tag":"SOUTHWIDE 1","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":29,"system_id":197,"tgid":1019,"name":"Southwide CH-2","alpha_tag":"SOUTHWIDE 2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":30,"system_id":197,"tgid":1022,"name":"Wide Area 7","alpha_tag":"WIDE AREA 7","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":31,"system_id":197,"tgid":1023,"name":"Wide Area 8","alpha_tag":"WIDE AREA 8","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":32,"system_id":197,"tgid":1025,"name":"Inland Marine Interop","alpha_tag":"Inland Marine IO","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":33,"system_id":197,"tgid":1037,"name":"Southside CH 5","alpha_tag":"SOUTHSIDE 5","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":34,"system_id":197,"tgid":1173,"name":"North Wide 1","alpha_tag":"NORTHWIDE1","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":35,"system_id":197,"tgid":1174,"name":"North Wide 2","alpha_tag":"NORTHWIDE2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":36,"system_id":197,"tgid":1177,"name":"North Wide 5","alpha_tag":"NORTHWIDE5","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":37,"system_id":197,"tgid":1185,"name":"Metro Wide 1","alpha_tag":"METROWIDE1","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":38,"system_id":197,"tgid":1186,"name":"Metro Wide 2","alpha_tag":"METROWIDE2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":39,"system_id":197,"tgid":1187,"name":"Metro Wide 3","alpha_tag":"METROWIDE3","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":40,"system_id":197,"tgid":1335,"name":"East Wide 1","alpha_tag":"EASTWIDE 1","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":41,"system_id":197,"tgid":1336,"name":"East Wide 2","alpha_tag":"EASTWIDE 2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":42,"system_id":197,"tgid":1337,"name":"East Wide 3","alpha_tag":"EASTWIDE 3","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":43,"system_id":197,"tgid":11186,"name":"Metro Wide 2","alpha_tag":"METROWIDE2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":44,"system_id":197,"tgid":1033,"name":"Tanker Taskforce ","alpha_tag":"TANK TF","tg_group":"Statewide Emergency Response","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":45,"system_id":197,"tgid":1034,"name":"Hazmat 1","alpha_tag":"HZT DC1","tg_group":"Statewide Emergency Response","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":46,"system_id":197,"tgid":1035,"name":"Hazmat 2","alpha_tag":"HZT DC2","tg_group":"Statewide Emergency Response","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":47,"system_id":197,"tgid":176,"name":"Department of Transportation - Primary","alpha_tag":"RIDOT Primary","tg_group":"Department of Transportation","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":48,"system_id":197,"tgid":4421,"name":"Newport Pell Bridge Operations","alpha_tag":"RITBA - Pell Bdg","tg_group":"Tunnel and Bridge Authority","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":49,"system_id":197,"tgid":274,"name":"Providence VA Police","alpha_tag":"VA Police","tg_group":"Federal","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":50,"system_id":197,"tgid":186,"name":"Rhode Island Public Transit Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":{"encrypted":true},"tags":["Transportation"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":51,"system_id":197,"tgid":187,"name":"Rhode Island Public Transit Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":null,"tags":["Transportation"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":52,"system_id":197,"tgid":188,"name":"Rhode Island Public Transit Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":null,"tags":["Transportation"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":53,"system_id":197,"tgid":189,"name":"Rhode Island Public Transit Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":null,"tags":["Transportation"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":54,"system_id":197,"tgid":190,"name":"Rhode Island Public Transit. Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":null,"tags":["Transportation"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":55,"system_id":197,"tgid":304,"name":"Fire Operations","alpha_tag":"Quonset ANGB FD","tg_group":"Quonset ANGB","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":56,"system_id":197,"tgid":17,"name":"Airport Police Operations","alpha_tag":"TF Green PD","tg_group":"Rhode Island Airport Commission","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":57,"system_id":197,"tgid":19,"name":"Airport Fire Operations","alpha_tag":"TF Green FD","tg_group":"Rhode Island Airport Commission","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":58,"system_id":197,"tgid":1126,"name":"University of Rhode Island Police - Dispatch","alpha_tag":"URI PD","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":59,"system_id":197,"tgid":1131,"name":"University of Rhode Island - EMS","alpha_tag":"URI EMS","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["EMS Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":60,"system_id":197,"tgid":1348,"name":"St. George's School (Middletown) - Security","alpha_tag":"St George Sec","tg_group":"College/Education Security","frequency":null,"metadata":null,"tags":["Security"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":61,"system_id":197,"tgid":10228,"name":"Rhode Island School of Design - Security","alpha_tag":"RISD Secuty","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Security"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":62,"system_id":197,"tgid":10229,"name":"Providence College Security - Dispatch","alpha_tag":"PROV COLL","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Security"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":63,"system_id":197,"tgid":10230,"name":"Rhode Island College Security","alpha_tag":"RI COL SEC","tg_group":"College/Education Security","frequency":null,"metadata":null,"tags":["Security"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":64,"system_id":197,"tgid":11001,"name":"Brown University Police - Dispatch","alpha_tag":"BROWN UNIV","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":65,"system_id":197,"tgid":11002,"name":"Brown University Police - Car-to-Car","alpha_tag":"BROWN CAR","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":66,"system_id":197,"tgid":11003,"name":"Brown University Police - Tactical","alpha_tag":"BROWN TAC","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":67,"system_id":197,"tgid":12,"name":"Metro Wide 2","alpha_tag":"METROWIDE2","tg_group":"Statewide Misc.","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":68,"system_id":197,"tgid":14,"name":"Metro Wide 4","alpha_tag":"METROWIDE4","tg_group":"Statewide Misc.","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":69,"system_id":197,"tgid":70,"name":"RI Traffic Tribunal Security","alpha_tag":"TFC TRIBUNAL","tg_group":"Statewide Misc.","frequency":null,"metadata":{"encrypted":true},"tags":["Security"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":70,"system_id":197,"tgid":168,"name":"Rhode Island Red Cross - Primary","alpha_tag":"Red Cross 1","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Other"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":71,"system_id":197,"tgid":169,"name":"Rhode Island Red Cross - Secondary","alpha_tag":"Red Cross 2","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Other"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":72,"system_id":197,"tgid":223,"name":"Statewide Nursing Homes Net","alpha_tag":"NURSING HM","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Other"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":73,"system_id":197,"tgid":243,"name":"Hospital Operations","alpha_tag":"Slater Hosp Ops","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Business"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":74,"system_id":197,"tgid":244,"name":"Slater Hospital Security","alpha_tag":"Slater Hosp Sec","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Security"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":75,"system_id":197,"tgid":1042,"name":"County Fireground","alpha_tag":"WashCo FireG","tg_group":"Washington County","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":76,"system_id":197,"tgid":1479,"name":"County Fire Station/Station","alpha_tag":"WashCo FireS","tg_group":"Washington County","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":77,"system_id":197,"tgid":1712,"name":"Fire 1 Dispatch","alpha_tag":"BarringtnFD1","tg_group":"Barrington","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":78,"system_id":197,"tgid":1713,"name":"Fire 2","alpha_tag":"BarringtnFD2","tg_group":"Barrington","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":79,"system_id":197,"tgid":1715,"name":"Police Operations","alpha_tag":"BarringtonPD 1","tg_group":"Barrington","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":80,"system_id":197,"tgid":1716,"name":"Police Secondary","alpha_tag":"BarringtonPD 2","tg_group":"Barrington","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":81,"system_id":197,"tgid":1744,"name":"Fire Operations (Patch from VHF)","alpha_tag":"Bristol FD","tg_group":"Bristol","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":82,"system_id":197,"tgid":1755,"name":"Harbormaster","alpha_tag":"Bristol Harbor","tg_group":"Bristol","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":83,"system_id":197,"tgid":2003,"name":"Police","alpha_tag":"Burrville PD","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":84,"system_id":197,"tgid":2004,"name":"Police 2","alpha_tag":"Burrvl PD2","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":85,"system_id":197,"tgid":2005,"name":"Police 3 Detectives","alpha_tag":"Burrvl PD3","tg_group":"Burrillville","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":86,"system_id":197,"tgid":2006,"name":"Police 4","alpha_tag":"Burrvl PD4","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":87,"system_id":197,"tgid":2000,"name":"Fire Misc (Ops are VHF)","alpha_tag":"Burrvl FD","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":88,"system_id":197,"tgid":2001,"name":"Fire TAC-1","alpha_tag":"Burvl FDTAC1","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":89,"system_id":197,"tgid":2009,"name":"Fire TAC-2","alpha_tag":"Burvl FDTAC2","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":90,"system_id":197,"tgid":2002,"name":"EMS Misc (Ops are VHF)","alpha_tag":"Burrvl EMS","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["EMS-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":91,"system_id":197,"tgid":2007,"name":"Town-Wide","alpha_tag":"Burrvl Town","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Multi-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":92,"system_id":197,"tgid":2008,"name":"Emergency Management","alpha_tag":"Burrvl EMA","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":93,"system_id":197,"tgid":1838,"name":"Police 1 Dispatch","alpha_tag":"CentFallsPD1","tg_group":"Central Falls","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":94,"system_id":197,"tgid":1839,"name":"Police 2","alpha_tag":"CentFallsPD2","tg_group":"Central Falls","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":95,"system_id":197,"tgid":1835,"name":"Fire Dispatch (Simulcast of UHF)","alpha_tag":"CentFalls FD 1","tg_group":"Central Falls","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":96,"system_id":197,"tgid":1836,"name":"Fireground","alpha_tag":"CentFalls FD 2","tg_group":"Central Falls","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":97,"system_id":197,"tgid":1425,"name":"Police Operations - Simulcast of UHF","alpha_tag":"CharlestownPD","tg_group":"Charlestown","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":98,"system_id":197,"tgid":1429,"name":"EMS - Linked to 151.3325","alpha_tag":"Chastown EMS","tg_group":"Charlestown","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":99,"system_id":197,"tgid":1483,"name":"Police 1 - Dispatch","alpha_tag":"Coventry PD","tg_group":"Coventry","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":100,"system_id":197,"tgid":1484,"name":"Police 2","alpha_tag":"Coventry PD2","tg_group":"Coventry","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":101,"system_id":197,"tgid":1480,"name":"Fire","alpha_tag":"Coventry FD","tg_group":"Coventry","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":102,"system_id":197,"tgid":1500,"name":"Fire - Dispatch/Operations","alpha_tag":"Cranston FD Disp","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":103,"system_id":197,"tgid":1501,"name":"Fire - Fireground 2","alpha_tag":"Cranston FD FG2","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":104,"system_id":197,"tgid":1502,"name":"Fire - Fireground 3","alpha_tag":"Cranston FD FG3","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":105,"system_id":197,"tgid":1503,"name":"Fire - Fireground 4","alpha_tag":"Cranston FD FG4","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":106,"system_id":197,"tgid":1504,"name":"Fire - Admin/Alt Fireground 5","alpha_tag":"Cranston FD Admi","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":107,"system_id":197,"tgid":1520,"name":"Fire","alpha_tag":"Cumberland FD","tg_group":"Cumberland","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":108,"system_id":197,"tgid":1523,"name":"Police Secondary","alpha_tag":"Cumberland PD","tg_group":"Cumberland","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":109,"system_id":197,"tgid":1776,"name":"Fire Talk Around","alpha_tag":"E Greenwich F-TA","tg_group":"East Greenwich","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":110,"system_id":197,"tgid":1779,"name":"Police Operations","alpha_tag":"E Greenwich PD","tg_group":"East Greenwich","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":111,"system_id":197,"tgid":1869,"name":"Police 1 - Dispatch","alpha_tag":"E Prov PD 1","tg_group":"East Providence","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":112,"system_id":197,"tgid":1872,"name":"Police 2","alpha_tag":"E Prov PD 2","tg_group":"East Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":113,"system_id":197,"tgid":1870,"name":"Police 3","alpha_tag":"E Prov PD 3","tg_group":"East Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":114,"system_id":197,"tgid":1883,"name":"Detectives","alpha_tag":"E Prov PD12","tg_group":"East Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":115,"system_id":197,"tgid":1866,"name":"Fire - Dispatch/Operations","alpha_tag":"E Prov FD 1","tg_group":"East Providence","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":116,"system_id":197,"tgid":1867,"name":"Fire \"Channel 2\"","alpha_tag":"E Prov FD 2","tg_group":"East Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":117,"system_id":197,"tgid":1878,"name":"Fire \"Channel 3\"","alpha_tag":"E Prov FD 3","tg_group":"East Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":118,"system_id":197,"tgid":2064,"name":"Fire - Fireground","alpha_tag":"Exeter FD-G","tg_group":"Exeter","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":119,"system_id":197,"tgid":1904,"name":"Fire","alpha_tag":"Foster Fire","tg_group":"Foster","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":120,"system_id":197,"tgid":1939,"name":"Police","alpha_tag":"Glocester PD","tg_group":"Glocester","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":121,"system_id":197,"tgid":1940,"name":"Police Secondary","alpha_tag":"Glocester PD 2","tg_group":"Glocester","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":122,"system_id":197,"tgid":1410,"name":"Police","alpha_tag":"Hopkinton PD","tg_group":"Hopkinton","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":123,"system_id":197,"tgid":1100,"name":"Police 1 - Dispatch","alpha_tag":"Jamestown PD 1","tg_group":"Jamestown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":124,"system_id":197,"tgid":1101,"name":"Police 2","alpha_tag":"Jamestown PD 2","tg_group":"Jamestown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":125,"system_id":197,"tgid":1108,"name":"Fire","alpha_tag":"Jamestown FD","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":126,"system_id":197,"tgid":1120,"name":"Fireground 1","alpha_tag":"Jamestown FG 1","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":127,"system_id":197,"tgid":1121,"name":"Fireground 2","alpha_tag":"Jamestown FG 2","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":128,"system_id":197,"tgid":1114,"name":"Public Works","alpha_tag":"Jamestown DPW","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":129,"system_id":197,"tgid":1107,"name":"Town Schools","alpha_tag":"Jamestown School","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Schools"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":130,"system_id":197,"tgid":1619,"name":"Police Operations","alpha_tag":"Johnston PD","tg_group":"Johnston","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":131,"system_id":197,"tgid":1616,"name":"Fire Operations","alpha_tag":"Johnston FD","tg_group":"Johnston","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":132,"system_id":197,"tgid":1617,"name":"Fireground","alpha_tag":"Johnston FG","tg_group":"Johnston","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":133,"system_id":197,"tgid":1683,"name":"Police F1","alpha_tag":"Lincoln Police","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":134,"system_id":197,"tgid":1684,"name":"Police F2","alpha_tag":"Lincoln Police 2","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":135,"system_id":197,"tgid":1680,"name":"Fire Dispatch","alpha_tag":"Lincoln Fire 1","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":136,"system_id":197,"tgid":1681,"name":"Fireground 2","alpha_tag":"Lincoln Fire 2","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":137,"system_id":197,"tgid":1691,"name":"Fireground 3","alpha_tag":"Lincoln Fire 3","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":138,"system_id":197,"tgid":1682,"name":"EMS","alpha_tag":"Lincoln EMS","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":139,"system_id":197,"tgid":1688,"name":"Emergency Management","alpha_tag":"Lincoln EMA","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":140,"system_id":197,"tgid":1687,"name":"Townwide","alpha_tag":"Lincoln Townwide","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":141,"system_id":197,"tgid":1692,"name":"Public Works","alpha_tag":"Lincoln DPW","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":142,"system_id":197,"tgid":1264,"name":"Police","alpha_tag":"LittleCompPD","tg_group":"Little Compton","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":143,"system_id":197,"tgid":1266,"name":"Fire","alpha_tag":"LittleCompFD","tg_group":"Little Compton","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":144,"system_id":197,"tgid":1338,"name":"Police Operations","alpha_tag":"MiddletownPD","tg_group":"Middletown","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":145,"system_id":197,"tgid":1343,"name":"Fire Operations","alpha_tag":"Middletown FD","tg_group":"Middletown","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":146,"system_id":197,"tgid":1345,"name":"Townwide","alpha_tag":"MiddletownTW","tg_group":"Middletown","frequency":null,"metadata":null,"tags":["Multi-Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":147,"system_id":197,"tgid":1001,"name":"Police - Dispatch","alpha_tag":"Narrag PD 1","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":148,"system_id":197,"tgid":1002,"name":"Police - Car/Car","alpha_tag":"Narrag PD 2","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":149,"system_id":197,"tgid":1003,"name":"Police - Special Details 1/Town Beaches","alpha_tag":"Narrag PD 3","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":150,"system_id":197,"tgid":1004,"name":"Police - Special Details 2","alpha_tag":"Narrag PD 4","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":151,"system_id":197,"tgid":1005,"name":"Police - Harbormaster","alpha_tag":"Narrag PD 5","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":152,"system_id":197,"tgid":1007,"name":"Police - Detectives","alpha_tag":"Narrag PD 7","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":153,"system_id":197,"tgid":1008,"name":"Police - Detectives","alpha_tag":"Narrag PD 8","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":154,"system_id":197,"tgid":1006,"name":"Fire - Dispatch","alpha_tag":"Narrag FD","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":155,"system_id":197,"tgid":1012,"name":"Fire - Fireground 1","alpha_tag":"Narrag FDFG1","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":156,"system_id":197,"tgid":1013,"name":"Fire - Fireground 2","alpha_tag":"Narrag FDFG2","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":157,"system_id":197,"tgid":1016,"name":"Fire - Administration","alpha_tag":"Narrag FD AD","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":158,"system_id":197,"tgid":1014,"name":"Fire - EMS Ops","alpha_tag":"Narrag EMS","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":159,"system_id":197,"tgid":1017,"name":"Public Works","alpha_tag":"Narrag DPW","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":160,"system_id":197,"tgid":1010,"name":"Town Administration","alpha_tag":"Narrag TownA","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Other"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":161,"system_id":197,"tgid":1011,"name":"Townwide Interop","alpha_tag":"Narrag IOP","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":162,"system_id":197,"tgid":1376,"name":"Police","alpha_tag":"New Shore PD","tg_group":"New Shoreham","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":163,"system_id":197,"tgid":1300,"name":"Police 1 - Dispatch","alpha_tag":"Newport PD 1","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":164,"system_id":197,"tgid":1302,"name":"Police 2 - Records","alpha_tag":"Newport PD 2","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":165,"system_id":197,"tgid":1304,"name":"Police 4 - Tactical 1","alpha_tag":"Newport PD 4","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":166,"system_id":197,"tgid":1307,"name":"Police 7 - Tactical 4","alpha_tag":"Newport PD 7","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":167,"system_id":197,"tgid":1308,"name":"Police 8 - Tactical 5","alpha_tag":"Newport PD 8","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":168,"system_id":197,"tgid":1303,"name":"Fire Dispatch/Operations","alpha_tag":"Newport FD1","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":169,"system_id":197,"tgid":1305,"name":"Fireground Ops 1","alpha_tag":"Newport FG1","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":170,"system_id":197,"tgid":1306,"name":"Fireground Ops 2","alpha_tag":"Newport FG2","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":171,"system_id":197,"tgid":1301,"name":"Fire - Training","alpha_tag":"Newport FDT","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":172,"system_id":197,"tgid":1291,"name":"Water Department","alpha_tag":"Newport Water","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":173,"system_id":197,"tgid":1293,"name":"Public Works","alpha_tag":"Newport DPW","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":174,"system_id":197,"tgid":1297,"name":"Citywide Events","alpha_tag":"Newport Evnt","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":175,"system_id":197,"tgid":1312,"name":"Newport Citywide","alpha_tag":"Newport CW","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":176,"system_id":197,"tgid":1285,"name":"Police 1 - Dispatch","alpha_tag":"NKing PD 1","tg_group":"North Kingstown","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":177,"system_id":197,"tgid":1286,"name":"Police 2 - Admin","alpha_tag":"NKing PD 2","tg_group":"North Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":178,"system_id":197,"tgid":1287,"name":"Police 3 - Car/Car","alpha_tag":"NKing PD 3","tg_group":"North Kingstown","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":179,"system_id":197,"tgid":1280,"name":"Fire - Dispatch","alpha_tag":"NKing Fire D","tg_group":"North Kingstown","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":180,"system_id":197,"tgid":1281,"name":"Fire - Fireground","alpha_tag":"NKing Fire G","tg_group":"North Kingstown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":181,"system_id":197,"tgid":1536,"name":"Police 1 - Dispatch","alpha_tag":"NorthPrv PD1","tg_group":"North Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":182,"system_id":197,"tgid":1537,"name":"Police 2 - Car/Car","alpha_tag":"NorthPrv PD2","tg_group":"North Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":183,"system_id":197,"tgid":1538,"name":"Police 3 - Tactical","alpha_tag":"NorthPrv PD3","tg_group":"North Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":184,"system_id":197,"tgid":1547,"name":"Fire Dispatch ","alpha_tag":"NorthPrv FDD","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":185,"system_id":197,"tgid":1548,"name":"Fire 2","alpha_tag":"NorthPrv Fire 2","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":186,"system_id":197,"tgid":1549,"name":"Fire 3","alpha_tag":"NorthPrv Fire 3","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":187,"system_id":197,"tgid":1550,"name":"Fire 4","alpha_tag":"NorthPrv Fire 4","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":188,"system_id":197,"tgid":1551,"name":"Fire 5","alpha_tag":"NorthPrv Fire 5","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":189,"system_id":197,"tgid":1552,"name":"Fire 6","alpha_tag":"NorthPrv Fire 6","tg_group":"North Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":190,"system_id":197,"tgid":1544,"name":"Townwide 1","alpha_tag":"NorthPrv TownW 1","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":191,"system_id":197,"tgid":1545,"name":"Townwide 2","alpha_tag":"NorthPrv TownW 2","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Interop"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":192,"system_id":197,"tgid":1554,"name":"Public Works","alpha_tag":"NorthPrv DPW","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":193,"system_id":197,"tgid":1971,"name":"Police","alpha_tag":"N Smithfd PD","tg_group":"North Smithfield","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":194,"system_id":197,"tgid":1968,"name":"Fire Dispatch/Operations","alpha_tag":"N Smithfield FD","tg_group":"North Smithfield","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":195,"system_id":197,"tgid":1969,"name":"Fire Secondary","alpha_tag":"N Smithfield FD2","tg_group":"North Smithfield","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":196,"system_id":197,"tgid":1981,"name":"Fireground","alpha_tag":"N Smithfield FD3","tg_group":"North Smithfield","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":197,"system_id":197,"tgid":1440,"name":"Fire - Operations","alpha_tag":"Pawtucket FD 1","tg_group":"Pawtucket","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":198,"system_id":197,"tgid":1441,"name":"Fireground","alpha_tag":"Pawtucket FG","tg_group":"Pawtucket","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":199,"system_id":197,"tgid":1442,"name":"EMS Tac","alpha_tag":"Pawtucket EMSTac","tg_group":"Pawtucket","frequency":null,"metadata":null,"tags":["EMS-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":200,"system_id":197,"tgid":1248,"name":"Police","alpha_tag":"PortsmouthPD","tg_group":"Portsmouth","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":201,"system_id":197,"tgid":1253,"name":"Fire Dispatch (Patch to VHF Primary)","alpha_tag":"Portsmouth FD","tg_group":"Portsmouth","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":202,"system_id":197,"tgid":1255,"name":"Fireground","alpha_tag":"Portsmouth FG","tg_group":"Portsmouth","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":203,"system_id":197,"tgid":1262,"name":"Island Fire Dispatch","alpha_tag":"Prudence Isl FD","tg_group":"Portsmouth","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":204,"system_id":197,"tgid":10000,"name":"Police - All Call - Emergency Broadcasts","alpha_tag":"PPD ATG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":205,"system_id":197,"tgid":10001,"name":"Police 1 - Dispatch","alpha_tag":"PPD CH 1","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":206,"system_id":197,"tgid":10002,"name":"Police 2","alpha_tag":"PPD CH 2","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":207,"system_id":197,"tgid":10003,"name":"Police 3","alpha_tag":"PPD CH 3","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":208,"system_id":197,"tgid":10004,"name":"Police 4","alpha_tag":"PPD CH-4","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":209,"system_id":197,"tgid":10005,"name":"Police 5 -Detectives 1","alpha_tag":"PPD DETEC 1","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":210,"system_id":197,"tgid":10006,"name":"Police 6 - Car-to-Car","alpha_tag":"PPD T/A","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":211,"system_id":197,"tgid":10007,"name":"Police 7 - Narcotics 1","alpha_tag":"PPD NARC 1","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":212,"system_id":197,"tgid":10008,"name":"Police 8 - Narcotics 2","alpha_tag":"PPD NARC 2","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":213,"system_id":197,"tgid":10009,"name":"Police 9 - Detectives 2","alpha_tag":"PPD DETEC 2","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":214,"system_id":197,"tgid":10010,"name":"Police 10 - Special Details 1","alpha_tag":"PPD DETAIL 1","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":215,"system_id":197,"tgid":10011,"name":"Police 11 - Special Details 2","alpha_tag":"PPD DETAIL 2","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":216,"system_id":197,"tgid":10012,"name":"Police 12 - Corrections Security","alpha_tag":"PPD CORR SEC","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":217,"system_id":197,"tgid":10013,"name":"Police 13 - Special Response Unit","alpha_tag":"PPD SRU","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":218,"system_id":197,"tgid":10014,"name":"Police 14 - Administration","alpha_tag":"PPD ADMIN","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":219,"system_id":197,"tgid":10100,"name":"Fire All Call - Emergency Broadcasts","alpha_tag":"PROV FD ATG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":220,"system_id":197,"tgid":10101,"name":"Fire Dispatch","alpha_tag":"PFD DISPATCH","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":221,"system_id":197,"tgid":10107,"name":"Fireground 2","alpha_tag":"PFD CH-2 FG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":222,"system_id":197,"tgid":10108,"name":"Fireground 3","alpha_tag":"PFD CH-3 FG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":223,"system_id":197,"tgid":10109,"name":"Fireground 4","alpha_tag":"PFD CH-4 FG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":224,"system_id":197,"tgid":10102,"name":"Fire 5","alpha_tag":"PFD CH-5","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":225,"system_id":197,"tgid":10103,"name":"Fire 6","alpha_tag":"PFD CH-6","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":226,"system_id":197,"tgid":10104,"name":"Fire 7","alpha_tag":"PFD CH-7","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":227,"system_id":197,"tgid":10110,"name":"Fire - Mutual Aid 1","alpha_tag":"PFD M/A 1","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":228,"system_id":197,"tgid":10111,"name":"Fire - Mutual Aid 2","alpha_tag":"PFD M/A 2","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":229,"system_id":197,"tgid":10112,"name":"Fire - Mutual Aid 3","alpha_tag":"PFD M/A 3","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":230,"system_id":197,"tgid":10113,"name":"Fireground 8","alpha_tag":"PFD Fireground 8","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":231,"system_id":197,"tgid":10105,"name":"Fire - Administration","alpha_tag":"PFD ADMIN","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":232,"system_id":197,"tgid":10106,"name":"Fire - Communications","alpha_tag":"PFD COMM","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":233,"system_id":197,"tgid":10207,"name":"Public Works","alpha_tag":"PROV DPW","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":234,"system_id":197,"tgid":2035,"name":"Police","alpha_tag":"Richmond PD","tg_group":"Richmond","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":235,"system_id":197,"tgid":2042,"name":"Chariho Regional High School","alpha_tag":"Chariho Reg HS","tg_group":"Richmond","frequency":null,"metadata":null,"tags":["Schools"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":236,"system_id":197,"tgid":1460,"name":"Police","alpha_tag":"Scituate PD","tg_group":"Scituate","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":237,"system_id":197,"tgid":1463,"name":"Fire Operations","alpha_tag":"Scituate FD","tg_group":"Scituate","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":238,"system_id":197,"tgid":1651,"name":"Police Operations","alpha_tag":"SmithfieldPD","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":239,"system_id":197,"tgid":1652,"name":"Police Secondary","alpha_tag":"Smfld PD 2","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":240,"system_id":197,"tgid":1653,"name":"Police Detectives","alpha_tag":"Smfld PD Det","tg_group":"Smithfield","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":241,"system_id":197,"tgid":1654,"name":"Police Admin","alpha_tag":"Smfld PD Adm","tg_group":"Smithfield","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":242,"system_id":197,"tgid":1661,"name":"Police Details","alpha_tag":"Smfld PD Dtl","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":243,"system_id":197,"tgid":1648,"name":"Fire - Fireground","alpha_tag":"SmithfieldFD","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":244,"system_id":197,"tgid":1655,"name":"Town-Wide","alpha_tag":"Smfld Town","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Multi-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":245,"system_id":197,"tgid":1657,"name":"Emergency Management","alpha_tag":"Smfld EMA","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":246,"system_id":197,"tgid":1660,"name":"Public Works","alpha_tag":"Smfld DPW","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":247,"system_id":197,"tgid":1225,"name":"Police 1 - Dispatch","alpha_tag":"SKing PD 1","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":248,"system_id":197,"tgid":1226,"name":"Police 2 - Car/Car","alpha_tag":"SKing PD 2","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":249,"system_id":197,"tgid":1235,"name":"Police 3 - Tactical","alpha_tag":"SKing PD 3","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":250,"system_id":197,"tgid":1236,"name":"Police 5 - Tactical","alpha_tag":"SKing PD 5","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":251,"system_id":197,"tgid":1232,"name":"Fire - UHF Simulcast","alpha_tag":"SKing FD Lnk","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":252,"system_id":197,"tgid":1240,"name":"Fire - Detail","alpha_tag":"SKing Fire D","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":253,"system_id":197,"tgid":1227,"name":"Union Fire District - Fireground 1","alpha_tag":"UnionFD FG 1","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":254,"system_id":197,"tgid":1237,"name":"Union Fire District - Fireground 2","alpha_tag":"UnionFD FG 2","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":255,"system_id":197,"tgid":1026,"name":"Union Fire District - Special Events","alpha_tag":"UnionFD Evnt","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":256,"system_id":197,"tgid":1015,"name":"EMS","alpha_tag":"SKing EMS","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["EMS Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":257,"system_id":197,"tgid":1316,"name":"Police (Simulcast 482.9625)","alpha_tag":"Tiverton PD","tg_group":"Tiverton","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":258,"system_id":197,"tgid":1315,"name":"Fire (Simulcast 471.7875)","alpha_tag":"Tiverton FD","tg_group":"Tiverton","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":259,"system_id":197,"tgid":1162,"name":"Fire","alpha_tag":"Warwick FD","tg_group":"Warwick","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":260,"system_id":197,"tgid":1170,"name":"Fireground","alpha_tag":"Warwick FG","tg_group":"Warwick","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":261,"system_id":197,"tgid":1805,"name":"Police","alpha_tag":"W Greenwh PD","tg_group":"West Greenwich","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":262,"system_id":197,"tgid":1806,"name":"Police Secondary","alpha_tag":"W GreenwichPD2","tg_group":"West Greenwich","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":263,"system_id":197,"tgid":1208,"name":"Fire Operations","alpha_tag":"W Warwick FD","tg_group":"West Warwick","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":264,"system_id":197,"tgid":1050,"name":"Police 1 - Dispatch","alpha_tag":"Westerly PD1","tg_group":"Westerly","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":265,"system_id":197,"tgid":1051,"name":"Police 2","alpha_tag":"Westerly PD2","tg_group":"Westerly","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":266,"system_id":197,"tgid":1052,"name":"Police 3","alpha_tag":"Westerly PD3","tg_group":"Westerly","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":267,"system_id":197,"tgid":1053,"name":"Police 4","alpha_tag":"Westerly PD4","tg_group":"Westerly","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":268,"system_id":197,"tgid":1054,"name":"Police 5 - Reserve Officers","alpha_tag":"Westerly PD5","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":269,"system_id":197,"tgid":1064,"name":"Police 6 - Traffic Division","alpha_tag":"Westerly PD6","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":270,"system_id":197,"tgid":1063,"name":"Fire Operations","alpha_tag":"Westerly FD","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":271,"system_id":197,"tgid":1072,"name":"Police/Fire/EMS Ops","alpha_tag":"Westerly PFE","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["Multi-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":272,"system_id":197,"tgid":1082,"name":"EMS Operations","alpha_tag":"Westerly EMS ","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":273,"system_id":197,"tgid":1363,"name":"Police 1 - Dispatch","alpha_tag":"Woonskt PD 1","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":274,"system_id":197,"tgid":1364,"name":"Police 2","alpha_tag":"Woonskt PD 2","tg_group":"Woonsocket","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":275,"system_id":197,"tgid":1360,"name":"Fire Dispatch - Operations","alpha_tag":"Woonsocket FD D","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":276,"system_id":197,"tgid":1361,"name":"Fire Secondary","alpha_tag":"Woonsocket FD 2","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":277,"system_id":197,"tgid":1354,"name":"Fire - Fireground 3","alpha_tag":"Woonskt FD 3","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":278,"system_id":197,"tgid":1367,"name":"Citywide","alpha_tag":"Woonskt City","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Multi-Talk"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":279,"system_id":197,"tgid":1368,"name":"Public Works - Streets","alpha_tag":"Woonsocket PW","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":280,"system_id":197,"tgid":1,"name":"RISCON Radio Technicians","alpha_tag":"Radio Techs","tg_group":"Radio Technicians","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":281,"system_id":197,"tgid":10125,"name":"RISCON Radio Technicians","alpha_tag":"Radio Techs","tg_group":"Radio Technicians","frequency":null,"metadata":null,"tags":["Public Works"],"alert":false,"alert_config":null,"weight":1,"system":{"id":197,"name":"RISCON"},"learned":false}] \ No newline at end of file +[{"id":0,"system_id":197,"tgid":2,"name":"Intercity Fire","alpha_tag":"Intercity FD","tg_group":"Statewide Mutual Aid/Intersystem","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":1,"system_id":197,"tgid":3,"name":"Intercity Police","alpha_tag":"Intercity PD","tg_group":"Statewide Mutual Aid/Intersystem","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":2,"system_id":197,"tgid":21,"name":"North Dispatch ","alpha_tag":"RISP N Disp","tg_group":"State Police - District A (North)","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":3,"system_id":197,"tgid":22,"name":"North Car-to-Car/Information","alpha_tag":"RISP N Car","tg_group":"State Police - District A (North)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":4,"system_id":197,"tgid":24,"name":"North Tactical Ops 1","alpha_tag":"RISP N Tac 1","tg_group":"State Police - District A (North)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":5,"system_id":197,"tgid":23,"name":"North Tactical Ops 2","alpha_tag":"RISP N Tac 2","tg_group":"State Police - District A (North)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":6,"system_id":197,"tgid":25,"name":"South Dispatch ","alpha_tag":"RISP S Disp","tg_group":"State Police - District B (South)","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":7,"system_id":197,"tgid":27,"name":"South Car-to-Car/Information","alpha_tag":"RISP S Car","tg_group":"State Police - District B (South)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":8,"system_id":197,"tgid":16,"name":"State Fire Marshall","alpha_tag":"State FMO","tg_group":"Statewide Fire","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":9,"system_id":197,"tgid":1038,"name":"Northern Rhode Island Fire Chiefs","alpha_tag":"NRI Fire Chi","tg_group":"Statewide Fire","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":10,"system_id":197,"tgid":1041,"name":"Southern Rhode Island Fire Chiefs","alpha_tag":"SRI Fire Chi","tg_group":"Statewide Fire","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":11,"system_id":197,"tgid":1314,"name":"Tanker Taskforce 1","alpha_tag":"Tanker TF 1","tg_group":"Statewide Fire","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":12,"system_id":197,"tgid":194,"name":"Lifepact Ambulance (Statewide)","alpha_tag":"Lifepact Amb","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":13,"system_id":197,"tgid":212,"name":"Fatima St Josephs","alpha_tag":"Fatima-St Joes","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["Business"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":14,"system_id":197,"tgid":220,"name":"Health 1","alpha_tag":"Health 1","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":{"encrypted":true},"tags":["EMS-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":15,"system_id":197,"tgid":221,"name":"Health 2","alpha_tag":"Health 2","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":{"encrypted":true},"tags":["EMS-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":16,"system_id":197,"tgid":222,"name":"Department of Health - Statewide","alpha_tag":"Dept of HealthSW","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["EMS-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":17,"system_id":197,"tgid":228,"name":"DMAT South","alpha_tag":"DMAT South","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":{"encrypted":true},"tags":["Emergency Ops"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":18,"system_id":197,"tgid":232,"name":"Life Span Net 1","alpha_tag":"Life Span 1","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["EMS-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":19,"system_id":197,"tgid":234,"name":"RI Hospital Operations","alpha_tag":"RI Hosp Ops","tg_group":"Statewide EMS and Hospitals","frequency":null,"metadata":null,"tags":["Business"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":20,"system_id":197,"tgid":120,"name":"Law Enforcement Operations","alpha_tag":"DEM PD Ops","tg_group":"Department of Environmental Management","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":21,"system_id":197,"tgid":122,"name":"Law Enforcement Police","alpha_tag":"DEM Police","tg_group":"Department of Environmental Management","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":22,"system_id":197,"tgid":10,"name":"Emergency Management Agency 1","alpha_tag":"EMA-1","tg_group":"Emergency Management Agency","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":23,"system_id":197,"tgid":20,"name":"Emergency Management Agency","alpha_tag":"EMA","tg_group":"Emergency Management Agency","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":24,"system_id":197,"tgid":4,"name":"Wide Area 3","alpha_tag":"Wide Area 3","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":25,"system_id":197,"tgid":5,"name":"Wide Area 4","alpha_tag":"Wide Area 4","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":26,"system_id":197,"tgid":6,"name":"Wide Area 5","alpha_tag":"Wide Area 5","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":27,"system_id":197,"tgid":7,"name":"Wide Area 6","alpha_tag":"Wide Area 6","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":28,"system_id":197,"tgid":1018,"name":"Southwide CH-1","alpha_tag":"SOUTHWIDE 1","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":29,"system_id":197,"tgid":1019,"name":"Southwide CH-2","alpha_tag":"SOUTHWIDE 2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":30,"system_id":197,"tgid":1022,"name":"Wide Area 7","alpha_tag":"WIDE AREA 7","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":31,"system_id":197,"tgid":1023,"name":"Wide Area 8","alpha_tag":"WIDE AREA 8","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":32,"system_id":197,"tgid":1025,"name":"Inland Marine Interop","alpha_tag":"Inland Marine IO","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":33,"system_id":197,"tgid":1037,"name":"Southside CH 5","alpha_tag":"SOUTHSIDE 5","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":34,"system_id":197,"tgid":1173,"name":"North Wide 1","alpha_tag":"NORTHWIDE1","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":35,"system_id":197,"tgid":1174,"name":"North Wide 2","alpha_tag":"NORTHWIDE2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":36,"system_id":197,"tgid":1177,"name":"North Wide 5","alpha_tag":"NORTHWIDE5","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":37,"system_id":197,"tgid":1185,"name":"Metro Wide 1","alpha_tag":"METROWIDE1","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":38,"system_id":197,"tgid":1186,"name":"Metro Wide 2","alpha_tag":"METROWIDE2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":39,"system_id":197,"tgid":1187,"name":"Metro Wide 3","alpha_tag":"METROWIDE3","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":40,"system_id":197,"tgid":1335,"name":"East Wide 1","alpha_tag":"EASTWIDE 1","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":41,"system_id":197,"tgid":1336,"name":"East Wide 2","alpha_tag":"EASTWIDE 2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":42,"system_id":197,"tgid":1337,"name":"East Wide 3","alpha_tag":"EASTWIDE 3","tg_group":"Statewide Area/Events","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":43,"system_id":197,"tgid":11186,"name":"Metro Wide 2","alpha_tag":"METROWIDE2","tg_group":"Statewide Area/Events","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":44,"system_id":197,"tgid":1033,"name":"Tanker Taskforce ","alpha_tag":"TANK TF","tg_group":"Statewide Emergency Response","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":45,"system_id":197,"tgid":1034,"name":"Hazmat 1","alpha_tag":"HZT DC1","tg_group":"Statewide Emergency Response","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":46,"system_id":197,"tgid":1035,"name":"Hazmat 2","alpha_tag":"HZT DC2","tg_group":"Statewide Emergency Response","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":47,"system_id":197,"tgid":176,"name":"Department of Transportation - Primary","alpha_tag":"RIDOT Primary","tg_group":"Department of Transportation","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":48,"system_id":197,"tgid":4421,"name":"Newport Pell Bridge Operations","alpha_tag":"RITBA - Pell Bdg","tg_group":"Tunnel and Bridge Authority","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":49,"system_id":197,"tgid":274,"name":"Providence VA Police","alpha_tag":"VA Police","tg_group":"Federal","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":50,"system_id":197,"tgid":186,"name":"Rhode Island Public Transit Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":{"encrypted":true},"tags":["Transportation"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":51,"system_id":197,"tgid":187,"name":"Rhode Island Public Transit Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":null,"tags":["Transportation"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":52,"system_id":197,"tgid":188,"name":"Rhode Island Public Transit Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":null,"tags":["Transportation"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":53,"system_id":197,"tgid":189,"name":"Rhode Island Public Transit Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":null,"tags":["Transportation"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":54,"system_id":197,"tgid":190,"name":"Rhode Island Public Transit. Auth.","alpha_tag":"RIPTA","tg_group":"RIPTA","frequency":null,"metadata":null,"tags":["Transportation"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":55,"system_id":197,"tgid":304,"name":"Fire Operations","alpha_tag":"Quonset ANGB FD","tg_group":"Quonset ANGB","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":56,"system_id":197,"tgid":17,"name":"Airport Police Operations","alpha_tag":"TF Green PD","tg_group":"Rhode Island Airport Commission","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":57,"system_id":197,"tgid":19,"name":"Airport Fire Operations","alpha_tag":"TF Green FD","tg_group":"Rhode Island Airport Commission","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":58,"system_id":197,"tgid":1126,"name":"University of Rhode Island Police - Dispatch","alpha_tag":"URI PD","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":59,"system_id":197,"tgid":1131,"name":"University of Rhode Island - EMS","alpha_tag":"URI EMS","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["EMS Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":60,"system_id":197,"tgid":1348,"name":"St. George's School (Middletown) - Security","alpha_tag":"St George Sec","tg_group":"College/Education Security","frequency":null,"metadata":null,"tags":["Security"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":61,"system_id":197,"tgid":10228,"name":"Rhode Island School of Design - Security","alpha_tag":"RISD Secuty","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Security"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":62,"system_id":197,"tgid":10229,"name":"Providence College Security - Dispatch","alpha_tag":"PROV COLL","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Security"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":63,"system_id":197,"tgid":10230,"name":"Rhode Island College Security","alpha_tag":"RI COL SEC","tg_group":"College/Education Security","frequency":null,"metadata":null,"tags":["Security"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":64,"system_id":197,"tgid":11001,"name":"Brown University Police - Dispatch","alpha_tag":"BROWN UNIV","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":65,"system_id":197,"tgid":11002,"name":"Brown University Police - Car-to-Car","alpha_tag":"BROWN CAR","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":66,"system_id":197,"tgid":11003,"name":"Brown University Police - Tactical","alpha_tag":"BROWN TAC","tg_group":"College/Education Security","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":67,"system_id":197,"tgid":12,"name":"Metro Wide 2","alpha_tag":"METROWIDE2","tg_group":"Statewide Misc.","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":68,"system_id":197,"tgid":14,"name":"Metro Wide 4","alpha_tag":"METROWIDE4","tg_group":"Statewide Misc.","frequency":null,"metadata":{"encrypted":true},"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":69,"system_id":197,"tgid":70,"name":"RI Traffic Tribunal Security","alpha_tag":"TFC TRIBUNAL","tg_group":"Statewide Misc.","frequency":null,"metadata":{"encrypted":true},"tags":["Security"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":70,"system_id":197,"tgid":168,"name":"Rhode Island Red Cross - Primary","alpha_tag":"Red Cross 1","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Other"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":71,"system_id":197,"tgid":169,"name":"Rhode Island Red Cross - Secondary","alpha_tag":"Red Cross 2","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Other"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":72,"system_id":197,"tgid":223,"name":"Statewide Nursing Homes Net","alpha_tag":"NURSING HM","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Other"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":73,"system_id":197,"tgid":243,"name":"Hospital Operations","alpha_tag":"Slater Hosp Ops","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Business"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":74,"system_id":197,"tgid":244,"name":"Slater Hospital Security","alpha_tag":"Slater Hosp Sec","tg_group":"Statewide Misc.","frequency":null,"metadata":null,"tags":["Security"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":75,"system_id":197,"tgid":1042,"name":"County Fireground","alpha_tag":"WashCo FireG","tg_group":"Washington County","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":76,"system_id":197,"tgid":1479,"name":"County Fire Station/Station","alpha_tag":"WashCo FireS","tg_group":"Washington County","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":77,"system_id":197,"tgid":1712,"name":"Fire 1 Dispatch","alpha_tag":"BarringtnFD1","tg_group":"Barrington","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":78,"system_id":197,"tgid":1713,"name":"Fire 2","alpha_tag":"BarringtnFD2","tg_group":"Barrington","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":79,"system_id":197,"tgid":1715,"name":"Police Operations","alpha_tag":"BarringtonPD 1","tg_group":"Barrington","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":80,"system_id":197,"tgid":1716,"name":"Police Secondary","alpha_tag":"BarringtonPD 2","tg_group":"Barrington","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":81,"system_id":197,"tgid":1744,"name":"Fire Operations (Patch from VHF)","alpha_tag":"Bristol FD","tg_group":"Bristol","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":82,"system_id":197,"tgid":1755,"name":"Harbormaster","alpha_tag":"Bristol Harbor","tg_group":"Bristol","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":83,"system_id":197,"tgid":2003,"name":"Police","alpha_tag":"Burrville PD","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":84,"system_id":197,"tgid":2004,"name":"Police 2","alpha_tag":"Burrvl PD2","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":85,"system_id":197,"tgid":2005,"name":"Police 3 Detectives","alpha_tag":"Burrvl PD3","tg_group":"Burrillville","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":86,"system_id":197,"tgid":2006,"name":"Police 4","alpha_tag":"Burrvl PD4","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":87,"system_id":197,"tgid":2000,"name":"Fire Misc (Ops are VHF)","alpha_tag":"Burrvl FD","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":88,"system_id":197,"tgid":2001,"name":"Fire TAC-1","alpha_tag":"Burvl FDTAC1","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":89,"system_id":197,"tgid":2009,"name":"Fire TAC-2","alpha_tag":"Burvl FDTAC2","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":90,"system_id":197,"tgid":2002,"name":"EMS Misc (Ops are VHF)","alpha_tag":"Burrvl EMS","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["EMS-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":91,"system_id":197,"tgid":2007,"name":"Town-Wide","alpha_tag":"Burrvl Town","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Multi-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":92,"system_id":197,"tgid":2008,"name":"Emergency Management","alpha_tag":"Burrvl EMA","tg_group":"Burrillville","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":93,"system_id":197,"tgid":1838,"name":"Police 1 Dispatch","alpha_tag":"CentFallsPD1","tg_group":"Central Falls","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":94,"system_id":197,"tgid":1839,"name":"Police 2","alpha_tag":"CentFallsPD2","tg_group":"Central Falls","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":95,"system_id":197,"tgid":1835,"name":"Fire Dispatch (Simulcast of UHF)","alpha_tag":"CentFalls FD 1","tg_group":"Central Falls","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":96,"system_id":197,"tgid":1836,"name":"Fireground","alpha_tag":"CentFalls FD 2","tg_group":"Central Falls","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":97,"system_id":197,"tgid":1425,"name":"Police Operations - Simulcast of UHF","alpha_tag":"CharlestownPD","tg_group":"Charlestown","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":98,"system_id":197,"tgid":1429,"name":"EMS - Linked to 151.3325","alpha_tag":"Chastown EMS","tg_group":"Charlestown","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":99,"system_id":197,"tgid":1483,"name":"Police 1 - Dispatch","alpha_tag":"Coventry PD","tg_group":"Coventry","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":100,"system_id":197,"tgid":1484,"name":"Police 2","alpha_tag":"Coventry PD2","tg_group":"Coventry","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":101,"system_id":197,"tgid":1480,"name":"Fire","alpha_tag":"Coventry FD","tg_group":"Coventry","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":102,"system_id":197,"tgid":1500,"name":"Fire - Dispatch/Operations","alpha_tag":"Cranston FD Disp","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":103,"system_id":197,"tgid":1501,"name":"Fire - Fireground 2","alpha_tag":"Cranston FD FG2","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":104,"system_id":197,"tgid":1502,"name":"Fire - Fireground 3","alpha_tag":"Cranston FD FG3","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":105,"system_id":197,"tgid":1503,"name":"Fire - Fireground 4","alpha_tag":"Cranston FD FG4","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":106,"system_id":197,"tgid":1504,"name":"Fire - Admin/Alt Fireground 5","alpha_tag":"Cranston FD Admi","tg_group":"Cranston","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":107,"system_id":197,"tgid":1520,"name":"Fire","alpha_tag":"Cumberland FD","tg_group":"Cumberland","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":108,"system_id":197,"tgid":1523,"name":"Police Secondary","alpha_tag":"Cumberland PD","tg_group":"Cumberland","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":109,"system_id":197,"tgid":1776,"name":"Fire Talk Around","alpha_tag":"E Greenwich F-TA","tg_group":"East Greenwich","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":110,"system_id":197,"tgid":1779,"name":"Police Operations","alpha_tag":"E Greenwich PD","tg_group":"East Greenwich","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":111,"system_id":197,"tgid":1869,"name":"Police 1 - Dispatch","alpha_tag":"E Prov PD 1","tg_group":"East Providence","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":112,"system_id":197,"tgid":1872,"name":"Police 2","alpha_tag":"E Prov PD 2","tg_group":"East Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":113,"system_id":197,"tgid":1870,"name":"Police 3","alpha_tag":"E Prov PD 3","tg_group":"East Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":114,"system_id":197,"tgid":1883,"name":"Detectives","alpha_tag":"E Prov PD12","tg_group":"East Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":115,"system_id":197,"tgid":1866,"name":"Fire - Dispatch/Operations","alpha_tag":"E Prov FD 1","tg_group":"East Providence","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":116,"system_id":197,"tgid":1867,"name":"Fire \"Channel 2\"","alpha_tag":"E Prov FD 2","tg_group":"East Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":117,"system_id":197,"tgid":1878,"name":"Fire \"Channel 3\"","alpha_tag":"E Prov FD 3","tg_group":"East Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":118,"system_id":197,"tgid":2064,"name":"Fire - Fireground","alpha_tag":"Exeter FD-G","tg_group":"Exeter","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":119,"system_id":197,"tgid":1904,"name":"Fire","alpha_tag":"Foster Fire","tg_group":"Foster","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":120,"system_id":197,"tgid":1939,"name":"Police","alpha_tag":"Glocester PD","tg_group":"Glocester","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":121,"system_id":197,"tgid":1940,"name":"Police Secondary","alpha_tag":"Glocester PD 2","tg_group":"Glocester","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":122,"system_id":197,"tgid":1410,"name":"Police","alpha_tag":"Hopkinton PD","tg_group":"Hopkinton","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":123,"system_id":197,"tgid":1100,"name":"Police 1 - Dispatch","alpha_tag":"Jamestown PD 1","tg_group":"Jamestown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":124,"system_id":197,"tgid":1101,"name":"Police 2","alpha_tag":"Jamestown PD 2","tg_group":"Jamestown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":125,"system_id":197,"tgid":1108,"name":"Fire","alpha_tag":"Jamestown FD","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":126,"system_id":197,"tgid":1120,"name":"Fireground 1","alpha_tag":"Jamestown FG 1","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":127,"system_id":197,"tgid":1121,"name":"Fireground 2","alpha_tag":"Jamestown FG 2","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":128,"system_id":197,"tgid":1114,"name":"Public Works","alpha_tag":"Jamestown DPW","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":129,"system_id":197,"tgid":1107,"name":"Town Schools","alpha_tag":"Jamestown School","tg_group":"Jamestown","frequency":null,"metadata":null,"tags":["Schools"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":130,"system_id":197,"tgid":1619,"name":"Police Operations","alpha_tag":"Johnston PD","tg_group":"Johnston","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":131,"system_id":197,"tgid":1616,"name":"Fire Operations","alpha_tag":"Johnston FD","tg_group":"Johnston","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":132,"system_id":197,"tgid":1617,"name":"Fireground","alpha_tag":"Johnston FG","tg_group":"Johnston","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":133,"system_id":197,"tgid":1683,"name":"Police F1","alpha_tag":"Lincoln Police","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":134,"system_id":197,"tgid":1684,"name":"Police F2","alpha_tag":"Lincoln Police 2","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":135,"system_id":197,"tgid":1680,"name":"Fire Dispatch","alpha_tag":"Lincoln Fire 1","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":136,"system_id":197,"tgid":1681,"name":"Fireground 2","alpha_tag":"Lincoln Fire 2","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":137,"system_id":197,"tgid":1691,"name":"Fireground 3","alpha_tag":"Lincoln Fire 3","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":138,"system_id":197,"tgid":1682,"name":"EMS","alpha_tag":"Lincoln EMS","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":139,"system_id":197,"tgid":1688,"name":"Emergency Management","alpha_tag":"Lincoln EMA","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":140,"system_id":197,"tgid":1687,"name":"Townwide","alpha_tag":"Lincoln Townwide","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":141,"system_id":197,"tgid":1692,"name":"Public Works","alpha_tag":"Lincoln DPW","tg_group":"Lincoln","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":142,"system_id":197,"tgid":1264,"name":"Police","alpha_tag":"LittleCompPD","tg_group":"Little Compton","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":143,"system_id":197,"tgid":1266,"name":"Fire","alpha_tag":"LittleCompFD","tg_group":"Little Compton","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":144,"system_id":197,"tgid":1338,"name":"Police Operations","alpha_tag":"MiddletownPD","tg_group":"Middletown","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":145,"system_id":197,"tgid":1343,"name":"Fire Operations","alpha_tag":"Middletown FD","tg_group":"Middletown","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":146,"system_id":197,"tgid":1345,"name":"Townwide","alpha_tag":"MiddletownTW","tg_group":"Middletown","frequency":null,"metadata":null,"tags":["Multi-Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":147,"system_id":197,"tgid":1001,"name":"Police - Dispatch","alpha_tag":"Narrag PD 1","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":148,"system_id":197,"tgid":1002,"name":"Police - Car/Car","alpha_tag":"Narrag PD 2","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":149,"system_id":197,"tgid":1003,"name":"Police - Special Details 1/Town Beaches","alpha_tag":"Narrag PD 3","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":150,"system_id":197,"tgid":1004,"name":"Police - Special Details 2","alpha_tag":"Narrag PD 4","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":151,"system_id":197,"tgid":1005,"name":"Police - Harbormaster","alpha_tag":"Narrag PD 5","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":152,"system_id":197,"tgid":1007,"name":"Police - Detectives","alpha_tag":"Narrag PD 7","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":153,"system_id":197,"tgid":1008,"name":"Police - Detectives","alpha_tag":"Narrag PD 8","tg_group":"Narragansett","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":154,"system_id":197,"tgid":1006,"name":"Fire - Dispatch","alpha_tag":"Narrag FD","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":155,"system_id":197,"tgid":1012,"name":"Fire - Fireground 1","alpha_tag":"Narrag FDFG1","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":156,"system_id":197,"tgid":1013,"name":"Fire - Fireground 2","alpha_tag":"Narrag FDFG2","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":157,"system_id":197,"tgid":1016,"name":"Fire - Administration","alpha_tag":"Narrag FD AD","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":158,"system_id":197,"tgid":1014,"name":"Fire - EMS Ops","alpha_tag":"Narrag EMS","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":159,"system_id":197,"tgid":1017,"name":"Public Works","alpha_tag":"Narrag DPW","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":160,"system_id":197,"tgid":1010,"name":"Town Administration","alpha_tag":"Narrag TownA","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Other"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":161,"system_id":197,"tgid":1011,"name":"Townwide Interop","alpha_tag":"Narrag IOP","tg_group":"Narragansett","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":162,"system_id":197,"tgid":1376,"name":"Police","alpha_tag":"New Shore PD","tg_group":"New Shoreham","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":163,"system_id":197,"tgid":1300,"name":"Police 1 - Dispatch","alpha_tag":"Newport PD 1","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":164,"system_id":197,"tgid":1302,"name":"Police 2 - Records","alpha_tag":"Newport PD 2","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":165,"system_id":197,"tgid":1304,"name":"Police 4 - Tactical 1","alpha_tag":"Newport PD 4","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":166,"system_id":197,"tgid":1307,"name":"Police 7 - Tactical 4","alpha_tag":"Newport PD 7","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":167,"system_id":197,"tgid":1308,"name":"Police 8 - Tactical 5","alpha_tag":"Newport PD 8","tg_group":"Newport","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":168,"system_id":197,"tgid":1303,"name":"Fire Dispatch/Operations","alpha_tag":"Newport FD1","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":169,"system_id":197,"tgid":1305,"name":"Fireground Ops 1","alpha_tag":"Newport FG1","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":170,"system_id":197,"tgid":1306,"name":"Fireground Ops 2","alpha_tag":"Newport FG2","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":171,"system_id":197,"tgid":1301,"name":"Fire - Training","alpha_tag":"Newport FDT","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":172,"system_id":197,"tgid":1291,"name":"Water Department","alpha_tag":"Newport Water","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":173,"system_id":197,"tgid":1293,"name":"Public Works","alpha_tag":"Newport DPW","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":174,"system_id":197,"tgid":1297,"name":"Citywide Events","alpha_tag":"Newport Evnt","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":175,"system_id":197,"tgid":1312,"name":"Newport Citywide","alpha_tag":"Newport CW","tg_group":"Newport","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":176,"system_id":197,"tgid":1285,"name":"Police 1 - Dispatch","alpha_tag":"NKing PD 1","tg_group":"North Kingstown","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":177,"system_id":197,"tgid":1286,"name":"Police 2 - Admin","alpha_tag":"NKing PD 2","tg_group":"North Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":178,"system_id":197,"tgid":1287,"name":"Police 3 - Car/Car","alpha_tag":"NKing PD 3","tg_group":"North Kingstown","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":179,"system_id":197,"tgid":1280,"name":"Fire - Dispatch","alpha_tag":"NKing Fire D","tg_group":"North Kingstown","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":180,"system_id":197,"tgid":1281,"name":"Fire - Fireground","alpha_tag":"NKing Fire G","tg_group":"North Kingstown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":181,"system_id":197,"tgid":1536,"name":"Police 1 - Dispatch","alpha_tag":"NorthPrv PD1","tg_group":"North Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":182,"system_id":197,"tgid":1537,"name":"Police 2 - Car/Car","alpha_tag":"NorthPrv PD2","tg_group":"North Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":183,"system_id":197,"tgid":1538,"name":"Police 3 - Tactical","alpha_tag":"NorthPrv PD3","tg_group":"North Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":184,"system_id":197,"tgid":1547,"name":"Fire Dispatch ","alpha_tag":"NorthPrv FDD","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":185,"system_id":197,"tgid":1548,"name":"Fire 2","alpha_tag":"NorthPrv Fire 2","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":186,"system_id":197,"tgid":1549,"name":"Fire 3","alpha_tag":"NorthPrv Fire 3","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":187,"system_id":197,"tgid":1550,"name":"Fire 4","alpha_tag":"NorthPrv Fire 4","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":188,"system_id":197,"tgid":1551,"name":"Fire 5","alpha_tag":"NorthPrv Fire 5","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":189,"system_id":197,"tgid":1552,"name":"Fire 6","alpha_tag":"NorthPrv Fire 6","tg_group":"North Providence","frequency":null,"metadata":{"encrypted":true},"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":190,"system_id":197,"tgid":1544,"name":"Townwide 1","alpha_tag":"NorthPrv TownW 1","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":191,"system_id":197,"tgid":1545,"name":"Townwide 2","alpha_tag":"NorthPrv TownW 2","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Interop"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":192,"system_id":197,"tgid":1554,"name":"Public Works","alpha_tag":"NorthPrv DPW","tg_group":"North Providence","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":193,"system_id":197,"tgid":1971,"name":"Police","alpha_tag":"N Smithfd PD","tg_group":"North Smithfield","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":194,"system_id":197,"tgid":1968,"name":"Fire Dispatch/Operations","alpha_tag":"N Smithfield FD","tg_group":"North Smithfield","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":195,"system_id":197,"tgid":1969,"name":"Fire Secondary","alpha_tag":"N Smithfield FD2","tg_group":"North Smithfield","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":196,"system_id":197,"tgid":1981,"name":"Fireground","alpha_tag":"N Smithfield FD3","tg_group":"North Smithfield","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":197,"system_id":197,"tgid":1440,"name":"Fire - Operations","alpha_tag":"Pawtucket FD 1","tg_group":"Pawtucket","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":198,"system_id":197,"tgid":1441,"name":"Fireground","alpha_tag":"Pawtucket FG","tg_group":"Pawtucket","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":199,"system_id":197,"tgid":1442,"name":"EMS Tac","alpha_tag":"Pawtucket EMSTac","tg_group":"Pawtucket","frequency":null,"metadata":null,"tags":["EMS-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":200,"system_id":197,"tgid":1248,"name":"Police","alpha_tag":"PortsmouthPD","tg_group":"Portsmouth","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":201,"system_id":197,"tgid":1253,"name":"Fire Dispatch (Patch to VHF Primary)","alpha_tag":"Portsmouth FD","tg_group":"Portsmouth","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":202,"system_id":197,"tgid":1255,"name":"Fireground","alpha_tag":"Portsmouth FG","tg_group":"Portsmouth","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":203,"system_id":197,"tgid":1262,"name":"Island Fire Dispatch","alpha_tag":"Prudence Isl FD","tg_group":"Portsmouth","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":204,"system_id":197,"tgid":10000,"name":"Police - All Call - Emergency Broadcasts","alpha_tag":"PPD ATG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":205,"system_id":197,"tgid":10001,"name":"Police 1 - Dispatch","alpha_tag":"PPD CH 1","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":206,"system_id":197,"tgid":10002,"name":"Police 2","alpha_tag":"PPD CH 2","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":207,"system_id":197,"tgid":10003,"name":"Police 3","alpha_tag":"PPD CH 3","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":208,"system_id":197,"tgid":10004,"name":"Police 4","alpha_tag":"PPD CH-4","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":209,"system_id":197,"tgid":10005,"name":"Police 5 -Detectives 1","alpha_tag":"PPD DETEC 1","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":210,"system_id":197,"tgid":10006,"name":"Police 6 - Car-to-Car","alpha_tag":"PPD T/A","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":211,"system_id":197,"tgid":10007,"name":"Police 7 - Narcotics 1","alpha_tag":"PPD NARC 1","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":212,"system_id":197,"tgid":10008,"name":"Police 8 - Narcotics 2","alpha_tag":"PPD NARC 2","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":213,"system_id":197,"tgid":10009,"name":"Police 9 - Detectives 2","alpha_tag":"PPD DETEC 2","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":214,"system_id":197,"tgid":10010,"name":"Police 10 - Special Details 1","alpha_tag":"PPD DETAIL 1","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":215,"system_id":197,"tgid":10011,"name":"Police 11 - Special Details 2","alpha_tag":"PPD DETAIL 2","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":216,"system_id":197,"tgid":10012,"name":"Police 12 - Corrections Security","alpha_tag":"PPD CORR SEC","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":217,"system_id":197,"tgid":10013,"name":"Police 13 - Special Response Unit","alpha_tag":"PPD SRU","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":218,"system_id":197,"tgid":10014,"name":"Police 14 - Administration","alpha_tag":"PPD ADMIN","tg_group":"Providence (City)","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":219,"system_id":197,"tgid":10100,"name":"Fire All Call - Emergency Broadcasts","alpha_tag":"PROV FD ATG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":220,"system_id":197,"tgid":10101,"name":"Fire Dispatch","alpha_tag":"PFD DISPATCH","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":221,"system_id":197,"tgid":10107,"name":"Fireground 2","alpha_tag":"PFD CH-2 FG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":222,"system_id":197,"tgid":10108,"name":"Fireground 3","alpha_tag":"PFD CH-3 FG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":223,"system_id":197,"tgid":10109,"name":"Fireground 4","alpha_tag":"PFD CH-4 FG","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":224,"system_id":197,"tgid":10102,"name":"Fire 5","alpha_tag":"PFD CH-5","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":225,"system_id":197,"tgid":10103,"name":"Fire 6","alpha_tag":"PFD CH-6","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":226,"system_id":197,"tgid":10104,"name":"Fire 7","alpha_tag":"PFD CH-7","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":227,"system_id":197,"tgid":10110,"name":"Fire - Mutual Aid 1","alpha_tag":"PFD M/A 1","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":228,"system_id":197,"tgid":10111,"name":"Fire - Mutual Aid 2","alpha_tag":"PFD M/A 2","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":229,"system_id":197,"tgid":10112,"name":"Fire - Mutual Aid 3","alpha_tag":"PFD M/A 3","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":230,"system_id":197,"tgid":10113,"name":"Fireground 8","alpha_tag":"PFD Fireground 8","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":231,"system_id":197,"tgid":10105,"name":"Fire - Administration","alpha_tag":"PFD ADMIN","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":232,"system_id":197,"tgid":10106,"name":"Fire - Communications","alpha_tag":"PFD COMM","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":233,"system_id":197,"tgid":10207,"name":"Public Works","alpha_tag":"PROV DPW","tg_group":"Providence (City)","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":234,"system_id":197,"tgid":2035,"name":"Police","alpha_tag":"Richmond PD","tg_group":"Richmond","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":235,"system_id":197,"tgid":2042,"name":"Chariho Regional High School","alpha_tag":"Chariho Reg HS","tg_group":"Richmond","frequency":null,"metadata":null,"tags":["Schools"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":236,"system_id":197,"tgid":1460,"name":"Police","alpha_tag":"Scituate PD","tg_group":"Scituate","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":237,"system_id":197,"tgid":1463,"name":"Fire Operations","alpha_tag":"Scituate FD","tg_group":"Scituate","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":238,"system_id":197,"tgid":1651,"name":"Police Operations","alpha_tag":"SmithfieldPD","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":239,"system_id":197,"tgid":1652,"name":"Police Secondary","alpha_tag":"Smfld PD 2","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":240,"system_id":197,"tgid":1653,"name":"Police Detectives","alpha_tag":"Smfld PD Det","tg_group":"Smithfield","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":241,"system_id":197,"tgid":1654,"name":"Police Admin","alpha_tag":"Smfld PD Adm","tg_group":"Smithfield","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":242,"system_id":197,"tgid":1661,"name":"Police Details","alpha_tag":"Smfld PD Dtl","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":243,"system_id":197,"tgid":1648,"name":"Fire - Fireground","alpha_tag":"SmithfieldFD","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":244,"system_id":197,"tgid":1655,"name":"Town-Wide","alpha_tag":"Smfld Town","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Multi-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":245,"system_id":197,"tgid":1657,"name":"Emergency Management","alpha_tag":"Smfld EMA","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Emergency Ops"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":246,"system_id":197,"tgid":1660,"name":"Public Works","alpha_tag":"Smfld DPW","tg_group":"Smithfield","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":247,"system_id":197,"tgid":1225,"name":"Police 1 - Dispatch","alpha_tag":"SKing PD 1","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":248,"system_id":197,"tgid":1226,"name":"Police 2 - Car/Car","alpha_tag":"SKing PD 2","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":249,"system_id":197,"tgid":1235,"name":"Police 3 - Tactical","alpha_tag":"SKing PD 3","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":250,"system_id":197,"tgid":1236,"name":"Police 5 - Tactical","alpha_tag":"SKing PD 5","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":251,"system_id":197,"tgid":1232,"name":"Fire - UHF Simulcast","alpha_tag":"SKing FD Lnk","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":252,"system_id":197,"tgid":1240,"name":"Fire - Detail","alpha_tag":"SKing Fire D","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":253,"system_id":197,"tgid":1227,"name":"Union Fire District - Fireground 1","alpha_tag":"UnionFD FG 1","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":254,"system_id":197,"tgid":1237,"name":"Union Fire District - Fireground 2","alpha_tag":"UnionFD FG 2","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":255,"system_id":197,"tgid":1026,"name":"Union Fire District - Special Events","alpha_tag":"UnionFD Evnt","tg_group":"South Kingstown","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":256,"system_id":197,"tgid":1015,"name":"EMS","alpha_tag":"SKing EMS","tg_group":"South Kingstown","frequency":null,"metadata":{"encrypted":true},"tags":["EMS Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":257,"system_id":197,"tgid":1316,"name":"Police (Simulcast 482.9625)","alpha_tag":"Tiverton PD","tg_group":"Tiverton","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":258,"system_id":197,"tgid":1315,"name":"Fire (Simulcast 471.7875)","alpha_tag":"Tiverton FD","tg_group":"Tiverton","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":259,"system_id":197,"tgid":1162,"name":"Fire","alpha_tag":"Warwick FD","tg_group":"Warwick","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":260,"system_id":197,"tgid":1170,"name":"Fireground","alpha_tag":"Warwick FG","tg_group":"Warwick","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":261,"system_id":197,"tgid":1805,"name":"Police","alpha_tag":"W Greenwh PD","tg_group":"West Greenwich","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":262,"system_id":197,"tgid":1806,"name":"Police Secondary","alpha_tag":"W GreenwichPD2","tg_group":"West Greenwich","frequency":null,"metadata":null,"tags":["Law Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":263,"system_id":197,"tgid":1208,"name":"Fire Operations","alpha_tag":"W Warwick FD","tg_group":"West Warwick","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":264,"system_id":197,"tgid":1050,"name":"Police 1 - Dispatch","alpha_tag":"Westerly PD1","tg_group":"Westerly","frequency":null,"metadata":{"encrypted":true},"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":265,"system_id":197,"tgid":1051,"name":"Police 2","alpha_tag":"Westerly PD2","tg_group":"Westerly","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":266,"system_id":197,"tgid":1052,"name":"Police 3","alpha_tag":"Westerly PD3","tg_group":"Westerly","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":267,"system_id":197,"tgid":1053,"name":"Police 4","alpha_tag":"Westerly PD4","tg_group":"Westerly","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":268,"system_id":197,"tgid":1054,"name":"Police 5 - Reserve Officers","alpha_tag":"Westerly PD5","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":269,"system_id":197,"tgid":1064,"name":"Police 6 - Traffic Division","alpha_tag":"Westerly PD6","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":270,"system_id":197,"tgid":1063,"name":"Fire Operations","alpha_tag":"Westerly FD","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":271,"system_id":197,"tgid":1072,"name":"Police/Fire/EMS Ops","alpha_tag":"Westerly PFE","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["Multi-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":272,"system_id":197,"tgid":1082,"name":"EMS Operations","alpha_tag":"Westerly EMS ","tg_group":"Westerly","frequency":null,"metadata":null,"tags":["EMS Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":273,"system_id":197,"tgid":1363,"name":"Police 1 - Dispatch","alpha_tag":"Woonskt PD 1","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Law Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":274,"system_id":197,"tgid":1364,"name":"Police 2","alpha_tag":"Woonskt PD 2","tg_group":"Woonsocket","frequency":null,"metadata":{"encrypted":true},"tags":["Law Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":275,"system_id":197,"tgid":1360,"name":"Fire Dispatch - Operations","alpha_tag":"Woonsocket FD D","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Fire-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":276,"system_id":197,"tgid":1361,"name":"Fire Secondary","alpha_tag":"Woonsocket FD 2","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Fire Dispatch"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":277,"system_id":197,"tgid":1354,"name":"Fire - Fireground 3","alpha_tag":"Woonskt FD 3","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Fire-Tac"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":278,"system_id":197,"tgid":1367,"name":"Citywide","alpha_tag":"Woonskt City","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Multi-Talk"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":279,"system_id":197,"tgid":1368,"name":"Public Works - Streets","alpha_tag":"Woonsocket PW","tg_group":"Woonsocket","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":280,"system_id":197,"tgid":1,"name":"RISCON Radio Technicians","alpha_tag":"Radio Techs","tg_group":"Radio Technicians","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false},{"id":281,"system_id":197,"tgid":10125,"name":"RISCON Radio Technicians","alpha_tag":"Radio Techs","tg_group":"Radio Technicians","frequency":null,"metadata":null,"tags":["Public Works"],"alert":true,"alert_config":null,"weight":1,"ignored":false,"system":{"id":197,"name":"RISCON"},"learned":false}] \ No newline at end of file diff --git a/pkg/talkgroups/store.go b/pkg/talkgroups/tgstore/store.go similarity index 68% rename from pkg/talkgroups/store.go rename to pkg/talkgroups/tgstore/store.go index 1d39246..9ec106f 100644 --- a/pkg/talkgroups/store.go +++ b/pkg/talkgroups/tgstore/store.go @@ -1,4 +1,4 @@ -package talkgroups +package tgstore import ( "context" @@ -8,14 +8,17 @@ import ( "time" "dynatron.me/x/stillbox/internal/common" + "dynatron.me/x/stillbox/pkg/auth" "dynatron.me/x/stillbox/pkg/config" "dynatron.me/x/stillbox/pkg/database" + "dynatron.me/x/stillbox/pkg/calls" + tgsp "dynatron.me/x/stillbox/pkg/talkgroups" "github.com/jackc/pgx/v5" "github.com/rs/zerolog/log" ) -type tgMap map[ID]*Talkgroup +type tgMap map[tgsp.ID]*tgsp.Talkgroup var ( ErrNotFound = errors.New("talkgroup not found") @@ -24,25 +27,28 @@ var ( type Store interface { // UpdateTG updates a talkgroup record. - UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) + UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*tgsp.Talkgroup, error) // UpsertTGs upserts a slice of talkgroups. - UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*Talkgroup, error) + UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*tgsp.Talkgroup, error) // TG retrieves a Talkgroup from the Store. - TG(ctx context.Context, tg ID) (*Talkgroup, error) + TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) // TGs retrieves many talkgroups from the Store. - TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error) + TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error) + + // LearnTG learns the talkgroup from a Call. + LearnTG(ctx context.Context, call *calls.Call) (*tgsp.Talkgroup, error) // SystemTGs retrieves all Talkgroups associated with a System. - SystemTGs(ctx context.Context, systemID int32) ([]*Talkgroup, error) + SystemTGs(ctx context.Context, systemID int32) ([]*tgsp.Talkgroup, error) // SystemName retrieves a system name from the store. It returns the record and whether one was found. SystemName(ctx context.Context, id int) (string, bool) // Hint hints the Store that the provided talkgroups will be asked for. - Hint(ctx context.Context, tgs []ID) error + Hint(ctx context.Context, tgs []tgsp.ID) error // Load loads the provided talkgroup ID tuples into the Store. Load(ctx context.Context, tgs database.TGTuples) error @@ -51,7 +57,7 @@ type Store interface { Invalidate() // Weight returns the final weight of this talkgroup, including its static and rules-derived weight. - Weight(ctx context.Context, id ID, t time.Time) float64 + Weight(ctx context.Context, id tgsp.ID, t time.Time) float64 // Hupper HUP(*config.Config) @@ -65,7 +71,7 @@ func CtxWithStore(ctx context.Context, s Store) context.Context { return context.WithValue(ctx, StoreCtxKey, s) } -func StoreFrom(ctx context.Context) Store { +func FromCtx(ctx context.Context) Store { s, ok := ctx.Value(StoreCtxKey).(Store) if !ok { return NewCache() @@ -101,7 +107,7 @@ func NewCache() Store { return tgc } -func (t *cache) Hint(ctx context.Context, tgs []ID) error { +func (t *cache) Hint(ctx context.Context, tgs []tgsp.ID) error { t.RLock() var toLoad database.TGTuples if len(t.tgs) > len(tgs)/2 { // TODO: instrument this @@ -129,11 +135,11 @@ func (t *cache) Hint(ctx context.Context, tgs []ID) error { return nil } -func (t *cache) add(rec *Talkgroup) { +func (t *cache) add(rec *tgsp.Talkgroup) { t.Lock() defer t.Unlock() - tg := TG(rec.System.ID, rec.Talkgroup.TGID) + tg := tgsp.TG(rec.System.ID, rec.Talkgroup.TGID) t.tgs[tg] = rec t.systems[int32(rec.System.ID)] = rec.System.Name } @@ -146,15 +152,15 @@ type row interface { GetLearned() bool } -func rowToTalkgroup[T row](r T) *Talkgroup { - return &Talkgroup{ +func rowToTalkgroup[T row](r T) *tgsp.Talkgroup { + return &tgsp.Talkgroup{ Talkgroup: r.GetTalkgroup(), System: r.GetSystem(), Learned: r.GetLearned(), } } -func addToRowList[T row](t *cache, r []*Talkgroup, tgRecords []T) []*Talkgroup { +func addToRowList[T row](t *cache, r []*tgsp.Talkgroup, tgRecords []T) []*tgsp.Talkgroup { for _, rec := range tgRecords { tg := rowToTalkgroup(rec) t.add(tg) @@ -165,11 +171,11 @@ func addToRowList[T row](t *cache, r []*Talkgroup, tgRecords []T) []*Talkgroup { return r } -func (t *cache) TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error) { - r := make([]*Talkgroup, 0, len(tgs)) +func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error) { + r := make([]*tgsp.Talkgroup, 0, len(tgs)) var err error if tgs != nil { - toGet := make(IDs, 0, len(tgs)) + toGet := make(tgsp.IDs, 0, len(tgs)) t.RLock() for _, id := range tgs { rec, has := t.tgs[id] @@ -210,7 +216,7 @@ func (t *cache) Load(ctx context.Context, tgs database.TGTuples) error { return nil } -func (t *cache) Weight(ctx context.Context, id ID, tm time.Time) float64 { +func (t *cache) Weight(ctx context.Context, id tgsp.ID, tm time.Time) float64 { tg, err := t.TG(ctx, id) if err != nil { return 1.0 @@ -223,17 +229,17 @@ func (t *cache) Weight(ctx context.Context, id ID, tm time.Time) float64 { return float64(m) } -func (t *cache) SystemTGs(ctx context.Context, systemID int32) ([]*Talkgroup, error) { +func (t *cache) SystemTGs(ctx context.Context, systemID int32) ([]*tgsp.Talkgroup, error) { recs, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySystem(ctx, systemID) if err != nil { return nil, err } - r := make([]*Talkgroup, 0, len(recs)) + r := make([]*tgsp.Talkgroup, 0, len(recs)) return addToRowList(t, r, recs), nil } -func (t *cache) TG(ctx context.Context, tg ID) (*Talkgroup, error) { +func (t *cache) TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) { t.RLock() rec, has := t.tgs[tg] t.RUnlock() @@ -278,7 +284,7 @@ func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool) return n, has } -func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) { +func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*tgsp.Talkgroup, error) { sysName, has := t.SystemName(ctx, int(*input.SystemID)) if !has { return nil, ErrNoSuchSystem @@ -289,7 +295,7 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara return nil, err } - record := &Talkgroup{ + record := &tgsp.Talkgroup{ Talkgroup: tg, System: database.System{ID: int(tg.SystemID), Name: sysName}, } @@ -298,7 +304,40 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara return record, nil } -func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*Talkgroup, error) { +func (t *cache) LearnTG(ctx context.Context, c *calls.Call) (*tgsp.Talkgroup, error) { + db := database.FromCtx(ctx) + + sys, has := t.SystemName(ctx, c.System) + if !has { + return nil, ErrNoSuchSystem + } + + tgm, err := db.AddLearnedTalkgroup(ctx, database.AddLearnedTalkgroupParams{ + SystemID: int32(c.System), + TGID: int32(c.Talkgroup), + Name: c.TalkgroupLabel, + AlphaTag: c.TGAlphaTag, + TGGroup: c.TalkgroupGroup, + }) + if err != nil { + return nil, err + } + + tg := &tgsp.Talkgroup{ + Talkgroup: tgm, + System: database.System{ + ID: c.System, + Name: sys, + }, + Learned: tgm.Learned, + } + + t.add(tg) + + return tg, nil +} + +func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*tgsp.Talkgroup, error) { db := database.FromCtx(ctx) sysName, hasSys := t.SystemName(ctx, system) if !hasSys { @@ -309,9 +348,10 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse Name: sysName, } - tgs := make([]*Talkgroup, 0, len(input)) + tgs := make([]*tgsp.Talkgroup, 0, len(input)) err := db.InTx(ctx, func(db database.Store) error { + versionParams := make([]database.StoreTGVersionParams, 0, len(input)) for i := range input { // normalize tags for j, tag := range input[i].Tags { @@ -320,19 +360,26 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse input[i].SystemID = int32(system) input[i].Learned = common.PtrTo(false) + + } var oerr error - batch := db.UpsertTalkgroup(ctx, input) - defer batch.Close() + tgUpsertBatch := db.UpsertTalkgroup(ctx, input) + defer tgUpsertBatch.Close() - batch.QueryRow(func(_ int, r database.Talkgroup, err error) { + tgUpsertBatch.QueryRow(func(_ int, r database.Talkgroup, err error) { if err != nil { oerr = err return } - tgs = append(tgs, &Talkgroup{ + versionParams = append(versionParams, database.StoreTGVersionParams{ + SystemID: int32(system), + TGID: r.TGID, + Submitter: auth.UIDFrom(ctx), + }) + tgs = append(tgs, &tgsp.Talkgroup{ Talkgroup: r, System: sys, Learned: r.Learned, @@ -343,7 +390,17 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse return oerr } - return nil + versionBatch := db.StoreTGVersion(ctx, versionParams) + defer versionBatch.Close() + + versionBatch.Exec(func(_ int, err error) { + if err != nil { + oerr = err + return + } + }) + + return oerr }, pgx.TxOptions{}) if err != nil { diff --git a/sql/postgres/migrations/001_initial.up.sql b/sql/postgres/migrations/001_initial.up.sql index 59aa9ec..14695d5 100644 --- a/sql/postgres/migrations/001_initial.up.sql +++ b/sql/postgres/migrations/001_initial.up.sql @@ -37,6 +37,7 @@ CREATE TABLE IF NOT EXISTS talkgroups( alert_config JSONB, weight REAL NOT NULL DEFAULT 1.0, learned BOOLEAN NOT NULL DEFAULT FALSE, + ignored BOOLEAN NOT NULL DEFAULT FALSE, UNIQUE (system_id, tgid) ); @@ -44,15 +45,25 @@ CREATE INDEX talkgroups_system_tgid_idx ON talkgroups (system_id, tgid); CREATE INDEX IF NOT EXISTS talkgroup_id_tags ON talkgroups USING GIN (tags); -CREATE TABLE IF NOT EXISTS talkgroups_learned( +CREATE TABLE IF NOT EXISTS talkgroup_versions( + -- version metadata id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, - system_id INTEGER REFERENCES systems(id) NOT NULL, - tgid INTEGER NOT NULL, - name TEXT NOT NULL, + time TIMESTAMPTZ NOT NULL, + created_by INTEGER REFERENCES users(id), + -- talkgroup snapshot + system_id INT4 REFERENCES systems(id), + tgid INT4, + name TEXT, alpha_tag TEXT, tg_group TEXT, - ignored BOOLEAN, - UNIQUE (system_id, tgid, name) + frequency INTEGER, + metadata JSONB, + tags TEXT[], + alert BOOLEAN, + alert_config JSONB, + weight REAL, + learned BOOLEAN, + ignored BOOLEAN ); CREATE TABLE IF NOT EXISTS alerts( diff --git a/sql/postgres/queries/talkgroups.sql b/sql/postgres/queries/talkgroups.sql index fe106a4..9be53fe 100644 --- a/sql/postgres/queries/talkgroups.sql +++ b/sql/postgres/queries/talkgroups.sql @@ -29,47 +29,20 @@ SELECT sqlc.embed(tg), sqlc.embed(sys) FROM talkgroups tg JOIN systems sys ON tg.system_id = sys.id -WHERE (tg.system_id, tg.tgid) = (@system_id, @tgid) AND tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id -WHERE tgl.system_id = @system_id AND tgl.tgid = @tgid AND ignored IS NOT TRUE; +WHERE (tg.system_id, tg.tgid) = (@system_id, @tgid); -- name: GetTalkgroupsWithLearnedBySystem :many SELECT sqlc.embed(tg), sqlc.embed(sys) FROM talkgroups tg JOIN systems sys ON tg.system_id = sys.id -WHERE tg.system_id = @system AND tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id -WHERE tgl.system_id = @system AND ignored IS NOT TRUE; +WHERE tg.system_id = @system; -- name: GetTalkgroupsWithLearned :many SELECT sqlc.embed(tg), sqlc.embed(sys) FROM talkgroups tg JOIN systems sys ON tg.system_id = sys.id -WHERE tg.learned IS NOT TRUE -UNION -SELECT -tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name, -tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB, -CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END, -NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name -FROM talkgroups_learned tgl -JOIN systems sys ON tgl.system_id = sys.id WHERE ignored IS NOT TRUE; -- name: GetSystemName :one @@ -122,28 +95,92 @@ SET learned = COALESCE(sqlc.narg('learned'), tg.learned) RETURNING *; --- name: AddTalkgroupWithLearnedFlag :exec -INSERT INTO talkgroups ( +-- name: StoreTGVersion :batchexec +INSERT INTO talkgroup_versions(time, created_by, system_id, tgid, + name, + alpha_tag, + tg_group, + frequency, + metadata, + tags, + alert, + alert_config, + weight, learned -) VALUES( - @system_id, - @tgid, - TRUE -); +) SELECT NOW(), @submitter, + tg.system_id, + tg.tgid, + tg.name, + tg.alpha_tag, + tg.tg_group, + tg.frequency, + tg.metadata, + tg.tags, + tg.alert, + tg.alert_config, + tg.weight, + tg.learned +FROM talkgroups tg WHERE tg.system_id = @system_id AND tg.tgid = @tgid; -- name: AddLearnedTalkgroup :one -INSERT INTO talkgroups_learned( +INSERT INTO talkgroups( system_id, tgid, + learned, name, alpha_tag, tg_group ) VALUES ( @system_id, @tgid, + TRUE, sqlc.narg('name'), sqlc.narg('alpha_tag'), sqlc.narg('tg_group') -) RETURNING id; +) RETURNING *; + +-- name: RestoreTalkgroupVersion :one +INSERT INTO talkgroups( + system_id, + tgid, + name, + alpha_tag, + tg_group, + frequency, + metadata, + tags, + alert, + alert_config, + weight, + learned, + ignored +) +SELECT + system_id, + tgid, + name, + alpha_tag, + tg_group, + frequency, + metadata, + tags, + alert, + alert_config, + weight, + learned, + ignored +FROM talkgroup_versions tgv ON CONFLICT (system_id, tgid) DO UPDATE SET + name = excluded.name, + alpha_tag = excluded.alpha_tag, + tg_group = excluded.tg_group, + metadata = excluded.metadata, + tags = excluded.tags, + alert = excluded.alert, + alert_config = excluded.alert_config, + weight = excluded.weight, + learned = excluded.learner, + ignored = excluded.ignored +WHERE tgv.id = ANY(@version_ids) +RETURNING *;