aboutsummaryrefslogtreecommitdiff
path: root/PagerMessage.cs
blob: dc30a051abedccdf52aff11e96b50cd0cc2f2c8d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.RegularExpressions;

namespace PagerParser;

public enum AlertLevel {
    Code1 = 1,
    Code3 = 3
}

[Flags]
public enum EmergencyService {
    Ambulance = 1,
    Fire      = 2,
    Police    = 3,
    Rescue    = 4,
    SES       = 5
}

[Index(nameof(Message))]
public class PagerMessage {
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int      PagerMessageId { get; set; }
    public DateTime Timestamp      { get; set; }
    public string   Message        { get; set; }

    public virtual ParsedPagerMessage? ParsedMessage { get; set; }

    public override int GetHashCode() =>
        (Timestamp, Message).GetHashCode();
}

[Index(nameof(FirecomJobNo))]
public class ParsedPagerMessage {
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int              ParsedPagerMessageId { get; set; }
    public int              FirecomJobNo         { get; set; }
    public string           AssignmentArea       { get; set; }
    public string           JobType              { get; set; }
    public AlertLevel       AlertLevel           { get; set; }
    public string           Description          { get; set; }
    public int?             MelwaysMapNo         { get; set; }
    public string?          MelwaysGrid          { get; set; }
    public int?             GridReference        { get; set; }
    public EmergencyService AttendingServices    { get; set; }
    public string?          Note                 { get; set; }
    public int?             FireGroundChannel    { get; set; }
    public string           PageDestination      { get; set; }

    public virtual GpsPosition? GpsPosition { get; set; }

    [ForeignKey(nameof(PagerMessage))]
    public virtual PagerMessage OriginalMessage { get; set; }
}

public interface IPagerMessageParserService {
    public ParsedPagerMessage  Parse(string message);
    public ParsedPagerMessage? TryParse(string message);
}

// We run the pager message parser as a service with a single instance
// as this allows us to pre-compile the parsing regular expression
// which will help when potentially processing 1000's of pager messages.
//
// Running a single instance of the service isn't an issue, as this
// service is only used by the fetch service for parsing new messages.
public class PagerMessageParserService : IPagerMessageParserService {
    private const string Pattern =
        @"^@@ALERT\s+([A-Z]+[0-9]+)\s+([A-Z&]+)C([13])\s+(.*)\s+M\s+(\d+)\s+([A-Z]\d+)\s+\((\d+)\)\s+(\*\s+[^*]+\*\s+)?([AFPRS]+)\s+((C[A-Z]+)\s+)+FGD(\d+)\s+(P\d+\s+)?F(\d+)\s+\[([A-Z]+)\]$";

    private Regex pageMessageRegex = new Regex(Pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);

    public ParsedPagerMessage Parse(string message) {
        ParsedPagerMessage m = new();

        var match = pageMessageRegex.Match(message);

        if(!match.Success)
            throw new ArgumentException();

        // Parse non-optional message components
        m.AssignmentArea  = match.Groups[1].Value;
        m.JobType         = match.Groups[2].Value;
        m.Description     = match.Groups[4].Value;
        m.FirecomJobNo    = Int32.Parse(match.Groups[14].Value);
        m.PageDestination = match.Groups[15].Value;

        // Parse optional message components
        if(match.Groups[5].Success)  m.MelwaysMapNo      = Int32.Parse(match.Groups[5].Value);
        if(match.Groups[6].Success)  m.MelwaysGrid       = match.Groups[6].Value;
        if(match.Groups[7].Success)  m.GridReference     = Int32.Parse(match.Groups[7].Value);
        if(match.Groups[8].Success)  m.Note              = match.Groups[8].Value;
        if(match.Groups[12].Success) m.FireGroundChannel = Int32.Parse(match.Groups[12].Value);

        switch(match.Groups[3].Value) {
            case "1": m.AlertLevel = AlertLevel.Code1; break;
            case "3": m.AlertLevel = AlertLevel.Code3; break;
            default: throw new ArgumentException();
        }

        // Handle each character to construct a bitmap of attending services
        foreach(char c in match.Groups[9].Value) {
            switch(c) {
                case 'A': m.AttendingServices |= EmergencyService.Ambulance; break;
                case 'F': m.AttendingServices |= EmergencyService.Fire;      break;
                case 'P': m.AttendingServices |= EmergencyService.Police;    break;
                case 'R': m.AttendingServices |= EmergencyService.Rescue;    break;
                case 'S': m.AttendingServices |= EmergencyService.SES;       break;
            }
        }

        return m;
    }

    public ParsedPagerMessage? TryParse(string message) {
        try {
            return Parse(message);
        } catch {
            return null;
        }
    }
}