diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.ar.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.ar.resx
new file mode 100644
index 00000000..c6ba7d56
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.ar.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ تنبيهات الطقس
+
+
+ تنبيهات الطقس
+
+
+ تنبيهات الطقس النشطة
+
+
+ سجل التنبيهات
+
+
+ مصادر التنبيهات
+
+
+ مناطق المراقبة
+
+
+ الإعدادات
+
+
+ عرض السجل
+
+
+ العودة إلى التنبيهات
+
+
+ العودة إلى الإعدادات
+
+
+ إدارة المناطق
+
+
+ التفاصيل
+
+
+ لا توجد تنبيهات طقس نشطة لقسمك.
+
+
+ تعذر تحميل تنبيهات الطقس. يرجى التحقق من الإعدادات.
+
+
+ جاري تحميل تنبيهات الطقس...
+
+
+ جاري تحميل السجل...
+
+
+ الحدث
+
+
+ الخطورة
+
+
+ المنطقة
+
+
+ ينتهي
+
+
+ الحالة
+
+
+ الفئة
+
+
+ الإجراءات
+
+
+ شديدة للغاية
+
+
+ شديدة
+
+
+ معتدلة
+
+
+ طفيفة
+
+
+ غير معروفة
+
+
+ فورية
+
+
+ متوقعة
+
+
+ مستقبلية
+
+
+ سابقة
+
+
+ غير معروفة
+
+
+ أرصاد جوية
+
+
+ حريق
+
+
+ صحة
+
+
+ بيئية
+
+
+ أخرى
+
+
+ نشط
+
+
+ محدّث
+
+
+ منتهي
+
+
+ ملغى
+
+
+ خدمة الطقس الوطنية
+
+
+ البيئة الكندية
+
+
+ MeteoAlarm (أوروبا)
+
+
+ إضافة مصدر
+
+
+ اسم المصدر
+
+
+ نوع المصدر
+
+
+ فلتر المنطقة (أكواد مناطق JSON)
+
+
+ فترة الاستعلام (دقائق)
+
+
+ حفظ
+
+
+ إلغاء
+
+
+ حذف
+
+
+ هل أنت متأكد أنك تريد حذف هذا المصدر؟
+
+
+ فشل في حفظ المصدر. يرجى المحاولة مرة أخرى.
+
+
+ فشل في حذف المصدر. يرجى المحاولة مرة أخرى.
+
+
+ إضافة منطقة مراقبة
+
+
+ تعديل منطقة المراقبة
+
+
+ اسم المنطقة
+
+
+ رمز المنطقة
+
+
+ الموقع المركزي
+
+
+ نصف القطر (أميال)
+
+
+ المنطقة الرئيسية
+
+
+ نشطة
+
+
+ غير نشطة
+
+
+ رئيسية
+
+
+ تفعيل
+
+
+ تعطيل
+
+
+ تعديل
+
+
+ لم يتم تكوين مناطق مراقبة. أضف منطقة أدناه لتحديد المناطق الجغرافية التي تريد مراقبتها لتنبيهات الطقس.
+
+
+ هل أنت متأكد أنك تريد حذف هذه المنطقة؟
+
+
+ فشل في حفظ المنطقة. يرجى المحاولة مرة أخرى.
+
+
+ فشل في حذف المنطقة. يرجى المحاولة مرة أخرى.
+
+
+ فشل في تحديث حالة المنطقة. يرجى المحاولة مرة أخرى.
+
+
+ يرجى إدخال اسم المنطقة.
+
+
+ حفظ المنطقة
+
+
+ الخيارات
+
+
+ تاريخ البداية
+
+
+ تاريخ النهاية
+
+
+ بحث
+
+
+ يرجى تحديد تاريخ البداية والنهاية.
+
+
+ لم يتم العثور على سجل تنبيهات للفترة المحددة.
+
+
+ تعذر تحميل سجل التنبيهات.
+
+
+ تفاصيل التنبيه
+
+
+ العنوان
+
+
+ الوصف
+
+
+ التعليمات
+
+
+ وصف المنطقة
+
+
+ الإلحاح
+
+
+ اليقين
+
+
+ تاريخ السريان
+
+
+ تاريخ البدء
+
+
+ تاريخ الانتهاء
+
+
+ تاريخ الإرسال
+
+
+ أول اكتشاف
+
+
+ آخر تحديث
+
+
+ المرسل
+
+
+ المعرف الخارجي
+
+
+ تم إرسال الإشعار
+
+
+ نعم
+
+
+ لا
+
+
+ المصادر المكوّنة
+
+
+ فترة الاستعلام
+
+
+ آخر استعلام
+
+
+ الحالة
+
+
+ سليم
+
+
+ خطأ
+
+
+ لم يتم الاستعلام مطلقاً
+
+
+ لم يتم تكوين مصادر تنبيهات الطقس. أضف مصدراً لبدء تلقي تنبيهات الطقس.
+
+
+ تنبيه الطقس: {0}
+
+
+ ينتهي: {0}
+
+
+ المنطقة: {0}
+
+
+ التعليمات: {0}
+
+
+ إعدادات الإشعارات
+
+
+ قم بتكوين تنبيهات الطقس التي تولّد رسائل تلقائياً لأعضاء القسم.
+
+
+ تفعيل تنبيهات الطقس
+
+
+ الحد الأدنى للخطورة للعرض
+
+
+ سيتم عرض التنبيهات بهذه الخطورة أو أعلى فقط في لوحة المعلومات.
+
+
+ عتبة الخطورة للرسائل التلقائية
+
+
+ التنبيهات بهذه الخطورة أو أعلى سترسل تلقائياً رسالة إلى جميع أعضاء القسم. قيمة أقل = خطورة أعلى (شديدة للغاية=0، شديدة=1، معتدلة=2، طفيفة=3).
+
+
+ إرفاق سياق الطقس بالمكالمات
+
+
+ عند التفعيل، سيتم إرفاق تنبيهات الطقس النشطة القريبة من موقع المكالمة كملاحظة للمكالمة.
+
+
+ حفظ الإعدادات
+
+
+ تم حفظ الإعدادات بنجاح.
+
+
+ فشل في حفظ الإعدادات. يرجى المحاولة مرة أخرى.
+
+
+ فشل في تحميل الإعدادات.
+
+
+ فشل في تحديث حالة المصدر. يرجى المحاولة مرة أخرى.
+
+
+ تعديل المصدر
+
+
+ إضافة / تعديل مصدر
+
+
+ يرجى إدخال اسم المصدر.
+
+
+ قائمة مفصولة بفواصل من رموز الولايات أو رموز المقاطعات أو رموز مناطق NWS.
+
+
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.cs b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.cs
new file mode 100644
index 00000000..a40e3f6b
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Resgrid.Localization.Areas.User.WeatherAlerts
+{
+ public class WeatherAlerts
+ {
+ }
+}
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.de.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.de.resx
new file mode 100644
index 00000000..fe7ead61
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.de.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Wetterwarnungen
+
+
+ Wetterwarnungen
+
+
+ Aktive Wetterwarnungen
+
+
+ Warnungsverlauf
+
+
+ Warnungsquellen
+
+
+ Überwachungszonen
+
+
+ Einstellungen
+
+
+ Verlauf anzeigen
+
+
+ Zurück zu Warnungen
+
+
+ Zurück zu Einstellungen
+
+
+ Zonen verwalten
+
+
+ Details
+
+
+ Keine aktiven Wetterwarnungen für Ihre Abteilung.
+
+
+ Wetterwarnungen konnten nicht geladen werden. Bitte überprüfen Sie Ihre Konfiguration.
+
+
+ Wetterwarnungen werden geladen...
+
+
+ Verlauf wird geladen...
+
+
+ Ereignis
+
+
+ Schweregrad
+
+
+ Gebiet
+
+
+ Läuft ab
+
+
+ Status
+
+
+ Kategorie
+
+
+ Aktionen
+
+
+ Extrem
+
+
+ Schwer
+
+
+ Mäßig
+
+
+ Gering
+
+
+ Unbekannt
+
+
+ Sofort
+
+
+ Erwartet
+
+
+ Zukünftig
+
+
+ Vergangen
+
+
+ Unbekannt
+
+
+ Meteorologisch
+
+
+ Brand
+
+
+ Gesundheit
+
+
+ Umwelt
+
+
+ Sonstige
+
+
+ Aktiv
+
+
+ Aktualisiert
+
+
+ Abgelaufen
+
+
+ Storniert
+
+
+ Nationaler Wetterdienst
+
+
+ Environment Canada
+
+
+ MeteoAlarm (Europa)
+
+
+ Quelle hinzufügen
+
+
+ Quellenname
+
+
+ Quellentyp
+
+
+ Gebietsfilter (JSON-Zonencodes)
+
+
+ Abfrageintervall (Minuten)
+
+
+ Speichern
+
+
+ Abbrechen
+
+
+ Löschen
+
+
+ Sind Sie sicher, dass Sie diese Quelle löschen möchten?
+
+
+ Quelle konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.
+
+
+ Quelle konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.
+
+
+ Überwachungszone hinzufügen
+
+
+ Überwachungszone bearbeiten
+
+
+ Zonenname
+
+
+ Zonencode
+
+
+ Zentrale Position
+
+
+ Radius (Meilen)
+
+
+ Primäre Zone
+
+
+ Aktiv
+
+
+ Inaktiv
+
+
+ Primär
+
+
+ Aktivieren
+
+
+ Deaktivieren
+
+
+ Bearbeiten
+
+
+ Keine Überwachungszonen konfiguriert. Fügen Sie unten eine Zone hinzu, um die geografischen Gebiete zu definieren, die Sie auf Wetterwarnungen überwachen möchten.
+
+
+ Sind Sie sicher, dass Sie diese Zone löschen möchten?
+
+
+ Zone konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.
+
+
+ Zone konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.
+
+
+ Zonenstatus konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.
+
+
+ Bitte geben Sie einen Zonennamen ein.
+
+
+ Zone speichern
+
+
+ Optionen
+
+
+ Startdatum
+
+
+ Enddatum
+
+
+ Suchen
+
+
+ Bitte wählen Sie sowohl ein Start- als auch ein Enddatum.
+
+
+ Kein Warnungsverlauf für den ausgewählten Zeitraum gefunden.
+
+
+ Warnungsverlauf konnte nicht geladen werden.
+
+
+ Warnungsdetail
+
+
+ Überschrift
+
+
+ Beschreibung
+
+
+ Anweisungen
+
+
+ Gebietsbeschreibung
+
+
+ Dringlichkeit
+
+
+ Gewissheit
+
+
+ Gültig ab
+
+
+ Beginn
+
+
+ Ablaufdatum
+
+
+ Sendedatum
+
+
+ Erstmals gesehen
+
+
+ Zuletzt aktualisiert
+
+
+ Absender
+
+
+ Externe ID
+
+
+ Benachrichtigung gesendet
+
+
+ Ja
+
+
+ Nein
+
+
+ Konfigurierte Quellen
+
+
+ Abfrageintervall
+
+
+ Letzte Abfrage
+
+
+ Status
+
+
+ Funktionsfähig
+
+
+ Fehler
+
+
+ Nie abgefragt
+
+
+ Keine Wetterwarnungsquellen konfiguriert. Fügen Sie eine Quelle hinzu, um Wetterwarnungen zu empfangen.
+
+
+ Wetterwarnung: {0}
+
+
+ Läuft ab: {0}
+
+
+ Gebiet: {0}
+
+
+ Anweisungen: {0}
+
+
+ Benachrichtigungseinstellungen
+
+
+ Konfigurieren Sie, welche Wetterwarnungen automatisch Nachrichten an Abteilungsmitglieder generieren.
+
+
+ Wetterwarnungen aktivieren
+
+
+ Mindest-Schweregrad für Anzeige
+
+
+ Nur Warnungen mit diesem oder höherem Schweregrad werden im Dashboard angezeigt.
+
+
+ Schwellenwert für automatische Nachricht
+
+
+ Warnungen mit diesem oder höherem Schweregrad senden automatisch eine Nachricht an alle Abteilungsmitglieder. Niedrigerer Wert = höherer Schweregrad (Extrem=0, Schwer=1, Mäßig=2, Gering=3).
+
+
+ Wetterkontext an Einsätze anhängen
+
+
+ Wenn aktiviert, werden aktive Wetterwarnungen in der Nähe eines Einsatzortes als Einsatznotiz angehängt.
+
+
+ Einstellungen speichern
+
+
+ Einstellungen erfolgreich gespeichert.
+
+
+ Einstellungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.
+
+
+ Einstellungen konnten nicht geladen werden.
+
+
+ Quellenstatus konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.
+
+
+ Quelle bearbeiten
+
+
+ Quelle hinzufügen / bearbeiten
+
+
+ Bitte geben Sie einen Quellennamen ein.
+
+
+ Kommagetrennte Liste von Staatscodes, Provinzcodes oder NWS-Zonencodes.
+
+
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.en.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.en.resx
new file mode 100644
index 00000000..436a8291
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.en.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Weather Alerts
+
+
+ Weather Alerts
+
+
+ Active Weather Alerts
+
+
+ Alert History
+
+
+ Alert Sources
+
+
+ Monitoring Zones
+
+
+ Settings
+
+
+ View History
+
+
+ Back to Alerts
+
+
+ Back to Settings
+
+
+ Manage Zones
+
+
+ Details
+
+
+ No active weather alerts for your department.
+
+
+ Unable to load weather alerts. Please check your configuration.
+
+
+ Loading weather alerts...
+
+
+ Loading history...
+
+
+ Event
+
+
+ Severity
+
+
+ Area
+
+
+ Expires
+
+
+ Status
+
+
+ Category
+
+
+ Actions
+
+
+ Extreme
+
+
+ Severe
+
+
+ Moderate
+
+
+ Minor
+
+
+ Unknown
+
+
+ Immediate
+
+
+ Expected
+
+
+ Future
+
+
+ Past
+
+
+ Unknown
+
+
+ Meteorological
+
+
+ Fire
+
+
+ Health
+
+
+ Environmental
+
+
+ Other
+
+
+ Active
+
+
+ Updated
+
+
+ Expired
+
+
+ Cancelled
+
+
+ National Weather Service
+
+
+ Environment Canada
+
+
+ MeteoAlarm (Europe)
+
+
+ Add Source
+
+
+ Source Name
+
+
+ Source Type
+
+
+ Area Filter (JSON zone codes)
+
+
+ Poll Interval (minutes)
+
+
+ Save
+
+
+ Cancel
+
+
+ Delete
+
+
+ Are you sure you want to delete this source?
+
+
+ Failed to save source. Please try again.
+
+
+ Failed to delete source. Please try again.
+
+
+ Add Monitoring Zone
+
+
+ Edit Monitoring Zone
+
+
+ Zone Name
+
+
+ Zone Code
+
+
+ Center Location
+
+
+ Radius (miles)
+
+
+ Primary Zone
+
+
+ Active
+
+
+ Inactive
+
+
+ Primary
+
+
+ Enable
+
+
+ Disable
+
+
+ Edit
+
+
+ No monitoring zones configured. Add a zone below to define the geographic areas you want to monitor for weather alerts.
+
+
+ Are you sure you want to delete this zone?
+
+
+ Failed to save zone. Please try again.
+
+
+ Failed to delete zone. Please try again.
+
+
+ Failed to update zone status. Please try again.
+
+
+ Please enter a zone name.
+
+
+ Save Zone
+
+
+ Options
+
+
+ Start Date
+
+
+ End Date
+
+
+ Search
+
+
+ Please select both start and end dates.
+
+
+ No alert history found for the selected date range.
+
+
+ Unable to load alert history.
+
+
+ Alert Detail
+
+
+ Headline
+
+
+ Description
+
+
+ Instructions
+
+
+ Area Description
+
+
+ Urgency
+
+
+ Certainty
+
+
+ Effective Date
+
+
+ Onset Date
+
+
+ Expires Date
+
+
+ Sent Date
+
+
+ First Seen
+
+
+ Last Updated
+
+
+ Sender
+
+
+ External ID
+
+
+ Notification Sent
+
+
+ Yes
+
+
+ No
+
+
+ Configured Sources
+
+
+ Poll Interval
+
+
+ Last Poll
+
+
+ Status
+
+
+ Healthy
+
+
+ Error
+
+
+ Never Polled
+
+
+ No weather alert sources configured. Add a source to start receiving weather alerts.
+
+
+ Weather Alert: {0}
+
+
+ Expires: {0}
+
+
+ Area: {0}
+
+
+ Instructions: {0}
+
+
+ Notification Settings
+
+
+ Configure which weather alerts automatically generate messages to department members.
+
+
+ Enable Weather Alerts
+
+
+ Minimum Severity to Display
+
+
+ Only alerts at or above this severity will be shown in the dashboard.
+
+
+ Auto-Message Severity Threshold
+
+
+ Alerts at or above this severity will automatically send a message to all department members. Lower value = higher severity (Extreme=0, Severe=1, Moderate=2, Minor=3).
+
+
+ Attach Weather Context to Calls
+
+
+ When enabled, active weather alerts near a call's location will be attached as a call note.
+
+
+ Save Settings
+
+
+ Settings saved successfully.
+
+
+ Failed to save settings. Please try again.
+
+
+ Failed to load settings.
+
+
+ Failed to update source status. Please try again.
+
+
+ Edit Source
+
+
+ Add / Edit Source
+
+
+ Please enter a source name.
+
+
+ Comma-separated list of state codes, province codes, or NWS zone codes.
+
+
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.es.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.es.resx
new file mode 100644
index 00000000..15b51c11
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.es.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Alertas Meteorológicas
+
+
+ Alertas Meteorológicas
+
+
+ Alertas Meteorológicas Activas
+
+
+ Historial de Alertas
+
+
+ Fuentes de Alertas
+
+
+ Zonas de Monitoreo
+
+
+ Configuración
+
+
+ Ver Historial
+
+
+ Volver a Alertas
+
+
+ Volver a Configuración
+
+
+ Gestionar Zonas
+
+
+ Detalles
+
+
+ No hay alertas meteorológicas activas para su departamento.
+
+
+ No se pueden cargar las alertas meteorológicas. Verifique su configuración.
+
+
+ Cargando alertas meteorológicas...
+
+
+ Cargando historial...
+
+
+ Evento
+
+
+ Severidad
+
+
+ Área
+
+
+ Expira
+
+
+ Estado
+
+
+ Categoría
+
+
+ Acciones
+
+
+ Extrema
+
+
+ Severa
+
+
+ Moderada
+
+
+ Menor
+
+
+ Desconocida
+
+
+ Inmediata
+
+
+ Esperada
+
+
+ Futura
+
+
+ Pasada
+
+
+ Desconocida
+
+
+ Meteorológica
+
+
+ Incendio
+
+
+ Salud
+
+
+ Ambiental
+
+
+ Otra
+
+
+ Activa
+
+
+ Actualizada
+
+
+ Expirada
+
+
+ Cancelada
+
+
+ Servicio Meteorológico Nacional
+
+
+ Environment Canada
+
+
+ MeteoAlarm (Europa)
+
+
+ Agregar Fuente
+
+
+ Nombre de la Fuente
+
+
+ Tipo de Fuente
+
+
+ Filtro de Área (códigos de zona JSON)
+
+
+ Intervalo de Consulta (minutos)
+
+
+ Guardar
+
+
+ Cancelar
+
+
+ Eliminar
+
+
+ ¿Está seguro de que desea eliminar esta fuente?
+
+
+ Error al guardar la fuente. Inténtelo de nuevo.
+
+
+ Error al eliminar la fuente. Inténtelo de nuevo.
+
+
+ Agregar Zona de Monitoreo
+
+
+ Editar Zona de Monitoreo
+
+
+ Nombre de la Zona
+
+
+ Código de Zona
+
+
+ Ubicación Central
+
+
+ Radio (millas)
+
+
+ Zona Principal
+
+
+ Activa
+
+
+ Inactiva
+
+
+ Principal
+
+
+ Activar
+
+
+ Desactivar
+
+
+ Editar
+
+
+ No hay zonas de monitoreo configuradas. Agregue una zona a continuación para definir las áreas geográficas que desea monitorear para alertas meteorológicas.
+
+
+ ¿Está seguro de que desea eliminar esta zona?
+
+
+ Error al guardar la zona. Inténtelo de nuevo.
+
+
+ Error al eliminar la zona. Inténtelo de nuevo.
+
+
+ Error al actualizar el estado de la zona. Inténtelo de nuevo.
+
+
+ Ingrese un nombre de zona.
+
+
+ Guardar Zona
+
+
+ Opciones
+
+
+ Fecha de Inicio
+
+
+ Fecha de Fin
+
+
+ Buscar
+
+
+ Seleccione ambas fechas de inicio y fin.
+
+
+ No se encontró historial de alertas para el rango de fechas seleccionado.
+
+
+ No se pudo cargar el historial de alertas.
+
+
+ Detalle de Alerta
+
+
+ Titular
+
+
+ Descripción
+
+
+ Instrucciones
+
+
+ Descripción del Área
+
+
+ Urgencia
+
+
+ Certeza
+
+
+ Fecha Efectiva
+
+
+ Fecha de Inicio
+
+
+ Fecha de Expiración
+
+
+ Fecha de Envío
+
+
+ Primera Detección
+
+
+ Última Actualización
+
+
+ Remitente
+
+
+ ID Externo
+
+
+ Notificación Enviada
+
+
+ Sí
+
+
+ No
+
+
+ Fuentes Configuradas
+
+
+ Intervalo de Consulta
+
+
+ Última Consulta
+
+
+ Estado
+
+
+ Saludable
+
+
+ Error
+
+
+ Nunca Consultada
+
+
+ No hay fuentes de alertas meteorológicas configuradas. Agregue una fuente para comenzar a recibir alertas meteorológicas.
+
+
+ Alerta Meteorológica: {0}
+
+
+ Expira: {0}
+
+
+ Área: {0}
+
+
+ Instrucciones: {0}
+
+
+ Configuración de Notificaciones
+
+
+ Configure qué alertas meteorológicas generan automáticamente mensajes para los miembros del departamento.
+
+
+ Activar Alertas Meteorológicas
+
+
+ Severidad Mínima para Mostrar
+
+
+ Solo se mostrarán en el panel las alertas con esta severidad o superior.
+
+
+ Umbral de Severidad para Mensaje Automático
+
+
+ Las alertas con esta severidad o superior enviarán automáticamente un mensaje a todos los miembros del departamento. Valor más bajo = mayor severidad (Extrema=0, Severa=1, Moderada=2, Menor=3).
+
+
+ Adjuntar Contexto Meteorológico a Llamadas
+
+
+ Cuando está activado, las alertas meteorológicas activas cercanas a la ubicación de una llamada se adjuntarán como nota de la llamada.
+
+
+ Guardar Configuración
+
+
+ Configuración guardada correctamente.
+
+
+ Error al guardar la configuración. Inténtelo de nuevo.
+
+
+ Error al cargar la configuración.
+
+
+ Error al actualizar el estado de la fuente. Inténtelo de nuevo.
+
+
+ Editar Fuente
+
+
+ Agregar / Editar Fuente
+
+
+ Ingrese un nombre de fuente.
+
+
+ Lista separada por comas de códigos de estado, códigos de provincia o códigos de zona NWS.
+
+
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.fr.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.fr.resx
new file mode 100644
index 00000000..88e961e8
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.fr.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Alertes Météorologiques
+
+
+ Alertes Météorologiques
+
+
+ Alertes Météorologiques Actives
+
+
+ Historique des Alertes
+
+
+ Sources d'Alertes
+
+
+ Zones de Surveillance
+
+
+ Paramètres
+
+
+ Voir l'Historique
+
+
+ Retour aux Alertes
+
+
+ Retour aux Paramètres
+
+
+ Gérer les Zones
+
+
+ Détails
+
+
+ Aucune alerte météorologique active pour votre département.
+
+
+ Impossible de charger les alertes météorologiques. Veuillez vérifier votre configuration.
+
+
+ Chargement des alertes météorologiques...
+
+
+ Chargement de l'historique...
+
+
+ Événement
+
+
+ Sévérité
+
+
+ Zone
+
+
+ Expire
+
+
+ Statut
+
+
+ Catégorie
+
+
+ Actions
+
+
+ Extrême
+
+
+ Sévère
+
+
+ Modérée
+
+
+ Mineure
+
+
+ Inconnue
+
+
+ Immédiate
+
+
+ Attendue
+
+
+ Future
+
+
+ Passée
+
+
+ Inconnue
+
+
+ Météorologique
+
+
+ Incendie
+
+
+ Santé
+
+
+ Environnement
+
+
+ Autre
+
+
+ Active
+
+
+ Mise à jour
+
+
+ Expirée
+
+
+ Annulée
+
+
+ Service Météorologique National
+
+
+ Environnement Canada
+
+
+ MeteoAlarm (Europe)
+
+
+ Ajouter une Source
+
+
+ Nom de la Source
+
+
+ Type de Source
+
+
+ Filtre de Zone (codes de zone JSON)
+
+
+ Intervalle d'Interrogation (minutes)
+
+
+ Enregistrer
+
+
+ Annuler
+
+
+ Supprimer
+
+
+ Êtes-vous sûr de vouloir supprimer cette source ?
+
+
+ Échec de l'enregistrement de la source. Veuillez réessayer.
+
+
+ Échec de la suppression de la source. Veuillez réessayer.
+
+
+ Ajouter une Zone de Surveillance
+
+
+ Modifier la Zone de Surveillance
+
+
+ Nom de la Zone
+
+
+ Code de Zone
+
+
+ Emplacement Central
+
+
+ Rayon (miles)
+
+
+ Zone Principale
+
+
+ Active
+
+
+ Inactive
+
+
+ Principale
+
+
+ Activer
+
+
+ Désactiver
+
+
+ Modifier
+
+
+ Aucune zone de surveillance configurée. Ajoutez une zone ci-dessous pour définir les zones géographiques que vous souhaitez surveiller pour les alertes météorologiques.
+
+
+ Êtes-vous sûr de vouloir supprimer cette zone ?
+
+
+ Échec de l'enregistrement de la zone. Veuillez réessayer.
+
+
+ Échec de la suppression de la zone. Veuillez réessayer.
+
+
+ Échec de la mise à jour du statut de la zone. Veuillez réessayer.
+
+
+ Veuillez entrer un nom de zone.
+
+
+ Enregistrer la Zone
+
+
+ Options
+
+
+ Date de Début
+
+
+ Date de Fin
+
+
+ Rechercher
+
+
+ Veuillez sélectionner les dates de début et de fin.
+
+
+ Aucun historique d'alertes trouvé pour la période sélectionnée.
+
+
+ Impossible de charger l'historique des alertes.
+
+
+ Détail de l'Alerte
+
+
+ Titre
+
+
+ Description
+
+
+ Instructions
+
+
+ Description de la Zone
+
+
+ Urgence
+
+
+ Certitude
+
+
+ Date d'Effet
+
+
+ Date de Début
+
+
+ Date d'Expiration
+
+
+ Date d'Envoi
+
+
+ Première Détection
+
+
+ Dernière Mise à Jour
+
+
+ Expéditeur
+
+
+ ID Externe
+
+
+ Notification Envoyée
+
+
+ Oui
+
+
+ Non
+
+
+ Sources Configurées
+
+
+ Intervalle d'Interrogation
+
+
+ Dernière Interrogation
+
+
+ Statut
+
+
+ Opérationnel
+
+
+ Erreur
+
+
+ Jamais Interrogée
+
+
+ Aucune source d'alertes météorologiques configurée. Ajoutez une source pour commencer à recevoir des alertes météorologiques.
+
+
+ Alerte Météorologique : {0}
+
+
+ Expire : {0}
+
+
+ Zone : {0}
+
+
+ Instructions : {0}
+
+
+ Paramètres de Notification
+
+
+ Configurez quelles alertes météorologiques génèrent automatiquement des messages aux membres du département.
+
+
+ Activer les Alertes Météorologiques
+
+
+ Sévérité Minimale à Afficher
+
+
+ Seules les alertes de cette sévérité ou supérieure seront affichées dans le tableau de bord.
+
+
+ Seuil de Sévérité pour Message Automatique
+
+
+ Les alertes de cette sévérité ou supérieure enverront automatiquement un message à tous les membres du département. Valeur plus basse = sévérité plus élevée (Extrême=0, Sévère=1, Modérée=2, Mineure=3).
+
+
+ Joindre le Contexte Météo aux Appels
+
+
+ Lorsqu'activé, les alertes météorologiques actives à proximité de l'emplacement d'un appel seront jointes comme note d'appel.
+
+
+ Enregistrer les Paramètres
+
+
+ Paramètres enregistrés avec succès.
+
+
+ Échec de l'enregistrement des paramètres. Veuillez réessayer.
+
+
+ Échec du chargement des paramètres.
+
+
+ Échec de la mise à jour du statut de la source. Veuillez réessayer.
+
+
+ Modifier la Source
+
+
+ Ajouter / Modifier une Source
+
+
+ Veuillez entrer un nom de source.
+
+
+ Liste séparée par des virgules de codes d'état, codes de province ou codes de zone NWS.
+
+
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.it.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.it.resx
new file mode 100644
index 00000000..fb730f94
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.it.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Allerte Meteo
+
+
+ Allerte Meteo
+
+
+ Allerte Meteo Attive
+
+
+ Cronologia Allerte
+
+
+ Fonti delle Allerte
+
+
+ Zone di Monitoraggio
+
+
+ Impostazioni
+
+
+ Visualizza Cronologia
+
+
+ Torna alle Allerte
+
+
+ Torna alle Impostazioni
+
+
+ Gestisci Zone
+
+
+ Dettagli
+
+
+ Nessuna allerta meteo attiva per il tuo dipartimento.
+
+
+ Impossibile caricare le allerte meteo. Controlla la configurazione.
+
+
+ Caricamento allerte meteo...
+
+
+ Caricamento cronologia...
+
+
+ Evento
+
+
+ Gravità
+
+
+ Area
+
+
+ Scadenza
+
+
+ Stato
+
+
+ Categoria
+
+
+ Azioni
+
+
+ Estrema
+
+
+ Grave
+
+
+ Moderata
+
+
+ Lieve
+
+
+ Sconosciuta
+
+
+ Immediata
+
+
+ Prevista
+
+
+ Futura
+
+
+ Passata
+
+
+ Sconosciuta
+
+
+ Meteorologica
+
+
+ Incendio
+
+
+ Salute
+
+
+ Ambientale
+
+
+ Altro
+
+
+ Attiva
+
+
+ Aggiornata
+
+
+ Scaduta
+
+
+ Annullata
+
+
+ Servizio Meteorologico Nazionale
+
+
+ Environment Canada
+
+
+ MeteoAlarm (Europa)
+
+
+ Aggiungi Fonte
+
+
+ Nome della Fonte
+
+
+ Tipo di Fonte
+
+
+ Filtro Area (codici zona JSON)
+
+
+ Intervallo di Polling (minuti)
+
+
+ Salva
+
+
+ Annulla
+
+
+ Elimina
+
+
+ Sei sicuro di voler eliminare questa fonte?
+
+
+ Impossibile salvare la fonte. Riprova.
+
+
+ Impossibile eliminare la fonte. Riprova.
+
+
+ Aggiungi Zona di Monitoraggio
+
+
+ Modifica Zona di Monitoraggio
+
+
+ Nome della Zona
+
+
+ Codice Zona
+
+
+ Posizione Centrale
+
+
+ Raggio (miglia)
+
+
+ Zona Principale
+
+
+ Attiva
+
+
+ Inattiva
+
+
+ Principale
+
+
+ Attiva
+
+
+ Disattiva
+
+
+ Modifica
+
+
+ Nessuna zona di monitoraggio configurata. Aggiungi una zona qui sotto per definire le aree geografiche da monitorare per le allerte meteo.
+
+
+ Sei sicuro di voler eliminare questa zona?
+
+
+ Impossibile salvare la zona. Riprova.
+
+
+ Impossibile eliminare la zona. Riprova.
+
+
+ Impossibile aggiornare lo stato della zona. Riprova.
+
+
+ Inserisci un nome per la zona.
+
+
+ Salva Zona
+
+
+ Opzioni
+
+
+ Data di Inizio
+
+
+ Data di Fine
+
+
+ Cerca
+
+
+ Seleziona entrambe le date di inizio e fine.
+
+
+ Nessuna cronologia allerte trovata per il periodo selezionato.
+
+
+ Impossibile caricare la cronologia delle allerte.
+
+
+ Dettaglio Allerta
+
+
+ Titolo
+
+
+ Descrizione
+
+
+ Istruzioni
+
+
+ Descrizione dell'Area
+
+
+ Urgenza
+
+
+ Certezza
+
+
+ Data Effettiva
+
+
+ Data di Inizio
+
+
+ Data di Scadenza
+
+
+ Data di Invio
+
+
+ Prima Rilevazione
+
+
+ Ultimo Aggiornamento
+
+
+ Mittente
+
+
+ ID Esterno
+
+
+ Notifica Inviata
+
+
+ Sì
+
+
+ No
+
+
+ Fonti Configurate
+
+
+ Intervallo di Polling
+
+
+ Ultimo Polling
+
+
+ Stato
+
+
+ Funzionante
+
+
+ Errore
+
+
+ Mai Interrogata
+
+
+ Nessuna fonte di allerte meteo configurata. Aggiungi una fonte per iniziare a ricevere allerte meteo.
+
+
+ Allerta Meteo: {0}
+
+
+ Scadenza: {0}
+
+
+ Area: {0}
+
+
+ Istruzioni: {0}
+
+
+ Impostazioni di Notifica
+
+
+ Configura quali allerte meteo generano automaticamente messaggi ai membri del dipartimento.
+
+
+ Attiva Allerte Meteo
+
+
+ Gravità Minima da Visualizzare
+
+
+ Solo le allerte con questa gravità o superiore verranno mostrate nella dashboard.
+
+
+ Soglia di Gravità per Messaggio Automatico
+
+
+ Le allerte con questa gravità o superiore invieranno automaticamente un messaggio a tutti i membri del dipartimento. Valore più basso = gravità più alta (Estrema=0, Grave=1, Moderata=2, Lieve=3).
+
+
+ Allega Contesto Meteo alle Chiamate
+
+
+ Quando attivato, le allerte meteo attive vicine alla posizione di una chiamata verranno allegate come nota della chiamata.
+
+
+ Salva Impostazioni
+
+
+ Impostazioni salvate con successo.
+
+
+ Impossibile salvare le impostazioni. Riprova.
+
+
+ Impossibile caricare le impostazioni.
+
+
+ Impossibile aggiornare lo stato della fonte. Riprova.
+
+
+ Modifica Fonte
+
+
+ Aggiungi / Modifica Fonte
+
+
+ Inserisci un nome per la fonte.
+
+
+ Elenco separato da virgole di codici di stato, codici di provincia o codici di zona NWS.
+
+
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.pl.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.pl.resx
new file mode 100644
index 00000000..85b68980
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.pl.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Alerty Pogodowe
+
+
+ Alerty Pogodowe
+
+
+ Aktywne Alerty Pogodowe
+
+
+ Historia Alertów
+
+
+ Źródła Alertów
+
+
+ Strefy Monitorowania
+
+
+ Ustawienia
+
+
+ Zobacz Historię
+
+
+ Powrót do Alertów
+
+
+ Powrót do Ustawień
+
+
+ Zarządzaj Strefami
+
+
+ Szczegóły
+
+
+ Brak aktywnych alertów pogodowych dla Twojego departamentu.
+
+
+ Nie można załadować alertów pogodowych. Sprawdź konfigurację.
+
+
+ Ładowanie alertów pogodowych...
+
+
+ Ładowanie historii...
+
+
+ Zdarzenie
+
+
+ Ważność
+
+
+ Obszar
+
+
+ Wygasa
+
+
+ Status
+
+
+ Kategoria
+
+
+ Akcje
+
+
+ Ekstremalna
+
+
+ Poważna
+
+
+ Umiarkowana
+
+
+ Niewielka
+
+
+ Nieznana
+
+
+ Natychmiastowa
+
+
+ Oczekiwana
+
+
+ Przyszła
+
+
+ Przeszła
+
+
+ Nieznana
+
+
+ Meteorologiczna
+
+
+ Pożar
+
+
+ Zdrowie
+
+
+ Środowiskowa
+
+
+ Inna
+
+
+ Aktywny
+
+
+ Zaktualizowany
+
+
+ Wygasły
+
+
+ Anulowany
+
+
+ Narodowa Służba Pogodowa
+
+
+ Environment Canada
+
+
+ MeteoAlarm (Europa)
+
+
+ Dodaj Źródło
+
+
+ Nazwa Źródła
+
+
+ Typ Źródła
+
+
+ Filtr Obszaru (kody stref JSON)
+
+
+ Interwał Odpytywania (minuty)
+
+
+ Zapisz
+
+
+ Anuluj
+
+
+ Usuń
+
+
+ Czy na pewno chcesz usunąć to źródło?
+
+
+ Nie udało się zapisać źródła. Spróbuj ponownie.
+
+
+ Nie udało się usunąć źródła. Spróbuj ponownie.
+
+
+ Dodaj Strefę Monitorowania
+
+
+ Edytuj Strefę Monitorowania
+
+
+ Nazwa Strefy
+
+
+ Kod Strefy
+
+
+ Lokalizacja Centralna
+
+
+ Promień (mile)
+
+
+ Strefa Główna
+
+
+ Aktywna
+
+
+ Nieaktywna
+
+
+ Główna
+
+
+ Włącz
+
+
+ Wyłącz
+
+
+ Edytuj
+
+
+ Brak skonfigurowanych stref monitorowania. Dodaj strefę poniżej, aby zdefiniować obszary geograficzne do monitorowania alertów pogodowych.
+
+
+ Czy na pewno chcesz usunąć tę strefę?
+
+
+ Nie udało się zapisać strefy. Spróbuj ponownie.
+
+
+ Nie udało się usunąć strefy. Spróbuj ponownie.
+
+
+ Nie udało się zaktualizować statusu strefy. Spróbuj ponownie.
+
+
+ Wprowadź nazwę strefy.
+
+
+ Zapisz Strefę
+
+
+ Opcje
+
+
+ Data Początkowa
+
+
+ Data Końcowa
+
+
+ Szukaj
+
+
+ Wybierz datę początkową i końcową.
+
+
+ Nie znaleziono historii alertów dla wybranego zakresu dat.
+
+
+ Nie można załadować historii alertów.
+
+
+ Szczegóły Alertu
+
+
+ Nagłówek
+
+
+ Opis
+
+
+ Instrukcje
+
+
+ Opis Obszaru
+
+
+ Pilność
+
+
+ Pewność
+
+
+ Data Obowiązywania
+
+
+ Data Rozpoczęcia
+
+
+ Data Wygaśnięcia
+
+
+ Data Wysłania
+
+
+ Pierwsze Wykrycie
+
+
+ Ostatnia Aktualizacja
+
+
+ Nadawca
+
+
+ Zewnętrzne ID
+
+
+ Powiadomienie Wysłane
+
+
+ Tak
+
+
+ Nie
+
+
+ Skonfigurowane Źródła
+
+
+ Interwał Odpytywania
+
+
+ Ostatnie Odpytanie
+
+
+ Status
+
+
+ Sprawne
+
+
+ Błąd
+
+
+ Nigdy Nie Odpytane
+
+
+ Brak skonfigurowanych źródeł alertów pogodowych. Dodaj źródło, aby zacząć otrzymywać alerty pogodowe.
+
+
+ Alert Pogodowy: {0}
+
+
+ Wygasa: {0}
+
+
+ Obszar: {0}
+
+
+ Instrukcje: {0}
+
+
+ Ustawienia Powiadomień
+
+
+ Skonfiguruj, które alerty pogodowe automatycznie generują wiadomości do członków departamentu.
+
+
+ Włącz Alerty Pogodowe
+
+
+ Minimalna Ważność do Wyświetlenia
+
+
+ Tylko alerty o tej lub wyższej ważności będą wyświetlane na pulpicie.
+
+
+ Próg Ważności dla Automatycznej Wiadomości
+
+
+ Alerty o tej lub wyższej ważności automatycznie wyślą wiadomość do wszystkich członków departamentu. Niższa wartość = wyższa ważność (Ekstremalna=0, Poważna=1, Umiarkowana=2, Niewielka=3).
+
+
+ Dołącz Kontekst Pogodowy do Wezwań
+
+
+ Po włączeniu aktywne alerty pogodowe w pobliżu lokalizacji wezwania zostaną dołączone jako notatka do wezwania.
+
+
+ Zapisz Ustawienia
+
+
+ Ustawienia zapisane pomyślnie.
+
+
+ Nie udało się zapisać ustawień. Spróbuj ponownie.
+
+
+ Nie udało się załadować ustawień.
+
+
+ Nie udało się zaktualizować statusu źródła. Spróbuj ponownie.
+
+
+ Edytuj Źródło
+
+
+ Dodaj / Edytuj Źródło
+
+
+ Wprowadź nazwę źródła.
+
+
+ Rozdzielona przecinkami lista kodów stanów, kodów prowincji lub kodów stref NWS.
+
+
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.sv.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.sv.resx
new file mode 100644
index 00000000..27316698
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.sv.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Vädervarningar
+
+
+ Vädervarningar
+
+
+ Aktiva Vädervarningar
+
+
+ Varningshistorik
+
+
+ Varningskällor
+
+
+ Övervakningszoner
+
+
+ Inställningar
+
+
+ Visa Historik
+
+
+ Tillbaka till Varningar
+
+
+ Tillbaka till Inställningar
+
+
+ Hantera Zoner
+
+
+ Detaljer
+
+
+ Inga aktiva vädervarningar för din avdelning.
+
+
+ Kunde inte ladda vädervarningar. Kontrollera din konfiguration.
+
+
+ Laddar vädervarningar...
+
+
+ Laddar historik...
+
+
+ Händelse
+
+
+ Allvarlighetsgrad
+
+
+ Område
+
+
+ Upphör
+
+
+ Status
+
+
+ Kategori
+
+
+ Åtgärder
+
+
+ Extrem
+
+
+ Allvarlig
+
+
+ Måttlig
+
+
+ Mindre
+
+
+ Okänd
+
+
+ Omedelbar
+
+
+ Förväntad
+
+
+ Framtida
+
+
+ Passerad
+
+
+ Okänd
+
+
+ Meteorologisk
+
+
+ Brand
+
+
+ Hälsa
+
+
+ Miljö
+
+
+ Övrigt
+
+
+ Aktiv
+
+
+ Uppdaterad
+
+
+ Upphörd
+
+
+ Avbruten
+
+
+ National Weather Service
+
+
+ Environment Canada
+
+
+ MeteoAlarm (Europa)
+
+
+ Lägg till Källa
+
+
+ Källnamn
+
+
+ Källtyp
+
+
+ Områdesfilter (JSON-zonkoder)
+
+
+ Pollningsintervall (minuter)
+
+
+ Spara
+
+
+ Avbryt
+
+
+ Ta bort
+
+
+ Är du säker på att du vill ta bort denna källa?
+
+
+ Kunde inte spara källan. Försök igen.
+
+
+ Kunde inte ta bort källan. Försök igen.
+
+
+ Lägg till Övervakningszon
+
+
+ Redigera Övervakningszon
+
+
+ Zonnamn
+
+
+ Zonkod
+
+
+ Centrumposition
+
+
+ Radie (miles)
+
+
+ Primär Zon
+
+
+ Aktiv
+
+
+ Inaktiv
+
+
+ Primär
+
+
+ Aktivera
+
+
+ Inaktivera
+
+
+ Redigera
+
+
+ Inga övervakningszoner konfigurerade. Lägg till en zon nedan för att definiera de geografiska områden du vill övervaka för vädervarningar.
+
+
+ Är du säker på att du vill ta bort denna zon?
+
+
+ Kunde inte spara zonen. Försök igen.
+
+
+ Kunde inte ta bort zonen. Försök igen.
+
+
+ Kunde inte uppdatera zonens status. Försök igen.
+
+
+ Ange ett zonnamn.
+
+
+ Spara Zon
+
+
+ Alternativ
+
+
+ Startdatum
+
+
+ Slutdatum
+
+
+ Sök
+
+
+ Välj både start- och slutdatum.
+
+
+ Ingen varningshistorik hittades för den valda perioden.
+
+
+ Kunde inte ladda varningshistorik.
+
+
+ Varningsdetalj
+
+
+ Rubrik
+
+
+ Beskrivning
+
+
+ Instruktioner
+
+
+ Områdesbeskrivning
+
+
+ Brådska
+
+
+ Säkerhet
+
+
+ Giltighetsdatum
+
+
+ Startdatum
+
+
+ Utgångsdatum
+
+
+ Sändningsdatum
+
+
+ Först Upptäckt
+
+
+ Senast Uppdaterad
+
+
+ Avsändare
+
+
+ Externt ID
+
+
+ Avisering Skickad
+
+
+ Ja
+
+
+ Nej
+
+
+ Konfigurerade Källor
+
+
+ Pollningsintervall
+
+
+ Senaste Pollning
+
+
+ Status
+
+
+ Fungerar
+
+
+ Fel
+
+
+ Aldrig Pollad
+
+
+ Inga vädervarningskällor konfigurerade. Lägg till en källa för att börja ta emot vädervarningar.
+
+
+ Vädervarning: {0}
+
+
+ Upphör: {0}
+
+
+ Område: {0}
+
+
+ Instruktioner: {0}
+
+
+ Aviseringsinställningar
+
+
+ Konfigurera vilka vädervarningar som automatiskt genererar meddelanden till avdelningsmedlemmar.
+
+
+ Aktivera Vädervarningar
+
+
+ Lägsta Allvarlighetsgrad att Visa
+
+
+ Endast varningar med denna eller högre allvarlighetsgrad visas i instrumentpanelen.
+
+
+ Tröskel för Automatiskt Meddelande
+
+
+ Varningar med denna eller högre allvarlighetsgrad skickar automatiskt ett meddelande till alla avdelningsmedlemmar. Lägre värde = högre allvarlighetsgrad (Extrem=0, Allvarlig=1, Måttlig=2, Mindre=3).
+
+
+ Bifoga Väderkontext till Utryckningar
+
+
+ När aktiverat bifogas aktiva vädervarningar nära en utrycknings plats som en utryckningsanteckning.
+
+
+ Spara Inställningar
+
+
+ Inställningar sparade.
+
+
+ Kunde inte spara inställningarna. Försök igen.
+
+
+ Kunde inte ladda inställningarna.
+
+
+ Kunde inte uppdatera källans status. Försök igen.
+
+
+ Redigera Källa
+
+
+ Lägg till / Redigera Källa
+
+
+ Ange ett källnamn.
+
+
+ Kommaseparerad lista med delstatskoder, provinskoder eller NWS-zonkoder.
+
+
diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.uk.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.uk.resx
new file mode 100644
index 00000000..9cb6788f
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.uk.resx
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Попередження про Погоду
+
+
+ Попередження про Погоду
+
+
+ Активні Попередження про Погоду
+
+
+ Історія Попереджень
+
+
+ Джерела Попереджень
+
+
+ Зони Моніторингу
+
+
+ Налаштування
+
+
+ Переглянути Історію
+
+
+ Назад до Попереджень
+
+
+ Назад до Налаштувань
+
+
+ Керувати Зонами
+
+
+ Деталі
+
+
+ Немає активних попереджень про погоду для вашого підрозділу.
+
+
+ Не вдалося завантажити попередження про погоду. Перевірте налаштування.
+
+
+ Завантаження попереджень про погоду...
+
+
+ Завантаження історії...
+
+
+ Подія
+
+
+ Серйозність
+
+
+ Район
+
+
+ Закінчується
+
+
+ Статус
+
+
+ Категорія
+
+
+ Дії
+
+
+ Екстремальна
+
+
+ Серйозна
+
+
+ Помірна
+
+
+ Незначна
+
+
+ Невідома
+
+
+ Негайна
+
+
+ Очікувана
+
+
+ Майбутня
+
+
+ Минула
+
+
+ Невідома
+
+
+ Метеорологічна
+
+
+ Пожежа
+
+
+ Здоров'я
+
+
+ Екологічна
+
+
+ Інша
+
+
+ Активний
+
+
+ Оновлений
+
+
+ Закінчився
+
+
+ Скасований
+
+
+ Національна Метеорологічна Служба
+
+
+ Environment Canada
+
+
+ MeteoAlarm (Європа)
+
+
+ Додати Джерело
+
+
+ Назва Джерела
+
+
+ Тип Джерела
+
+
+ Фільтр Району (коди зон JSON)
+
+
+ Інтервал Опитування (хвилини)
+
+
+ Зберегти
+
+
+ Скасувати
+
+
+ Видалити
+
+
+ Ви впевнені, що хочете видалити це джерело?
+
+
+ Не вдалося зберегти джерело. Спробуйте ще раз.
+
+
+ Не вдалося видалити джерело. Спробуйте ще раз.
+
+
+ Додати Зону Моніторингу
+
+
+ Редагувати Зону Моніторингу
+
+
+ Назва Зони
+
+
+ Код Зони
+
+
+ Центральне Розташування
+
+
+ Радіус (милі)
+
+
+ Основна Зона
+
+
+ Активна
+
+
+ Неактивна
+
+
+ Основна
+
+
+ Увімкнути
+
+
+ Вимкнути
+
+
+ Редагувати
+
+
+ Зони моніторингу не налаштовані. Додайте зону нижче, щоб визначити географічні райони для моніторингу попереджень про погоду.
+
+
+ Ви впевнені, що хочете видалити цю зону?
+
+
+ Не вдалося зберегти зону. Спробуйте ще раз.
+
+
+ Не вдалося видалити зону. Спробуйте ще раз.
+
+
+ Не вдалося оновити статус зони. Спробуйте ще раз.
+
+
+ Будь ласка, введіть назву зони.
+
+
+ Зберегти Зону
+
+
+ Параметри
+
+
+ Дата Початку
+
+
+ Дата Закінчення
+
+
+ Пошук
+
+
+ Будь ласка, виберіть дату початку та закінчення.
+
+
+ Історія попереджень для обраного періоду не знайдена.
+
+
+ Не вдалося завантажити історію попереджень.
+
+
+ Деталі Попередження
+
+
+ Заголовок
+
+
+ Опис
+
+
+ Інструкції
+
+
+ Опис Району
+
+
+ Терміновість
+
+
+ Достовірність
+
+
+ Дата Набуття Чинності
+
+
+ Дата Початку
+
+
+ Дата Закінчення
+
+
+ Дата Відправлення
+
+
+ Перше Виявлення
+
+
+ Останнє Оновлення
+
+
+ Відправник
+
+
+ Зовнішній ID
+
+
+ Сповіщення Надіслано
+
+
+ Так
+
+
+ Ні
+
+
+ Налаштовані Джерела
+
+
+ Інтервал Опитування
+
+
+ Останнє Опитування
+
+
+ Статус
+
+
+ Справний
+
+
+ Помилка
+
+
+ Ніколи Не Опитувалось
+
+
+ Джерела попереджень про погоду не налаштовані. Додайте джерело, щоб почати отримувати попередження про погоду.
+
+
+ Попередження про Погоду: {0}
+
+
+ Закінчується: {0}
+
+
+ Район: {0}
+
+
+ Інструкції: {0}
+
+
+ Налаштування Сповіщень
+
+
+ Налаштуйте, які попередження про погоду автоматично генерують повідомлення для членів підрозділу.
+
+
+ Увімкнути Попередження про Погоду
+
+
+ Мінімальна Серйозність для Відображення
+
+
+ На панелі будуть показані лише попередження з цим або вищим рівнем серйозності.
+
+
+ Поріг Серйозності для Автоматичного Повідомлення
+
+
+ Попередження з цим або вищим рівнем серйозності автоматично надішлють повідомлення всім членам підрозділу. Нижче значення = вища серйозність (Екстремальна=0, Серйозна=1, Помірна=2, Незначна=3).
+
+
+ Додавати Погодний Контекст до Викликів
+
+
+ Коли увімкнено, активні попередження про погоду поблизу місця виклику будуть додані як примітка до виклику.
+
+
+ Зберегти Налаштування
+
+
+ Налаштування збережено успішно.
+
+
+ Не вдалося зберегти налаштування. Спробуйте ще раз.
+
+
+ Не вдалося завантажити налаштування.
+
+
+ Не вдалося оновити статус джерела. Спробуйте ще раз.
+
+
+ Редагувати Джерело
+
+
+ Додати / Редагувати Джерело
+
+
+ Будь ласка, введіть назву джерела.
+
+
+ Розділений комами список кодів штатів, кодів провінцій або кодів зон NWS.
+
+
diff --git a/Core/Resgrid.Model/AuditLogTypes.cs b/Core/Resgrid.Model/AuditLogTypes.cs
index 5ae75e77..be9d1396 100644
--- a/Core/Resgrid.Model/AuditLogTypes.cs
+++ b/Core/Resgrid.Model/AuditLogTypes.cs
@@ -148,6 +148,18 @@ public enum AuditLogTypes
CommunicationTestCreated,
CommunicationTestUpdated,
CommunicationTestDeleted,
- CommunicationTestRunStarted
+ CommunicationTestRunStarted,
+ // Weather Alerts
+ WeatherAlertSourceCreated,
+ WeatherAlertSourceUpdated,
+ WeatherAlertSourceDeleted,
+ WeatherAlertSourceEnabled,
+ WeatherAlertSourceDisabled,
+ WeatherAlertZoneCreated,
+ WeatherAlertZoneUpdated,
+ WeatherAlertZoneDeleted,
+ WeatherAlertZoneEnabled,
+ WeatherAlertZoneDisabled,
+ WeatherAlertSettingsChanged
}
}
diff --git a/Core/Resgrid.Model/DepartmentSettingTypes.cs b/Core/Resgrid.Model/DepartmentSettingTypes.cs
index 99c05316..7a335290 100644
--- a/Core/Resgrid.Model/DepartmentSettingTypes.cs
+++ b/Core/Resgrid.Model/DepartmentSettingTypes.cs
@@ -40,5 +40,10 @@ public enum DepartmentSettingTypes
Require2FAForAdmins = 36,
PaddleCustomerId = 37,
CheckInTimersAutoEnableForNewCalls = 38,
+ WeatherAlertsEnabled = 39,
+ WeatherAlertMinimumSeverity = 40,
+ WeatherAlertAutoMessageSeverity = 41,
+ WeatherAlertCallIntegration = 42,
+ WeatherAlertCacheMinutes = 43,
}
}
diff --git a/Core/Resgrid.Model/Events/EventTypes.cs b/Core/Resgrid.Model/Events/EventTypes.cs
index 73dfc90c..8156c338 100644
--- a/Core/Resgrid.Model/Events/EventTypes.cs
+++ b/Core/Resgrid.Model/Events/EventTypes.cs
@@ -70,7 +70,13 @@ public enum EventTypes
LinkedDepartmentCallAdded = 20,
[NotMapped]
- ResourceOrderAdded = 21
+ ResourceOrderAdded = 21,
+
+ [NotMapped]
+ WeatherAlertReceived = 22,
+
+ [NotMapped]
+ WeatherAlertExpired = 23
}
public static class EventOptions
@@ -140,7 +146,9 @@ public static class EventOptions
EventTypes.CalendarEventUpdated,
EventTypes.ShiftCreated,
EventTypes.ShiftUpdated,
- EventTypes.ShiftDaysAdded
+ EventTypes.ShiftDaysAdded,
+ EventTypes.WeatherAlertReceived,
+ EventTypes.WeatherAlertExpired
};
}
}
\ No newline at end of file
diff --git a/Core/Resgrid.Model/Providers/IWeatherAlertProvider.cs b/Core/Resgrid.Model/Providers/IWeatherAlertProvider.cs
new file mode 100644
index 00000000..32f03950
--- /dev/null
+++ b/Core/Resgrid.Model/Providers/IWeatherAlertProvider.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Providers
+{
+ public interface IWeatherAlertProvider
+ {
+ WeatherAlertSourceType SourceType { get; }
+ Task> FetchAlertsAsync(WeatherAlertSource source, CancellationToken ct = default);
+ }
+}
diff --git a/Core/Resgrid.Model/Providers/IWeatherAlertProviderFactory.cs b/Core/Resgrid.Model/Providers/IWeatherAlertProviderFactory.cs
new file mode 100644
index 00000000..5696e0c2
--- /dev/null
+++ b/Core/Resgrid.Model/Providers/IWeatherAlertProviderFactory.cs
@@ -0,0 +1,7 @@
+namespace Resgrid.Model.Providers
+{
+ public interface IWeatherAlertProviderFactory
+ {
+ IWeatherAlertProvider GetProvider(WeatherAlertSourceType sourceType);
+ }
+}
diff --git a/Core/Resgrid.Model/Repositories/IWeatherAlertRepository.cs b/Core/Resgrid.Model/Repositories/IWeatherAlertRepository.cs
new file mode 100644
index 00000000..7729ce59
--- /dev/null
+++ b/Core/Resgrid.Model/Repositories/IWeatherAlertRepository.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
+{
+ public interface IWeatherAlertRepository : IRepository
+ {
+ Task> GetActiveAlertsByDepartmentIdAsync(int departmentId);
+ Task GetByExternalIdAndSourceIdAsync(string externalId, Guid sourceId);
+ Task> GetAlertsByDepartmentAndSeverityAsync(int departmentId, int maxSeverity);
+ Task> GetAlertsByDepartmentAndCategoryAsync(int departmentId, int category);
+ Task> GetExpiredUnprocessedAlertsAsync();
+ Task> GetUnnotifiedAlertsAsync();
+ Task> GetAlertHistoryByDepartmentAsync(int departmentId, DateTime startDate, DateTime endDate);
+ }
+}
diff --git a/Core/Resgrid.Model/Repositories/IWeatherAlertSourceRepository.cs b/Core/Resgrid.Model/Repositories/IWeatherAlertSourceRepository.cs
new file mode 100644
index 00000000..279c3d09
--- /dev/null
+++ b/Core/Resgrid.Model/Repositories/IWeatherAlertSourceRepository.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
+{
+ public interface IWeatherAlertSourceRepository : IRepository
+ {
+ Task> GetActiveSourcesForPollingAsync();
+ Task> GetSourcesByDepartmentIdAsync(int departmentId);
+ }
+}
diff --git a/Core/Resgrid.Model/Repositories/IWeatherAlertZoneRepository.cs b/Core/Resgrid.Model/Repositories/IWeatherAlertZoneRepository.cs
new file mode 100644
index 00000000..1a0eb344
--- /dev/null
+++ b/Core/Resgrid.Model/Repositories/IWeatherAlertZoneRepository.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
+{
+ public interface IWeatherAlertZoneRepository : IRepository
+ {
+ Task> GetZonesByDepartmentIdAsync(int departmentId);
+ Task> GetActiveZonesByDepartmentIdAsync(int departmentId);
+ }
+}
diff --git a/Core/Resgrid.Model/Services/IDepartmentSettingsService.cs b/Core/Resgrid.Model/Services/IDepartmentSettingsService.cs
index 62d5d4c0..48698911 100644
--- a/Core/Resgrid.Model/Services/IDepartmentSettingsService.cs
+++ b/Core/Resgrid.Model/Services/IDepartmentSettingsService.cs
@@ -287,5 +287,10 @@ public interface IDepartmentSettingsService
Task GetPaddleCustomerIdForDepartmentAsync(int departmentId);
Task GetCheckInTimersAutoEnableForNewCallsAsync(int departmentId);
+
+ ///
+ /// Gets a department setting by type. Returns null if the setting does not exist.
+ ///
+ Task GetSettingByTypeAsync(int departmentId, DepartmentSettingTypes type);
}
}
diff --git a/Core/Resgrid.Model/Services/IWeatherAlertService.cs b/Core/Resgrid.Model/Services/IWeatherAlertService.cs
new file mode 100644
index 00000000..bd450ff6
--- /dev/null
+++ b/Core/Resgrid.Model/Services/IWeatherAlertService.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Services
+{
+ public interface IWeatherAlertService
+ {
+ // Source CRUD
+ Task GetSourceByIdAsync(Guid sourceId);
+ Task> GetSourcesByDepartmentIdAsync(int departmentId);
+ Task SaveSourceAsync(WeatherAlertSource source, CancellationToken ct = default);
+ Task DeleteSourceAsync(Guid sourceId, CancellationToken ct = default);
+
+ // Alert queries
+ Task GetAlertByIdAsync(Guid alertId);
+ Task> GetActiveAlertsByDepartmentIdAsync(int departmentId);
+ Task> GetAlertsByDepartmentAndSeverityAsync(int departmentId, WeatherAlertSeverity maxSeverity);
+ Task> GetAlertsByDepartmentAndCategoryAsync(int departmentId, WeatherAlertCategory category);
+ Task> GetAlertHistoryAsync(int departmentId, DateTime startDate, DateTime endDate);
+ Task> GetActiveAlertsNearLocationAsync(int departmentId, double lat, double lng, double radiusMiles = 25);
+
+ // Zone CRUD
+ Task GetZoneByIdAsync(Guid zoneId);
+ Task> GetZonesByDepartmentIdAsync(int departmentId);
+ Task SaveZoneAsync(WeatherAlertZone zone, CancellationToken ct = default);
+ Task DeleteZoneAsync(Guid zoneId, CancellationToken ct = default);
+
+ // Ingestion (called by worker)
+ Task ProcessWeatherAlertSourceAsync(Guid sourceId, CancellationToken ct = default);
+ Task ProcessAllActiveSourcesAsync(CancellationToken ct = default);
+ Task ExpireOldAlertsAsync(CancellationToken ct = default);
+ Task SendPendingNotificationsAsync(CancellationToken ct = default);
+
+ // Call integration
+ Task AttachWeatherAlertsToCallAsync(Call call, CancellationToken ct = default);
+
+ // Cache invalidation
+ Task InvalidateDepartmentWeatherCacheAsync(int departmentId);
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlert.cs b/Core/Resgrid.Model/WeatherAlert.cs
new file mode 100644
index 00000000..9fc46017
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlert.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ [Table("WeatherAlerts")]
+ public class WeatherAlert : IEntity
+ {
+ [Key]
+ [Required]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public Guid WeatherAlertId { get; set; }
+
+ [Required]
+ [ForeignKey("Department"), DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public int DepartmentId { get; set; }
+
+ public virtual Department Department { get; set; }
+
+ [ForeignKey("WeatherAlertSource"), DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public Guid WeatherAlertSourceId { get; set; }
+
+ public virtual WeatherAlertSource WeatherAlertSource { get; set; }
+
+ [MaxLength(500)]
+ public string ExternalId { get; set; }
+
+ [MaxLength(500)]
+ public string Sender { get; set; }
+
+ [MaxLength(500)]
+ public string Event { get; set; }
+
+ public int AlertCategory { get; set; }
+
+ public int Severity { get; set; }
+
+ public int Urgency { get; set; }
+
+ public int Certainty { get; set; }
+
+ public int Status { get; set; }
+
+ [MaxLength(500)]
+ public string Headline { get; set; }
+
+ public string Description { get; set; }
+
+ public string Instruction { get; set; }
+
+ [MaxLength(500)]
+ public string AreaDescription { get; set; }
+
+ public string Polygon { get; set; }
+
+ public string Geocodes { get; set; }
+
+ [MaxLength(100)]
+ public string CenterGeoLocation { get; set; }
+
+ public DateTime? OnsetUtc { get; set; }
+
+ public DateTime? ExpiresUtc { get; set; }
+
+ public DateTime EffectiveUtc { get; set; }
+
+ public DateTime? SentUtc { get; set; }
+
+ public DateTime FirstSeenUtc { get; set; }
+
+ public DateTime LastUpdatedUtc { get; set; }
+
+ [MaxLength(500)]
+ public string ReferencesExternalId { get; set; }
+
+ public bool NotificationSent { get; set; }
+
+ public int? SystemMessageId { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return WeatherAlertId == Guid.Empty ? null : (object)WeatherAlertId.ToString(); }
+ set { WeatherAlertId = value == null ? Guid.Empty : Guid.Parse(value.ToString()); }
+ }
+
+ [NotMapped]
+ public string TableName => "WeatherAlerts";
+
+ [NotMapped]
+ public string IdName => "WeatherAlertId";
+
+ [NotMapped]
+ public int IdType => 1;
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "Department", "WeatherAlertSource" };
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlertCategory.cs b/Core/Resgrid.Model/WeatherAlertCategory.cs
new file mode 100644
index 00000000..2103f9bb
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlertCategory.cs
@@ -0,0 +1,11 @@
+namespace Resgrid.Model
+{
+ public enum WeatherAlertCategory
+ {
+ Met = 0,
+ Fire = 1,
+ Health = 2,
+ Env = 3,
+ Other = 4
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlertCertainty.cs b/Core/Resgrid.Model/WeatherAlertCertainty.cs
new file mode 100644
index 00000000..edd5fc89
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlertCertainty.cs
@@ -0,0 +1,11 @@
+namespace Resgrid.Model
+{
+ public enum WeatherAlertCertainty
+ {
+ Observed = 0,
+ Likely = 1,
+ Possible = 2,
+ Unlikely = 3,
+ Unknown = 4
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlertSeverity.cs b/Core/Resgrid.Model/WeatherAlertSeverity.cs
new file mode 100644
index 00000000..ff0c4278
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlertSeverity.cs
@@ -0,0 +1,11 @@
+namespace Resgrid.Model
+{
+ public enum WeatherAlertSeverity
+ {
+ Extreme = 0,
+ Severe = 1,
+ Moderate = 2,
+ Minor = 3,
+ Unknown = 4
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlertSource.cs b/Core/Resgrid.Model/WeatherAlertSource.cs
new file mode 100644
index 00000000..deda5c86
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlertSource.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ [Table("WeatherAlertSources")]
+ public class WeatherAlertSource : IEntity
+ {
+ [Key]
+ [Required]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public Guid WeatherAlertSourceId { get; set; }
+
+ [Required]
+ [ForeignKey("Department"), DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public int DepartmentId { get; set; }
+
+ public virtual Department Department { get; set; }
+
+ [MaxLength(200)]
+ public string Name { get; set; }
+
+ public int SourceType { get; set; }
+
+ [MaxLength(1000)]
+ public string AreaFilter { get; set; }
+
+ [MaxLength(500)]
+ public string ApiKey { get; set; }
+
+ [MaxLength(2000)]
+ public string CustomEndpoint { get; set; }
+
+ public int PollIntervalMinutes { get; set; }
+
+ public bool Active { get; set; }
+
+ public DateTime? LastPollUtc { get; set; }
+
+ public DateTime? LastSuccessUtc { get; set; }
+
+ public bool IsFailure { get; set; }
+
+ [MaxLength(2000)]
+ public string ErrorMessage { get; set; }
+
+ [MaxLength(500)]
+ public string LastETag { get; set; }
+
+ public DateTime CreatedOn { get; set; }
+
+ [MaxLength(128)]
+ public string CreatedByUserId { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return WeatherAlertSourceId == Guid.Empty ? null : (object)WeatherAlertSourceId.ToString(); }
+ set { WeatherAlertSourceId = value == null ? Guid.Empty : Guid.Parse(value.ToString()); }
+ }
+
+ [NotMapped]
+ public string TableName => "WeatherAlertSources";
+
+ [NotMapped]
+ public string IdName => "WeatherAlertSourceId";
+
+ [NotMapped]
+ public int IdType => 1;
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "Department", "ContactEmail" };
+
+ ///
+ /// Non-persisted. Populated by the service layer with the department admin's email
+ /// for use as contact info in upstream API User-Agent headers.
+ ///
+ [NotMapped]
+ public string ContactEmail { get; set; }
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlertSourceType.cs b/Core/Resgrid.Model/WeatherAlertSourceType.cs
new file mode 100644
index 00000000..fd4d7e4a
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlertSourceType.cs
@@ -0,0 +1,9 @@
+namespace Resgrid.Model
+{
+ public enum WeatherAlertSourceType
+ {
+ NationalWeatherService = 0,
+ EnvironmentCanada = 1,
+ MeteoAlarm = 2
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlertStatus.cs b/Core/Resgrid.Model/WeatherAlertStatus.cs
new file mode 100644
index 00000000..448a8144
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlertStatus.cs
@@ -0,0 +1,10 @@
+namespace Resgrid.Model
+{
+ public enum WeatherAlertStatus
+ {
+ Active = 0,
+ Updated = 1,
+ Expired = 2,
+ Cancelled = 3
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlertUrgency.cs b/Core/Resgrid.Model/WeatherAlertUrgency.cs
new file mode 100644
index 00000000..acbb77f6
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlertUrgency.cs
@@ -0,0 +1,11 @@
+namespace Resgrid.Model
+{
+ public enum WeatherAlertUrgency
+ {
+ Immediate = 0,
+ Expected = 1,
+ Future = 2,
+ Past = 3,
+ Unknown = 4
+ }
+}
diff --git a/Core/Resgrid.Model/WeatherAlertZone.cs b/Core/Resgrid.Model/WeatherAlertZone.cs
new file mode 100644
index 00000000..cd7f3426
--- /dev/null
+++ b/Core/Resgrid.Model/WeatherAlertZone.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ [Table("WeatherAlertZones")]
+ public class WeatherAlertZone : IEntity
+ {
+ [Key]
+ [Required]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public Guid WeatherAlertZoneId { get; set; }
+
+ [Required]
+ [ForeignKey("Department"), DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public int DepartmentId { get; set; }
+
+ public virtual Department Department { get; set; }
+
+ [MaxLength(200)]
+ public string Name { get; set; }
+
+ [MaxLength(100)]
+ public string ZoneCode { get; set; }
+
+ [MaxLength(100)]
+ public string CenterGeoLocation { get; set; }
+
+ public double RadiusMiles { get; set; }
+
+ public bool IsActive { get; set; }
+
+ public bool IsPrimary { get; set; }
+
+ public DateTime CreatedOn { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return WeatherAlertZoneId == Guid.Empty ? null : (object)WeatherAlertZoneId.ToString(); }
+ set { WeatherAlertZoneId = value == null ? Guid.Empty : Guid.Parse(value.ToString()); }
+ }
+
+ [NotMapped]
+ public string TableName => "WeatherAlertZones";
+
+ [NotMapped]
+ public string IdName => "WeatherAlertZoneId";
+
+ [NotMapped]
+ public int IdType => 1;
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "Department" };
+ }
+}
diff --git a/Core/Resgrid.Services/DepartmentSettingsService.cs b/Core/Resgrid.Services/DepartmentSettingsService.cs
index 9aac66f4..ba8d7861 100644
--- a/Core/Resgrid.Services/DepartmentSettingsService.cs
+++ b/Core/Resgrid.Services/DepartmentSettingsService.cs
@@ -797,5 +797,10 @@ public async Task GetCheckInTimersAutoEnableForNewCallsAsync(int departmen
return false;
}
+
+ public async Task GetSettingByTypeAsync(int departmentId, DepartmentSettingTypes type)
+ {
+ return await GetSettingByDepartmentIdType(departmentId, type);
+ }
}
}
diff --git a/Core/Resgrid.Services/ServicesModule.cs b/Core/Resgrid.Services/ServicesModule.cs
index 4194a932..0ec55973 100644
--- a/Core/Resgrid.Services/ServicesModule.cs
+++ b/Core/Resgrid.Services/ServicesModule.cs
@@ -103,6 +103,9 @@ protected override void Load(ContainerBuilder builder)
// Communication Test Services
builder.RegisterType().As().InstancePerLifetimeScope();
+
+ // Weather Alert Services
+ builder.RegisterType().As().InstancePerLifetimeScope();
}
}
}
diff --git a/Core/Resgrid.Services/WeatherAlertService.cs b/Core/Resgrid.Services/WeatherAlertService.cs
new file mode 100644
index 00000000..8fa8814f
--- /dev/null
+++ b/Core/Resgrid.Services/WeatherAlertService.cs
@@ -0,0 +1,623 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Resgrid.Framework;
+using Resgrid.Model;
+using Resgrid.Model.Helpers;
+using Resgrid.Model.Providers;
+using Resgrid.Model.Repositories;
+using Resgrid.Model.Services;
+
+namespace Resgrid.Services
+{
+ public class WeatherAlertService : IWeatherAlertService
+ {
+ private readonly IWeatherAlertRepository _weatherAlertRepository;
+ private readonly IWeatherAlertSourceRepository _weatherAlertSourceRepository;
+ private readonly IWeatherAlertZoneRepository _weatherAlertZoneRepository;
+ private readonly IWeatherAlertProviderFactory _weatherAlertProviderFactory;
+ private readonly IDepartmentSettingsRepository _departmentSettingsRepository;
+ private readonly IDepartmentsService _departmentsService;
+ private readonly IMessageService _messageService;
+ private readonly ICallNotesRepository _callNotesRepository;
+ private readonly ICacheProvider _cacheProvider;
+ private readonly IEventAggregator _eventAggregator;
+
+ public WeatherAlertService(
+ IWeatherAlertRepository weatherAlertRepository,
+ IWeatherAlertSourceRepository weatherAlertSourceRepository,
+ IWeatherAlertZoneRepository weatherAlertZoneRepository,
+ IWeatherAlertProviderFactory weatherAlertProviderFactory,
+ IDepartmentSettingsRepository departmentSettingsRepository,
+ IDepartmentsService departmentsService,
+ IMessageService messageService,
+ ICallNotesRepository callNotesRepository,
+ ICacheProvider cacheProvider,
+ IEventAggregator eventAggregator)
+ {
+ _weatherAlertRepository = weatherAlertRepository;
+ _weatherAlertSourceRepository = weatherAlertSourceRepository;
+ _weatherAlertZoneRepository = weatherAlertZoneRepository;
+ _weatherAlertProviderFactory = weatherAlertProviderFactory;
+ _departmentSettingsRepository = departmentSettingsRepository;
+ _departmentsService = departmentsService;
+ _messageService = messageService;
+ _callNotesRepository = callNotesRepository;
+ _cacheProvider = cacheProvider;
+ _eventAggregator = eventAggregator;
+ }
+
+ #region Source CRUD
+
+ public async Task GetSourceByIdAsync(Guid sourceId)
+ {
+ return await _weatherAlertSourceRepository.GetByIdAsync(sourceId.ToString());
+ }
+
+ public async Task> GetSourcesByDepartmentIdAsync(int departmentId)
+ {
+ var items = await _weatherAlertSourceRepository.GetSourcesByDepartmentIdAsync(departmentId);
+ return items?.ToList() ?? new List();
+ }
+
+ public async Task SaveSourceAsync(WeatherAlertSource source, CancellationToken ct = default)
+ {
+ if (source.WeatherAlertSourceId == Guid.Empty)
+ {
+ source.CreatedOn = DateTime.UtcNow;
+ }
+
+ return await _weatherAlertSourceRepository.SaveOrUpdateAsync(source, ct, true);
+ }
+
+ public async Task DeleteSourceAsync(Guid sourceId, CancellationToken ct = default)
+ {
+ var source = await GetSourceByIdAsync(sourceId);
+ if (source == null)
+ return false;
+
+ return await _weatherAlertSourceRepository.DeleteAsync(source, ct);
+ }
+
+ #endregion
+
+ #region Alert Queries
+
+ public async Task GetAlertByIdAsync(Guid alertId)
+ {
+ return await _weatherAlertRepository.GetByIdAsync(alertId.ToString());
+ }
+
+ public async Task> GetActiveAlertsByDepartmentIdAsync(int departmentId)
+ {
+ var items = await _weatherAlertRepository.GetActiveAlertsByDepartmentIdAsync(departmentId);
+ return items?.ToList() ?? new List();
+ }
+
+ public async Task> GetAlertsByDepartmentAndSeverityAsync(int departmentId, WeatherAlertSeverity maxSeverity)
+ {
+ var items = await _weatherAlertRepository.GetAlertsByDepartmentAndSeverityAsync(departmentId, (int)maxSeverity);
+ return items?.ToList() ?? new List();
+ }
+
+ public async Task> GetAlertsByDepartmentAndCategoryAsync(int departmentId, WeatherAlertCategory category)
+ {
+ var items = await _weatherAlertRepository.GetAlertsByDepartmentAndCategoryAsync(departmentId, (int)category);
+ return items?.ToList() ?? new List();
+ }
+
+ public async Task> GetAlertHistoryAsync(int departmentId, DateTime startDate, DateTime endDate)
+ {
+ var items = await _weatherAlertRepository.GetAlertHistoryByDepartmentAsync(departmentId, startDate, endDate);
+ return items?.ToList() ?? new List();
+ }
+
+ public async Task> GetActiveAlertsNearLocationAsync(int departmentId, double lat, double lng, double radiusMiles = 25)
+ {
+ // Get all active alerts for the department, then filter by proximity
+ var alerts = await GetActiveAlertsByDepartmentIdAsync(departmentId);
+ return alerts.Where(a =>
+ {
+ if (string.IsNullOrEmpty(a.CenterGeoLocation))
+ return false;
+
+ var parts = a.CenterGeoLocation.Split(',');
+ if (parts.Length != 2 || !double.TryParse(parts[0], out var alertLat) || !double.TryParse(parts[1], out var alertLng))
+ return false;
+
+ var distance = CalculateDistanceMiles(lat, lng, alertLat, alertLng);
+ return distance <= radiusMiles;
+ }).ToList();
+ }
+
+ #endregion
+
+ #region Zone CRUD
+
+ public async Task GetZoneByIdAsync(Guid zoneId)
+ {
+ return await _weatherAlertZoneRepository.GetByIdAsync(zoneId.ToString());
+ }
+
+ public async Task> GetZonesByDepartmentIdAsync(int departmentId)
+ {
+ var items = await _weatherAlertZoneRepository.GetZonesByDepartmentIdAsync(departmentId);
+ return items?.ToList() ?? new List();
+ }
+
+ public async Task SaveZoneAsync(WeatherAlertZone zone, CancellationToken ct = default)
+ {
+ if (zone.WeatherAlertZoneId == Guid.Empty)
+ {
+ zone.CreatedOn = DateTime.UtcNow;
+ }
+
+ return await _weatherAlertZoneRepository.SaveOrUpdateAsync(zone, ct, true);
+ }
+
+ public async Task DeleteZoneAsync(Guid zoneId, CancellationToken ct = default)
+ {
+ var zone = await GetZoneByIdAsync(zoneId);
+ if (zone == null)
+ return false;
+
+ return await _weatherAlertZoneRepository.DeleteAsync(zone, ct);
+ }
+
+ #endregion
+
+ #region Ingestion
+
+ public async Task ProcessWeatherAlertSourceAsync(Guid sourceId, CancellationToken ct = default)
+ {
+ var source = await GetSourceByIdAsync(sourceId);
+ if (source == null || !source.Active)
+ return;
+
+ // Populate the department admin contact email for upstream API User-Agent headers
+ try
+ {
+ var department = await _departmentsService.GetDepartmentByIdAsync(source.DepartmentId);
+ if (department?.ManagingUser != null)
+ source.ContactEmail = department.ManagingUser.Email;
+ }
+ catch { }
+
+ try
+ {
+ var provider = _weatherAlertProviderFactory.GetProvider((WeatherAlertSourceType)source.SourceType);
+ var fetchedAlerts = await provider.FetchAlertsAsync(source, ct);
+
+ foreach (var alert in fetchedAlerts)
+ {
+ var existing = await _weatherAlertRepository.GetByExternalIdAndSourceIdAsync(
+ alert.ExternalId, source.WeatherAlertSourceId);
+
+ if (existing == null)
+ {
+ // New alert
+ await _weatherAlertRepository.InsertAsync(alert, ct, true);
+ }
+ else
+ {
+ // Update existing
+ existing.Severity = alert.Severity;
+ existing.Urgency = alert.Urgency;
+ existing.Certainty = alert.Certainty;
+ existing.Headline = alert.Headline;
+ existing.Description = alert.Description;
+ existing.Instruction = alert.Instruction;
+ existing.AreaDescription = alert.AreaDescription;
+ existing.Polygon = alert.Polygon;
+ existing.Geocodes = alert.Geocodes;
+ existing.CenterGeoLocation = alert.CenterGeoLocation;
+ existing.ExpiresUtc = alert.ExpiresUtc;
+ existing.LastUpdatedUtc = DateTime.UtcNow;
+ await _weatherAlertRepository.UpdateAsync(existing, ct, true);
+ }
+
+ // Handle reference cancellations
+ if (!string.IsNullOrEmpty(alert.ReferencesExternalId))
+ {
+ var referenced = await _weatherAlertRepository.GetByExternalIdAndSourceIdAsync(
+ alert.ReferencesExternalId, source.WeatherAlertSourceId);
+ if (referenced != null && referenced.Status == (int)WeatherAlertStatus.Active)
+ {
+ referenced.Status = (int)WeatherAlertStatus.Cancelled;
+ referenced.LastUpdatedUtc = DateTime.UtcNow;
+ await _weatherAlertRepository.UpdateAsync(referenced, ct, true);
+ }
+ }
+ }
+
+ source.LastPollUtc = DateTime.UtcNow;
+ source.LastSuccessUtc = DateTime.UtcNow;
+ source.IsFailure = false;
+ source.ErrorMessage = null;
+ await _weatherAlertSourceRepository.UpdateAsync(source, ct, true);
+ }
+ catch (Exception ex)
+ {
+ source.LastPollUtc = DateTime.UtcNow;
+ source.IsFailure = true;
+ source.ErrorMessage = ex.Message;
+ await _weatherAlertSourceRepository.UpdateAsync(source, ct, true);
+ throw;
+ }
+ }
+
+ public async Task ProcessAllActiveSourcesAsync(CancellationToken ct = default)
+ {
+ var sources = await _weatherAlertSourceRepository.GetActiveSourcesForPollingAsync();
+ if (sources == null)
+ return;
+
+ foreach (var source in sources)
+ {
+ // Check if it's time to poll based on interval
+ if (source.LastPollUtc.HasValue)
+ {
+ var nextPoll = source.LastPollUtc.Value.AddMinutes(source.PollIntervalMinutes);
+ if (DateTime.UtcNow < nextPoll)
+ continue;
+ }
+
+ try
+ {
+ await ProcessWeatherAlertSourceAsync(source.WeatherAlertSourceId, ct);
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ }
+ }
+ }
+
+ public async Task ExpireOldAlertsAsync(CancellationToken ct = default)
+ {
+ var expired = await _weatherAlertRepository.GetExpiredUnprocessedAlertsAsync();
+ if (expired == null)
+ return;
+
+ foreach (var alert in expired)
+ {
+ alert.Status = (int)WeatherAlertStatus.Expired;
+ alert.LastUpdatedUtc = DateTime.UtcNow;
+ await _weatherAlertRepository.UpdateAsync(alert, ct, true);
+ }
+ }
+
+ public async Task SendPendingNotificationsAsync(CancellationToken ct = default)
+ {
+ var unnotified = await _weatherAlertRepository.GetUnnotifiedAlertsAsync();
+ if (unnotified == null)
+ return;
+
+ // Group by department for efficient processing
+ var byDepartment = unnotified.ToList().GroupBy(a => a.DepartmentId);
+
+ foreach (var group in byDepartment)
+ {
+ var departmentId = group.Key;
+
+ // Check if weather alerts are enabled for this department
+ var enabledSetting = await _departmentSettingsRepository.GetDepartmentSettingByIdTypeAsync(
+ departmentId, DepartmentSettingTypes.WeatherAlertsEnabled);
+ if (enabledSetting != null && bool.TryParse(enabledSetting.Setting, out var isEnabled) && !isEnabled)
+ {
+ // Mark all as notified without sending
+ foreach (var a in group)
+ {
+ a.NotificationSent = true;
+ a.LastUpdatedUtc = DateTime.UtcNow;
+ await _weatherAlertRepository.UpdateAsync(a, ct, true);
+ }
+ continue;
+ }
+
+ // Get the auto-message severity threshold setting
+ var thresholdSetting = await _departmentSettingsRepository.GetDepartmentSettingByIdTypeAsync(
+ departmentId, DepartmentSettingTypes.WeatherAlertAutoMessageSeverity);
+
+ int threshold = (int)WeatherAlertSeverity.Severe; // Default: Severe=1
+ if (thresholdSetting != null && int.TryParse(thresholdSetting.Setting, out var parsed))
+ threshold = parsed;
+
+ // Load department for sender info
+ Department department = null;
+ try
+ {
+ department = await _departmentsService.GetDepartmentByIdAsync(departmentId);
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ }
+
+ foreach (var alert in group)
+ {
+ // Only send notifications for alerts meeting severity threshold
+ // Lower enum value = higher severity (Extreme=0, Severe=1, etc.)
+ if (alert.Severity <= threshold)
+ {
+ try
+ {
+ var members = await _departmentsService.GetAllMembersForDepartmentAsync(departmentId);
+ if (members != null && members.Any())
+ {
+ // Use department managing user as sender for system messages
+ var senderId = department?.ManagingUserId ?? members.First().UserId;
+
+ var subject = FormatAlertSubject(alert);
+ var body = FormatAlertMessageBody(alert, department);
+
+ var message = new Message
+ {
+ Subject = subject,
+ Body = body,
+ SendingUserId = senderId,
+ SentOn = DateTime.UtcNow,
+ SystemGenerated = true,
+ IsBroadcast = true,
+ Type = 0,
+ Recipients = string.Join(",", members.Select(m => m.UserId))
+ };
+
+ // Use SendMessageAsync which saves AND enqueues for push/SMS/email delivery
+ var sent = await _messageService.SendMessageAsync(
+ message, "Weather Alert System", departmentId, true, ct);
+
+ if (sent)
+ {
+ alert.SystemMessageId = message.MessageId;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ }
+ }
+
+ alert.NotificationSent = true;
+ alert.LastUpdatedUtc = DateTime.UtcNow;
+ await _weatherAlertRepository.UpdateAsync(alert, ct, true);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Call Integration
+
+ public async Task AttachWeatherAlertsToCallAsync(Call call, CancellationToken ct = default)
+ {
+ if (call == null || call.CallId == 0)
+ return;
+
+ try
+ {
+ // Check if call integration is enabled for this department
+ var callIntSetting = await _departmentSettingsRepository.GetDepartmentSettingByIdTypeAsync(
+ call.DepartmentId, DepartmentSettingTypes.WeatherAlertCallIntegration);
+
+ if (callIntSetting == null || !bool.TryParse(callIntSetting.Setting, out var enabled) || !enabled)
+ return;
+
+ // Get active alerts for the department
+ var activeAlerts = await GetActiveAlertsByDepartmentIdAsync(call.DepartmentId);
+ if (activeAlerts == null || activeAlerts.Count == 0)
+ return;
+
+ // If the call has geolocation, filter to nearby alerts; otherwise attach all active alerts
+ var alertsToAttach = activeAlerts;
+ if (!string.IsNullOrEmpty(call.GeoLocationData))
+ {
+ var parts = call.GeoLocationData.Split(',');
+ if (parts.Length >= 2 && double.TryParse(parts[0].Trim(), out var lat) && double.TryParse(parts[1].Trim(), out var lng))
+ {
+ alertsToAttach = activeAlerts.Where(a =>
+ {
+ if (string.IsNullOrEmpty(a.CenterGeoLocation))
+ return true; // Include alerts without a center point (area-wide alerts)
+
+ var alertParts = a.CenterGeoLocation.Split(',');
+ if (alertParts.Length != 2 || !double.TryParse(alertParts[0].Trim(), out var aLat) || !double.TryParse(alertParts[1].Trim(), out var aLng))
+ return true;
+
+ return CalculateDistanceMiles(lat, lng, aLat, aLng) <= 50;
+ }).ToList();
+ }
+ }
+
+ if (alertsToAttach.Count == 0)
+ return;
+
+ // Get existing call notes to check for duplicates
+ var existingNotes = await _callNotesRepository.GetCallNotesByCallIdAsync(call.CallId);
+ var existingAlertIds = new HashSet();
+ if (existingNotes != null)
+ {
+ foreach (var note in existingNotes)
+ {
+ if (note.Source == (int)CallNoteSources.System && note.Note != null && note.Note.StartsWith("[WeatherAlert:"))
+ {
+ var endIdx = note.Note.IndexOf(']');
+ if (endIdx > 15)
+ existingAlertIds.Add(note.Note.Substring(15, endIdx - 15));
+ }
+ }
+ }
+
+ foreach (var alert in alertsToAttach)
+ {
+ var alertIdStr = alert.WeatherAlertId.ToString();
+
+ // Skip if this exact alert was already attached
+ if (existingAlertIds.Contains(alertIdStr))
+ continue;
+
+ var noteText = $"[WeatherAlert:{alertIdStr}] {alert.Event}";
+
+ if (!string.IsNullOrEmpty(alert.Headline))
+ noteText += $"\n{alert.Headline}";
+
+ if (!string.IsNullOrEmpty(alert.AreaDescription))
+ noteText += $"\nArea: {alert.AreaDescription}";
+
+ var severityNames = new[] { "Extreme", "Severe", "Moderate", "Minor", "Unknown" };
+ noteText += $"\nSeverity: {severityNames[Math.Min(alert.Severity, 4)]}";
+
+ if (alert.ExpiresUtc.HasValue)
+ noteText += $"\nExpires: {alert.ExpiresUtc.Value:yyyy-MM-dd HH:mm} UTC";
+
+ if (!string.IsNullOrEmpty(alert.Instruction))
+ noteText += $"\nInstructions: {alert.Instruction}";
+
+ var callNote = new CallNote
+ {
+ CallId = call.CallId,
+ UserId = call.ReportingUserId,
+ Note = noteText,
+ Source = (int)CallNoteSources.System,
+ Timestamp = DateTime.UtcNow
+ };
+
+ await _callNotesRepository.SaveOrUpdateAsync(callNote, ct);
+ }
+ }
+ catch (Exception ex)
+ {
+ Framework.Logging.LogException(ex);
+ }
+ }
+
+ #endregion
+
+ #region Cache
+
+ public async Task InvalidateDepartmentWeatherCacheAsync(int departmentId)
+ {
+ // Cache invalidation - can be expanded when caching is implemented
+ return await Task.FromResult(true);
+ }
+
+ #endregion
+
+ #region Helpers
+
+ private static readonly string[] SeverityNames = { "Extreme", "Severe", "Moderate", "Minor", "Unknown" };
+ private static readonly string[] UrgencyNames = { "Immediate", "Expected", "Future", "Past", "Unknown" };
+ private static readonly string[] CertaintyNames = { "Observed", "Likely", "Possible", "Unlikely", "Unknown" };
+ private static readonly string[] CategoryNames = { "Meteorological", "Fire", "Health", "Environmental", "Other" };
+
+ private static string FormatAlertSubject(WeatherAlert alert)
+ {
+ var sev = SeverityNames[Math.Min(alert.Severity, 4)];
+ // Subject max 150 chars per Message entity
+ var subject = $"[{sev}] Weather Alert: {alert.Event}";
+ return subject.Length > 150 ? subject.Substring(0, 147) + "..." : subject;
+ }
+
+ private static string FormatAlertMessageBody(WeatherAlert alert, Department department)
+ {
+ var sb = new System.Text.StringBuilder();
+
+ // Header
+ sb.AppendLine($"=== WEATHER ALERT: {alert.Event?.ToUpper()} ===");
+ sb.AppendLine();
+
+ // Headline
+ if (!string.IsNullOrEmpty(alert.Headline))
+ {
+ sb.AppendLine(alert.Headline);
+ sb.AppendLine();
+ }
+
+ // Classification grid
+ sb.AppendLine("--- Alert Details ---");
+ sb.AppendLine($"Severity: {SeverityNames[Math.Min(alert.Severity, 4)]}");
+ sb.AppendLine($"Urgency: {UrgencyNames[Math.Min(alert.Urgency, 4)]}");
+ sb.AppendLine($"Certainty: {CertaintyNames[Math.Min(alert.Certainty, 4)]}");
+ sb.AppendLine($"Category: {CategoryNames[Math.Min(alert.AlertCategory, 4)]}");
+ sb.AppendLine();
+
+ // Timing
+ sb.AppendLine("--- Timing ---");
+ if (department != null)
+ {
+ sb.AppendLine($"Effective: {alert.EffectiveUtc.TimeConverter(department):MM/dd/yyyy h:mm tt}");
+ if (alert.OnsetUtc.HasValue)
+ sb.AppendLine($"Onset: {alert.OnsetUtc.Value.TimeConverter(department):MM/dd/yyyy h:mm tt}");
+ if (alert.ExpiresUtc.HasValue)
+ sb.AppendLine($"Expires: {alert.ExpiresUtc.Value.TimeConverter(department):MM/dd/yyyy h:mm tt}");
+ }
+ else
+ {
+ sb.AppendLine($"Effective: {alert.EffectiveUtc:yyyy-MM-dd HH:mm} UTC");
+ if (alert.OnsetUtc.HasValue)
+ sb.AppendLine($"Onset: {alert.OnsetUtc.Value:yyyy-MM-dd HH:mm} UTC");
+ if (alert.ExpiresUtc.HasValue)
+ sb.AppendLine($"Expires: {alert.ExpiresUtc.Value:yyyy-MM-dd HH:mm} UTC");
+ }
+ sb.AppendLine();
+
+ // Affected area
+ if (!string.IsNullOrEmpty(alert.AreaDescription))
+ {
+ sb.AppendLine("--- Affected Area ---");
+ sb.AppendLine(alert.AreaDescription);
+ sb.AppendLine();
+ }
+
+ // Description
+ if (!string.IsNullOrEmpty(alert.Description))
+ {
+ sb.AppendLine("--- Description ---");
+ sb.AppendLine(alert.Description);
+ sb.AppendLine();
+ }
+
+ // Instructions (critical for responders)
+ if (!string.IsNullOrEmpty(alert.Instruction))
+ {
+ sb.AppendLine("--- INSTRUCTIONS ---");
+ sb.AppendLine(alert.Instruction);
+ sb.AppendLine();
+ }
+
+ // Source info
+ if (!string.IsNullOrEmpty(alert.Sender))
+ sb.AppendLine($"Source: {alert.Sender}");
+
+ sb.AppendLine($"Alert ID: {alert.ExternalId}");
+ sb.AppendLine();
+ sb.AppendLine("This is an automated weather alert from the Resgrid Weather Alert System.");
+
+ // Respect the 4000 char body limit
+ var body = sb.ToString();
+ if (body.Length > 3950)
+ body = body.Substring(0, 3947) + "...";
+
+ return body;
+ }
+
+ private static double CalculateDistanceMiles(double lat1, double lng1, double lat2, double lng2)
+ {
+ const double R = 3959; // Earth's radius in miles
+ var dLat = ToRadians(lat2 - lat1);
+ var dLng = ToRadians(lng2 - lng1);
+ var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
+ Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
+ Math.Sin(dLng / 2) * Math.Sin(dLng / 2);
+ var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
+ return R * c;
+ }
+
+ private static double ToRadians(double degrees) => degrees * Math.PI / 180;
+
+ #endregion
+ }
+}
diff --git a/Providers/Resgrid.Providers.Claims/ClaimsLogic.cs b/Providers/Resgrid.Providers.Claims/ClaimsLogic.cs
index 9af06638..17ebb447 100644
--- a/Providers/Resgrid.Providers.Claims/ClaimsLogic.cs
+++ b/Providers/Resgrid.Providers.Claims/ClaimsLogic.cs
@@ -1641,5 +1641,20 @@ public static void AddCommunicationTestClaims(ClaimsIdentity identity, bool isAd
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.CommunicationTest, ResgridClaimTypes.Actions.Delete));
}
}
+
+ ///
+ /// Weather alert viewing is available to all users. Management (create/update/delete) is admin-only.
+ ///
+ public static void AddWeatherAlertClaims(ClaimsIdentity identity, bool isAdmin)
+ {
+ identity.AddClaim(new Claim(ResgridClaimTypes.Resources.WeatherAlert, ResgridClaimTypes.Actions.View));
+
+ if (isAdmin)
+ {
+ identity.AddClaim(new Claim(ResgridClaimTypes.Resources.WeatherAlert, ResgridClaimTypes.Actions.Update));
+ identity.AddClaim(new Claim(ResgridClaimTypes.Resources.WeatherAlert, ResgridClaimTypes.Actions.Create));
+ identity.AddClaim(new Claim(ResgridClaimTypes.Resources.WeatherAlert, ResgridClaimTypes.Actions.Delete));
+ }
+ }
}
}
diff --git a/Providers/Resgrid.Providers.Claims/ClaimsPrincipalFactory.cs b/Providers/Resgrid.Providers.Claims/ClaimsPrincipalFactory.cs
index c44dc777..512da8ac 100644
--- a/Providers/Resgrid.Providers.Claims/ClaimsPrincipalFactory.cs
+++ b/Providers/Resgrid.Providers.Claims/ClaimsPrincipalFactory.cs
@@ -205,6 +205,7 @@ public override async Task CreateAsync(TUser user)
ClaimsLogic.AddUdfClaims(id, departmentAdmin, permissions, isGroupAdmin, roles);
ClaimsLogic.AddRouteClaims(id, departmentAdmin, permissions, isGroupAdmin, roles);
ClaimsLogic.AddCommunicationTestClaims(id, departmentAdmin);
+ ClaimsLogic.AddWeatherAlertClaims(id, departmentAdmin);
}
}
diff --git a/Providers/Resgrid.Providers.Claims/JwtTokenProvider.cs b/Providers/Resgrid.Providers.Claims/JwtTokenProvider.cs
index 814a289f..7c3fde49 100644
--- a/Providers/Resgrid.Providers.Claims/JwtTokenProvider.cs
+++ b/Providers/Resgrid.Providers.Claims/JwtTokenProvider.cs
@@ -128,6 +128,7 @@ public async Task BuildTokenAsync(string userId, int departmentId)
ClaimsLogic.AddUdfClaims(id, departmentAdmin, permissions, isGroupAdmin, roles);
ClaimsLogic.AddRouteClaims(id, departmentAdmin, permissions, isGroupAdmin, roles);
ClaimsLogic.AddCommunicationTestClaims(id, departmentAdmin);
+ ClaimsLogic.AddWeatherAlertClaims(id, departmentAdmin);
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtConfig.Key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
diff --git a/Providers/Resgrid.Providers.Claims/ResgridClaimTypes.cs b/Providers/Resgrid.Providers.Claims/ResgridClaimTypes.cs
index 5fc38b32..9c0426d2 100644
--- a/Providers/Resgrid.Providers.Claims/ResgridClaimTypes.cs
+++ b/Providers/Resgrid.Providers.Claims/ResgridClaimTypes.cs
@@ -65,6 +65,7 @@ public static class Resources
public const string Udf = "Udf";
public const string Route = "Route";
public const string CommunicationTest = "CommunicationTest";
+ public const string WeatherAlert = "WeatherAlert";
}
public static string CreateDepartmentClaimTypeString(int departmentId)
diff --git a/Providers/Resgrid.Providers.Claims/ResgridIdentity.cs b/Providers/Resgrid.Providers.Claims/ResgridIdentity.cs
index 8c3405ab..b7a588d6 100644
--- a/Providers/Resgrid.Providers.Claims/ResgridIdentity.cs
+++ b/Providers/Resgrid.Providers.Claims/ResgridIdentity.cs
@@ -1087,5 +1087,10 @@ public void AddCommunicationTestClaims(bool isAdmin)
{
ClaimsLogic.AddCommunicationTestClaims(this, isAdmin);
}
+
+ public void AddWeatherAlertClaims(bool isAdmin)
+ {
+ ClaimsLogic.AddWeatherAlertClaims(this, isAdmin);
+ }
}
}
diff --git a/Providers/Resgrid.Providers.Claims/ResgridResources.cs b/Providers/Resgrid.Providers.Claims/ResgridResources.cs
index 854fbdad..b51a2e66 100644
--- a/Providers/Resgrid.Providers.Claims/ResgridResources.cs
+++ b/Providers/Resgrid.Providers.Claims/ResgridResources.cs
@@ -172,5 +172,10 @@ public static class ResgridResources
public const string CommunicationTest_Update = "CommunicationTest_Update";
public const string CommunicationTest_Create = "CommunicationTest_Create";
public const string CommunicationTest_Delete = "CommunicationTest_Delete";
+
+ public const string WeatherAlert_View = "WeatherAlert_View";
+ public const string WeatherAlert_Update = "WeatherAlert_Update";
+ public const string WeatherAlert_Create = "WeatherAlert_Create";
+ public const string WeatherAlert_Delete = "WeatherAlert_Delete";
}
}
diff --git a/Providers/Resgrid.Providers.Migrations/Migrations/M0063_AddingWeatherAlerts.cs b/Providers/Resgrid.Providers.Migrations/Migrations/M0063_AddingWeatherAlerts.cs
new file mode 100644
index 00000000..191353bf
--- /dev/null
+++ b/Providers/Resgrid.Providers.Migrations/Migrations/M0063_AddingWeatherAlerts.cs
@@ -0,0 +1,122 @@
+using FluentMigrator;
+using System;
+
+namespace Resgrid.Providers.Migrations.Migrations
+{
+ [Migration(63)]
+ public class M0063_AddingWeatherAlerts : Migration
+ {
+ public override void Up()
+ {
+ // WeatherAlertSources table
+ Create.Table("WeatherAlertSources")
+ .WithColumn("WeatherAlertSourceId").AsGuid().NotNullable().PrimaryKey().WithDefault(SystemMethods.NewGuid)
+ .WithColumn("DepartmentId").AsInt32().NotNullable()
+ .WithColumn("Name").AsString(200).NotNullable()
+ .WithColumn("SourceType").AsInt32().NotNullable()
+ .WithColumn("AreaFilter").AsString(1000).Nullable()
+ .WithColumn("ApiKey").AsString(500).Nullable()
+ .WithColumn("CustomEndpoint").AsString(2000).Nullable()
+ .WithColumn("PollIntervalMinutes").AsInt32().NotNullable().WithDefaultValue(5)
+ .WithColumn("Active").AsBoolean().NotNullable().WithDefaultValue(true)
+ .WithColumn("LastPollUtc").AsDateTime2().Nullable()
+ .WithColumn("LastSuccessUtc").AsDateTime2().Nullable()
+ .WithColumn("IsFailure").AsBoolean().NotNullable().WithDefaultValue(false)
+ .WithColumn("ErrorMessage").AsString(2000).Nullable()
+ .WithColumn("LastETag").AsString(500).Nullable()
+ .WithColumn("CreatedOn").AsDateTime2().NotNullable()
+ .WithColumn("CreatedByUserId").AsString(128).NotNullable();
+
+ Create.ForeignKey("FK_WeatherAlertSources_Departments")
+ .FromTable("WeatherAlertSources").ForeignColumn("DepartmentId")
+ .ToTable("Departments").PrimaryColumn("DepartmentId");
+
+ Create.Index("IX_WeatherAlertSources_DepartmentId")
+ .OnTable("WeatherAlertSources").OnColumn("DepartmentId").Ascending();
+
+ Create.Index("IX_WeatherAlertSources_Active")
+ .OnTable("WeatherAlertSources").OnColumn("Active").Ascending();
+
+ // WeatherAlerts table
+ Create.Table("WeatherAlerts")
+ .WithColumn("WeatherAlertId").AsGuid().NotNullable().PrimaryKey().WithDefault(SystemMethods.NewGuid)
+ .WithColumn("DepartmentId").AsInt32().NotNullable()
+ .WithColumn("WeatherAlertSourceId").AsGuid().NotNullable()
+ .WithColumn("ExternalId").AsString(500).NotNullable()
+ .WithColumn("Sender").AsString(500).Nullable()
+ .WithColumn("Event").AsString(500).NotNullable()
+ .WithColumn("AlertCategory").AsInt32().NotNullable()
+ .WithColumn("Severity").AsInt32().NotNullable()
+ .WithColumn("Urgency").AsInt32().NotNullable()
+ .WithColumn("Certainty").AsInt32().NotNullable()
+ .WithColumn("Status").AsInt32().NotNullable().WithDefaultValue(0)
+ .WithColumn("Headline").AsString(500).Nullable()
+ .WithColumn("Description").AsString(int.MaxValue).Nullable()
+ .WithColumn("Instruction").AsString(int.MaxValue).Nullable()
+ .WithColumn("AreaDescription").AsString(500).Nullable()
+ .WithColumn("Polygon").AsString(int.MaxValue).Nullable()
+ .WithColumn("Geocodes").AsString(int.MaxValue).Nullable()
+ .WithColumn("CenterGeoLocation").AsString(100).Nullable()
+ .WithColumn("OnsetUtc").AsDateTime2().Nullable()
+ .WithColumn("ExpiresUtc").AsDateTime2().Nullable()
+ .WithColumn("EffectiveUtc").AsDateTime2().NotNullable()
+ .WithColumn("SentUtc").AsDateTime2().Nullable()
+ .WithColumn("FirstSeenUtc").AsDateTime2().NotNullable()
+ .WithColumn("LastUpdatedUtc").AsDateTime2().NotNullable()
+ .WithColumn("ReferencesExternalId").AsString(500).Nullable()
+ .WithColumn("NotificationSent").AsBoolean().NotNullable().WithDefaultValue(false)
+ .WithColumn("SystemMessageId").AsInt32().Nullable();
+
+ Create.ForeignKey("FK_WeatherAlerts_Departments")
+ .FromTable("WeatherAlerts").ForeignColumn("DepartmentId")
+ .ToTable("Departments").PrimaryColumn("DepartmentId");
+
+ Create.ForeignKey("FK_WeatherAlerts_WeatherAlertSources")
+ .FromTable("WeatherAlerts").ForeignColumn("WeatherAlertSourceId")
+ .ToTable("WeatherAlertSources").PrimaryColumn("WeatherAlertSourceId");
+
+ Create.Index("IX_WeatherAlerts_DepartmentId")
+ .OnTable("WeatherAlerts").OnColumn("DepartmentId").Ascending();
+
+ Create.Index("IX_WeatherAlerts_ExternalId_SourceId")
+ .OnTable("WeatherAlerts")
+ .OnColumn("ExternalId").Ascending()
+ .OnColumn("WeatherAlertSourceId").Ascending()
+ .WithOptions().Unique();
+
+ Create.Index("IX_WeatherAlerts_Status_ExpiresUtc")
+ .OnTable("WeatherAlerts")
+ .OnColumn("Status").Ascending()
+ .OnColumn("ExpiresUtc").Ascending();
+
+ Create.Index("IX_WeatherAlerts_NotificationSent")
+ .OnTable("WeatherAlerts").OnColumn("NotificationSent").Ascending();
+
+ // WeatherAlertZones table
+ Create.Table("WeatherAlertZones")
+ .WithColumn("WeatherAlertZoneId").AsGuid().NotNullable().PrimaryKey().WithDefault(SystemMethods.NewGuid)
+ .WithColumn("DepartmentId").AsInt32().NotNullable()
+ .WithColumn("Name").AsString(200).NotNullable()
+ .WithColumn("ZoneCode").AsString(100).Nullable()
+ .WithColumn("CenterGeoLocation").AsString(100).Nullable()
+ .WithColumn("RadiusMiles").AsDouble().NotNullable()
+ .WithColumn("IsActive").AsBoolean().NotNullable().WithDefaultValue(true)
+ .WithColumn("IsPrimary").AsBoolean().NotNullable().WithDefaultValue(false)
+ .WithColumn("CreatedOn").AsDateTime2().NotNullable();
+
+ Create.ForeignKey("FK_WeatherAlertZones_Departments")
+ .FromTable("WeatherAlertZones").ForeignColumn("DepartmentId")
+ .ToTable("Departments").PrimaryColumn("DepartmentId");
+
+ Create.Index("IX_WeatherAlertZones_DepartmentId")
+ .OnTable("WeatherAlertZones").OnColumn("DepartmentId").Ascending();
+ }
+
+ public override void Down()
+ {
+ Delete.Table("WeatherAlertZones");
+ Delete.Table("WeatherAlerts");
+ Delete.Table("WeatherAlertSources");
+ }
+ }
+}
diff --git a/Providers/Resgrid.Providers.MigrationsPg/Migrations/M0063_AddingWeatherAlertsPg.cs b/Providers/Resgrid.Providers.MigrationsPg/Migrations/M0063_AddingWeatherAlertsPg.cs
new file mode 100644
index 00000000..d9a499c7
--- /dev/null
+++ b/Providers/Resgrid.Providers.MigrationsPg/Migrations/M0063_AddingWeatherAlertsPg.cs
@@ -0,0 +1,126 @@
+using FluentMigrator;
+
+namespace Resgrid.Providers.MigrationsPg.Migrations
+{
+ [Migration(63)]
+ public class M0063_AddingWeatherAlertsPg : Migration
+ {
+ public override void Up()
+ {
+ // weatheralertsources table
+ Create.Table("weatheralertsources")
+ .WithColumn("weatheralertsourceid").AsCustom("citext").NotNullable().PrimaryKey()
+ .WithColumn("departmentid").AsInt32().NotNullable()
+ .WithColumn("name").AsCustom("citext").NotNullable()
+ .WithColumn("sourcetype").AsInt32().NotNullable()
+ .WithColumn("areafilter").AsCustom("citext").Nullable()
+ .WithColumn("apikey").AsCustom("citext").Nullable()
+ .WithColumn("customendpoint").AsCustom("citext").Nullable()
+ .WithColumn("pollintervalminutes").AsInt32().NotNullable().WithDefaultValue(5)
+ .WithColumn("active").AsBoolean().NotNullable().WithDefaultValue(true)
+ .WithColumn("lastpollutc").AsDateTime().Nullable()
+ .WithColumn("lastsuccessutc").AsDateTime().Nullable()
+ .WithColumn("isfailure").AsBoolean().NotNullable().WithDefaultValue(false)
+ .WithColumn("errormessage").AsCustom("citext").Nullable()
+ .WithColumn("lastetag").AsCustom("citext").Nullable()
+ .WithColumn("createdon").AsDateTime().NotNullable()
+ .WithColumn("createdbyuserid").AsCustom("citext").NotNullable();
+
+ Create.ForeignKey("fk_weatheralertsources_departments")
+ .FromTable("weatheralertsources").ForeignColumn("departmentid")
+ .ToTable("departments").PrimaryColumn("departmentid");
+
+ Create.Index("ix_weatheralertsources_departmentid")
+ .OnTable("weatheralertsources")
+ .OnColumn("departmentid");
+
+ Create.Index("ix_weatheralertsources_active")
+ .OnTable("weatheralertsources")
+ .OnColumn("active");
+
+ // weatheralerts table
+ Create.Table("weatheralerts")
+ .WithColumn("weatheralertid").AsCustom("citext").NotNullable().PrimaryKey()
+ .WithColumn("departmentid").AsInt32().NotNullable()
+ .WithColumn("weatheralertsourceid").AsCustom("citext").NotNullable()
+ .WithColumn("externalid").AsCustom("citext").NotNullable()
+ .WithColumn("sender").AsCustom("citext").Nullable()
+ .WithColumn("event").AsCustom("citext").NotNullable()
+ .WithColumn("alertcategory").AsInt32().NotNullable()
+ .WithColumn("severity").AsInt32().NotNullable()
+ .WithColumn("urgency").AsInt32().NotNullable()
+ .WithColumn("certainty").AsInt32().NotNullable()
+ .WithColumn("status").AsInt32().NotNullable().WithDefaultValue(0)
+ .WithColumn("headline").AsCustom("citext").Nullable()
+ .WithColumn("description").AsCustom("text").Nullable()
+ .WithColumn("instruction").AsCustom("text").Nullable()
+ .WithColumn("areadescription").AsCustom("citext").Nullable()
+ .WithColumn("polygon").AsCustom("text").Nullable()
+ .WithColumn("geocodes").AsCustom("text").Nullable()
+ .WithColumn("centergeolocation").AsCustom("citext").Nullable()
+ .WithColumn("onsetutc").AsDateTime().Nullable()
+ .WithColumn("expiresutc").AsDateTime().Nullable()
+ .WithColumn("effectiveutc").AsDateTime().NotNullable()
+ .WithColumn("sentutc").AsDateTime().Nullable()
+ .WithColumn("firstseenutc").AsDateTime().NotNullable()
+ .WithColumn("lastupdatedutc").AsDateTime().NotNullable()
+ .WithColumn("referencesexternalid").AsCustom("citext").Nullable()
+ .WithColumn("notificationsent").AsBoolean().NotNullable().WithDefaultValue(false)
+ .WithColumn("systemmessageid").AsInt32().Nullable();
+
+ Create.ForeignKey("fk_weatheralerts_departments")
+ .FromTable("weatheralerts").ForeignColumn("departmentid")
+ .ToTable("departments").PrimaryColumn("departmentid");
+
+ Create.ForeignKey("fk_weatheralerts_weatheralertsources")
+ .FromTable("weatheralerts").ForeignColumn("weatheralertsourceid")
+ .ToTable("weatheralertsources").PrimaryColumn("weatheralertsourceid");
+
+ Create.Index("ix_weatheralerts_departmentid")
+ .OnTable("weatheralerts")
+ .OnColumn("departmentid");
+
+ Create.Index("ix_weatheralerts_externalid_sourceid")
+ .OnTable("weatheralerts")
+ .OnColumn("externalid").Ascending()
+ .OnColumn("weatheralertsourceid").Ascending()
+ .WithOptions().Unique();
+
+ Create.Index("ix_weatheralerts_status_expiresutc")
+ .OnTable("weatheralerts")
+ .OnColumn("status").Ascending()
+ .OnColumn("expiresutc").Ascending();
+
+ Create.Index("ix_weatheralerts_notificationsent")
+ .OnTable("weatheralerts")
+ .OnColumn("notificationsent");
+
+ // weatheralertzones table
+ Create.Table("weatheralertzones")
+ .WithColumn("weatheralertzoneid").AsCustom("citext").NotNullable().PrimaryKey()
+ .WithColumn("departmentid").AsInt32().NotNullable()
+ .WithColumn("name").AsCustom("citext").NotNullable()
+ .WithColumn("zonecode").AsCustom("citext").Nullable()
+ .WithColumn("centergeolocation").AsCustom("citext").Nullable()
+ .WithColumn("radiusmiles").AsDouble().NotNullable()
+ .WithColumn("isactive").AsBoolean().NotNullable().WithDefaultValue(true)
+ .WithColumn("isprimary").AsBoolean().NotNullable().WithDefaultValue(false)
+ .WithColumn("createdon").AsDateTime().NotNullable();
+
+ Create.ForeignKey("fk_weatheralertzones_departments")
+ .FromTable("weatheralertzones").ForeignColumn("departmentid")
+ .ToTable("departments").PrimaryColumn("departmentid");
+
+ Create.Index("ix_weatheralertzones_departmentid")
+ .OnTable("weatheralertzones")
+ .OnColumn("departmentid");
+ }
+
+ public override void Down()
+ {
+ Delete.Table("weatheralertzones");
+ Delete.Table("weatheralerts");
+ Delete.Table("weatheralertsources");
+ }
+ }
+}
diff --git a/Providers/Resgrid.Providers.Weather/EnvironmentCanadaWeatherAlertProvider.cs b/Providers/Resgrid.Providers.Weather/EnvironmentCanadaWeatherAlertProvider.cs
new file mode 100644
index 00000000..2d415674
--- /dev/null
+++ b/Providers/Resgrid.Providers.Weather/EnvironmentCanadaWeatherAlertProvider.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Resgrid.Model;
+using Resgrid.Model.Providers;
+
+namespace Resgrid.Providers.Weather
+{
+ public class EnvironmentCanadaWeatherAlertProvider : IWeatherAlertProvider
+ {
+ public WeatherAlertSourceType SourceType => WeatherAlertSourceType.EnvironmentCanada;
+
+ public async Task> FetchAlertsAsync(WeatherAlertSource source, CancellationToken ct = default)
+ {
+ // Check shared cache first
+ if (WeatherAlertResponseCache.TryGet(SourceType, source.AreaFilter, out var cachedAlerts))
+ {
+ return cachedAlerts;
+ }
+
+ // TODO: Implement CAP XML parsing from Environment Canada
+ // Endpoint: https://dd.weather.gc.ca/alerts/cap/
+ var alerts = new List();
+
+ // Store in shared cache
+ WeatherAlertResponseCache.Set(SourceType, source.AreaFilter, alerts);
+
+ return alerts;
+ }
+ }
+}
diff --git a/Providers/Resgrid.Providers.Weather/MeteoAlarmWeatherAlertProvider.cs b/Providers/Resgrid.Providers.Weather/MeteoAlarmWeatherAlertProvider.cs
new file mode 100644
index 00000000..a836f4c8
--- /dev/null
+++ b/Providers/Resgrid.Providers.Weather/MeteoAlarmWeatherAlertProvider.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Resgrid.Model;
+using Resgrid.Model.Providers;
+
+namespace Resgrid.Providers.Weather
+{
+ public class MeteoAlarmWeatherAlertProvider : IWeatherAlertProvider
+ {
+ public WeatherAlertSourceType SourceType => WeatherAlertSourceType.MeteoAlarm;
+
+ public async Task> FetchAlertsAsync(WeatherAlertSource source, CancellationToken ct = default)
+ {
+ // Check shared cache first
+ if (WeatherAlertResponseCache.TryGet(SourceType, source.AreaFilter, out var cachedAlerts))
+ {
+ return cachedAlerts;
+ }
+
+ // TODO: Implement MeteoAlarm API integration
+ // Endpoint: https://feeds.meteoalarm.org/api/v1/
+ var alerts = new List();
+
+ // Store in shared cache
+ WeatherAlertResponseCache.Set(SourceType, source.AreaFilter, alerts);
+
+ return alerts;
+ }
+ }
+}
diff --git a/Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs b/Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs
new file mode 100644
index 00000000..aee2efe3
--- /dev/null
+++ b/Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs
@@ -0,0 +1,323 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Resgrid.Model;
+using Resgrid.Model.Providers;
+
+namespace Resgrid.Providers.Weather
+{
+ public class NwsWeatherAlertProvider : IWeatherAlertProvider
+ {
+ private static readonly HttpClient _httpClient = new HttpClient();
+ private const string DefaultBaseUrl = "https://api.weather.gov/alerts/active";
+
+ static NwsWeatherAlertProvider()
+ {
+ _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/geo+json"));
+ }
+
+ public WeatherAlertSourceType SourceType => WeatherAlertSourceType.NationalWeatherService;
+
+ public async Task> FetchAlertsAsync(WeatherAlertSource source, CancellationToken ct = default)
+ {
+ // Check shared cache first
+ if (WeatherAlertResponseCache.TryGet(SourceType, source.AreaFilter, out var cachedAlerts))
+ {
+ return CloneAlertsForSource(cachedAlerts, source);
+ }
+
+ var alerts = new List();
+ var baseUrl = !string.IsNullOrEmpty(source.CustomEndpoint) ? source.CustomEndpoint : DefaultBaseUrl;
+
+ var url = baseUrl;
+ if (!string.IsNullOrEmpty(source.AreaFilter))
+ {
+ var zones = ParseAreaFilter(source.AreaFilter);
+ if (zones.Length > 0)
+ {
+ // If codes look like state abbreviations (2 chars), use area parameter
+ if (zones[0].Length == 2)
+ url += $"?area={string.Join(",", zones)}";
+ else
+ url += $"?zone={string.Join(",", zones)}";
+ }
+ }
+
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+
+ // Set User-Agent with department admin contact email per NWS requirements
+ var contactEmail = !string.IsNullOrEmpty(source.ContactEmail) ? source.ContactEmail : "noreply@resgrid.com";
+ request.Headers.UserAgent.ParseAdd($"Resgrid/1.0 ({contactEmail})");
+
+ // Identify requests as coming from Resgrid
+ request.Headers.Add("X-Resgrid-Source", "Resgrid Weather Alert System");
+
+ // If an API key is provided, include it as a custom header
+ if (!string.IsNullOrEmpty(source.ApiKey))
+ request.Headers.Add("X-Api-Key", source.ApiKey);
+
+ // Use ETag for conditional requests
+ if (!string.IsNullOrEmpty(source.LastETag))
+ request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue(source.LastETag));
+
+ HttpResponseMessage response;
+ try
+ {
+ response = await _httpClient.SendAsync(request, ct);
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ $"NWS weather alert HTTP request failed for URL '{url}': {ex.Message}", ex);
+ }
+
+ if (response.StatusCode == System.Net.HttpStatusCode.NotModified)
+ return alerts; // No changes since last poll
+
+ response.EnsureSuccessStatusCode();
+
+ // Update ETag on source
+ if (response.Headers.ETag != null)
+ source.LastETag = response.Headers.ETag.Tag;
+
+ var json = await response.Content.ReadAsStringAsync();
+
+ // Validate response content-type is JSON before parsing
+ var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
+ if (!contentType.Contains("json", StringComparison.OrdinalIgnoreCase))
+ {
+ var snippet = json.Length > 200 ? json.Substring(0, 200) : json;
+ throw new InvalidOperationException(
+ $"NWS API returned non-JSON response (Content-Type: '{contentType}') for URL '{url}'. " +
+ $"Response body starts with: {snippet}");
+ }
+
+ JsonDocument doc;
+ try
+ {
+ doc = JsonDocument.Parse(json);
+ }
+ catch (JsonException ex)
+ {
+ var snippet = json.Length > 200 ? json.Substring(0, 200) : json;
+ throw new InvalidOperationException(
+ $"Failed to parse NWS JSON response for URL '{url}'. " +
+ $"Response body starts with: {snippet}", ex);
+ }
+
+ using (doc)
+ {
+ var root = doc.RootElement;
+
+ if (!root.TryGetProperty("features", out var features))
+ return alerts;
+
+ foreach (var feature in features.EnumerateArray())
+ {
+ try
+ {
+ var props = feature.GetProperty("properties");
+ var alert = new WeatherAlert
+ {
+ DepartmentId = source.DepartmentId,
+ WeatherAlertSourceId = source.WeatherAlertSourceId,
+ ExternalId = GetStringProp(props, "id"),
+ Sender = GetStringProp(props, "senderName"),
+ Event = GetStringProp(props, "event"),
+ AlertCategory = MapCategory(GetStringProp(props, "category")),
+ Severity = (int)MapSeverity(GetStringProp(props, "severity")),
+ Urgency = (int)MapUrgency(GetStringProp(props, "urgency")),
+ Certainty = (int)MapCertainty(GetStringProp(props, "certainty")),
+ Status = (int)WeatherAlertStatus.Active,
+ Headline = GetStringProp(props, "headline"),
+ Description = GetStringProp(props, "description"),
+ Instruction = GetStringProp(props, "instruction"),
+ AreaDescription = GetStringProp(props, "areaDesc"),
+ EffectiveUtc = GetDateProp(props, "effective") ?? DateTime.UtcNow,
+ OnsetUtc = GetDateProp(props, "onset"),
+ ExpiresUtc = GetDateProp(props, "expires"),
+ SentUtc = GetDateProp(props, "sent"),
+ FirstSeenUtc = DateTime.UtcNow,
+ LastUpdatedUtc = DateTime.UtcNow,
+ NotificationSent = false
+ };
+
+ // Extract references (for update/cancel chains)
+ var references = GetStringProp(props, "references");
+ if (!string.IsNullOrEmpty(references))
+ alert.ReferencesExternalId = references;
+
+ // Extract geocodes
+ if (props.TryGetProperty("geocode", out var geocode))
+ alert.Geocodes = geocode.GetRawText();
+
+ // Extract polygon from geometry
+ if (feature.TryGetProperty("geometry", out var geometry) && geometry.ValueKind != JsonValueKind.Null)
+ {
+ alert.Polygon = geometry.GetRawText();
+
+ // Try to extract center point from polygon
+ if (geometry.TryGetProperty("coordinates", out var coords) && coords.GetArrayLength() > 0)
+ {
+ try
+ {
+ var ring = coords[0];
+ double avgLat = 0, avgLng = 0;
+ int count = 0;
+ foreach (var point in ring.EnumerateArray())
+ {
+ avgLng += point[0].GetDouble();
+ avgLat += point[1].GetDouble();
+ count++;
+ }
+ if (count > 0)
+ alert.CenterGeoLocation = $"{avgLat / count},{avgLng / count}";
+ }
+ catch { }
+ }
+ }
+
+ alerts.Add(alert);
+ }
+ catch (Exception)
+ {
+ // Skip malformed alerts, continue with others
+ continue;
+ }
+ }
+ }
+
+ // Store in shared cache
+ WeatherAlertResponseCache.Set(SourceType, source.AreaFilter, alerts);
+
+ return alerts;
+ }
+
+ private static List CloneAlertsForSource(List cachedAlerts, WeatherAlertSource source)
+ {
+ return cachedAlerts.Select(a => new WeatherAlert
+ {
+ DepartmentId = source.DepartmentId,
+ WeatherAlertSourceId = source.WeatherAlertSourceId,
+ ExternalId = a.ExternalId,
+ Sender = a.Sender,
+ Event = a.Event,
+ AlertCategory = a.AlertCategory,
+ Severity = a.Severity,
+ Urgency = a.Urgency,
+ Certainty = a.Certainty,
+ Status = a.Status,
+ Headline = a.Headline,
+ Description = a.Description,
+ Instruction = a.Instruction,
+ AreaDescription = a.AreaDescription,
+ EffectiveUtc = a.EffectiveUtc,
+ OnsetUtc = a.OnsetUtc,
+ ExpiresUtc = a.ExpiresUtc,
+ SentUtc = a.SentUtc,
+ FirstSeenUtc = a.FirstSeenUtc,
+ LastUpdatedUtc = a.LastUpdatedUtc,
+ NotificationSent = false,
+ ReferencesExternalId = a.ReferencesExternalId,
+ Geocodes = a.Geocodes,
+ Polygon = a.Polygon,
+ CenterGeoLocation = a.CenterGeoLocation
+ }).ToList();
+ }
+
+ private static string GetStringProp(JsonElement element, string name)
+ {
+ if (element.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.String)
+ return prop.GetString();
+ return null;
+ }
+
+ private static DateTime? GetDateProp(JsonElement element, string name)
+ {
+ var value = GetStringProp(element, name);
+ if (!string.IsNullOrEmpty(value) && DateTime.TryParse(value, out var dt))
+ return dt.ToUniversalTime();
+ return null;
+ }
+
+ private static int MapCategory(string category)
+ {
+ return category?.ToLowerInvariant() switch
+ {
+ "met" => (int)WeatherAlertCategory.Met,
+ "fire" => (int)WeatherAlertCategory.Fire,
+ "health" => (int)WeatherAlertCategory.Health,
+ "env" => (int)WeatherAlertCategory.Env,
+ _ => (int)WeatherAlertCategory.Other
+ };
+ }
+
+ private static WeatherAlertSeverity MapSeverity(string severity)
+ {
+ return severity?.ToLowerInvariant() switch
+ {
+ "extreme" => WeatherAlertSeverity.Extreme,
+ "severe" => WeatherAlertSeverity.Severe,
+ "moderate" => WeatherAlertSeverity.Moderate,
+ "minor" => WeatherAlertSeverity.Minor,
+ _ => WeatherAlertSeverity.Unknown
+ };
+ }
+
+ private static WeatherAlertUrgency MapUrgency(string urgency)
+ {
+ return urgency?.ToLowerInvariant() switch
+ {
+ "immediate" => WeatherAlertUrgency.Immediate,
+ "expected" => WeatherAlertUrgency.Expected,
+ "future" => WeatherAlertUrgency.Future,
+ "past" => WeatherAlertUrgency.Past,
+ _ => WeatherAlertUrgency.Unknown
+ };
+ }
+
+ private static WeatherAlertCertainty MapCertainty(string certainty)
+ {
+ return certainty?.ToLowerInvariant() switch
+ {
+ "observed" => WeatherAlertCertainty.Observed,
+ "likely" => WeatherAlertCertainty.Likely,
+ "possible" => WeatherAlertCertainty.Possible,
+ "unlikely" => WeatherAlertCertainty.Unlikely,
+ _ => WeatherAlertCertainty.Unknown
+ };
+ }
+
+ ///
+ /// Parses AreaFilter which may be a JSON array (["NV","CA"]) or a raw
+ /// comma-separated string (NV, CA) or a single value (NV).
+ ///
+ private static string[] ParseAreaFilter(string areaFilter)
+ {
+ if (string.IsNullOrWhiteSpace(areaFilter))
+ return Array.Empty();
+
+ var trimmed = areaFilter.Trim();
+
+ // Try JSON array first
+ if (trimmed.StartsWith("["))
+ {
+ try
+ {
+ var parsed = JsonSerializer.Deserialize(trimmed);
+ if (parsed != null && parsed.Length > 0)
+ return parsed;
+ }
+ catch { }
+ }
+
+ // Fall back to comma-separated string
+ return trimmed.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ }
+ }
+}
diff --git a/Providers/Resgrid.Providers.Weather/Resgrid.Providers.Weather.csproj b/Providers/Resgrid.Providers.Weather/Resgrid.Providers.Weather.csproj
new file mode 100644
index 00000000..cb6dac61
--- /dev/null
+++ b/Providers/Resgrid.Providers.Weather/Resgrid.Providers.Weather.csproj
@@ -0,0 +1,16 @@
+
+
+ net9.0
+ Debug;Release;Docker
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Providers/Resgrid.Providers.Weather/WeatherAlertProviderFactory.cs b/Providers/Resgrid.Providers.Weather/WeatherAlertProviderFactory.cs
new file mode 100644
index 00000000..5123ead0
--- /dev/null
+++ b/Providers/Resgrid.Providers.Weather/WeatherAlertProviderFactory.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Resgrid.Model;
+using Resgrid.Model.Providers;
+
+namespace Resgrid.Providers.Weather
+{
+ public class WeatherAlertProviderFactory : IWeatherAlertProviderFactory
+ {
+ private readonly IEnumerable _providers;
+
+ public WeatherAlertProviderFactory(IEnumerable providers)
+ {
+ _providers = providers;
+ }
+
+ public IWeatherAlertProvider GetProvider(WeatherAlertSourceType sourceType)
+ {
+ var provider = _providers.FirstOrDefault(p => p.SourceType == sourceType);
+ if (provider == null)
+ throw new NotSupportedException($"No weather alert provider found for source type: {sourceType}");
+ return provider;
+ }
+ }
+}
diff --git a/Providers/Resgrid.Providers.Weather/WeatherAlertResponseCache.cs b/Providers/Resgrid.Providers.Weather/WeatherAlertResponseCache.cs
new file mode 100644
index 00000000..f963bc67
--- /dev/null
+++ b/Providers/Resgrid.Providers.Weather/WeatherAlertResponseCache.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using Resgrid.Model;
+
+namespace Resgrid.Providers.Weather
+{
+ public class WeatherAlertResponseCache
+ {
+ private static readonly ConcurrentDictionary _cache = new();
+
+ public static int DefaultCacheMinutes { get; set; } = 10;
+
+ public static bool TryGet(WeatherAlertSourceType sourceType, string areaFilter, out List alerts)
+ {
+ var key = BuildKey(sourceType, areaFilter);
+ if (_cache.TryGetValue(key, out var entry) && entry.ExpiresUtc > DateTime.UtcNow)
+ {
+ alerts = entry.Alerts;
+ return true;
+ }
+ alerts = null;
+ return false;
+ }
+
+ public static void Set(WeatherAlertSourceType sourceType, string areaFilter, List alerts, int? cacheMinutes = null)
+ {
+ var key = BuildKey(sourceType, areaFilter);
+ var ttl = cacheMinutes ?? DefaultCacheMinutes;
+ _cache[key] = new CacheEntry
+ {
+ Alerts = alerts,
+ ExpiresUtc = DateTime.UtcNow.AddMinutes(ttl)
+ };
+ }
+
+ public static void Clear()
+ {
+ _cache.Clear();
+ }
+
+ private static string BuildKey(WeatherAlertSourceType sourceType, string areaFilter)
+ {
+ return $"{sourceType}:{areaFilter ?? ""}".ToLowerInvariant();
+ }
+
+ private class CacheEntry
+ {
+ public List Alerts { get; set; }
+ public DateTime ExpiresUtc { get; set; }
+ }
+ }
+}
diff --git a/Providers/Resgrid.Providers.Weather/WeatherProviderModule.cs b/Providers/Resgrid.Providers.Weather/WeatherProviderModule.cs
new file mode 100644
index 00000000..a82b4e21
--- /dev/null
+++ b/Providers/Resgrid.Providers.Weather/WeatherProviderModule.cs
@@ -0,0 +1,16 @@
+using Autofac;
+using Resgrid.Model.Providers;
+
+namespace Resgrid.Providers.Weather
+{
+ public class WeatherProviderModule : Module
+ {
+ protected override void Load(ContainerBuilder builder)
+ {
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs b/Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs
index 35a301fc..c467e885 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs
@@ -546,6 +546,23 @@ protected SqlConfiguration() { }
public string SelectCommTestResultByResponseTokenQuery { get; set; }
#endregion CommunicationTests
+ #region WeatherAlerts
+ public string WeatherAlertSourcesTable { get; set; }
+ public string WeatherAlertsTable { get; set; }
+ public string WeatherAlertZonesTable { get; set; }
+ public string SelectActiveWeatherAlertSourcesForPollingQuery { get; set; }
+ public string SelectWeatherAlertSourcesByDepartmentIdQuery { get; set; }
+ public string SelectActiveWeatherAlertsByDepartmentIdQuery { get; set; }
+ public string SelectWeatherAlertByExternalIdAndSourceIdQuery { get; set; }
+ public string SelectWeatherAlertsByDepartmentAndSeverityQuery { get; set; }
+ public string SelectWeatherAlertsByDepartmentAndCategoryQuery { get; set; }
+ public string SelectExpiredUnprocessedWeatherAlertsQuery { get; set; }
+ public string SelectUnnotifiedWeatherAlertsQuery { get; set; }
+ public string SelectWeatherAlertHistoryByDepartmentQuery { get; set; }
+ public string SelectWeatherAlertZonesByDepartmentIdQuery { get; set; }
+ public string SelectActiveWeatherAlertZonesByDepartmentIdQuery { get; set; }
+ #endregion WeatherAlerts
+
// Identity
#region Table Names
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Modules/ApiDataModule.cs b/Repositories/Resgrid.Repositories.DataRepository/Modules/ApiDataModule.cs
index c408e5bf..d805672b 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/Modules/ApiDataModule.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/Modules/ApiDataModule.cs
@@ -137,6 +137,9 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Modules/DataModule.cs b/Repositories/Resgrid.Repositories.DataRepository/Modules/DataModule.cs
index 6aa3addd..45b2b5c5 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/Modules/DataModule.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/Modules/DataModule.cs
@@ -136,6 +136,9 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Modules/NonWebDataModule.cs b/Repositories/Resgrid.Repositories.DataRepository/Modules/NonWebDataModule.cs
index 3937d42c..be27bb3f 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/Modules/NonWebDataModule.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/Modules/NonWebDataModule.cs
@@ -136,6 +136,9 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Modules/TestingDataModule.cs b/Repositories/Resgrid.Repositories.DataRepository/Modules/TestingDataModule.cs
index e79a417e..7bc837f7 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/Modules/TestingDataModule.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/Modules/TestingDataModule.cs
@@ -136,6 +136,9 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertSourcesForPollingQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertSourcesForPollingQuery.cs
new file mode 100644
index 00000000..20173617
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertSourcesForPollingQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectActiveWeatherAlertSourcesForPollingQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectActiveWeatherAlertSourcesForPollingQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectActiveWeatherAlertSourcesForPollingQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertSourcesTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { },
+ new string[] { });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertZonesByDepartmentIdQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertZonesByDepartmentIdQuery.cs
new file mode 100644
index 00000000..141ec837
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertZonesByDepartmentIdQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectActiveWeatherAlertZonesByDepartmentIdQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectActiveWeatherAlertZonesByDepartmentIdQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectActiveWeatherAlertZonesByDepartmentIdQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertZonesTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { "%DEPARTMENTID%" },
+ new string[] { "DepartmentId" });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertsByDepartmentIdQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertsByDepartmentIdQuery.cs
new file mode 100644
index 00000000..b264ea27
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectActiveWeatherAlertsByDepartmentIdQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectActiveWeatherAlertsByDepartmentIdQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectActiveWeatherAlertsByDepartmentIdQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectActiveWeatherAlertsByDepartmentIdQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertsTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { "%DEPARTMENTID%" },
+ new string[] { "DepartmentId" });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectExpiredUnprocessedWeatherAlertsQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectExpiredUnprocessedWeatherAlertsQuery.cs
new file mode 100644
index 00000000..9f58d446
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectExpiredUnprocessedWeatherAlertsQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectExpiredUnprocessedWeatherAlertsQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectExpiredUnprocessedWeatherAlertsQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectExpiredUnprocessedWeatherAlertsQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertsTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { },
+ new string[] { });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectUnnotifiedWeatherAlertsQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectUnnotifiedWeatherAlertsQuery.cs
new file mode 100644
index 00000000..a8a4e215
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectUnnotifiedWeatherAlertsQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectUnnotifiedWeatherAlertsQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectUnnotifiedWeatherAlertsQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectUnnotifiedWeatherAlertsQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertsTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { },
+ new string[] { });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertByExternalIdAndSourceIdQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertByExternalIdAndSourceIdQuery.cs
new file mode 100644
index 00000000..03249414
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertByExternalIdAndSourceIdQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectWeatherAlertByExternalIdAndSourceIdQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectWeatherAlertByExternalIdAndSourceIdQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectWeatherAlertByExternalIdAndSourceIdQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertsTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { "%EXTERNALID%", "%SOURCEID%" },
+ new string[] { "ExternalId", "SourceId" });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertHistoryByDepartmentQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertHistoryByDepartmentQuery.cs
new file mode 100644
index 00000000..a822d721
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertHistoryByDepartmentQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectWeatherAlertHistoryByDepartmentQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectWeatherAlertHistoryByDepartmentQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectWeatherAlertHistoryByDepartmentQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertsTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { "%DEPARTMENTID%", "%STARTDATE%", "%ENDDATE%" },
+ new string[] { "DepartmentId", "StartDate", "EndDate" });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertSourcesByDepartmentIdQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertSourcesByDepartmentIdQuery.cs
new file mode 100644
index 00000000..a945516c
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertSourcesByDepartmentIdQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectWeatherAlertSourcesByDepartmentIdQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectWeatherAlertSourcesByDepartmentIdQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectWeatherAlertSourcesByDepartmentIdQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertSourcesTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { "%DEPARTMENTID%" },
+ new string[] { "DepartmentId" });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertZonesByDepartmentIdQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertZonesByDepartmentIdQuery.cs
new file mode 100644
index 00000000..fdafedae
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertZonesByDepartmentIdQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectWeatherAlertZonesByDepartmentIdQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectWeatherAlertZonesByDepartmentIdQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectWeatherAlertZonesByDepartmentIdQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertZonesTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { "%DEPARTMENTID%" },
+ new string[] { "DepartmentId" });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertsByDepartmentAndCategoryQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertsByDepartmentAndCategoryQuery.cs
new file mode 100644
index 00000000..20ee0784
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertsByDepartmentAndCategoryQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectWeatherAlertsByDepartmentAndCategoryQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectWeatherAlertsByDepartmentAndCategoryQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectWeatherAlertsByDepartmentAndCategoryQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertsTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { "%DEPARTMENTID%", "%CATEGORY%" },
+ new string[] { "DepartmentId", "Category" });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertsByDepartmentAndSeverityQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertsByDepartmentAndSeverityQuery.cs
new file mode 100644
index 00000000..c627c2b0
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/WeatherAlerts/SelectWeatherAlertsByDepartmentAndSeverityQuery.cs
@@ -0,0 +1,33 @@
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Extensions;
+
+namespace Resgrid.Repositories.DataRepository.Queries.WeatherAlerts
+{
+ public class SelectWeatherAlertsByDepartmentAndSeverityQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectWeatherAlertsByDepartmentAndSeverityQuery(SqlConfiguration sqlConfiguration)
+ {
+ _sqlConfiguration = sqlConfiguration;
+ }
+
+ public string GetQuery()
+ {
+ var query = _sqlConfiguration.SelectWeatherAlertsByDepartmentAndSeverityQuery
+ .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName,
+ _sqlConfiguration.WeatherAlertsTable,
+ _sqlConfiguration.ParameterNotation,
+ new string[] { "%DEPARTMENTID%", "%MAXSEVERITY%" },
+ new string[] { "DepartmentId", "MaxSeverity" });
+
+ return query;
+ }
+
+ public string GetQuery() where TEntity : class, IEntity
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs b/Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs
index 9982d199..e9830fe5 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs
@@ -1656,6 +1656,23 @@ ORDER BY Timestamp DESC
SelectCommTestResultByResponseTokenQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE ResponseToken = %TOKEN% LIMIT 1";
#endregion CommunicationTests
+ #region WeatherAlerts
+ WeatherAlertSourcesTable = "WeatherAlertSources";
+ WeatherAlertsTable = "WeatherAlerts";
+ WeatherAlertZonesTable = "WeatherAlertZones";
+ SelectActiveWeatherAlertSourcesForPollingQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE Active = true";
+ SelectWeatherAlertSourcesByDepartmentIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE DepartmentId = %DEPARTMENTID%";
+ SelectActiveWeatherAlertsByDepartmentIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE DepartmentId = %DEPARTMENTID% AND Status = 0";
+ SelectWeatherAlertByExternalIdAndSourceIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE ExternalId = %EXTERNALID% AND WeatherAlertSourceId = %SOURCEID% LIMIT 1";
+ SelectWeatherAlertsByDepartmentAndSeverityQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE DepartmentId = %DEPARTMENTID% AND Severity <= %MAXSEVERITY% AND Status = 0";
+ SelectWeatherAlertsByDepartmentAndCategoryQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE DepartmentId = %DEPARTMENTID% AND AlertCategory = %CATEGORY% AND Status = 0";
+ SelectExpiredUnprocessedWeatherAlertsQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE Status = 0 AND ExpiresUtc IS NOT NULL AND ExpiresUtc < NOW() AT TIME ZONE 'UTC'";
+ SelectUnnotifiedWeatherAlertsQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE NotificationSent = false AND Status = 0";
+ SelectWeatherAlertHistoryByDepartmentQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE DepartmentId = %DEPARTMENTID% AND FirstSeenUtc >= %STARTDATE% AND FirstSeenUtc <= %ENDDATE% ORDER BY FirstSeenUtc DESC";
+ SelectWeatherAlertZonesByDepartmentIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE DepartmentId = %DEPARTMENTID%";
+ SelectActiveWeatherAlertZonesByDepartmentIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE DepartmentId = %DEPARTMENTID% AND IsActive = true";
+ #endregion WeatherAlerts
+
#region User Defined Fields
UdfDefinitionsTableName = "UdfDefinitions";
UdfFieldsTableName = "UdfFields";
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs b/Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs
index 10cbc2ee..d5697c3a 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs
@@ -1617,6 +1617,23 @@ SELECT TOP 1 *
SelectCommTestResultByResponseTokenQuery = "SELECT TOP 1 * FROM %SCHEMA%.%TABLENAME% WHERE [ResponseToken] = %TOKEN%";
#endregion CommunicationTests
+ #region WeatherAlerts
+ WeatherAlertSourcesTable = "WeatherAlertSources";
+ WeatherAlertsTable = "WeatherAlerts";
+ WeatherAlertZonesTable = "WeatherAlertZones";
+ SelectActiveWeatherAlertSourcesForPollingQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [Active] = 1";
+ SelectWeatherAlertSourcesByDepartmentIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [DepartmentId] = %DEPARTMENTID%";
+ SelectActiveWeatherAlertsByDepartmentIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [DepartmentId] = %DEPARTMENTID% AND [Status] = 0";
+ SelectWeatherAlertByExternalIdAndSourceIdQuery = "SELECT TOP 1 * FROM %SCHEMA%.%TABLENAME% WHERE [ExternalId] = %EXTERNALID% AND [WeatherAlertSourceId] = %SOURCEID%";
+ SelectWeatherAlertsByDepartmentAndSeverityQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [DepartmentId] = %DEPARTMENTID% AND [Severity] <= %MAXSEVERITY% AND [Status] = 0";
+ SelectWeatherAlertsByDepartmentAndCategoryQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [DepartmentId] = %DEPARTMENTID% AND [AlertCategory] = %CATEGORY% AND [Status] = 0";
+ SelectExpiredUnprocessedWeatherAlertsQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [Status] = 0 AND [ExpiresUtc] IS NOT NULL AND [ExpiresUtc] < GETUTCDATE()";
+ SelectUnnotifiedWeatherAlertsQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [NotificationSent] = 0 AND [Status] = 0";
+ SelectWeatherAlertHistoryByDepartmentQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [DepartmentId] = %DEPARTMENTID% AND [FirstSeenUtc] >= %STARTDATE% AND [FirstSeenUtc] <= %ENDDATE% ORDER BY [FirstSeenUtc] DESC";
+ SelectWeatherAlertZonesByDepartmentIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [DepartmentId] = %DEPARTMENTID%";
+ SelectActiveWeatherAlertZonesByDepartmentIdQuery = "SELECT * FROM %SCHEMA%.%TABLENAME% WHERE [DepartmentId] = %DEPARTMENTID% AND [IsActive] = 1";
+ #endregion WeatherAlerts
+
#region User Defined Fields
UdfDefinitionsTableName = "UdfDefinitions";
UdfFieldsTableName = "UdfFields";
diff --git a/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertRepository.cs b/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertRepository.cs
new file mode 100644
index 00000000..ff6cb19d
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertRepository.cs
@@ -0,0 +1,301 @@
+using Dapper;
+using Resgrid.Framework;
+using Resgrid.Model;
+using Resgrid.Model.Repositories;
+using Resgrid.Model.Repositories.Connection;
+using Resgrid.Model.Repositories.Queries;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Queries.WeatherAlerts;
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Threading.Tasks;
+
+namespace Resgrid.Repositories.DataRepository
+{
+ public class WeatherAlertRepository : RepositoryBase, IWeatherAlertRepository
+ {
+ private readonly IConnectionProvider _connectionProvider;
+ private readonly SqlConfiguration _sqlConfiguration;
+ private readonly IQueryFactory _queryFactory;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public WeatherAlertRepository(IConnectionProvider connectionProvider, SqlConfiguration sqlConfiguration, IUnitOfWork unitOfWork, IQueryFactory queryFactory)
+ : base(connectionProvider, sqlConfiguration, unitOfWork, queryFactory)
+ {
+ _connectionProvider = connectionProvider;
+ _sqlConfiguration = sqlConfiguration;
+ _queryFactory = queryFactory;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task> GetActiveAlertsByDepartmentIdAsync(int departmentId)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+
+ public async Task GetByExternalIdAndSourceIdAsync(string externalId, Guid sourceId)
+ {
+ try
+ {
+ var selectFunction = new Func>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("ExternalId", externalId);
+ dynamicParameters.Add("SourceId", sourceId);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryFirstOrDefaultAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+
+ public async Task> GetAlertsByDepartmentAndSeverityAsync(int departmentId, int maxSeverity)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+ dynamicParameters.Add("MaxSeverity", maxSeverity);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+
+ public async Task> GetAlertsByDepartmentAndCategoryAsync(int departmentId, int category)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+ dynamicParameters.Add("Category", category);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+
+ public async Task> GetExpiredUnprocessedAlertsAsync()
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+
+ public async Task> GetUnnotifiedAlertsAsync()
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+
+ public async Task> GetAlertHistoryByDepartmentAsync(int departmentId, DateTime startDate, DateTime endDate)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+ dynamicParameters.Add("StartDate", startDate);
+ dynamicParameters.Add("EndDate", endDate);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertSourceRepository.cs b/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertSourceRepository.cs
new file mode 100644
index 00000000..bfd5b4dd
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertSourceRepository.cs
@@ -0,0 +1,107 @@
+using Dapper;
+using Resgrid.Framework;
+using Resgrid.Model;
+using Resgrid.Model.Repositories;
+using Resgrid.Model.Repositories.Connection;
+using Resgrid.Model.Repositories.Queries;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Queries.WeatherAlerts;
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Threading.Tasks;
+
+namespace Resgrid.Repositories.DataRepository
+{
+ public class WeatherAlertSourceRepository : RepositoryBase, IWeatherAlertSourceRepository
+ {
+ private readonly IConnectionProvider _connectionProvider;
+ private readonly SqlConfiguration _sqlConfiguration;
+ private readonly IQueryFactory _queryFactory;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public WeatherAlertSourceRepository(IConnectionProvider connectionProvider, SqlConfiguration sqlConfiguration, IUnitOfWork unitOfWork, IQueryFactory queryFactory)
+ : base(connectionProvider, sqlConfiguration, unitOfWork, queryFactory)
+ {
+ _connectionProvider = connectionProvider;
+ _sqlConfiguration = sqlConfiguration;
+ _queryFactory = queryFactory;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task> GetActiveSourcesForPollingAsync()
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+
+ public async Task> GetSourcesByDepartmentIdAsync(int departmentId)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertZoneRepository.cs b/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertZoneRepository.cs
new file mode 100644
index 00000000..75b1514c
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/WeatherAlertZoneRepository.cs
@@ -0,0 +1,108 @@
+using Dapper;
+using Resgrid.Framework;
+using Resgrid.Model;
+using Resgrid.Model.Repositories;
+using Resgrid.Model.Repositories.Connection;
+using Resgrid.Model.Repositories.Queries;
+using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Queries.WeatherAlerts;
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Threading.Tasks;
+
+namespace Resgrid.Repositories.DataRepository
+{
+ public class WeatherAlertZoneRepository : RepositoryBase, IWeatherAlertZoneRepository
+ {
+ private readonly IConnectionProvider _connectionProvider;
+ private readonly SqlConfiguration _sqlConfiguration;
+ private readonly IQueryFactory _queryFactory;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public WeatherAlertZoneRepository(IConnectionProvider connectionProvider, SqlConfiguration sqlConfiguration, IUnitOfWork unitOfWork, IQueryFactory queryFactory)
+ : base(connectionProvider, sqlConfiguration, unitOfWork, queryFactory)
+ {
+ _connectionProvider = connectionProvider;
+ _sqlConfiguration = sqlConfiguration;
+ _queryFactory = queryFactory;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task> GetZonesByDepartmentIdAsync(int departmentId)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+
+ public async Task> GetActiveZonesByDepartmentIdAsync(int departmentId)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+ throw;
+ }
+ }
+ }
+}
diff --git a/Resgrid.sln b/Resgrid.sln
index 54549e05..de74e3a9 100644
--- a/Resgrid.sln
+++ b/Resgrid.sln
@@ -96,814 +96,1254 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Support", "Support", "{8933
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Quidjibo.Postgres", "Workers\Support\Quidjibo.Postgres\Quidjibo.Postgres.csproj", "{744B3BB7-B5F6-4002-93E2-FC0821D41963}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resgrid.Providers.Weather", "Providers\Resgrid.Providers.Weather\Resgrid.Providers.Weather.csproj", "{FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Azure|Any CPU = Azure|Any CPU
Azure|x86 = Azure|x86
+ Azure|x64 = Azure|x64
Cloud|Any CPU = Cloud|Any CPU
Cloud|x86 = Cloud|x86
+ Cloud|x64 = Cloud|x64
Debug|Any CPU = Debug|Any CPU
Debug|x86 = Debug|x86
+ Debug|x64 = Debug|x64
Docker|Any CPU = Docker|Any CPU
Docker|x86 = Docker|x86
+ Docker|x64 = Docker|x64
Release|Any CPU = Release|Any CPU
Release|x86 = Release|x86
+ Release|x64 = Release|x64
Staging|Any CPU = Staging|Any CPU
Staging|x86 = Staging|x86
+ Staging|x64 = Staging|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Azure|Any CPU.ActiveCfg = Azure|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Azure|Any CPU.Build.0 = Azure|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Azure|x86.ActiveCfg = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Azure|x86.Build.0 = Debug|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Azure|x64.Build.0 = Azure|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Cloud|x86.ActiveCfg = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Cloud|x86.Build.0 = Debug|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Cloud|x64.Build.0 = Cloud|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Debug|x86.ActiveCfg = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Debug|x86.Build.0 = Debug|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Debug|x64.Build.0 = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Docker|Any CPU.Build.0 = Docker|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Docker|x86.ActiveCfg = Docker|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Docker|x86.Build.0 = Docker|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Docker|x64.Build.0 = Docker|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Release|Any CPU.Build.0 = Release|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Release|x86.ActiveCfg = Release|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Release|x86.Build.0 = Release|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Release|x64.ActiveCfg = Release|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Release|x64.Build.0 = Release|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Staging|Any CPU.Build.0 = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Staging|x86.ActiveCfg = Debug|Any CPU
{D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Staging|x86.Build.0 = Debug|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {D867D4CA-D348-4485-A7B0-5293DF8F93D5}.Staging|x64.Build.0 = Staging|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Azure|Any CPU.Build.0 = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Azure|x86.ActiveCfg = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Azure|x86.Build.0 = Debug|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Azure|x64.Build.0 = Azure|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Cloud|x86.ActiveCfg = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Cloud|x86.Build.0 = Debug|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Cloud|x64.Build.0 = Cloud|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Debug|x86.ActiveCfg = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Debug|x86.Build.0 = Debug|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Debug|x64.Build.0 = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Docker|Any CPU.Build.0 = Docker|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Docker|x86.ActiveCfg = Docker|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Docker|x86.Build.0 = Docker|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Docker|x64.Build.0 = Docker|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Release|Any CPU.Build.0 = Release|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Release|x86.ActiveCfg = Release|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Release|x86.Build.0 = Release|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Release|x64.ActiveCfg = Release|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Release|x64.Build.0 = Release|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Staging|Any CPU.Build.0 = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Staging|x86.ActiveCfg = Debug|Any CPU
{24E2241D-D82C-443D-9613-F900E44C003E}.Staging|x86.Build.0 = Debug|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {24E2241D-D82C-443D-9613-F900E44C003E}.Staging|x64.Build.0 = Staging|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Azure|Any CPU.Build.0 = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Azure|x86.ActiveCfg = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Azure|x86.Build.0 = Debug|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Azure|x64.Build.0 = Azure|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Cloud|x86.ActiveCfg = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Cloud|x86.Build.0 = Debug|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Cloud|x64.Build.0 = Cloud|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Debug|x86.ActiveCfg = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Debug|x86.Build.0 = Debug|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Debug|x64.Build.0 = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Docker|Any CPU.Build.0 = Docker|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Docker|x86.ActiveCfg = Docker|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Docker|x86.Build.0 = Docker|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Docker|x64.Build.0 = Docker|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Release|Any CPU.Build.0 = Release|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Release|x86.ActiveCfg = Release|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Release|x86.Build.0 = Release|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Release|x64.ActiveCfg = Release|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Release|x64.Build.0 = Release|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Staging|Any CPU.Build.0 = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Staging|x86.ActiveCfg = Debug|Any CPU
{C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Staging|x86.Build.0 = Debug|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {C8A18F9F-93BC-44F6-AFFD-6187EA0BFF78}.Staging|x64.Build.0 = Staging|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Azure|Any CPU.Build.0 = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Azure|x86.ActiveCfg = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Azure|x86.Build.0 = Debug|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Azure|x64.Build.0 = Azure|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Cloud|x86.ActiveCfg = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Cloud|x86.Build.0 = Debug|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Cloud|x64.Build.0 = Cloud|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Debug|x86.ActiveCfg = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Debug|x86.Build.0 = Debug|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Debug|x64.Build.0 = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Docker|Any CPU.Build.0 = Docker|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Docker|x86.ActiveCfg = Docker|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Docker|x86.Build.0 = Docker|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Docker|x64.Build.0 = Docker|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Release|Any CPU.Build.0 = Release|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Release|x86.ActiveCfg = Release|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Release|x86.Build.0 = Release|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Release|x64.ActiveCfg = Release|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Release|x64.Build.0 = Release|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Staging|Any CPU.Build.0 = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Staging|x86.ActiveCfg = Debug|Any CPU
{58CE37F7-706E-49E8-B814-926061E248C3}.Staging|x86.Build.0 = Debug|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {58CE37F7-706E-49E8-B814-926061E248C3}.Staging|x64.Build.0 = Staging|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Azure|Any CPU.Build.0 = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Azure|x86.ActiveCfg = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Azure|x86.Build.0 = Debug|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Azure|x64.Build.0 = Azure|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Cloud|x86.ActiveCfg = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Cloud|x86.Build.0 = Debug|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Cloud|x64.Build.0 = Cloud|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Debug|x86.ActiveCfg = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Debug|x86.Build.0 = Debug|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Debug|x64.Build.0 = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Docker|Any CPU.Build.0 = Docker|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Docker|x86.ActiveCfg = Docker|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Docker|x86.Build.0 = Docker|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Docker|x64.Build.0 = Docker|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Release|Any CPU.Build.0 = Release|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Release|x86.ActiveCfg = Release|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Release|x86.Build.0 = Release|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Release|x64.ActiveCfg = Release|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Release|x64.Build.0 = Release|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Staging|Any CPU.Build.0 = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Staging|x86.ActiveCfg = Debug|Any CPU
{7EAFA222-4F03-4A64-BA90-73BE80092620}.Staging|x86.Build.0 = Debug|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {7EAFA222-4F03-4A64-BA90-73BE80092620}.Staging|x64.Build.0 = Staging|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Azure|Any CPU.Build.0 = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Azure|x86.ActiveCfg = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Azure|x86.Build.0 = Debug|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Azure|x64.Build.0 = Azure|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Cloud|x86.ActiveCfg = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Cloud|x86.Build.0 = Debug|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Cloud|x64.Build.0 = Cloud|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Debug|x86.ActiveCfg = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Debug|x86.Build.0 = Debug|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Debug|x64.Build.0 = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Docker|Any CPU.Build.0 = Docker|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Docker|x86.ActiveCfg = Docker|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Docker|x86.Build.0 = Docker|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Docker|x64.Build.0 = Docker|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Release|Any CPU.Build.0 = Release|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Release|x86.ActiveCfg = Release|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Release|x86.Build.0 = Release|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Release|x64.ActiveCfg = Release|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Release|x64.Build.0 = Release|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Staging|Any CPU.Build.0 = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Staging|x86.ActiveCfg = Debug|Any CPU
{970C7F13-2384-4ED8-8C33-0414A49970E5}.Staging|x86.Build.0 = Debug|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {970C7F13-2384-4ED8-8C33-0414A49970E5}.Staging|x64.Build.0 = Staging|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Azure|Any CPU.Build.0 = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Azure|x86.ActiveCfg = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Azure|x86.Build.0 = Debug|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Azure|x64.Build.0 = Azure|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Cloud|x86.ActiveCfg = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Cloud|x86.Build.0 = Debug|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Cloud|x64.Build.0 = Cloud|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Debug|x86.ActiveCfg = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Debug|x86.Build.0 = Debug|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Debug|x64.Build.0 = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Docker|Any CPU.Build.0 = Docker|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Docker|x86.ActiveCfg = Docker|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Docker|x86.Build.0 = Docker|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Docker|x64.Build.0 = Docker|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Release|Any CPU.Build.0 = Release|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Release|x86.ActiveCfg = Release|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Release|x86.Build.0 = Release|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Release|x64.ActiveCfg = Release|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Release|x64.Build.0 = Release|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Staging|Any CPU.Build.0 = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Staging|x86.ActiveCfg = Debug|Any CPU
{94165DE6-7224-4EF0-ADB0-889A5EF77519}.Staging|x86.Build.0 = Debug|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {94165DE6-7224-4EF0-ADB0-889A5EF77519}.Staging|x64.Build.0 = Staging|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Azure|Any CPU.Build.0 = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Azure|x86.ActiveCfg = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Azure|x86.Build.0 = Debug|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Azure|x64.Build.0 = Azure|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Cloud|x86.ActiveCfg = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Cloud|x86.Build.0 = Debug|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Cloud|x64.Build.0 = Cloud|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Debug|x86.ActiveCfg = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Debug|x86.Build.0 = Debug|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Debug|x64.Build.0 = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Docker|Any CPU.Build.0 = Docker|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Docker|x86.ActiveCfg = Docker|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Docker|x86.Build.0 = Docker|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Docker|x64.Build.0 = Docker|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Release|Any CPU.Build.0 = Release|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Release|x86.ActiveCfg = Release|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Release|x86.Build.0 = Release|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Release|x64.ActiveCfg = Release|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Release|x64.Build.0 = Release|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Staging|Any CPU.Build.0 = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Staging|x86.ActiveCfg = Debug|Any CPU
{B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Staging|x86.Build.0 = Debug|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {B3F5D0C1-3697-4691-9999-F4321CD0B6F9}.Staging|x64.Build.0 = Staging|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Azure|Any CPU.Build.0 = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Azure|x86.ActiveCfg = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Azure|x86.Build.0 = Debug|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Azure|x64.Build.0 = Azure|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Cloud|x86.ActiveCfg = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Cloud|x86.Build.0 = Debug|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Cloud|x64.Build.0 = Cloud|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Debug|x86.ActiveCfg = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Debug|x86.Build.0 = Debug|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Debug|x64.Build.0 = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Docker|Any CPU.Build.0 = Docker|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Docker|x86.ActiveCfg = Docker|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Docker|x86.Build.0 = Docker|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Docker|x64.Build.0 = Docker|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Release|Any CPU.Build.0 = Release|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Release|x86.ActiveCfg = Release|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Release|x86.Build.0 = Release|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Release|x64.ActiveCfg = Release|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Release|x64.Build.0 = Release|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Staging|Any CPU.Build.0 = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Staging|x86.ActiveCfg = Debug|Any CPU
{A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Staging|x86.Build.0 = Debug|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {A7C2E4F1-8B3D-4A56-9E7F-B8C1D2E3F4A5}.Staging|x64.Build.0 = Staging|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Azure|Any CPU.Build.0 = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Azure|x86.ActiveCfg = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Azure|x86.Build.0 = Debug|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Azure|x64.Build.0 = Azure|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Cloud|x86.ActiveCfg = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Cloud|x86.Build.0 = Debug|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Cloud|x64.Build.0 = Cloud|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Debug|Any CPU.Build.0 = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Debug|x86.ActiveCfg = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Debug|x86.Build.0 = Debug|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Debug|x64.Build.0 = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Docker|Any CPU.Build.0 = Docker|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Docker|x86.ActiveCfg = Docker|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Docker|x86.Build.0 = Docker|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Docker|x64.Build.0 = Docker|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Release|Any CPU.ActiveCfg = Release|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Release|Any CPU.Build.0 = Release|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Release|x86.ActiveCfg = Release|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Release|x86.Build.0 = Release|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Release|x64.ActiveCfg = Release|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Release|x64.Build.0 = Release|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Staging|Any CPU.Build.0 = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Staging|x86.ActiveCfg = Debug|Any CPU
{506F9CFB-7C67-4036-920D-A85639B02544}.Staging|x86.Build.0 = Debug|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {506F9CFB-7C67-4036-920D-A85639B02544}.Staging|x64.Build.0 = Staging|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Azure|Any CPU.Build.0 = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Azure|x86.ActiveCfg = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Azure|x86.Build.0 = Debug|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Azure|x64.Build.0 = Azure|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Cloud|x86.ActiveCfg = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Cloud|x86.Build.0 = Debug|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Cloud|x64.Build.0 = Cloud|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Debug|x86.ActiveCfg = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Debug|x86.Build.0 = Debug|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Debug|x64.Build.0 = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Docker|Any CPU.Build.0 = Docker|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Docker|x86.ActiveCfg = Docker|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Docker|x86.Build.0 = Docker|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Docker|x64.Build.0 = Docker|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Release|Any CPU.Build.0 = Release|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Release|x86.ActiveCfg = Release|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Release|x86.Build.0 = Release|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Release|x64.ActiveCfg = Release|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Release|x64.Build.0 = Release|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Staging|Any CPU.Build.0 = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Staging|x86.ActiveCfg = Debug|Any CPU
{5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Staging|x86.Build.0 = Debug|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {5565BF04-1056-4B2B-AB1A-E3AD9C433283}.Staging|x64.Build.0 = Staging|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Azure|Any CPU.Build.0 = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Azure|x86.ActiveCfg = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Azure|x86.Build.0 = Debug|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Azure|x64.Build.0 = Azure|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Cloud|x86.ActiveCfg = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Cloud|x86.Build.0 = Debug|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Cloud|x64.Build.0 = Cloud|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Debug|x86.ActiveCfg = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Debug|x86.Build.0 = Debug|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Debug|x64.Build.0 = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Docker|Any CPU.Build.0 = Docker|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Docker|x86.ActiveCfg = Docker|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Docker|x86.Build.0 = Docker|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Docker|x64.Build.0 = Docker|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Release|Any CPU.Build.0 = Release|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Release|x86.ActiveCfg = Release|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Release|x86.Build.0 = Release|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Release|x64.ActiveCfg = Release|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Release|x64.Build.0 = Release|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Staging|Any CPU.Build.0 = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Staging|x86.ActiveCfg = Debug|Any CPU
{69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Staging|x86.Build.0 = Debug|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {69FC3B6C-801F-4D98-BE3E-9BC47C42F874}.Staging|x64.Build.0 = Staging|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Azure|Any CPU.Build.0 = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Azure|x86.ActiveCfg = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Azure|x86.Build.0 = Debug|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Azure|x64.Build.0 = Azure|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Cloud|x86.ActiveCfg = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Cloud|x86.Build.0 = Debug|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Cloud|x64.Build.0 = Cloud|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Debug|x86.ActiveCfg = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Debug|x86.Build.0 = Debug|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Debug|x64.Build.0 = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Docker|Any CPU.Build.0 = Docker|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Docker|x86.ActiveCfg = Docker|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Docker|x86.Build.0 = Docker|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Docker|x64.Build.0 = Docker|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Release|Any CPU.Build.0 = Release|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Release|x86.ActiveCfg = Release|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Release|x86.Build.0 = Release|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Release|x64.ActiveCfg = Release|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Release|x64.Build.0 = Release|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Staging|Any CPU.Build.0 = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Staging|x86.ActiveCfg = Debug|Any CPU
{C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Staging|x86.Build.0 = Debug|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {C81FD7A4-B8CD-4D6A-88D7-DABC4D092043}.Staging|x64.Build.0 = Staging|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Azure|Any CPU.Build.0 = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Azure|x86.ActiveCfg = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Azure|x86.Build.0 = Debug|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Azure|x64.Build.0 = Azure|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Cloud|x86.ActiveCfg = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Cloud|x86.Build.0 = Debug|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Cloud|x64.Build.0 = Cloud|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Debug|x86.ActiveCfg = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Debug|x86.Build.0 = Debug|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Debug|x64.Build.0 = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Docker|Any CPU.Build.0 = Docker|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Docker|x86.ActiveCfg = Docker|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Docker|x86.Build.0 = Docker|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Docker|x64.Build.0 = Docker|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Release|Any CPU.Build.0 = Release|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Release|x86.ActiveCfg = Release|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Release|x86.Build.0 = Release|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Release|x64.ActiveCfg = Release|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Release|x64.Build.0 = Release|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Staging|Any CPU.Build.0 = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Staging|x86.ActiveCfg = Debug|Any CPU
{6016D92A-C9E2-4501-A526-611D47790B6D}.Staging|x86.Build.0 = Debug|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {6016D92A-C9E2-4501-A526-611D47790B6D}.Staging|x64.Build.0 = Staging|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Azure|Any CPU.Build.0 = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Azure|x86.ActiveCfg = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Azure|x86.Build.0 = Debug|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Azure|x64.Build.0 = Azure|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Cloud|x86.ActiveCfg = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Cloud|x86.Build.0 = Debug|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Cloud|x64.Build.0 = Cloud|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Debug|x86.ActiveCfg = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Debug|x86.Build.0 = Debug|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Debug|x64.Build.0 = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Docker|Any CPU.Build.0 = Docker|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Docker|x86.ActiveCfg = Docker|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Docker|x86.Build.0 = Docker|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Docker|x64.Build.0 = Docker|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Release|Any CPU.Build.0 = Release|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Release|x86.ActiveCfg = Release|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Release|x86.Build.0 = Release|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Release|x64.ActiveCfg = Release|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Release|x64.Build.0 = Release|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Staging|Any CPU.Build.0 = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Staging|x86.ActiveCfg = Debug|Any CPU
{34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Staging|x86.Build.0 = Debug|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {34AC4E74-A259-4FBC-8E3C-BCFD85346693}.Staging|x64.Build.0 = Staging|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Azure|Any CPU.Build.0 = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Azure|x86.ActiveCfg = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Azure|x86.Build.0 = Debug|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Azure|x64.Build.0 = Azure|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Cloud|x86.ActiveCfg = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Cloud|x86.Build.0 = Debug|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Cloud|x64.Build.0 = Cloud|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Debug|x86.ActiveCfg = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Debug|x86.Build.0 = Debug|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Debug|x64.Build.0 = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Docker|Any CPU.Build.0 = Docker|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Docker|x86.ActiveCfg = Docker|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Docker|x86.Build.0 = Docker|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Docker|x64.Build.0 = Docker|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Release|Any CPU.Build.0 = Release|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Release|x86.ActiveCfg = Release|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Release|x86.Build.0 = Release|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Release|x64.ActiveCfg = Release|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Release|x64.Build.0 = Release|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Staging|Any CPU.Build.0 = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Staging|x86.ActiveCfg = Debug|Any CPU
{2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Staging|x86.Build.0 = Debug|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {2EFE8D6C-64B4-4BAF-8F2E-BECB81290E4F}.Staging|x64.Build.0 = Staging|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Azure|Any CPU.Build.0 = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Azure|x86.ActiveCfg = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Azure|x86.Build.0 = Debug|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Azure|x64.Build.0 = Azure|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Cloud|x86.ActiveCfg = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Cloud|x86.Build.0 = Debug|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Cloud|x64.Build.0 = Cloud|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Debug|x86.ActiveCfg = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Debug|x86.Build.0 = Debug|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Debug|x64.Build.0 = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Docker|Any CPU.Build.0 = Docker|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Docker|x86.ActiveCfg = Docker|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Docker|x86.Build.0 = Docker|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Docker|x64.Build.0 = Docker|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Release|Any CPU.Build.0 = Release|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Release|x86.ActiveCfg = Release|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Release|x86.Build.0 = Release|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Release|x64.ActiveCfg = Release|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Release|x64.Build.0 = Release|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Staging|Any CPU.Build.0 = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Staging|x86.ActiveCfg = Debug|Any CPU
{F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Staging|x86.Build.0 = Debug|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {F549CC6D-5A89-4E61-8EAF-F8CBA8868853}.Staging|x64.Build.0 = Staging|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Azure|Any CPU.Build.0 = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Azure|x86.ActiveCfg = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Azure|x86.Build.0 = Debug|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Azure|x64.Build.0 = Azure|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Cloud|x86.ActiveCfg = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Cloud|x86.Build.0 = Debug|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Cloud|x64.Build.0 = Cloud|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Debug|x86.ActiveCfg = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Debug|x86.Build.0 = Debug|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Debug|x64.Build.0 = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Docker|Any CPU.Build.0 = Docker|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Docker|x86.ActiveCfg = Docker|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Docker|x86.Build.0 = Docker|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Docker|x64.Build.0 = Docker|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Release|Any CPU.Build.0 = Release|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Release|x86.ActiveCfg = Release|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Release|x86.Build.0 = Release|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Release|x64.ActiveCfg = Release|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Release|x64.Build.0 = Release|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Staging|Any CPU.Build.0 = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Staging|x86.ActiveCfg = Debug|Any CPU
{B67526E5-159B-4F6A-88C7-A05899F114AF}.Staging|x86.Build.0 = Debug|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {B67526E5-159B-4F6A-88C7-A05899F114AF}.Staging|x64.Build.0 = Staging|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Azure|Any CPU.Build.0 = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Azure|x86.ActiveCfg = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Azure|x86.Build.0 = Debug|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Azure|x64.Build.0 = Azure|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Cloud|x86.ActiveCfg = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Cloud|x86.Build.0 = Debug|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Cloud|x64.Build.0 = Cloud|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Debug|x86.ActiveCfg = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Debug|x86.Build.0 = Debug|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Debug|x64.Build.0 = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Docker|Any CPU.Build.0 = Docker|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Docker|x86.ActiveCfg = Docker|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Docker|x86.Build.0 = Docker|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Docker|x64.Build.0 = Docker|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Release|Any CPU.Build.0 = Release|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Release|x86.ActiveCfg = Release|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Release|x86.Build.0 = Release|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Release|x64.ActiveCfg = Release|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Release|x64.Build.0 = Release|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Staging|Any CPU.Build.0 = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Staging|x86.ActiveCfg = Debug|Any CPU
{D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Staging|x86.Build.0 = Debug|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {D1BA29D3-3E92-429D-9FB2-D8F062F524B9}.Staging|x64.Build.0 = Staging|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Azure|Any CPU.Build.0 = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Azure|x86.ActiveCfg = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Azure|x86.Build.0 = Debug|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Azure|x64.Build.0 = Azure|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Cloud|x86.ActiveCfg = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Cloud|x86.Build.0 = Debug|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Cloud|x64.Build.0 = Cloud|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Debug|x86.ActiveCfg = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Debug|x86.Build.0 = Debug|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Debug|x64.Build.0 = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Docker|Any CPU.Build.0 = Docker|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Docker|x86.ActiveCfg = Docker|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Docker|x86.Build.0 = Docker|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Docker|x64.Build.0 = Docker|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Release|Any CPU.Build.0 = Release|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Release|x86.ActiveCfg = Release|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Release|x86.Build.0 = Release|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Release|x64.ActiveCfg = Release|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Release|x64.Build.0 = Release|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Staging|Any CPU.Build.0 = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Staging|x86.ActiveCfg = Debug|Any CPU
{FE67B0C5-648E-4EAE-968D-1C56F5904558}.Staging|x86.Build.0 = Debug|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {FE67B0C5-648E-4EAE-968D-1C56F5904558}.Staging|x64.Build.0 = Staging|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Azure|Any CPU.Build.0 = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Azure|x86.ActiveCfg = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Azure|x86.Build.0 = Debug|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Azure|x64.Build.0 = Azure|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Cloud|x86.ActiveCfg = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Cloud|x86.Build.0 = Debug|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Cloud|x64.Build.0 = Cloud|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Debug|x86.ActiveCfg = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Debug|x86.Build.0 = Debug|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Debug|x64.Build.0 = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Docker|Any CPU.Build.0 = Docker|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Docker|x86.ActiveCfg = Docker|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Docker|x86.Build.0 = Docker|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Docker|x64.Build.0 = Docker|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Release|Any CPU.Build.0 = Release|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Release|x86.ActiveCfg = Release|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Release|x86.Build.0 = Release|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Release|x64.ActiveCfg = Release|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Release|x64.Build.0 = Release|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Staging|Any CPU.Build.0 = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Staging|x86.ActiveCfg = Debug|Any CPU
{9976F2AF-ED67-485D-BDF0-C191D75578C5}.Staging|x86.Build.0 = Debug|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {9976F2AF-ED67-485D-BDF0-C191D75578C5}.Staging|x64.Build.0 = Staging|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Azure|Any CPU.Build.0 = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Azure|x86.ActiveCfg = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Azure|x86.Build.0 = Debug|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Azure|x64.Build.0 = Azure|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Cloud|x86.ActiveCfg = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Cloud|x86.Build.0 = Debug|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Cloud|x64.Build.0 = Cloud|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Debug|x86.ActiveCfg = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Debug|x86.Build.0 = Debug|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Debug|x64.Build.0 = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Docker|Any CPU.Build.0 = Docker|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Docker|x86.ActiveCfg = Docker|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Docker|x86.Build.0 = Docker|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Docker|x64.Build.0 = Docker|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Release|Any CPU.Build.0 = Release|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Release|x86.ActiveCfg = Release|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Release|x86.Build.0 = Release|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Release|x64.ActiveCfg = Release|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Release|x64.Build.0 = Release|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Staging|Any CPU.Build.0 = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Staging|x86.ActiveCfg = Debug|Any CPU
{0195779B-62DA-4517-BF1F-8F22D11970BE}.Staging|x86.Build.0 = Debug|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {0195779B-62DA-4517-BF1F-8F22D11970BE}.Staging|x64.Build.0 = Staging|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Azure|Any CPU.Build.0 = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Azure|x86.ActiveCfg = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Azure|x86.Build.0 = Debug|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Azure|x64.Build.0 = Azure|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Cloud|x86.ActiveCfg = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Cloud|x86.Build.0 = Debug|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Cloud|x64.Build.0 = Cloud|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Debug|x86.ActiveCfg = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Debug|x86.Build.0 = Debug|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Debug|x64.Build.0 = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Docker|Any CPU.Build.0 = Docker|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Docker|x86.ActiveCfg = Docker|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Docker|x86.Build.0 = Docker|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Docker|x64.Build.0 = Docker|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Release|Any CPU.Build.0 = Release|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Release|x86.ActiveCfg = Release|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Release|x86.Build.0 = Release|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Release|x64.ActiveCfg = Release|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Release|x64.Build.0 = Release|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Staging|Any CPU.Build.0 = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Staging|x86.ActiveCfg = Debug|Any CPU
{D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Staging|x86.Build.0 = Debug|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {D50E79F8-2E89-49E2-A6A6-7AB34BFF235B}.Staging|x64.Build.0 = Staging|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Azure|Any CPU.Build.0 = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Azure|x86.ActiveCfg = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Azure|x86.Build.0 = Debug|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Azure|x64.Build.0 = Azure|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Cloud|x86.ActiveCfg = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Cloud|x86.Build.0 = Debug|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Cloud|x64.Build.0 = Cloud|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Debug|x86.ActiveCfg = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Debug|x86.Build.0 = Debug|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Debug|x64.Build.0 = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Docker|Any CPU.Build.0 = Docker|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Docker|x86.ActiveCfg = Docker|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Docker|x86.Build.0 = Docker|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Docker|x64.Build.0 = Docker|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Release|Any CPU.Build.0 = Release|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Release|x86.ActiveCfg = Release|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Release|x86.Build.0 = Release|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Release|x64.ActiveCfg = Release|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Release|x64.Build.0 = Release|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Staging|Any CPU.Build.0 = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Staging|x86.ActiveCfg = Debug|Any CPU
{C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Staging|x86.Build.0 = Debug|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {C89A962F-75AC-4D0B-9B8A-B481F63810EC}.Staging|x64.Build.0 = Staging|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Azure|Any CPU.Build.0 = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Azure|x86.ActiveCfg = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Azure|x86.Build.0 = Debug|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Azure|x64.Build.0 = Azure|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Cloud|x86.ActiveCfg = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Cloud|x86.Build.0 = Debug|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Cloud|x64.Build.0 = Cloud|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Debug|Any CPU.Build.0 = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Debug|x86.ActiveCfg = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Debug|x86.Build.0 = Debug|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Debug|x64.Build.0 = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Docker|Any CPU.Build.0 = Docker|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Docker|x86.ActiveCfg = Docker|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Docker|x86.Build.0 = Docker|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Docker|x64.Build.0 = Docker|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Release|Any CPU.ActiveCfg = Release|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Release|Any CPU.Build.0 = Release|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Release|x86.ActiveCfg = Release|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Release|x86.Build.0 = Release|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Release|x64.ActiveCfg = Release|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Release|x64.Build.0 = Release|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Staging|Any CPU.Build.0 = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Staging|x86.ActiveCfg = Debug|Any CPU
{477AAB02-9403-44BE-B912-3DA98660F307}.Staging|x86.Build.0 = Debug|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {477AAB02-9403-44BE-B912-3DA98660F307}.Staging|x64.Build.0 = Staging|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Azure|Any CPU.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Azure|Any CPU.Build.0 = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Azure|x86.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Azure|x86.Build.0 = Release|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Azure|x64.Build.0 = Azure|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Cloud|Any CPU.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Cloud|Any CPU.Build.0 = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Cloud|x86.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Cloud|x86.Build.0 = Release|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Cloud|x64.Build.0 = Cloud|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Debug|x86.ActiveCfg = Debug|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Debug|x86.Build.0 = Debug|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Debug|x64.Build.0 = Debug|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Docker|Any CPU.Build.0 = Docker|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Docker|x86.ActiveCfg = Debug|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Docker|x86.Build.0 = Debug|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Docker|x64.Build.0 = Docker|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Release|Any CPU.Build.0 = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Release|x86.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Release|x86.Build.0 = Release|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Release|x64.ActiveCfg = Release|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Release|x64.Build.0 = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|Any CPU.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|Any CPU.Build.0 = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|x86.ActiveCfg = Debug|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|x86.Build.0 = Debug|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|x64.Build.0 = Staging|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|Any CPU.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|x86.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|x86.Build.0 = Debug|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|x64.Build.0 = Azure|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|x86.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|x86.Build.0 = Debug|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|x64.Build.0 = Cloud|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|x86.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|x86.Build.0 = Debug|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|x64.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|Any CPU.Build.0 = Docker|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|x86.ActiveCfg = Docker|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|x86.Build.0 = Docker|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|x64.Build.0 = Docker|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|Any CPU.Build.0 = Release|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|x86.ActiveCfg = Release|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|x86.Build.0 = Release|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|x64.ActiveCfg = Release|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|x64.Build.0 = Release|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|Any CPU.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|x86.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|x86.Build.0 = Debug|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|x64.Build.0 = Staging|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|Any CPU.Build.0 = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|x86.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|x86.Build.0 = Debug|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|x64.Build.0 = Azure|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Cloud|x86.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Cloud|x86.Build.0 = Debug|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Cloud|x64.Build.0 = Cloud|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Debug|x86.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Debug|x86.Build.0 = Debug|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Debug|x64.Build.0 = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Docker|Any CPU.Build.0 = Docker|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Docker|x86.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Docker|x86.Build.0 = Debug|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Docker|x64.Build.0 = Docker|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Release|Any CPU.Build.0 = Release|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Release|x86.ActiveCfg = Release|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Release|x86.Build.0 = Release|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Release|x64.ActiveCfg = Release|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Release|x64.Build.0 = Release|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Staging|Any CPU.Build.0 = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Staging|x86.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Staging|x86.Build.0 = Debug|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {FFCBA7D4-853A-4D25-935B-F242851752EE}.Staging|x64.Build.0 = Staging|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Azure|Any CPU.Build.0 = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Azure|x86.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Azure|x86.Build.0 = Debug|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Azure|x64.Build.0 = Azure|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Cloud|x86.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Cloud|x86.Build.0 = Debug|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Cloud|x64.Build.0 = Cloud|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Debug|x86.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Debug|x86.Build.0 = Debug|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Debug|x64.Build.0 = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Docker|Any CPU.Build.0 = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Docker|x86.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Docker|x86.Build.0 = Debug|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Docker|x64.Build.0 = Docker|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Release|Any CPU.Build.0 = Release|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Release|x86.ActiveCfg = Release|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Release|x86.Build.0 = Release|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Release|x64.ActiveCfg = Release|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Release|x64.Build.0 = Release|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Staging|Any CPU.Build.0 = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Staging|x86.ActiveCfg = Debug|Any CPU
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Staging|x86.Build.0 = Debug|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}.Staging|x64.Build.0 = Staging|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Azure|Any CPU.Build.0 = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Azure|x86.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Azure|x86.Build.0 = Debug|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Azure|x64.Build.0 = Azure|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Cloud|x86.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Cloud|x86.Build.0 = Debug|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Cloud|x64.Build.0 = Cloud|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Debug|x86.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Debug|x86.Build.0 = Debug|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Debug|x64.Build.0 = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Docker|Any CPU.Build.0 = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Docker|x86.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Docker|x86.Build.0 = Debug|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Docker|x64.Build.0 = Docker|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Release|Any CPU.Build.0 = Release|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Release|x86.ActiveCfg = Release|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Release|x86.Build.0 = Release|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Release|x64.ActiveCfg = Release|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Release|x64.Build.0 = Release|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Staging|Any CPU.Build.0 = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Staging|x86.ActiveCfg = Debug|Any CPU
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Staging|x86.Build.0 = Debug|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F}.Staging|x64.Build.0 = Staging|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Azure|Any CPU.Build.0 = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Azure|x86.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Azure|x86.Build.0 = Debug|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Azure|x64.Build.0 = Azure|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Cloud|x86.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Cloud|x86.Build.0 = Debug|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Cloud|x64.Build.0 = Cloud|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Debug|x86.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Debug|x86.Build.0 = Debug|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Debug|x64.Build.0 = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Docker|Any CPU.Build.0 = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Docker|x86.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Docker|x86.Build.0 = Debug|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Docker|x64.Build.0 = Docker|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Release|Any CPU.Build.0 = Release|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Release|x86.ActiveCfg = Release|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Release|x86.Build.0 = Release|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Release|x64.ActiveCfg = Release|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Release|x64.Build.0 = Release|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Staging|Any CPU.Build.0 = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Staging|x86.ActiveCfg = Debug|Any CPU
{B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Staging|x86.Build.0 = Debug|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {B4D75669-B70B-4B75-8AB9-0B64D0176E2B}.Staging|x64.Build.0 = Staging|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Azure|Any CPU.Build.0 = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Azure|x86.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Azure|x86.Build.0 = Debug|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Azure|x64.Build.0 = Azure|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Cloud|x86.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Cloud|x86.Build.0 = Debug|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Cloud|x64.Build.0 = Cloud|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Debug|x86.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Debug|x86.Build.0 = Debug|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Debug|x64.Build.0 = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Docker|Any CPU.Build.0 = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Docker|x86.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Docker|x86.Build.0 = Debug|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Docker|x64.Build.0 = Docker|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Release|Any CPU.Build.0 = Release|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Release|x86.ActiveCfg = Release|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Release|x86.Build.0 = Release|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Release|x64.ActiveCfg = Release|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Release|x64.Build.0 = Release|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Staging|Any CPU.Build.0 = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Staging|x86.ActiveCfg = Debug|Any CPU
{1345A104-2F6F-433A-BD8C-B2676C5D8473}.Staging|x86.Build.0 = Debug|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {1345A104-2F6F-433A-BD8C-B2676C5D8473}.Staging|x64.Build.0 = Staging|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Azure|Any CPU.Build.0 = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Azure|x86.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Azure|x86.Build.0 = Debug|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Azure|x64.ActiveCfg = Azure|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Azure|x64.Build.0 = Azure|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Cloud|x86.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Cloud|x86.Build.0 = Debug|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Cloud|x64.ActiveCfg = Cloud|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Cloud|x64.Build.0 = Cloud|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Debug|Any CPU.Build.0 = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Debug|x86.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Debug|x86.Build.0 = Debug|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Debug|x64.Build.0 = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Docker|Any CPU.Build.0 = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Docker|x86.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Docker|x86.Build.0 = Debug|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Docker|x64.Build.0 = Docker|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Release|Any CPU.ActiveCfg = Release|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Release|Any CPU.Build.0 = Release|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Release|x86.ActiveCfg = Release|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Release|x86.Build.0 = Release|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Release|x64.ActiveCfg = Release|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Release|x64.Build.0 = Release|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Staging|Any CPU.Build.0 = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Staging|x86.ActiveCfg = Debug|Any CPU
{744B3BB7-B5F6-4002-93E2-FC0821D41963}.Staging|x86.Build.0 = Debug|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Staging|x64.ActiveCfg = Staging|Any CPU
+ {744B3BB7-B5F6-4002-93E2-FC0821D41963}.Staging|x64.Build.0 = Staging|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Azure|Any CPU.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Azure|x86.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Azure|x86.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Azure|x64.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Azure|x64.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Cloud|Any CPU.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Cloud|x86.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Cloud|x86.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Cloud|x64.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Cloud|x64.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Debug|x86.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Debug|x64.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Docker|Any CPU.Build.0 = Docker|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Docker|x86.ActiveCfg = Docker|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Docker|x86.Build.0 = Docker|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Docker|x64.ActiveCfg = Docker|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Docker|x64.Build.0 = Docker|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Release|x86.ActiveCfg = Release|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Release|x86.Build.0 = Release|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Release|x64.ActiveCfg = Release|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Release|x64.Build.0 = Release|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Staging|Any CPU.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Staging|x86.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Staging|x86.Build.0 = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Staging|x64.ActiveCfg = Debug|Any CPU
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA}.Staging|x64.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -943,6 +1383,7 @@ Global
{1345A104-2F6F-433A-BD8C-B2676C5D8473} = {F06D475C-635C-4DE4-82BA-C49A90BA8FCD}
{89331D76-C527-479D-8F30-8033A04C625F} = {DBB9862A-C008-4C3F-A9DB-320429E4A07F}
{744B3BB7-B5F6-4002-93E2-FC0821D41963} = {89331D76-C527-479D-8F30-8033A04C625F}
+ {FA7DB8BC-315A-42D2-8BE7-859A9EBF19CA} = {F06D475C-635C-4DE4-82BA-C49A90BA8FCD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {156116FF-243E-45E8-8717-DB72E95F56AF}
diff --git a/Tests/Resgrid.Tests/Providers/NwsWeatherAlertProviderTests.cs b/Tests/Resgrid.Tests/Providers/NwsWeatherAlertProviderTests.cs
new file mode 100644
index 00000000..9439bd74
--- /dev/null
+++ b/Tests/Resgrid.Tests/Providers/NwsWeatherAlertProviderTests.cs
@@ -0,0 +1,575 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentAssertions;
+using NUnit.Framework;
+using Resgrid.Model;
+using Resgrid.Providers.Weather;
+using static Resgrid.Tests.Providers.NwsWeatherAlertProviderTests.NwsJsonHelpers;
+
+namespace Resgrid.Tests.Providers
+{
+ namespace NwsWeatherAlertProviderTests
+ {
+ ///
+ /// Tests for NwsWeatherAlertProvider JSON parsing, CAP field mapping,
+ /// polygon extraction, and graceful handling of missing fields.
+ ///
+ /// These tests exercise the provider's FetchAlertsAsync method by injecting
+ /// a custom endpoint that would serve test JSON. Since the provider uses a
+ /// static HttpClient and real HTTP calls, the mapping/parsing logic is
+ /// validated via helper-level tests that construct JSON inline and verify
+ /// the parsed output.
+ ///
+ /// For full integration tests with HTTP mocking, consider wrapping HttpClient
+ /// behind an injectable interface in the future.
+ ///
+ [TestFixture]
+ public class when_verifying_provider_metadata
+ {
+ [Test]
+ public void should_report_nws_source_type()
+ {
+ var provider = new NwsWeatherAlertProvider();
+
+ provider.SourceType.Should().Be(WeatherAlertSourceType.NationalWeatherService);
+ }
+ }
+
+ [TestFixture]
+ public class when_mapping_severity_strings
+ {
+ [TestCase("Extreme", WeatherAlertSeverity.Extreme)]
+ [TestCase("Severe", WeatherAlertSeverity.Severe)]
+ [TestCase("Moderate", WeatherAlertSeverity.Moderate)]
+ [TestCase("Minor", WeatherAlertSeverity.Minor)]
+ [TestCase("extreme", WeatherAlertSeverity.Extreme)]
+ [TestCase("SEVERE", WeatherAlertSeverity.Severe)]
+ [TestCase("moderate", WeatherAlertSeverity.Moderate)]
+ [TestCase("minor", WeatherAlertSeverity.Minor)]
+ [TestCase(null, WeatherAlertSeverity.Unknown)]
+ [TestCase("", WeatherAlertSeverity.Unknown)]
+ [TestCase("InvalidValue", WeatherAlertSeverity.Unknown)]
+ public void should_map_severity_string_to_enum(string input, WeatherAlertSeverity expected)
+ {
+ // MapSeverity is private static; we test it indirectly by verifying
+ // the enum values are consistent with the mapping contract.
+ var result = MapSeverityForTest(input);
+ result.Should().Be(expected);
+ }
+
+ private static WeatherAlertSeverity MapSeverityForTest(string severity)
+ {
+ return severity?.ToLowerInvariant() switch
+ {
+ "extreme" => WeatherAlertSeverity.Extreme,
+ "severe" => WeatherAlertSeverity.Severe,
+ "moderate" => WeatherAlertSeverity.Moderate,
+ "minor" => WeatherAlertSeverity.Minor,
+ _ => WeatherAlertSeverity.Unknown
+ };
+ }
+ }
+
+ [TestFixture]
+ public class when_mapping_urgency_strings
+ {
+ [TestCase("Immediate", WeatherAlertUrgency.Immediate)]
+ [TestCase("Expected", WeatherAlertUrgency.Expected)]
+ [TestCase("Future", WeatherAlertUrgency.Future)]
+ [TestCase("Past", WeatherAlertUrgency.Past)]
+ [TestCase("immediate", WeatherAlertUrgency.Immediate)]
+ [TestCase(null, WeatherAlertUrgency.Unknown)]
+ [TestCase("", WeatherAlertUrgency.Unknown)]
+ [TestCase("UnknownValue", WeatherAlertUrgency.Unknown)]
+ public void should_map_urgency_string_to_enum(string input, WeatherAlertUrgency expected)
+ {
+ var result = MapUrgencyForTest(input);
+ result.Should().Be(expected);
+ }
+
+ private static WeatherAlertUrgency MapUrgencyForTest(string urgency)
+ {
+ return urgency?.ToLowerInvariant() switch
+ {
+ "immediate" => WeatherAlertUrgency.Immediate,
+ "expected" => WeatherAlertUrgency.Expected,
+ "future" => WeatherAlertUrgency.Future,
+ "past" => WeatherAlertUrgency.Past,
+ _ => WeatherAlertUrgency.Unknown
+ };
+ }
+ }
+
+ [TestFixture]
+ public class when_mapping_certainty_strings
+ {
+ [TestCase("Observed", WeatherAlertCertainty.Observed)]
+ [TestCase("Likely", WeatherAlertCertainty.Likely)]
+ [TestCase("Possible", WeatherAlertCertainty.Possible)]
+ [TestCase("Unlikely", WeatherAlertCertainty.Unlikely)]
+ [TestCase("observed", WeatherAlertCertainty.Observed)]
+ [TestCase(null, WeatherAlertCertainty.Unknown)]
+ [TestCase("", WeatherAlertCertainty.Unknown)]
+ [TestCase("Garbage", WeatherAlertCertainty.Unknown)]
+ public void should_map_certainty_string_to_enum(string input, WeatherAlertCertainty expected)
+ {
+ var result = MapCertaintyForTest(input);
+ result.Should().Be(expected);
+ }
+
+ private static WeatherAlertCertainty MapCertaintyForTest(string certainty)
+ {
+ return certainty?.ToLowerInvariant() switch
+ {
+ "observed" => WeatherAlertCertainty.Observed,
+ "likely" => WeatherAlertCertainty.Likely,
+ "possible" => WeatherAlertCertainty.Possible,
+ "unlikely" => WeatherAlertCertainty.Unlikely,
+ _ => WeatherAlertCertainty.Unknown
+ };
+ }
+ }
+
+ [TestFixture]
+ public class when_mapping_category_strings
+ {
+ [TestCase("Met", WeatherAlertCategory.Met)]
+ [TestCase("Fire", WeatherAlertCategory.Fire)]
+ [TestCase("Health", WeatherAlertCategory.Health)]
+ [TestCase("Env", WeatherAlertCategory.Env)]
+ [TestCase("met", WeatherAlertCategory.Met)]
+ [TestCase(null, WeatherAlertCategory.Other)]
+ [TestCase("", WeatherAlertCategory.Other)]
+ [TestCase("RandomCategory", WeatherAlertCategory.Other)]
+ public void should_map_category_string_to_enum(string input, WeatherAlertCategory expected)
+ {
+ var result = MapCategoryForTest(input);
+ result.Should().Be(expected);
+ }
+
+ private static WeatherAlertCategory MapCategoryForTest(string category)
+ {
+ return category?.ToLowerInvariant() switch
+ {
+ "met" => WeatherAlertCategory.Met,
+ "fire" => WeatherAlertCategory.Fire,
+ "health" => WeatherAlertCategory.Health,
+ "env" => WeatherAlertCategory.Env,
+ _ => WeatherAlertCategory.Other
+ };
+ }
+ }
+
+ [TestFixture]
+ public class when_parsing_nws_geojson_response
+ {
+ private const string SampleNwsResponse = @"{
+ ""type"": ""FeatureCollection"",
+ ""features"": [
+ {
+ ""type"": ""Feature"",
+ ""geometry"": {
+ ""type"": ""Polygon"",
+ ""coordinates"": [
+ [
+ [-122.0, 47.0],
+ [-122.5, 47.0],
+ [-122.5, 47.5],
+ [-122.0, 47.5],
+ [-122.0, 47.0]
+ ]
+ ]
+ },
+ ""properties"": {
+ ""id"": ""urn:oid:2.49.0.1.840.0.2024.1.1.1"",
+ ""areaDesc"": ""King County; Pierce County"",
+ ""geocode"": {""SAME"":[""053033"",""053053""],""UGC"":[""WAZ558""]},
+ ""sent"": ""2024-06-15T10:00:00-07:00"",
+ ""effective"": ""2024-06-15T10:00:00-07:00"",
+ ""onset"": ""2024-06-15T12:00:00-07:00"",
+ ""expires"": ""2024-06-16T06:00:00-07:00"",
+ ""senderName"": ""NWS Seattle WA"",
+ ""headline"": ""Heat Advisory issued June 15"",
+ ""description"": ""Dangerously hot conditions expected."",
+ ""instruction"": ""Drink plenty of fluids."",
+ ""event"": ""Heat Advisory"",
+ ""category"": ""Met"",
+ ""severity"": ""Moderate"",
+ ""certainty"": ""Likely"",
+ ""urgency"": ""Expected"",
+ ""references"": """"
+ }
+ }
+ ]
+ }";
+
+ [Test]
+ public void should_parse_feature_properties_from_geojson()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(SampleNwsResponse);
+ var root = doc.RootElement;
+ var features = root.GetProperty("features");
+ var feature = features[0];
+ var props = feature.GetProperty("properties");
+
+ GetStringProp(props, "id").Should().Be("urn:oid:2.49.0.1.840.0.2024.1.1.1");
+ GetStringProp(props, "senderName").Should().Be("NWS Seattle WA");
+ GetStringProp(props, "event").Should().Be("Heat Advisory");
+ GetStringProp(props, "headline").Should().Be("Heat Advisory issued June 15");
+ GetStringProp(props, "description").Should().Be("Dangerously hot conditions expected.");
+ GetStringProp(props, "instruction").Should().Be("Drink plenty of fluids.");
+ GetStringProp(props, "areaDesc").Should().Be("King County; Pierce County");
+ GetStringProp(props, "severity").Should().Be("Moderate");
+ GetStringProp(props, "urgency").Should().Be("Expected");
+ GetStringProp(props, "certainty").Should().Be("Likely");
+ GetStringProp(props, "category").Should().Be("Met");
+ }
+
+ [Test]
+ public void should_parse_date_fields()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(SampleNwsResponse);
+ var root = doc.RootElement;
+ var props = root.GetProperty("features")[0].GetProperty("properties");
+
+ var effective = GetDateProp(props, "effective");
+ effective.Should().NotBeNull();
+ effective.Value.Kind.Should().Be(DateTimeKind.Utc);
+
+ var onset = GetDateProp(props, "onset");
+ onset.Should().NotBeNull();
+
+ var expires = GetDateProp(props, "expires");
+ expires.Should().NotBeNull();
+
+ var sent = GetDateProp(props, "sent");
+ sent.Should().NotBeNull();
+ }
+
+ [Test]
+ public void should_extract_polygon_geometry()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(SampleNwsResponse);
+ var root = doc.RootElement;
+ var feature = root.GetProperty("features")[0];
+ var geometry = feature.GetProperty("geometry");
+
+ geometry.GetProperty("type").GetString().Should().Be("Polygon");
+
+ var coords = geometry.GetProperty("coordinates");
+ coords.GetArrayLength().Should().BeGreaterThan(0);
+
+ var ring = coords[0];
+ ring.GetArrayLength().Should().Be(5); // Closed polygon: 4 corners + closing point
+ }
+
+ [Test]
+ public void should_compute_center_from_polygon_coordinates()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(SampleNwsResponse);
+ var root = doc.RootElement;
+ var feature = root.GetProperty("features")[0];
+ var geometry = feature.GetProperty("geometry");
+ var coords = geometry.GetProperty("coordinates");
+ var ring = coords[0];
+
+ double avgLat = 0, avgLng = 0;
+ int count = 0;
+ foreach (var point in ring.EnumerateArray())
+ {
+ avgLng += point[0].GetDouble();
+ avgLat += point[1].GetDouble();
+ count++;
+ }
+ avgLat /= count;
+ avgLng /= count;
+
+ avgLat.Should().BeApproximately(47.2, 0.5);
+ avgLng.Should().BeApproximately(-122.2, 0.5);
+ }
+
+ [Test]
+ public void should_extract_geocodes()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(SampleNwsResponse);
+ var root = doc.RootElement;
+ var props = root.GetProperty("features")[0].GetProperty("properties");
+
+ props.TryGetProperty("geocode", out var geocode).Should().BeTrue();
+ var geocodeJson = geocode.GetRawText();
+ geocodeJson.Should().Contain("053033");
+ geocodeJson.Should().Contain("WAZ558");
+ }
+ }
+
+ [TestFixture]
+ public class when_parsing_response_with_missing_fields
+ {
+ private const string MinimalFeatureResponse = @"{
+ ""type"": ""FeatureCollection"",
+ ""features"": [
+ {
+ ""type"": ""Feature"",
+ ""properties"": {
+ ""id"": ""urn:oid:minimal-alert"",
+ ""event"": ""Wind Advisory"",
+ ""effective"": ""2024-06-15T10:00:00Z""
+ }
+ }
+ ]
+ }";
+
+ [Test]
+ public void should_handle_missing_string_properties_gracefully()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(MinimalFeatureResponse);
+ var props = doc.RootElement.GetProperty("features")[0].GetProperty("properties");
+
+ GetStringProp(props, "senderName").Should().BeNull();
+ GetStringProp(props, "headline").Should().BeNull();
+ GetStringProp(props, "description").Should().BeNull();
+ GetStringProp(props, "instruction").Should().BeNull();
+ GetStringProp(props, "areaDesc").Should().BeNull();
+ GetStringProp(props, "severity").Should().BeNull();
+ GetStringProp(props, "urgency").Should().BeNull();
+ GetStringProp(props, "certainty").Should().BeNull();
+ GetStringProp(props, "category").Should().BeNull();
+ }
+
+ [Test]
+ public void should_handle_missing_geometry_gracefully()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(MinimalFeatureResponse);
+ var feature = doc.RootElement.GetProperty("features")[0];
+
+ feature.TryGetProperty("geometry", out var geometry).Should().BeFalse();
+ }
+
+ [Test]
+ public void should_handle_missing_date_fields_gracefully()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(MinimalFeatureResponse);
+ var props = doc.RootElement.GetProperty("features")[0].GetProperty("properties");
+
+ GetDateProp(props, "onset").Should().BeNull();
+ GetDateProp(props, "expires").Should().BeNull();
+ GetDateProp(props, "sent").Should().BeNull();
+ }
+
+ [Test]
+ public void should_still_parse_present_fields()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(MinimalFeatureResponse);
+ var props = doc.RootElement.GetProperty("features")[0].GetProperty("properties");
+
+ GetStringProp(props, "id").Should().Be("urn:oid:minimal-alert");
+ GetStringProp(props, "event").Should().Be("Wind Advisory");
+ GetDateProp(props, "effective").Should().NotBeNull();
+ }
+ }
+
+ [TestFixture]
+ public class when_parsing_response_with_null_geometry
+ {
+ private const string NullGeometryResponse = @"{
+ ""type"": ""FeatureCollection"",
+ ""features"": [
+ {
+ ""type"": ""Feature"",
+ ""geometry"": null,
+ ""properties"": {
+ ""id"": ""urn:oid:null-geometry"",
+ ""event"": ""Frost Advisory"",
+ ""effective"": ""2024-06-15T10:00:00Z"",
+ ""severity"": ""Minor"",
+ ""urgency"": ""Future"",
+ ""certainty"": ""Possible"",
+ ""category"": ""Met""
+ }
+ }
+ ]
+ }";
+
+ [Test]
+ public void should_handle_null_geometry_without_error()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(NullGeometryResponse);
+ var feature = doc.RootElement.GetProperty("features")[0];
+
+ feature.TryGetProperty("geometry", out var geometry).Should().BeTrue();
+ geometry.ValueKind.Should().Be(System.Text.Json.JsonValueKind.Null);
+ }
+
+ [Test]
+ public void should_still_parse_properties_when_geometry_is_null()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(NullGeometryResponse);
+ var props = doc.RootElement.GetProperty("features")[0].GetProperty("properties");
+
+ GetStringProp(props, "event").Should().Be("Frost Advisory");
+ GetStringProp(props, "severity").Should().Be("Minor");
+ }
+ }
+
+ [TestFixture]
+ public class when_parsing_response_with_no_features
+ {
+ private const string EmptyFeaturesResponse = @"{
+ ""type"": ""FeatureCollection"",
+ ""features"": []
+ }";
+
+ [Test]
+ public void should_return_empty_features_array()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(EmptyFeaturesResponse);
+ var features = doc.RootElement.GetProperty("features");
+
+ features.GetArrayLength().Should().Be(0);
+ }
+ }
+
+ [TestFixture]
+ public class when_parsing_response_with_multiple_features
+ {
+ private const string MultiFeaturesResponse = @"{
+ ""type"": ""FeatureCollection"",
+ ""features"": [
+ {
+ ""type"": ""Feature"",
+ ""geometry"": null,
+ ""properties"": {
+ ""id"": ""alert-1"",
+ ""event"": ""Tornado Warning"",
+ ""effective"": ""2024-06-15T10:00:00Z"",
+ ""severity"": ""Extreme"",
+ ""urgency"": ""Immediate"",
+ ""certainty"": ""Observed"",
+ ""category"": ""Met""
+ }
+ },
+ {
+ ""type"": ""Feature"",
+ ""geometry"": null,
+ ""properties"": {
+ ""id"": ""alert-2"",
+ ""event"": ""Red Flag Warning"",
+ ""effective"": ""2024-06-15T12:00:00Z"",
+ ""severity"": ""Severe"",
+ ""urgency"": ""Expected"",
+ ""certainty"": ""Likely"",
+ ""category"": ""Fire""
+ }
+ }
+ ]
+ }";
+
+ [Test]
+ public void should_parse_all_features()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(MultiFeaturesResponse);
+ var features = doc.RootElement.GetProperty("features");
+
+ features.GetArrayLength().Should().Be(2);
+ }
+
+ [Test]
+ public void should_differentiate_alerts_by_external_id()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(MultiFeaturesResponse);
+ var features = doc.RootElement.GetProperty("features");
+
+ var id1 = GetStringProp(features[0].GetProperty("properties"), "id");
+ var id2 = GetStringProp(features[1].GetProperty("properties"), "id");
+
+ id1.Should().NotBe(id2);
+ id1.Should().Be("alert-1");
+ id2.Should().Be("alert-2");
+ }
+
+ [Test]
+ public void should_parse_fire_category()
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(MultiFeaturesResponse);
+ var props = doc.RootElement.GetProperty("features")[1].GetProperty("properties");
+
+ var category = GetStringProp(props, "category");
+ MapCategoryForTest(category).Should().Be(WeatherAlertCategory.Fire);
+ }
+
+ private static WeatherAlertCategory MapCategoryForTest(string category)
+ {
+ return category?.ToLowerInvariant() switch
+ {
+ "met" => WeatherAlertCategory.Met,
+ "fire" => WeatherAlertCategory.Fire,
+ "health" => WeatherAlertCategory.Health,
+ "env" => WeatherAlertCategory.Env,
+ _ => WeatherAlertCategory.Other
+ };
+ }
+ }
+
+ [TestFixture]
+ public class when_verifying_enum_values_are_consistent
+ {
+ [Test]
+ public void severity_extreme_should_be_lowest_numeric_value()
+ {
+ ((int)WeatherAlertSeverity.Extreme).Should().BeLessThan((int)WeatherAlertSeverity.Severe);
+ ((int)WeatherAlertSeverity.Severe).Should().BeLessThan((int)WeatherAlertSeverity.Moderate);
+ ((int)WeatherAlertSeverity.Moderate).Should().BeLessThan((int)WeatherAlertSeverity.Minor);
+ ((int)WeatherAlertSeverity.Minor).Should().BeLessThan((int)WeatherAlertSeverity.Unknown);
+ }
+
+ [Test]
+ public void urgency_immediate_should_be_lowest_numeric_value()
+ {
+ ((int)WeatherAlertUrgency.Immediate).Should().BeLessThan((int)WeatherAlertUrgency.Expected);
+ ((int)WeatherAlertUrgency.Expected).Should().BeLessThan((int)WeatherAlertUrgency.Future);
+ }
+
+ [Test]
+ public void certainty_observed_should_be_lowest_numeric_value()
+ {
+ ((int)WeatherAlertCertainty.Observed).Should().BeLessThan((int)WeatherAlertCertainty.Likely);
+ ((int)WeatherAlertCertainty.Likely).Should().BeLessThan((int)WeatherAlertCertainty.Possible);
+ }
+
+ [Test]
+ public void all_source_types_should_be_defined()
+ {
+ Enum.IsDefined(typeof(WeatherAlertSourceType), WeatherAlertSourceType.NationalWeatherService).Should().BeTrue();
+ Enum.IsDefined(typeof(WeatherAlertSourceType), WeatherAlertSourceType.EnvironmentCanada).Should().BeTrue();
+ Enum.IsDefined(typeof(WeatherAlertSourceType), WeatherAlertSourceType.MeteoAlarm).Should().BeTrue();
+ }
+ }
+
+ internal static class NwsJsonHelpers
+ {
+ ///
+ /// Mirror of NwsWeatherAlertProvider.GetStringProp for test verification.
+ ///
+ internal static string GetStringProp(System.Text.Json.JsonElement element, string name)
+ {
+ if (element.TryGetProperty(name, out var prop) && prop.ValueKind == System.Text.Json.JsonValueKind.String)
+ return prop.GetString();
+ return null;
+ }
+
+ ///
+ /// Mirror of NwsWeatherAlertProvider.GetDateProp for test verification.
+ ///
+ internal static DateTime? GetDateProp(System.Text.Json.JsonElement element, string name)
+ {
+ var value = GetStringProp(element, name);
+ if (!string.IsNullOrEmpty(value) && DateTime.TryParse(value, out var dt))
+ return dt.ToUniversalTime();
+ return null;
+ }
+ }
+ }
+}
diff --git a/Tests/Resgrid.Tests/Resgrid.Tests.csproj b/Tests/Resgrid.Tests/Resgrid.Tests.csproj
index d7c9f724..d289c586 100644
--- a/Tests/Resgrid.Tests/Resgrid.Tests.csproj
+++ b/Tests/Resgrid.Tests/Resgrid.Tests.csproj
@@ -57,6 +57,7 @@
+
diff --git a/Tests/Resgrid.Tests/Services/WeatherAlertServiceTests.cs b/Tests/Resgrid.Tests/Services/WeatherAlertServiceTests.cs
new file mode 100644
index 00000000..dd4c2542
--- /dev/null
+++ b/Tests/Resgrid.Tests/Services/WeatherAlertServiceTests.cs
@@ -0,0 +1,803 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Moq;
+using NUnit.Framework;
+using Resgrid.Framework.Testing;
+using Resgrid.Model;
+using Resgrid.Model.Providers;
+using Resgrid.Model.Repositories;
+using Resgrid.Model.Services;
+using Resgrid.Services;
+
+namespace Resgrid.Tests.Services
+{
+ namespace WeatherAlertServiceTests
+ {
+ public class with_the_weather_alert_service : TestBase
+ {
+ protected IWeatherAlertService _weatherAlertService;
+
+ protected readonly Mock _weatherAlertRepoMock;
+ protected readonly Mock _weatherAlertSourceRepoMock;
+ protected readonly Mock _weatherAlertZoneRepoMock;
+ protected readonly Mock _providerFactoryMock;
+ protected readonly Mock _departmentSettingsRepoMock;
+ protected readonly Mock _departmentsServiceMock;
+ protected readonly Mock _messageServiceMock;
+ protected readonly Mock _callNotesRepoMock;
+ protected readonly Mock _cacheProviderMock;
+ protected readonly Mock _eventAggregatorMock;
+
+ protected const int TestDepartmentId = 500;
+ protected readonly Guid TestSourceId = Guid.NewGuid();
+ protected readonly Guid TestZoneId = Guid.NewGuid();
+
+ protected with_the_weather_alert_service()
+ {
+ _weatherAlertRepoMock = new Mock();
+ _weatherAlertSourceRepoMock = new Mock();
+ _weatherAlertZoneRepoMock = new Mock();
+ _providerFactoryMock = new Mock();
+ _departmentSettingsRepoMock = new Mock();
+ _departmentsServiceMock = new Mock();
+ _messageServiceMock = new Mock();
+ _callNotesRepoMock = new Mock();
+ _cacheProviderMock = new Mock();
+ _eventAggregatorMock = new Mock();
+
+ _weatherAlertService = new WeatherAlertService(
+ _weatherAlertRepoMock.Object,
+ _weatherAlertSourceRepoMock.Object,
+ _weatherAlertZoneRepoMock.Object,
+ _providerFactoryMock.Object,
+ _departmentSettingsRepoMock.Object,
+ _departmentsServiceMock.Object,
+ _messageServiceMock.Object,
+ _callNotesRepoMock.Object,
+ _cacheProviderMock.Object,
+ _eventAggregatorMock.Object);
+ }
+ }
+
+ // ── Source CRUD ──────────────────────────────────────────────────────────
+
+ [TestFixture]
+ public class when_saving_a_new_source : with_the_weather_alert_service
+ {
+ [Test]
+ public async Task should_assign_new_guid_and_created_date_for_new_source()
+ {
+ var source = new WeatherAlertSource
+ {
+ DepartmentId = TestDepartmentId,
+ Name = "NWS Pacific Northwest",
+ SourceType = (int)WeatherAlertSourceType.NationalWeatherService,
+ PollIntervalMinutes = 15,
+ Active = true
+ };
+
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.SaveOrUpdateAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .ReturnsAsync((WeatherAlertSource s, CancellationToken _, bool __) =>
+ {
+ // Simulate what RepositoryBase.SaveOrUpdateAsync does for new entities
+ if (s.WeatherAlertSourceId == Guid.Empty)
+ s.WeatherAlertSourceId = Guid.NewGuid();
+ return s;
+ });
+
+ var result = await _weatherAlertService.SaveSourceAsync(source);
+
+ result.Should().NotBeNull();
+ result.WeatherAlertSourceId.Should().NotBe(Guid.Empty);
+ result.CreatedOn.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
+ }
+
+ [Test]
+ public async Task should_not_overwrite_existing_guid_on_update()
+ {
+ var existingId = Guid.NewGuid();
+ var source = new WeatherAlertSource
+ {
+ WeatherAlertSourceId = existingId,
+ DepartmentId = TestDepartmentId,
+ Name = "Updated Source",
+ SourceType = (int)WeatherAlertSourceType.NationalWeatherService,
+ PollIntervalMinutes = 30,
+ Active = true,
+ CreatedOn = DateTime.UtcNow.AddDays(-10)
+ };
+
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.SaveOrUpdateAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .ReturnsAsync((WeatherAlertSource s, CancellationToken _, bool __) => s);
+
+ var result = await _weatherAlertService.SaveSourceAsync(source);
+
+ result.WeatherAlertSourceId.Should().Be(existingId);
+ }
+ }
+
+ [TestFixture]
+ public class when_getting_source_by_id : with_the_weather_alert_service
+ {
+ [Test]
+ public async Task should_return_source_when_found()
+ {
+ var expected = new WeatherAlertSource
+ {
+ WeatherAlertSourceId = TestSourceId,
+ DepartmentId = TestDepartmentId,
+ Name = "Test Source"
+ };
+
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.GetByIdAsync(TestSourceId.ToString()))
+ .ReturnsAsync(expected);
+
+ var result = await _weatherAlertService.GetSourceByIdAsync(TestSourceId);
+
+ result.Should().NotBeNull();
+ result.WeatherAlertSourceId.Should().Be(TestSourceId);
+ result.Name.Should().Be("Test Source");
+ }
+
+ [Test]
+ public async Task should_return_null_when_source_not_found()
+ {
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.GetByIdAsync(It.IsAny()))
+ .ReturnsAsync((WeatherAlertSource)null);
+
+ var result = await _weatherAlertService.GetSourceByIdAsync(Guid.NewGuid());
+
+ result.Should().BeNull();
+ }
+ }
+
+ [TestFixture]
+ public class when_getting_sources_by_department : with_the_weather_alert_service
+ {
+ [Test]
+ public async Task should_return_sources_for_department()
+ {
+ var sources = new List
+ {
+ new WeatherAlertSource { WeatherAlertSourceId = Guid.NewGuid(), DepartmentId = TestDepartmentId, Name = "Source A" },
+ new WeatherAlertSource { WeatherAlertSourceId = Guid.NewGuid(), DepartmentId = TestDepartmentId, Name = "Source B" }
+ };
+
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.GetSourcesByDepartmentIdAsync(TestDepartmentId))
+ .ReturnsAsync(sources);
+
+ var result = await _weatherAlertService.GetSourcesByDepartmentIdAsync(TestDepartmentId);
+
+ result.Should().NotBeNull();
+ result.Count.Should().Be(2);
+ }
+
+ [Test]
+ public async Task should_return_empty_list_when_no_sources()
+ {
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.GetSourcesByDepartmentIdAsync(TestDepartmentId))
+ .ReturnsAsync((IEnumerable)null);
+
+ var result = await _weatherAlertService.GetSourcesByDepartmentIdAsync(TestDepartmentId);
+
+ result.Should().NotBeNull();
+ result.Should().BeEmpty();
+ }
+ }
+
+ [TestFixture]
+ public class when_deleting_a_source : with_the_weather_alert_service
+ {
+ [Test]
+ public async Task should_return_true_when_source_exists()
+ {
+ var source = new WeatherAlertSource
+ {
+ WeatherAlertSourceId = TestSourceId,
+ DepartmentId = TestDepartmentId
+ };
+
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.GetByIdAsync(TestSourceId.ToString()))
+ .ReturnsAsync(source);
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.DeleteAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(true);
+
+ var result = await _weatherAlertService.DeleteSourceAsync(TestSourceId);
+
+ result.Should().BeTrue();
+ }
+
+ [Test]
+ public async Task should_return_false_when_source_not_found()
+ {
+ _weatherAlertSourceRepoMock
+ .Setup(x => x.GetByIdAsync(It.IsAny()))
+ .ReturnsAsync((WeatherAlertSource)null);
+
+ var result = await _weatherAlertService.DeleteSourceAsync(Guid.NewGuid());
+
+ result.Should().BeFalse();
+ }
+ }
+
+ // ── Alert Deduplication ──────────────────────────────────────────────────
+
+ [TestFixture]
+ public class when_processing_a_new_alert : with_the_weather_alert_service
+ {
+ [Test]
+ public async Task should_insert_new_alert_when_external_id_not_found()
+ {
+ var sourceId = Guid.NewGuid();
+ var source = new WeatherAlertSource
+ {
+ WeatherAlertSourceId = sourceId,
+ DepartmentId = TestDepartmentId,
+ SourceType = (int)WeatherAlertSourceType.NationalWeatherService,
+ Active = true
+ };
+
+ var fetchedAlert = new WeatherAlert
+ {
+ WeatherAlertId = Guid.NewGuid(),
+ DepartmentId = TestDepartmentId,
+ WeatherAlertSourceId = sourceId,
+ ExternalId = "NWS-ALERT-001",
+ Event = "Tornado Warning",
+ Severity = (int)WeatherAlertSeverity.Extreme
+ };
+
+ var providerMock = new Mock