Powered by AppSignal & Oban Pro

Alerts

livebooks/reusable/alerts.livemd

Alerts

Imports and Aliases

import Dotcom.Utils.DateTime, only: [now: 0]

alias Test.Support.Factories.Alerts.{Alert, InformedEntity}

Stop the fetcher so it doesn’t update alerts

Supervisor.which_children(Dotcom.Supervisor)
|> Enum.find(fn {_, _, _, list} -> list == [Alerts.CacheSupervisor] end)
|> elem(1)
|> Supervisor.terminate_child(Alerts.Cache.Fetcher)

No alerts!

# Remove all other alerts and only use the ones you created
Alerts.Cache.Store.update([], nil)

now() |> Alerts.Repo.all()

Orange Line Suspension

# Create an informed entity for a route (Orange) and stop (Downtown Crossing)
informed_entity =
  InformedEntity.build(:informed_entity,
    activities: MapSet.new([:exit, :ride, :board]),
    route: "Orange",
    route_type: 1,
    stop: "place-dwnxg"
  )

informed_entity_set = Alerts.InformedEntitySet.new([informed_entity])

# Create a currently active suspension alert
alert =
  Alert.build(:alert,
    active_period: [
      {
        now() |> Timex.shift(hours: -4),
        now() |> Timex.shift(hours: 8)
      }
    ],
    effect: :suspension,
    informed_entity: informed_entity_set
  )

alerts = [alert]

# Remove all other alerts and only use the one's you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Red Line Single-Tracking with Endpoints

informed_entities =
  [
    nil,
    "place-jfk",
    "place-nqncy",
    "place-wlsta",
    "place-qnctr"
  ]
  |> Enum.map(
    &InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      route: "Red",
      route_type: 1,
      stop: &1
    )
  )

# Create a currently active suspension alert
alerts =
  [
    Alert.build(:alert,
      active_period: [
        {
          now() |> Timex.shift(hours: -4),
          now() |> Timex.shift(hours: 8)
        }
      ],
      effect: :single_tracking,
      cause: :single_tracking,
      severity: 1,
      informed_entity: Alerts.InformedEntitySet.new(informed_entities)
    )
  ]

# Remove all other alerts and only use the ones you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Symphony Closure - Eastbound

# Informed entity set for Eastbound platform
informed_entity_set =
  Alerts.InformedEntitySet.new([
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: 1,
      route: "Green-E",
      route_type: 0,
      stop: "place-symcl",
      trip: nil
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: 1,
      route: "Green-E",
      route_type: 0,
      stop: "70242",
      trip: nil
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: 1,
      route: "Green",
      route_type: 0,
      stop: "place-symcl",
      trip: nil
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: 1,
      route: "Green",
      route_type: 0,
      stop: "70242",
      trip: nil
    )
  ])

# Create a currently active alert
alert =
  Alert.build(:alert,
    active_period: [
      {
        now() |> Timex.shift(hours: -4),
        now() |> Timex.shift(hours: 8)
      }
    ],
    header:
      "Eastbound trains will not stop at Symphony in order to prank the engineers who work on displaying these alerts",
    effect: :station_closure,
    informed_entity: informed_entity_set,
    severity: 7,
    priority: :high,
    lifecycle: :new,
    banner: nil
  )

alerts = [alert]

# Remove all other alerts and only use the one's you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Symphony Closure - Both Directions

# Informed entity set for all of Symphony
informed_entity_set =
  Alerts.InformedEntitySet.new([
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: nil,
      route: "Green-E",
      route_type: 0,
      stop: "place-symcl",
      trip: nil
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: nil,
      route: "Green-E",
      route_type: 0,
      stop: "70241",
      trip: nil
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: nil,
      route: "Green-E",
      route_type: 0,
      stop: "70242",
      trip: nil
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: nil,
      route: "Green",
      route_type: 0,
      stop: "place-symcl",
      trip: nil
    )
  ])

# Create a currently active alert
alert =
  Alert.build(:alert,
    active_period: [
      {
        now() |> Timex.shift(hours: -4),
        now() |> Timex.shift(hours: 8)
      }
    ],
    header:
      "All trains will bypass Symphony in order to prank the engineers who work on displaying these alerts",
    effect: :station_closure,
    informed_entity: informed_entity_set,
    severity: 7,
    priority: :high,
    lifecycle: :new,
    banner: nil
  )

alerts = [alert]

# Remove all other alerts and only use the one's you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Tappan Street Closures (Time-based, now)

alerts = [
  Alert.build(:alert,
    active_period: [
      {
        Timex.parse!("2026-05-18T03:00:00-04:00", "{ISO:Extended}"),
        Timex.parse!("2026-06-13T02:59:59-04:00", "{ISO:Extended}")
      }
    ],
    header:
      "Tappan Street will be closed from Monday, May 18 through Friday, June 12 due to maintenance work on the C branch. Riders can board or exit trains one stop away, at either Washington Square or Dean Road.",
    effect: :station_closure,
    informed_entity:
      Alerts.InformedEntitySet.new([
        InformedEntity.build(:informed_entity,
          activities: MapSet.new([:exit, :ride, :board]),
          direction_id: nil,
          route: "Green-C",
          route_type: 0,
          stop: "place-tapst",
          trip: nil
        )
      ]),
    severity: 7,
    priority: :high,
    lifecycle: :new,
    banner: nil
  ),
  Alert.build(:alert,
    active_period: [
      {
        Timex.parse!("2026-06-13T03:00:00-04:00", "{ISO:Extended}"),
        Timex.parse!("2026-07-11T02:59:59-04:00", "{ISO:Extended}")
      }
    ],
    header:
      "Green Line C Branch: Due to infrastructure upgrades, outbound trains will bypass Tappan Street Monday, May 18 through Friday, July 10. Passengers can instead board or disembark one stop away at Washington Square or Dean Road.",
    effect: :station_closure,
    informed_entity:
      Alerts.InformedEntitySet.new([
        InformedEntity.build(:informed_entity,
          activities: MapSet.new([:exit, :ride, :board]),
          direction_id: 0,
          route: "Green-C",
          route_type: 0,
          stop: "place-tapst",
          trip: nil
        )
      ]),
    severity: 7,
    priority: :high,
    lifecycle: :new,
    banner: nil
  ),
  Alert.build(:alert,
    active_period: [
      {
        Timex.parse!("2026-05-18T03:00:00-04:00", "{ISO:Extended}"),
        Timex.parse!("2026-07-11T02:59:59-04:00", "{ISO:Extended}")
      }
    ],
    header:
      "Green Line C Branch: Due to maintenance work, outbound trains will bypass Englewood Avenue Monday, May 18 through Friday, July 10. Passengers can instead board or disembark one stop away at Cleveland Circle or Dean Road.",
    effect: :station_closure,
    informed_entity:
      Alerts.InformedEntitySet.new([
        InformedEntity.build(:informed_entity,
          activities: MapSet.new([:exit, :ride, :board]),
          direction_id: 0,
          route: "Green-C",
          route_type: 0,
          stop: "place-engav",
          trip: nil
        )
      ]),
    severity: 7,
    priority: :high,
    lifecycle: :new,
    banner: nil
  )
]

# Remove all other alerts and only use the one's you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Tappan Street Closures (later, with Englewood completely closed)

alerts = [
  Alert.build(:alert,
    active_period: [
      {
        Timex.parse!("2026-05-18T03:00:00-04:00", "{ISO:Extended}"),
        Timex.parse!("2026-07-11T02:59:59-04:00", "{ISO:Extended}")
      }
    ],
    header:
      "Green Line C Branch: Due to infrastructure upgrades, outbound trains will bypass Tappan Street Monday, May 18 through Friday, July 10. Passengers can instead board or disembark one stop away at Washington Square or Dean Road.",
    effect: :station_closure,
    informed_entity:
      Alerts.InformedEntitySet.new([
        InformedEntity.build(:informed_entity,
          activities: MapSet.new([:exit, :ride, :board]),
          direction_id: 0,
          route: "Green-C",
          route_type: 0,
          stop: "place-tapst",
          trip: nil
        )
      ]),
    severity: 7,
    priority: :high,
    lifecycle: :new,
    banner: nil
  ),
  Alert.build(:alert,
    active_period: [
      {
        Timex.parse!("2026-05-18T03:00:00-04:00", "{ISO:Extended}"),
        Timex.parse!("2026-07-11T02:59:59-04:00", "{ISO:Extended}")
      }
    ],
    header:
      "Green Line C Branch: Due to maintenance work, all trains will bypass Englewood Avenue Monday, May 18 through Friday, July 10. Passengers can instead board or disembark one stop away at Cleveland Circle or Dean Road.",
    effect: :station_closure,
    informed_entity:
      Alerts.InformedEntitySet.new([
        InformedEntity.build(:informed_entity,
          activities: MapSet.new([:exit, :ride, :board]),
          direction_id: nil,
          route: "Green-C",
          route_type: 0,
          stop: "place-engav",
          trip: nil
        )
      ]),
    severity: 7,
    priority: :high,
    lifecycle: :new,
    banner: nil
  )
]

# Remove all other alerts and only use the one's you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Two Red Line Stations Skipped

# Informed entity set for Northbound platform
informed_entity_set =
  Alerts.InformedEntitySet.new([
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: 1,
      route: "Red",
      route_type: 1,
      stop: "70076"
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: 1,
      route: "Red",
      route_type: 1,
      stop: "70078"
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: 1,
      route: "Red",
      route_type: 1,
      stop: "place-dwnxg"
    ),
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      direction_id: 1,
      route: "Red",
      route_type: 1,
      stop: "place-pktrm"
    )
  ])

# Create a currently active alert
alert =
  Alert.build(:alert,
    active_period: [
      {
        now() |> Timex.shift(hours: -4),
        now() |> Timex.shift(hours: 8)
      }
    ],
    header:
      "Downtown Crossing and Park Street will be bypassed in the Alewife-bound direction, just for fun.",
    effect: :station_closure,
    informed_entity: informed_entity_set,
    lifecycle: :new,
    priority: :high,
    severity: 7,
    banner: nil
  )

alerts = [alert]

# Remove all other alerts and only use the one's you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Commuter Rail Current and Upcoming Service Change Alerts

# Create an informed entity for a commuter rail route
cr_route_ids = Routes.Repo.by_type(2) |> Enum.map(& &1.id)

current_active_period = {
  now() |> Timex.shift(hours: -4),
  now() |> Timex.shift(hours: 8)
}

future_active_period = {
  now() |> Timex.shift(hours: 40),
  now() |> Timex.shift(hours: 98)
}

current_alert =
  Alert.build(:alert,
    active_period: [current_active_period],
    effect: :service_change,
    informed_entity:
      Alerts.InformedEntitySet.new([
        InformedEntity.build(:informed_entity,
          activities: MapSet.new([:exit, :ride, :board]),
          route: Faker.Util.pick(cr_route_ids),
          route_type: 2
        )
      ]),
    priority: :high,
    severity: 3
  )

future_alert =
  Alert.build(:alert,
    active_period: [future_active_period],
    effect: :service_change,
    informed_entity:
      Alerts.InformedEntitySet.new([
        InformedEntity.build(:informed_entity,
          activities: MapSet.new([:exit, :ride, :board]),
          route: Faker.Util.pick(cr_route_ids),
          route_type: 2
        )
      ]),
    priority: :high,
    severity: 3
  )

alerts = [current_alert, future_alert]

# Remove all other alerts and only use the one's you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Commuter Rail Multi-Trip Train Alert

# Create an informed entity for a commuter rail route
route = Routes.Repo.by_type(2) |> Faker.Util.pick()

schedules = Schedules.Repo.by_route_ids([route.id])

trip_ids =
  Faker.Util.sample_uniq(3, fn -> Faker.Util.pick(schedules) end)
  |> Enum.map(& &1.trip.id)

informed_entities =
  trip_ids
  |> Enum.map(
    &InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      route: route.id,
      route_type: 2,
      trip: &1
    )
  )

alert =
  Alert.build(:alert,
    active_period: [
      {
        now() |> Timex.shift(hours: -4),
        now() |> Timex.shift(hours: 8)
      }
    ],
    effect: Faker.Util.pick([:delay, :cancellation]),
    informed_entity: Alerts.InformedEntitySet.new(informed_entities),
    priority: :high,
    severity: 3
  )

# Remove all other alerts and only use the one you created
Alerts.Cache.Store.update([alert], nil)

now() |> Alerts.Repo.all()

Commuter Rail All Trains or Direction-Specific Alert

# Create an informed entity for a commuter rail route
route = Routes.Repo.by_type(2) |> Faker.Util.pick()

informed_entities = [
  InformedEntity.build(:informed_entity,
    activities: MapSet.new([:exit, :ride, :board]),
    direction_id: Faker.Util.pick([0, 1, nil]),
    route: route.id,
    route_type: 2,
    trip: nil
  )
]

alert =
  Alert.build(:alert,
    active_period: [
      {
        now() |> Timex.shift(hours: -4),
        now() |> Timex.shift(hours: 8)
      }
    ],
    effect: Faker.Util.pick([:delay, :cancellation]),
    informed_entity: Alerts.InformedEntitySet.new(informed_entities),
    priority: :high,
    severity: 3
  )

# Remove all other alerts and only use the one you created
Alerts.Cache.Store.update([alert], nil)

now() |> Alerts.Repo.all()

Commuter Rail Upcoming Shuttle and Delay

# Create an informed entity for a commuter rail route
route = Routes.Repo.by_type(2) |> Faker.Util.pick()

schedules = Schedules.Repo.by_route_ids([route.id])

trip_ids =
  Faker.Util.sample_uniq(3, fn -> Faker.Util.pick(schedules) end)
  |> Enum.map(& &1.trip.id)

alerts =
  [
    Alert.build(:alert,
      active_period: [
        {
          now() |> Timex.shift(hours: -4),
          now() |> Timex.shift(hours: 8)
        }
      ],
      effect: :delay,
      informed_entity:
        Alerts.InformedEntitySet.new(
          trip_ids
          |> Enum.map(
            &InformedEntity.build(:informed_entity,
              activities: MapSet.new([:exit, :ride, :board]),
              route: route.id,
              route_type: 2,
              trip: &1
            )
          )
        ),
      priority: :high,
      severity: 3
    ),
    Alert.build(:alert,
      active_period: [
        {
          now() |> Timex.shift(hours: 4),
          now() |> Timex.shift(hours: 8)
        }
      ],
      effect: :shuttle,
      informed_entity:
        Alerts.InformedEntitySet.new([
          InformedEntity.build(:informed_entity,
            activities: MapSet.new([:exit, :ride, :board]),
            route: route.id,
            route_type: 2
          )
        ]),
      priority: :high,
      severity: 3
    )
  ]

# Remove all other alerts and only use the one you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Commuter Rail Delays and Cancellations

# Create an informed entity for a commuter rail route
route = Routes.Repo.by_type(2) |> Faker.Util.pick()

schedules = Schedules.Repo.by_route_ids([route.id])

[trip_id_1, trip_id_2] =
  Faker.Util.sample_uniq(2, fn -> Faker.Util.pick(schedules) end)
  |> Enum.map(& &1.trip.id)

active_period = [
  {
    now() |> Timex.shift(hours: -4),
    now() |> Timex.shift(hours: 8)
  }
]

alerts =
  [
    Alert.build(:alert,
      active_period: active_period,
      effect: :cancellation,
      informed_entity:
        Alerts.InformedEntitySet.new([
          InformedEntity.build(:informed_entity,
            activities: MapSet.new([:exit, :ride, :board]),
            route: route.id,
            route_type: 2,
            trip: trip_id_1
          )
        ]),
      priority: :high,
      severity: 3
    ),
    Alert.build(:alert,
      active_period: active_period,
      effect: :delay,
      informed_entity:
        Alerts.InformedEntitySet.new([
          InformedEntity.build(:informed_entity,
            activities: MapSet.new([:exit, :ride, :board]),
            route: route.id,
            route_type: 2,
            trip: trip_id_2
          )
        ]),
      priority: :high,
      severity: 3
    )
  ]

# Remove all other alerts and only use the one you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()

Commuter Rail Collapsed Service Impacts

# Create an informed entity for a commuter rail route
route = Routes.Repo.by_type(2) |> Faker.Util.pick()

schedules = Schedules.Repo.by_route_ids([route.id])

active_period = [
  {
    now() |> Timex.shift(hours: -4),
    now() |> Timex.shift(hours: 8)
  }
]

informed_entities =
  Alerts.InformedEntitySet.new([
    InformedEntity.build(:informed_entity,
      activities: MapSet.new([:exit, :ride, :board]),
      route: route.id,
      route_type: 2
    )
  ])

alerts =
  [
    Alert.build(:alert,
      active_period: active_period,
      effect: :shuttle,
      informed_entity: informed_entities,
      priority: :high,
      severity: 3
    ),
    Alert.build(:alert,
      active_period: active_period,
      effect: :suspension,
      informed_entity: informed_entities,
      priority: :high,
      severity: 3
    )
  ]

# Remove all other alerts and only use the one you created
Alerts.Cache.Store.update(alerts, nil)

now() |> Alerts.Repo.all()