From 7b6f7d87ce02d0b7bf184187ec50122f19295bdd Mon Sep 17 00:00:00 2001 From: "ion.dormenco" Date: Sat, 17 Oct 2026 15:15:27 +0300 Subject: [PATCH] Add location breakdowns and id references to aggregated V2 endpoints - Form submission, incident report and citizen report V2 aggregates now list the Level1..Level5 locations of the submissions at the root, and every question aggregate holds its breakdown per location id. - Single select, multi select and rating aggregates count the distinct submissions and polling stations per option / value. - Texts, notes and attachments are listed once at the root with ids, and the question aggregates reference them by id. - V2 responses use dedicated read models, so V1 output is unchanged. - :aggregated-submissionsV2 (incident and citizen reports) accepts the V1 query params and applies them alongside the filter; :aggregated-submissions is marked obsolete. --- .../GetSubmissionsAggregated/Endpoint.cs | 2 + .../GetSubmissionsAggregatedV2/Endpoint.cs | 6 +- .../GetSubmissionsAggregatedV2/Request.cs | 32 + .../GetSubmissionsAggregatedV2/Response.cs | 2 +- .../GetAggregatedV2/Endpoint.cs | 4 +- .../GetAggregatedV2/Response.cs | 2 +- .../GetSubmissionsAggregated/Endpoint.cs | 2 + .../GetSubmissionsAggregatedV2/Endpoint.cs | 6 +- .../GetSubmissionsAggregatedV2/Request.cs | 38 + .../GetSubmissionsAggregatedV2/Response.cs | 2 +- .../Aggregators/AggregateLocation.cs | 35 + .../Aggregators/BaseAnswerAggregate.cs | 29 +- .../Aggregators/MultiSelectAnswerAggregate.cs | 32 +- .../Aggregators/RatingAnswerAggregate.cs | 15 + .../SingleSelectAnswerAggregate.cs | 32 +- .../Aggregators/SubmissionsAggregatesV2.cs | 101 + .../Aggregators/TextAnswerAggregate.cs | 17 +- api/src/Vote.Monitor.Api/Program.cs | 4 + .../RulesEngine/FilterRules.cs | 38 + ...17104516_AddLocationAggregates.Designer.cs | 7325 +++++++++++++++++ .../20261017104516_AddLocationAggregates.cs | 1707 ++++ .../GetSubmissionsAggregatedV2RequestTests.cs | 58 + .../GetSubmissionsAggregatedV2RequestTests.cs | 66 + .../RulesEngine/FilterRulesTests.cs | 63 + 24 files changed, 9597 insertions(+), 21 deletions(-) create mode 100644 api/src/Module.Answers/Aggregators/AggregateLocation.cs create mode 100644 api/src/Module.Answers/Aggregators/SubmissionsAggregatesV2.cs create mode 100644 api/src/Vote.Monitor.Core/RulesEngine/FilterRules.cs create mode 100644 api/src/Vote.Monitor.Domain/Migrations/20261017104516_AddLocationAggregates.Designer.cs create mode 100644 api/src/Vote.Monitor.Domain/Migrations/20261017104516_AddLocationAggregates.cs create mode 100644 api/tests/Feature.CitizenReports.UnitTests/Endpoints/GetSubmissionsAggregatedV2RequestTests.cs create mode 100644 api/tests/Feature.IncidentReports.UnitTests/Endpoints/GetSubmissionsAggregatedV2RequestTests.cs create mode 100644 api/tests/Vote.Monitor.Core.UnitTests/RulesEngine/FilterRulesTests.cs diff --git a/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs b/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs index 2775732b2..39226c8a6 100644 --- a/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs +++ b/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs @@ -5,6 +5,7 @@ namespace Feature.CitizenReports.GetSubmissionsAggregated; +[Obsolete("Use :aggregated-submissionsV2 instead.")] public class Endpoint( VoteMonitorContext context, IAuthorizationService authorizationService, @@ -21,6 +22,7 @@ public override void Configure() { s.Summary = "Gets aggregated citizen report form submissions aggregated with all the notes and attachments"; + s.Description = "Obsolete: use :aggregated-submissionsV2 instead."; }); } diff --git a/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Endpoint.cs b/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Endpoint.cs index 0076fa14c..b9cfe27e9 100644 --- a/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Endpoint.cs +++ b/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Endpoint.cs @@ -36,16 +36,16 @@ await authorizationService.AuthorizeAsync(User, } var filter = new FilterSqlCompiler(V2ReportFilterFields.CitizenReports, parameterPrefixer: FilterSqlCompiler.FunctionParameter) - .Build(req.Filter); + .Build(req.ToFilterRule()); const string sql = """ SELECT * FROM "GetCitizenReportsAggregate"(@electionRoundId, @ngoId, @formId, @filter, @filterParameters::jsonb) """; - CitizenReportFormSubmissionsAggregate? aggregate; + CitizenReportFormSubmissionsAggregateV2? aggregate; using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) { - aggregate = await dbConnection.QuerySingleOrDefaultAsync(new CommandDefinition(sql, new + aggregate = await dbConnection.QuerySingleOrDefaultAsync(new CommandDefinition(sql, new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, diff --git a/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Request.cs b/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Request.cs index c0d54a878..b502f0e98 100644 --- a/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Request.cs +++ b/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Request.cs @@ -1,4 +1,7 @@ +using Vote.Monitor.Core.RulesEngine; +using Vote.Monitor.Core.RulesEngine.Rules; using Vote.Monitor.Core.Security; +using Vote.Monitor.Domain.Entities.CitizenReportAggregate; namespace Feature.CitizenReports.GetSubmissionsAggregatedV2; @@ -10,4 +13,33 @@ public class Request : BaseFilterConditionsRequest public Guid NgoId { get; set; } public Guid FormId { get; set; } + + [QueryParam] public string? Level1Filter { get; set; } + [QueryParam] public string? Level2Filter { get; set; } + [QueryParam] public string? Level3Filter { get; set; } + [QueryParam] public string? Level4Filter { get; set; } + [QueryParam] public string? Level5Filter { get; set; } + [QueryParam] public Guid? LocationId { get; set; } + [QueryParam] public bool? HasFlaggedAnswers { get; set; } + [QueryParam] public CitizenReportFollowUpStatus? FollowUpStatus { get; set; } + [QueryParam] public bool? HasNotes { get; set; } + [QueryParam] public bool? HasAttachments { get; set; } + [QueryParam] public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } + + /// + /// combined with the query parameters. + /// + public FilterRule? ToFilterRule() => FilterRules.And( + Filter, + FilterRules.Eq("level1", Level1Filter), + FilterRules.Eq("level2", Level2Filter), + FilterRules.Eq("level3", Level3Filter), + FilterRules.Eq("level4", Level4Filter), + FilterRules.Eq("level5", Level5Filter), + FilterRules.Eq("locationId", LocationId), + FilterRules.Eq("hasFlaggedAnswers", HasFlaggedAnswers), + FilterRules.Eq("followUpStatus", FollowUpStatus?.Name), + FilterRules.Eq("hasNotes", HasNotes), + FilterRules.Eq("hasAttachments", HasAttachments), + FilterRules.Eq("questionsAnswered", QuestionsAnswered?.Name)); } diff --git a/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Response.cs b/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Response.cs index 991b2b0e3..cec332398 100644 --- a/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Response.cs +++ b/api/src/Feature.CitizenReports/GetSubmissionsAggregatedV2/Response.cs @@ -4,5 +4,5 @@ namespace Feature.CitizenReports.GetSubmissionsAggregatedV2; public class Response { - public CitizenReportFormSubmissionsAggregate SubmissionsAggregate { get; set; } = null!; + public CitizenReportFormSubmissionsAggregateV2 SubmissionsAggregate { get; set; } = null!; } diff --git a/api/src/Feature.Form.Submissions/GetAggregatedV2/Endpoint.cs b/api/src/Feature.Form.Submissions/GetAggregatedV2/Endpoint.cs index 49395fc56..0615d8e88 100644 --- a/api/src/Feature.Form.Submissions/GetAggregatedV2/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetAggregatedV2/Endpoint.cs @@ -34,10 +34,10 @@ public override async Task, NotFound>> ExecuteAsync(Request SELECT * FROM "GetFormSubmissionsAggregate"(@electionRoundId, @ngoId, @dataSource, @formId, @filter, @filterParameters::jsonb) """; - FormSubmissionsAggregate? aggregate; + FormSubmissionsAggregateV2? aggregate; using (var dbConnection = await connectionFactory.GetOpenConnectionAsync(ct)) { - aggregate = await dbConnection.QuerySingleOrDefaultAsync(new CommandDefinition(sql, new + aggregate = await dbConnection.QuerySingleOrDefaultAsync(new CommandDefinition(sql, new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, diff --git a/api/src/Feature.Form.Submissions/GetAggregatedV2/Response.cs b/api/src/Feature.Form.Submissions/GetAggregatedV2/Response.cs index c4be678b7..cdbde5f69 100644 --- a/api/src/Feature.Form.Submissions/GetAggregatedV2/Response.cs +++ b/api/src/Feature.Form.Submissions/GetAggregatedV2/Response.cs @@ -4,5 +4,5 @@ namespace Feature.Form.Submissions.GetAggregatedV2; public class Response { - public FormSubmissionsAggregate SubmissionsAggregate { get; set; } = null!; + public FormSubmissionsAggregateV2 SubmissionsAggregate { get; set; } = null!; } diff --git a/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs b/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs index 3a0803a69..0e0bc4e8e 100644 --- a/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs +++ b/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs @@ -4,6 +4,7 @@ namespace Feature.IncidentReports.GetSubmissionsAggregated; +[Obsolete("Use :aggregated-submissionsV2 instead.")] public class Endpoint( IAuthorizationService authorizationService, VoteMonitorContext context, @@ -19,6 +20,7 @@ public override void Configure() Summary(s => { s.Summary = "Gets aggregated incident report form submissions with all the notes and attachments"; + s.Description = "Obsolete: use :aggregated-submissionsV2 instead."; }); } diff --git a/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Endpoint.cs b/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Endpoint.cs index 814529128..ecabf6fe5 100644 --- a/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Endpoint.cs +++ b/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Endpoint.cs @@ -34,16 +34,16 @@ public override async Task, NotFound>> ExecuteAsync(Request } var filter = new FilterSqlCompiler(V2ReportFilterFields.IncidentReports, parameterPrefixer: FilterSqlCompiler.FunctionParameter) - .Build(req.Filter); + .Build(req.ToFilterRule()); const string sql = """ SELECT * FROM "GetIncidentReportsAggregate"(@electionRoundId, @ngoId, @dataSource, @formId, @filter, @filterParameters::jsonb) """; - FormSubmissionsAggregate? aggregate; + FormSubmissionsAggregateV2? aggregate; using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) { - aggregate = await dbConnection.QuerySingleOrDefaultAsync(new CommandDefinition(sql, new + aggregate = await dbConnection.QuerySingleOrDefaultAsync(new CommandDefinition(sql, new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, diff --git a/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Request.cs b/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Request.cs index 19b1dd186..88810a9e8 100644 --- a/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Request.cs +++ b/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Request.cs @@ -1,4 +1,7 @@ +using Vote.Monitor.Core.RulesEngine; +using Vote.Monitor.Core.RulesEngine.Rules; using Vote.Monitor.Core.Security; +using Vote.Monitor.Domain.Entities.IncidentReportAggregate; namespace Feature.IncidentReports.GetSubmissionsAggregatedV2; @@ -10,4 +13,39 @@ public class Request : BaseFilterConditionsRequest public Guid NgoId { get; set; } public Guid FormId { get; set; } + + [QueryParam] public string? Level1Filter { get; set; } + [QueryParam] public string? Level2Filter { get; set; } + [QueryParam] public string? Level3Filter { get; set; } + [QueryParam] public string? Level4Filter { get; set; } + [QueryParam] public string? Level5Filter { get; set; } + [QueryParam] public string? PollingStationNumberFilter { get; set; } + [QueryParam] public Guid? PollingStationId { get; set; } + [QueryParam] public bool? HasFlaggedAnswers { get; set; } + [QueryParam] public IncidentReportFollowUpStatus? FollowUpStatusFilter { get; set; } + [QueryParam] public IncidentReportLocationType? LocationTypeFilter { get; set; } + [QueryParam] public bool? HasNotes { get; set; } + [QueryParam] public bool? HasAttachments { get; set; } + [QueryParam] public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } + [QueryParam] public bool? IsCompletedFilter { get; set; } + + /// + /// combined with the query parameters. + /// + public FilterRule? ToFilterRule() => FilterRules.And( + Filter, + FilterRules.Eq("level1", Level1Filter), + FilterRules.Eq("level2", Level2Filter), + FilterRules.Eq("level3", Level3Filter), + FilterRules.Eq("level4", Level4Filter), + FilterRules.Eq("level5", Level5Filter), + FilterRules.Eq("pollingStationNumber", PollingStationNumberFilter), + FilterRules.Eq("pollingStationId", PollingStationId), + FilterRules.Eq("hasFlaggedAnswers", HasFlaggedAnswers), + FilterRules.Eq("followUpStatus", FollowUpStatusFilter?.Name), + FilterRules.Eq("locationType", LocationTypeFilter?.Name), + FilterRules.Eq("hasNotes", HasNotes), + FilterRules.Eq("hasAttachments", HasAttachments), + FilterRules.Eq("questionsAnswered", QuestionsAnswered?.Name), + FilterRules.Eq("isCompleted", IsCompletedFilter)); } diff --git a/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Response.cs b/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Response.cs index aa7c4ead8..c0023bfb7 100644 --- a/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Response.cs +++ b/api/src/Feature.IncidentReports/GetSubmissionsAggregatedV2/Response.cs @@ -4,5 +4,5 @@ namespace Feature.IncidentReports.GetSubmissionsAggregatedV2; public class Response { - public FormSubmissionsAggregate SubmissionsAggregate { get; set; } = null!; + public FormSubmissionsAggregateV2 SubmissionsAggregate { get; set; } = null!; } diff --git a/api/src/Module.Answers/Aggregators/AggregateLocation.cs b/api/src/Module.Answers/Aggregators/AggregateLocation.cs new file mode 100644 index 000000000..6d942054e --- /dev/null +++ b/api/src/Module.Answers/Aggregators/AggregateLocation.cs @@ -0,0 +1,35 @@ +namespace Module.Answers.Aggregators; + +/// +/// A Level1..Level5 path of the aggregated submissions, computed by "AggregateAnswersByLocation". +/// The question aggregates hold their breakdown per location in . +/// +public class AggregateLocation +{ + public int Id { get; init; } + + /// + /// 1..5: the deepest level of the path. + /// + public int Level { get; init; } + + /// + /// The location one level up with the same path; not set for Level1 locations. + /// + public int? ParentId { get; init; } + + public string? Level1 { get; init; } + public string? Level2 { get; init; } + public string? Level3 { get; init; } + public string? Level4 { get; init; } + public string? Level5 { get; init; } + + public int SubmissionCount { get; init; } + public int TotalNumberOfQuestionsAnswered { get; init; } + public int TotalNumberOfFlaggedAnswers { get; init; } + + /// + /// Number of distinct polling stations of the location's submissions. Not set for citizen reports. + /// + public int? PollingStationsCount { get; init; } +} diff --git a/api/src/Module.Answers/Aggregators/BaseAnswerAggregate.cs b/api/src/Module.Answers/Aggregators/BaseAnswerAggregate.cs index 8c0dfc303..4163ff919 100644 --- a/api/src/Module.Answers/Aggregators/BaseAnswerAggregate.cs +++ b/api/src/Module.Answers/Aggregators/BaseAnswerAggregate.cs @@ -25,9 +25,36 @@ public abstract class BaseAnswerAggregate [JsonInclude] public Guid QuestionId { get; private set; } public int DisplayOrder { get; set; } - [JsonInclude] public BaseQuestion Question { get; private set; } = null!; + /// + /// Not set on the aggregates of a location breakdown (see ). + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public BaseQuestion Question { get; private set; } = null!; [JsonInclude] public int AnswersAggregated { get; private set; } + /// + /// Ids of the question's notes in the aggregate's Notes. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList? NoteIds { get; private set; } + + /// + /// Ids of the question's attachments in the aggregate's Attachments. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList? AttachmentIds { get; private set; } + + /// + /// The question's aggregate per location id (see ), without the question itself and + /// referencing only the texts, notes and attachments of the location's submissions. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyDictionary? LocationAggregates { get; private set; } + /// /// Used when deserializing an aggregate computed by the database. /// diff --git a/api/src/Module.Answers/Aggregators/MultiSelectAnswerAggregate.cs b/api/src/Module.Answers/Aggregators/MultiSelectAnswerAggregate.cs index 7488901af..fc34ffbec 100644 --- a/api/src/Module.Answers/Aggregators/MultiSelectAnswerAggregate.cs +++ b/api/src/Module.Answers/Aggregators/MultiSelectAnswerAggregate.cs @@ -18,13 +18,39 @@ public IReadOnlyDictionary AnswersHistogram get => _answersHistogram.AsReadOnly(); private set => _answersHistogram = new Dictionary(value); } + + /// + /// Number of distinct submissions that picked each option. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyDictionary? SubmissionsHistogram { get; private set; } + + /// + /// Number of distinct polling stations where each option was picked. Only set when computed by the database, + /// and not for citizen reports. + /// [JsonInclude] - public IReadOnlyCollection FreeTexts + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyDictionary? PollingStationsHistogram { get; private set; } + /// + /// Not set when the free texts are referenced by id (). + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyCollection? FreeTexts { - get => _freeTexts.ToList().AsReadOnly(); - private set => _freeTexts = value.ToList(); + get => FreeTextIds is null ? _freeTexts.ToList().AsReadOnly() : null; + private set => _freeTexts = value?.ToList() ?? []; } + /// + /// Ids of the texts in the aggregate's Texts. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList? FreeTextIds { get; private set; } + [JsonConstructor] private MultiSelectAnswerAggregate() { diff --git a/api/src/Module.Answers/Aggregators/RatingAnswerAggregate.cs b/api/src/Module.Answers/Aggregators/RatingAnswerAggregate.cs index 4ddf7bfd1..1d0b9a4a8 100644 --- a/api/src/Module.Answers/Aggregators/RatingAnswerAggregate.cs +++ b/api/src/Module.Answers/Aggregators/RatingAnswerAggregate.cs @@ -18,6 +18,21 @@ public IReadOnlyDictionary AnswersHistogram private set => _answersHistogram = new Dictionary(value); } + /// + /// Number of distinct submissions that picked each rating value. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyDictionary? SubmissionsHistogram { get; private set; } + + /// + /// Number of distinct polling stations where each rating value was picked. Only set when computed by the database, + /// and not for citizen reports. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyDictionary? PollingStationsHistogram { get; private set; } + [JsonInclude] public int Min { get; private set; } = int.MaxValue; [JsonInclude] public int Max { get; private set; } = int.MinValue; [JsonInclude] public decimal Average { get; private set; } diff --git a/api/src/Module.Answers/Aggregators/SingleSelectAnswerAggregate.cs b/api/src/Module.Answers/Aggregators/SingleSelectAnswerAggregate.cs index 79d679adb..9b9066c72 100644 --- a/api/src/Module.Answers/Aggregators/SingleSelectAnswerAggregate.cs +++ b/api/src/Module.Answers/Aggregators/SingleSelectAnswerAggregate.cs @@ -16,13 +16,39 @@ public IReadOnlyDictionary AnswersHistogram get => _answersHistogram.AsReadOnly(); private set => _answersHistogram = new Dictionary(value); } + + /// + /// Number of distinct submissions that picked each option. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyDictionary? SubmissionsHistogram { get; private set; } + + /// + /// Number of distinct polling stations where each option was picked. Only set when computed by the database, + /// and not for citizen reports. + /// [JsonInclude] - public IReadOnlyList FreeTexts + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyDictionary? PollingStationsHistogram { get; private set; } + /// + /// Not set when the free texts are referenced by id (). + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList? FreeTexts { - get => _freeTexts.ToList().AsReadOnly(); - private set => _freeTexts = value.ToList(); + get => FreeTextIds is null ? _freeTexts.ToList().AsReadOnly() : null; + private set => _freeTexts = value?.ToList() ?? []; } + /// + /// Ids of the texts in the aggregate's Texts. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList? FreeTextIds { get; private set; } + [JsonConstructor] private SingleSelectAnswerAggregate() { diff --git a/api/src/Module.Answers/Aggregators/SubmissionsAggregatesV2.cs b/api/src/Module.Answers/Aggregators/SubmissionsAggregatesV2.cs new file mode 100644 index 000000000..71f626352 --- /dev/null +++ b/api/src/Module.Answers/Aggregators/SubmissionsAggregatesV2.cs @@ -0,0 +1,101 @@ +using System.Text.Json.Serialization; +using Ardalis.SmartEnum.SystemTextJson; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Domain.Entities.FormBase; + +namespace Module.Answers.Aggregators; + +/// +/// A text answer or free text of an aggregate, referenced by id from , +/// and . +/// +public record AggregateText(int Id, Guid SubmissionId, Guid ResponderId, string Value); + +/// +/// A note of an aggregate, referenced by id from . +/// +public record AggregateNote : NoteModel +{ + public int Id { get; init; } +} + +/// +/// An attachment of an aggregate, referenced by id from . +/// +public record AggregateAttachment : AggregatedSubmissionsAttachmentModel +{ + public int Id { get; init; } +} + +/// +/// Form submissions / incident reports aggregate computed by "GetFormSubmissionsAggregate" / "GetIncidentReportsAggregate". +/// The question aggregates reference , , and +/// by id. +/// +public class FormSubmissionsAggregateV2 +{ + public Guid FormId { get; init; } + public string FormCode { get; init; } = null!; + + [JsonConverter(typeof(SmartEnumNameConverter))] + public FormType FormType { get; init; } = null!; + + public TranslatedString Name { get; init; } = null!; + public TranslatedString Description { get; init; } = null!; + public string DefaultLanguage { get; init; } = null!; + public string[] Languages { get; init; } = []; + public IReadOnlyList Responders { get; init; } = []; + public int SubmissionCount { get; init; } + public int TotalNumberOfQuestionsAnswered { get; init; } + public int TotalNumberOfFlaggedAnswers { get; init; } + public int PollingStationsCount { get; init; } + + /// + /// Aggregated answers per question id + /// + public IReadOnlyDictionary Aggregates { get; init; } = null!; + + public AggregateText[] Texts { get; init; } = []; + public AggregateNote[] Notes { get; init; } = []; + public AggregateAttachment[] Attachments { get; set; } = []; + + /// + /// Level1..Level5 paths of the submissions, referenced by + /// + public AggregateLocation[] Locations { get; init; } = []; +} + +/// +/// Citizen reports aggregate computed by "GetCitizenReportsAggregate". +/// The question aggregates reference , , and +/// by id. +/// +public class CitizenReportFormSubmissionsAggregateV2 +{ + public Guid ElectionRoundId { get; init; } + public Guid MonitoringNgoId { get; init; } + public Guid FormId { get; init; } + public string FormCode { get; init; } = null!; + public FormType FormType { get; init; } = null!; + public TranslatedString Name { get; init; } = null!; + public TranslatedString Description { get; init; } = null!; + public string DefaultLanguage { get; init; } = null!; + public string[] Languages { get; init; } = []; + public int SubmissionCount { get; init; } + public int TotalNumberOfQuestionsAnswered { get; init; } + public int TotalNumberOfFlaggedAnswers { get; init; } + + /// + /// Aggregated answers per question id + /// + public IReadOnlyDictionary Aggregates { get; init; } = null!; + + public AggregateText[] Texts { get; init; } = []; + public AggregateNote[] Notes { get; init; } = []; + public AggregateAttachment[] Attachments { get; set; } = []; + + /// + /// Level1..Level5 paths of the submissions, referenced by + /// + public AggregateLocation[] Locations { get; init; } = []; +} diff --git a/api/src/Module.Answers/Aggregators/TextAnswerAggregate.cs b/api/src/Module.Answers/Aggregators/TextAnswerAggregate.cs index bad47a725..1597010d7 100644 --- a/api/src/Module.Answers/Aggregators/TextAnswerAggregate.cs +++ b/api/src/Module.Answers/Aggregators/TextAnswerAggregate.cs @@ -20,13 +20,24 @@ private TextAnswerAggregate() private List _answers = new(); + /// + /// Not set when the texts are referenced by id (). + /// [JsonInclude] - public IReadOnlyList Answers + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList? Answers { - get => _answers.AsReadOnly(); - private set => _answers = value.ToList(); + get => AnswerIds is null ? _answers.AsReadOnly() : null; + private set => _answers = value?.ToList() ?? []; } + /// + /// Ids of the texts in the aggregate's Texts. Only set when computed by the database. + /// + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList? AnswerIds { get; private set; } + protected override void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswer answer) { if (answer is not TextAnswer textAnswer) diff --git a/api/src/Vote.Monitor.Api/Program.cs b/api/src/Vote.Monitor.Api/Program.cs index 074368543..cefd7d74c 100644 --- a/api/src/Vote.Monitor.Api/Program.cs +++ b/api/src/Vote.Monitor.Api/Program.cs @@ -265,6 +265,10 @@ SqlMapper.AddTypeHandler(typeof(TranslatedString), new JsonToObjectConverter()); // jsonb columns of the submissions aggregates ("GetFormSubmissionsAggregate", "GetIncidentReportsAggregate", "GetCitizenReportsAggregate") SqlMapper.AddTypeHandler(typeof(IReadOnlyDictionary), new JsonToObjectConverter>()); +SqlMapper.AddTypeHandler(typeof(AggregateLocation[]), new JsonToObjectConverter()); +SqlMapper.AddTypeHandler(typeof(AggregateText[]), new JsonToObjectConverter()); +SqlMapper.AddTypeHandler(typeof(AggregateNote[]), new JsonToObjectConverter()); +SqlMapper.AddTypeHandler(typeof(AggregateAttachment[]), new JsonToObjectConverter()); SqlMapper.AddTypeHandler(typeof(IReadOnlyList), new JsonToObjectConverter>()); SqlMapper.AddTypeHandler(typeof(Vote.Monitor.Core.Models.NoteModel[]), new JsonToObjectConverter()); SqlMapper.AddTypeHandler(typeof(AggregatedSubmissionsAttachmentModel[]), new JsonToObjectConverter()); diff --git a/api/src/Vote.Monitor.Core/RulesEngine/FilterRules.cs b/api/src/Vote.Monitor.Core/RulesEngine/FilterRules.cs new file mode 100644 index 000000000..67da91cd9 --- /dev/null +++ b/api/src/Vote.Monitor.Core/RulesEngine/FilterRules.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using Vote.Monitor.Core.RulesEngine.Rules; + +namespace Vote.Monitor.Core.RulesEngine; + +/// +/// Builds filter rules in code, e.g. to combine legacy query parameters with a request's filter. +/// +public static class FilterRules +{ + /// + /// field = value, or null when there is no value (so an unset optional parameter adds no rule). + /// Blank strings count as no value. + /// + public static FilterRule? Eq(string field, object? value) + { + if (value is null || value is string text && string.IsNullOrWhiteSpace(text)) + { + return null; + } + + return new EqFilter { Field = field, Value = JsonSerializer.SerializeToElement(value) }; + } + + /// + /// All the given rules, ignoring nulls; null when there are none. + /// + public static FilterRule? And(params FilterRule?[] rules) + { + var values = rules.OfType().ToList(); + return values.Count switch + { + 0 => null, + 1 => values[0], + _ => new AndFilter { Values = values } + }; + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20261017104516_AddLocationAggregates.Designer.cs b/api/src/Vote.Monitor.Domain/Migrations/20261017104516_AddLocationAggregates.Designer.cs new file mode 100644 index 000000000..1f5dfccc6 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20261017104516_AddLocationAggregates.Designer.cs @@ -0,0 +1,7325 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vote.Monitor.Domain; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + [DbContext(typeof(VoteMonitorContext))] + [Migration("20261017104516_AddLocationAggregates")] + partial class AddLocationAggregates + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("265e94b0-50fe-4546-b21c-83cb7e94aeff"), + Name = "PlatformAdmin", + NormalizedName = "PLATFORMADMIN" + }, + new + { + Id = new Guid("3239f803-dda8-408b-93ad-0ed973a04e45"), + Name = "NgoAdmin", + NormalizedName = "NGOADMIN" + }, + new + { + Id = new Guid("d1cbef39-62e0-4120-a42b-b01b029dc6ad"), + Name = "Observer", + NormalizedName = "OBSERVER" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("text") + .HasComputedColumnSql("\"FirstName\" || ' ' || \"LastName\"", true); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitationToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("Preferences") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RefreshToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("SubmissionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("Attachments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.Auditing.Trail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AffectedColumns") + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NewValues") + .HasColumnType("text"); + + b.Property("OldValues") + .HasColumnType("text"); + + b.Property("PrimaryKey") + .HasColumnType("text"); + + b.Property("TableName") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("AuditTrails"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("CitizenNotifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("LocationId"); + + b.ToTable("CitizenReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportCommentAggregate.CitizenReportComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId", "CitizenReportId"); + + b.ToTable("CitizenReportComments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("LeaderId"); + + b.ToTable("Coalitions"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "FormId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionFormAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionGuideAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("GuideId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "GuideId"); + + b.HasIndex("GuideId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionGuideAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.HasKey("MonitoringNgoId", "CoalitionId"); + + b.HasIndex("CoalitionId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId", "ElectionRoundId") + .IsUnique(); + + b.HasIndex("MonitoringNgoId", "CoalitionId", "ElectionRoundId") + .IsUnique(); + + b.ToTable("CoalitionMemberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.HasKey("Id"); + + b.HasIndex("Iso2") + .IsUnique(); + + b.HasIndex("Iso3") + .IsUnique(); + + b.HasIndex("NumericCode") + .IsUnique(); + + b.ToTable("Countries"); + + b.HasData( + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Afghanistan", + Iso2 = "AF", + Iso3 = "AFG", + Name = "Afghanistan", + NumericCode = "004" + }, + new + { + Id = new Guid("a96fe9bb-4ef4-fca0-f38b-0ec729822f37"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Åland Islands", + Iso2 = "AX", + Iso3 = "ALA", + Name = "Åland Islands", + NumericCode = "248" + }, + new + { + Id = new Guid("5aa0aeb7-4dc8-6a29-fc2f-35daec1541dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Albania", + Iso2 = "AL", + Iso3 = "ALB", + Name = "Albania", + NumericCode = "008" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Democratic Republic of Algeria", + Iso2 = "DZ", + Iso3 = "DZA", + Name = "Algeria", + NumericCode = "012" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "American Samoa", + Iso2 = "AS", + Iso3 = "ASM", + Name = "American Samoa", + NumericCode = "016" + }, + new + { + Id = new Guid("bd4bbfc7-d8bc-9d8d-7f7c-7b299c94e9e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Andorra", + Iso2 = "AD", + Iso3 = "AND", + Name = "Andorra", + NumericCode = "020" + }, + new + { + Id = new Guid("478786f7-1842-8c1e-921c-12e7ed5329c5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Angola", + Iso2 = "AO", + Iso3 = "AGO", + Name = "Angola", + NumericCode = "024" + }, + new + { + Id = new Guid("2b68fb11-a0e0-3d23-5fb8-99721ecfc182"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Anguilla", + Iso2 = "AI", + Iso3 = "AIA", + Name = "Anguilla", + NumericCode = "660" + }, + new + { + Id = new Guid("a0098040-b7a0-59a1-e64b-0a9778b7f74c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antarctica (the territory South of 60 deg S)", + Iso2 = "AQ", + Iso3 = "ATA", + Name = "Antarctica", + NumericCode = "010" + }, + new + { + Id = new Guid("f3eef99a-661e-2c68-7a4c-3053e2f28007"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antigua and Barbuda", + Iso2 = "AG", + Iso3 = "ATG", + Name = "Antigua and Barbuda", + NumericCode = "028" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Argentine Republic", + Iso2 = "AR", + Iso3 = "ARG", + Name = "Argentina", + NumericCode = "032" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Armenia", + Iso2 = "AM", + Iso3 = "ARM", + Name = "Armenia", + NumericCode = "051" + }, + new + { + Id = new Guid("e6c7651f-182e-cf9c-1ef9-6293b95b500c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Aruba", + Iso2 = "AW", + Iso3 = "ABW", + Name = "Aruba", + NumericCode = "533" + }, + new + { + Id = new Guid("15639386-e4fc-120c-6916-c0c980e24be1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Australia", + Iso2 = "AU", + Iso3 = "AUS", + Name = "Australia", + NumericCode = "036" + }, + new + { + Id = new Guid("704254eb-6959-8ddc-a5df-ac8f9658dc68"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Austria", + Iso2 = "AT", + Iso3 = "AUT", + Name = "Austria", + NumericCode = "040" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Azerbaijan", + Iso2 = "AZ", + Iso3 = "AZE", + Name = "Azerbaijan", + NumericCode = "031" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Bahamas", + Iso2 = "BS", + Iso3 = "BHS", + Name = "Bahamas", + NumericCode = "044" + }, + new + { + Id = new Guid("44caa0f4-1e78-d2fb-96be-d01b3224bdc1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bahrain", + Iso2 = "BH", + Iso3 = "BHR", + Name = "Bahrain", + NumericCode = "048" + }, + new + { + Id = new Guid("809c3424-8654-b82c-cbd4-d857d096943e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of Bangladesh", + Iso2 = "BD", + Iso3 = "BGD", + Name = "Bangladesh", + NumericCode = "050" + }, + new + { + Id = new Guid("316c68fc-9144-f6e1-8bf1-899fc54b2327"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Barbados", + Iso2 = "BB", + Iso3 = "BRB", + Name = "Barbados", + NumericCode = "052" + }, + new + { + Id = new Guid("d97b5460-11ab-45c5-9a6f-ffa441ed70d6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Belarus", + Iso2 = "BY", + Iso3 = "BLR", + Name = "Belarus", + NumericCode = "112" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Belgium", + Iso2 = "BE", + Iso3 = "BEL", + Name = "Belgium", + NumericCode = "056" + }, + new + { + Id = new Guid("c89e02a0-9506-90df-5545-b98a2453cd63"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Belize", + Iso2 = "BZ", + Iso3 = "BLZ", + Name = "Belize", + NumericCode = "084" + }, + new + { + Id = new Guid("96a22cee-9af7-8f03-b483-b3e774a36d3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Benin", + Iso2 = "BJ", + Iso3 = "BEN", + Name = "Benin", + NumericCode = "204" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bermuda", + Iso2 = "BM", + Iso3 = "BMU", + Name = "Bermuda", + NumericCode = "060" + }, + new + { + Id = new Guid("8ed6a34e-8135-27fa-f86a-caa247b29768"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bhutan", + Iso2 = "BT", + Iso3 = "BTN", + Name = "Bhutan", + NumericCode = "064" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Plurinational State of Bolivia", + Iso2 = "BO", + Iso3 = "BOL", + Name = "Bolivia", + NumericCode = "068" + }, + new + { + Id = new Guid("d8101f9d-8313-4054-c5f3-42c7a1c72862"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bonaire, Sint Eustatius and Saba", + Iso2 = "BQ", + Iso3 = "BES", + Name = "Bonaire, Sint Eustatius and Saba", + NumericCode = "535" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bosnia and Herzegovina", + Iso2 = "BA", + Iso3 = "BIH", + Name = "Bosnia and Herzegovina", + NumericCode = "070" + }, + new + { + Id = new Guid("14f190c6-97c9-3e12-2eba-db17c59d6a04"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Botswana", + Iso2 = "BW", + Iso3 = "BWA", + Name = "Botswana", + NumericCode = "072" + }, + new + { + Id = new Guid("32da0208-9048-1339-a8ee-6955cfff4c12"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bouvet Island (Bouvetøya)", + Iso2 = "BV", + Iso3 = "BVT", + Name = "Bouvet Island (Bouvetøya)", + NumericCode = "074" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federative Republic of Brazil", + Iso2 = "BR", + Iso3 = "BRA", + Name = "Brazil", + NumericCode = "076" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Indian Ocean Territory (Chagos Archipelago)", + Iso2 = "IO", + Iso3 = "IOT", + Name = "British Indian Ocean Territory (Chagos Archipelago)", + NumericCode = "086" + }, + new + { + Id = new Guid("39be5e86-aea5-f64f-fd7e-1017fe24e543"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Virgin Islands", + Iso2 = "VG", + Iso3 = "VGB", + Name = "British Virgin Islands", + NumericCode = "092" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Brunei Darussalam", + Iso2 = "BN", + Iso3 = "BRN", + Name = "Brunei Darussalam", + NumericCode = "096" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Bulgaria", + Iso2 = "BG", + Iso3 = "BGR", + Name = "Bulgaria", + NumericCode = "100" + }, + new + { + Id = new Guid("42697d56-52cf-b411-321e-c51929f02f90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Burkina Faso", + Iso2 = "BF", + Iso3 = "BFA", + Name = "Burkina Faso", + NumericCode = "854" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Burundi", + Iso2 = "BI", + Iso3 = "BDI", + Name = "Burundi", + NumericCode = "108" + }, + new + { + Id = new Guid("c9702851-1f67-f2a6-89d4-37b3fbb12044"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Cambodia", + Iso2 = "KH", + Iso3 = "KHM", + Name = "Cambodia", + NumericCode = "116" + }, + new + { + Id = new Guid("c0b7e39e-223a-ebb0-b899-5404573bbdb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cameroon", + Iso2 = "CM", + Iso3 = "CMR", + Name = "Cameroon", + NumericCode = "120" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Canada", + Iso2 = "CA", + Iso3 = "CAN", + Name = "Canada", + NumericCode = "124" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cabo Verde", + Iso2 = "CV", + Iso3 = "CPV", + Name = "Cabo Verde", + NumericCode = "132" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cayman Islands", + Iso2 = "KY", + Iso3 = "CYM", + Name = "Cayman Islands", + NumericCode = "136" + }, + new + { + Id = new Guid("b4e0625c-7597-c185-b8ae-cfb35a731f2f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Central African Republic", + Iso2 = "CF", + Iso3 = "CAF", + Name = "Central African Republic", + NumericCode = "140" + }, + new + { + Id = new Guid("2a1ca5b6-fba0-cfa8-9928-d7a2382bc4d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chad", + Iso2 = "TD", + Iso3 = "TCD", + Name = "Chad", + NumericCode = "148" + }, + new + { + Id = new Guid("ad4f938a-bf7b-684b-2c9e-e824d3fa3863"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chile", + Iso2 = "CL", + Iso3 = "CHL", + Name = "Chile", + NumericCode = "152" + }, + new + { + Id = new Guid("8250c49f-9438-7c2e-f403-54d962db0c18"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of China", + Iso2 = "CN", + Iso3 = "CHN", + Name = "China", + NumericCode = "156" + }, + new + { + Id = new Guid("0f1ba59e-ade5-23e5-6fce-e2fd3282e114"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Christmas Island", + Iso2 = "CX", + Iso3 = "CXR", + Name = "Christmas Island", + NumericCode = "162" + }, + new + { + Id = new Guid("a16263a5-810c-bf6a-206d-72cb914e2d5c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cocos (Keeling) Islands", + Iso2 = "CC", + Iso3 = "CCK", + Name = "Cocos (Keeling) Islands", + NumericCode = "166" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Colombia", + Iso2 = "CO", + Iso3 = "COL", + Name = "Colombia", + NumericCode = "170" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Union of the Comoros", + Iso2 = "KM", + Iso3 = "COM", + Name = "Comoros", + NumericCode = "174" + }, + new + { + Id = new Guid("1258ec90-c47e-ff72-b7e3-f90c3ee320f8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of the Congo", + Iso2 = "CD", + Iso3 = "COD", + Name = "Congo", + NumericCode = "180" + }, + new + { + Id = new Guid("1934954c-66c2-6226-c5b6-491065a3e4c0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Congo", + Iso2 = "CG", + Iso3 = "COG", + Name = "Congo", + NumericCode = "178" + }, + new + { + Id = new Guid("af79558d-51fb-b08d-185b-afeb983ab99b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cook Islands", + Iso2 = "CK", + Iso3 = "COK", + Name = "Cook Islands", + NumericCode = "184" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Costa Rica", + Iso2 = "CR", + Iso3 = "CRI", + Name = "Costa Rica", + NumericCode = "188" + }, + new + { + Id = new Guid("5be18efe-6db8-a727-7f2a-62bd71bc6593"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cote d'Ivoire", + Iso2 = "CI", + Iso3 = "CIV", + Name = "Cote d'Ivoire", + NumericCode = "384" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Croatia", + Iso2 = "HR", + Iso3 = "HRV", + Name = "Croatia", + NumericCode = "191" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cuba", + Iso2 = "CU", + Iso3 = "CUB", + Name = "Cuba", + NumericCode = "192" + }, + new + { + Id = new Guid("3345e205-3e72-43ed-de1b-ac6e050543e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Curaçao", + Iso2 = "CW", + Iso3 = "CUW", + Name = "Curaçao", + NumericCode = "531" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cyprus", + Iso2 = "CY", + Iso3 = "CYP", + Name = "Cyprus", + NumericCode = "196" + }, + new + { + Id = new Guid("9d4ec95b-974a-f5bb-bb4b-ba6747440631"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Czech Republic", + Iso2 = "CZ", + Iso3 = "CZE", + Name = "Czechia", + NumericCode = "203" + }, + new + { + Id = new Guid("8a4fcb23-f3e6-fb5b-8cda-975872f600d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Denmark", + Iso2 = "DK", + Iso3 = "DNK", + Name = "Denmark", + NumericCode = "208" + }, + new + { + Id = new Guid("37a79267-d38a-aaef-577a-aa68a96880ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Djibouti", + Iso2 = "DJ", + Iso3 = "DJI", + Name = "Djibouti", + NumericCode = "262" + }, + new + { + Id = new Guid("19ea3a6a-1a76-23c8-8e4e-1d298f15207f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Dominica", + Iso2 = "DM", + Iso3 = "DMA", + Name = "Dominica", + NumericCode = "212" + }, + new + { + Id = new Guid("b2c4d2d7-7ada-7864-426f-10a28d9f9eba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Dominican Republic", + Iso2 = "DO", + Iso3 = "DOM", + Name = "Dominican Republic", + NumericCode = "214" + }, + new + { + Id = new Guid("49c82f1b-968d-b5e7-8559-e39567d46787"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ecuador", + Iso2 = "EC", + Iso3 = "ECU", + Name = "Ecuador", + NumericCode = "218" + }, + new + { + Id = new Guid("ee5dfc29-80f1-86ae-cde7-02484a18907a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Arab Republic of Egypt", + Iso2 = "EG", + Iso3 = "EGY", + Name = "Egypt", + NumericCode = "818" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of El Salvador", + Iso2 = "SV", + Iso3 = "SLV", + Name = "El Salvador", + NumericCode = "222" + }, + new + { + Id = new Guid("824392e8-a6cc-0cd4-af13-3067dad3258e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Equatorial Guinea", + Iso2 = "GQ", + Iso3 = "GNQ", + Name = "Equatorial Guinea", + NumericCode = "226" + }, + new + { + Id = new Guid("8b5a477a-070a-a84f-bd3b-f54dc2a172de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Eritrea", + Iso2 = "ER", + Iso3 = "ERI", + Name = "Eritrea", + NumericCode = "232" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Estonia", + Iso2 = "EE", + Iso3 = "EST", + Name = "Estonia", + NumericCode = "233" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Democratic Republic of Ethiopia", + Iso2 = "ET", + Iso3 = "ETH", + Name = "Ethiopia", + NumericCode = "231" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Faroe Islands", + Iso2 = "FO", + Iso3 = "FRO", + Name = "Faroe Islands", + NumericCode = "234" + }, + new + { + Id = new Guid("b86375dc-edbb-922c-9ed4-2f724094a5a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Falkland Islands (Malvinas)", + Iso2 = "FK", + Iso3 = "FLK", + Name = "Falkland Islands (Malvinas)", + NumericCode = "238" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Fiji", + Iso2 = "FJ", + Iso3 = "FJI", + Name = "Fiji", + NumericCode = "242" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Finland", + Iso2 = "FI", + Iso3 = "FIN", + Name = "Finland", + NumericCode = "246" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Republic", + Iso2 = "FR", + Iso3 = "FRA", + Name = "France", + NumericCode = "250" + }, + new + { + Id = new Guid("ac6cde6e-f645-d04e-8afc-0391ecf38a70"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Guiana", + Iso2 = "GF", + Iso3 = "GUF", + Name = "French Guiana", + NumericCode = "254" + }, + new + { + Id = new Guid("11dbce82-a154-7aee-7b5e-d5981f220572"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Polynesia", + Iso2 = "PF", + Iso3 = "PYF", + Name = "French Polynesia", + NumericCode = "258" + }, + new + { + Id = new Guid("903bee63-bcf0-0264-6eaf-a8cde95c5f41"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Southern Territories", + Iso2 = "TF", + Iso3 = "ATF", + Name = "French Southern Territories", + NumericCode = "260" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gabonese Republic", + Iso2 = "GA", + Iso3 = "GAB", + Name = "Gabon", + NumericCode = "266" + }, + new + { + Id = new Guid("a40b91b3-cc13-2470-65f0-a0fdc946f2a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Gambia", + Iso2 = "GM", + Iso3 = "GMB", + Name = "Gambia", + NumericCode = "270" + }, + new + { + Id = new Guid("980176e8-7d9d-9729-b3e9-ebc455fb8fc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Georgia", + Iso2 = "GE", + Iso3 = "GEO", + Name = "Georgia", + NumericCode = "268" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Germany", + Iso2 = "DE", + Iso3 = "DEU", + Name = "Germany", + NumericCode = "276" + }, + new + { + Id = new Guid("6d0c77a7-a4aa-c2bd-2db6-0e2ad2d61f8a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ghana", + Iso2 = "GH", + Iso3 = "GHA", + Name = "Ghana", + NumericCode = "288" + }, + new + { + Id = new Guid("8e0de349-f9ab-2bca-3910-efd48bf1170a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gibraltar", + Iso2 = "GI", + Iso3 = "GIB", + Name = "Gibraltar", + NumericCode = "292" + }, + new + { + Id = new Guid("4fc1a9dc-cc74-f6ce-5743-c5cee8d709ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hellenic Republic of Greece", + Iso2 = "GR", + Iso3 = "GRC", + Name = "Greece", + NumericCode = "300" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Greenland", + Iso2 = "GL", + Iso3 = "GRL", + Name = "Greenland", + NumericCode = "304" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grenada", + Iso2 = "GD", + Iso3 = "GRD", + Name = "Grenada", + NumericCode = "308" + }, + new + { + Id = new Guid("3bcd2aad-fb69-09f4-1ad7-2c7f5fa23f9f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guadeloupe", + Iso2 = "GP", + Iso3 = "GLP", + Name = "Guadeloupe", + NumericCode = "312" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guam", + Iso2 = "GU", + Iso3 = "GUM", + Name = "Guam", + NumericCode = "316" + }, + new + { + Id = new Guid("d24b46ba-8e9d-2a09-7995-e35e8ae54f6b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guatemala", + Iso2 = "GT", + Iso3 = "GTM", + Name = "Guatemala", + NumericCode = "320" + }, + new + { + Id = new Guid("5b0ee3be-596d-bdc1-f101-00ef33170655"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Guernsey", + Iso2 = "GG", + Iso3 = "GGY", + Name = "Guernsey", + NumericCode = "831" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea", + Iso2 = "GN", + Iso3 = "GIN", + Name = "Guinea", + NumericCode = "324" + }, + new + { + Id = new Guid("a9a5f440-a9bd-487d-e7f4-914df0d52fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea-Bissau", + Iso2 = "GW", + Iso3 = "GNB", + Name = "Guinea-Bissau", + NumericCode = "624" + }, + new + { + Id = new Guid("a9949ac7-8d2d-32b5-3f4f-e2a3ef291a67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Co-operative Republic of Guyana", + Iso2 = "GY", + Iso3 = "GUY", + Name = "Guyana", + NumericCode = "328" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Haiti", + Iso2 = "HT", + Iso3 = "HTI", + Name = "Haiti", + NumericCode = "332" + }, + new + { + Id = new Guid("592b4658-a210-ab0a-5660-3dcc673dc581"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Heard Island and McDonald Islands", + Iso2 = "HM", + Iso3 = "HMD", + Name = "Heard Island and McDonald Islands", + NumericCode = "334" + }, + new + { + Id = new Guid("d0e11a85-6623-69f5-bd95-3779dfeec297"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Holy See (Vatican City State)", + Iso2 = "VA", + Iso3 = "VAT", + Name = "Holy See (Vatican City State)", + NumericCode = "336" + }, + new + { + Id = new Guid("0aebadaa-91b2-8794-c153-4f903a2a1004"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Honduras", + Iso2 = "HN", + Iso3 = "HND", + Name = "Honduras", + NumericCode = "340" + }, + new + { + Id = new Guid("500bb0de-61f5-dc9b-0488-1c507456ea4d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hong Kong Special Administrative Region of China", + Iso2 = "HK", + Iso3 = "HKG", + Name = "Hong Kong", + NumericCode = "344" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hungary", + Iso2 = "HU", + Iso3 = "HUN", + Name = "Hungary", + NumericCode = "348" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Iceland", + Iso2 = "IS", + Iso3 = "ISL", + Name = "Iceland", + NumericCode = "352" + }, + new + { + Id = new Guid("72d8d1fe-d5f6-f440-1185-82ec69427027"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of India", + Iso2 = "IN", + Iso3 = "IND", + Name = "India", + NumericCode = "356" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Indonesia", + Iso2 = "ID", + Iso3 = "IDN", + Name = "Indonesia", + NumericCode = "360" + }, + new + { + Id = new Guid("b3460bab-2a35-57bc-17e2-4e117748bbb1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Iran", + Iso2 = "IR", + Iso3 = "IRN", + Name = "Iran", + NumericCode = "364" + }, + new + { + Id = new Guid("6c8be2e6-8c2e-cd80-68a6-d18c80d0eedc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Iraq", + Iso2 = "IQ", + Iso3 = "IRQ", + Name = "Iraq", + NumericCode = "368" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ireland", + Iso2 = "IE", + Iso3 = "IRL", + Name = "Ireland", + NumericCode = "372" + }, + new + { + Id = new Guid("a1b83be0-6a9b-c8a9-2cce-531705a29664"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Isle of Man", + Iso2 = "IM", + Iso3 = "IMN", + Name = "Isle of Man", + NumericCode = "833" + }, + new + { + Id = new Guid("7ffa909b-8a6a-3028-9589-fcc3dfa530a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Israel", + Iso2 = "IL", + Iso3 = "ISR", + Name = "Israel", + NumericCode = "376" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Italy", + Iso2 = "IT", + Iso3 = "ITA", + Name = "Italy", + NumericCode = "380" + }, + new + { + Id = new Guid("6699efd5-0939-7812-315e-21f37b279ee9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Jamaica", + Iso2 = "JM", + Iso3 = "JAM", + Name = "Jamaica", + NumericCode = "388" + }, + new + { + Id = new Guid("13c69e56-375d-8a7e-c326-be2be2fd4cd8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Japan", + Iso2 = "JP", + Iso3 = "JPN", + Name = "Japan", + NumericCode = "392" + }, + new + { + Id = new Guid("65d871be-4a1d-a632-9cdb-62e3ff04928d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Jersey", + Iso2 = "JE", + Iso3 = "JEY", + Name = "Jersey", + NumericCode = "832" + }, + new + { + Id = new Guid("9ae7ad80-9ce7-6657-75cf-28b4c0254238"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hashemite Kingdom of Jordan", + Iso2 = "JO", + Iso3 = "JOR", + Name = "Jordan", + NumericCode = "400" + }, + new + { + Id = new Guid("b723594d-7800-0f37-db86-0f6b85bb6cf9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kazakhstan", + Iso2 = "KZ", + Iso3 = "KAZ", + Name = "Kazakhstan", + NumericCode = "398" + }, + new + { + Id = new Guid("b32fe2b5-a06e-0d76-ffd2-f186c3e64b15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kenya", + Iso2 = "KE", + Iso3 = "KEN", + Name = "Kenya", + NumericCode = "404" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kiribati", + Iso2 = "KI", + Iso3 = "KIR", + Name = "Kiribati", + NumericCode = "296" + }, + new + { + Id = new Guid("f70ae426-f130-5637-0383-a5b63a06c500"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic People's Republic of Korea", + Iso2 = "KP", + Iso3 = "PRK", + Name = "Korea", + NumericCode = "408" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Korea", + Iso2 = "KR", + Iso3 = "KOR", + Name = "Korea", + NumericCode = "410" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Kuwait", + Iso2 = "KW", + Iso3 = "KWT", + Name = "Kuwait", + NumericCode = "414" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kyrgyz Republic", + Iso2 = "KG", + Iso3 = "KGZ", + Name = "Kyrgyz Republic", + NumericCode = "417" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lao People's Democratic Republic", + Iso2 = "LA", + Iso3 = "LAO", + Name = "Lao People's Democratic Republic", + NumericCode = "418" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Latvia", + Iso2 = "LV", + Iso3 = "LVA", + Name = "Latvia", + NumericCode = "428" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lebanese Republic", + Iso2 = "LB", + Iso3 = "LBN", + Name = "Lebanon", + NumericCode = "422" + }, + new + { + Id = new Guid("bf210ee6-6c75-cf08-052e-5c3e608aed15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Lesotho", + Iso2 = "LS", + Iso3 = "LSO", + Name = "Lesotho", + NumericCode = "426" + }, + new + { + Id = new Guid("ee926d09-799c-7c6a-2419-a6ff814b2c03"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Liberia", + Iso2 = "LR", + Iso3 = "LBR", + Name = "Liberia", + NumericCode = "430" + }, + new + { + Id = new Guid("695c85b3-a6c6-c217-9be8-3baebc7719ce"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Libya", + Iso2 = "LY", + Iso3 = "LBY", + Name = "Libya", + NumericCode = "434" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Liechtenstein", + Iso2 = "LI", + Iso3 = "LIE", + Name = "Liechtenstein", + NumericCode = "438" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Lithuania", + Iso2 = "LT", + Iso3 = "LTU", + Name = "Lithuania", + NumericCode = "440" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grand Duchy of Luxembourg", + Iso2 = "LU", + Iso3 = "LUX", + Name = "Luxembourg", + NumericCode = "442" + }, + new + { + Id = new Guid("8d32a12d-3230-1431-8fbb-72c789184345"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Macao Special Administrative Region of China", + Iso2 = "MO", + Iso3 = "MAC", + Name = "Macao", + NumericCode = "446" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Madagascar", + Iso2 = "MG", + Iso3 = "MDG", + Name = "Madagascar", + NumericCode = "450" + }, + new + { + Id = new Guid("fbf4479d-d70d-c76e-b053-699362443a17"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malawi", + Iso2 = "MW", + Iso3 = "MWI", + Name = "Malawi", + NumericCode = "454" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Malaysia", + Iso2 = "MY", + Iso3 = "MYS", + Name = "Malaysia", + NumericCode = "458" + }, + new + { + Id = new Guid("1d2aa3ab-e1c3-8c76-9be6-7a3b3eca35da"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Maldives", + Iso2 = "MV", + Iso3 = "MDV", + Name = "Maldives", + NumericCode = "462" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mali", + Iso2 = "ML", + Iso3 = "MLI", + Name = "Mali", + NumericCode = "466" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malta", + Iso2 = "MT", + Iso3 = "MLT", + Name = "Malta", + NumericCode = "470" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Marshall Islands", + Iso2 = "MH", + Iso3 = "MHL", + Name = "Marshall Islands", + NumericCode = "584" + }, + new + { + Id = new Guid("fc78fa89-b372-dcf7-7f1c-1e1bb14ecbe7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Martinique", + Iso2 = "MQ", + Iso3 = "MTQ", + Name = "Martinique", + NumericCode = "474" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Mauritania", + Iso2 = "MR", + Iso3 = "MRT", + Name = "Mauritania", + NumericCode = "478" + }, + new + { + Id = new Guid("1b634ca2-2b90-7e54-715a-74cee7e4d294"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mauritius", + Iso2 = "MU", + Iso3 = "MUS", + Name = "Mauritius", + NumericCode = "480" + }, + new + { + Id = new Guid("08a999e4-e420-b864-2864-bef78c138448"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mayotte", + Iso2 = "YT", + Iso3 = "MYT", + Name = "Mayotte", + NumericCode = "175" + }, + new + { + Id = new Guid("a9940e91-93ef-19f7-79c0-00d31c6a9f87"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Mexican States", + Iso2 = "MX", + Iso3 = "MEX", + Name = "Mexico", + NumericCode = "484" + }, + new + { + Id = new Guid("a2da72dc-5866-ba2f-6283-6575af00ade5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federated States of Micronesia", + Iso2 = "FM", + Iso3 = "FSM", + Name = "Micronesia", + NumericCode = "583" + }, + new + { + Id = new Guid("daf6bc7a-92c4-ef47-3111-e13199b86b90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Moldova", + Iso2 = "MD", + Iso3 = "MDA", + Name = "Moldova", + NumericCode = "498" + }, + new + { + Id = new Guid("5cab34ca-8c74-0766-c7ca-4a826b44c5bd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Monaco", + Iso2 = "MC", + Iso3 = "MCO", + Name = "Monaco", + NumericCode = "492" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mongolia", + Iso2 = "MN", + Iso3 = "MNG", + Name = "Mongolia", + NumericCode = "496" + }, + new + { + Id = new Guid("86db2170-be87-fd1d-bf57-05ff61ae83a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montenegro", + Iso2 = "ME", + Iso3 = "MNE", + Name = "Montenegro", + NumericCode = "499" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montserrat", + Iso2 = "MS", + Iso3 = "MSR", + Name = "Montserrat", + NumericCode = "500" + }, + new + { + Id = new Guid("915805f0-9ff0-48ff-39b3-44a4af5e0482"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Morocco", + Iso2 = "MA", + Iso3 = "MAR", + Name = "Morocco", + NumericCode = "504" + }, + new + { + Id = new Guid("10b58d9b-42ef-edb8-54a3-712636fda55a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mozambique", + Iso2 = "MZ", + Iso3 = "MOZ", + Name = "Mozambique", + NumericCode = "508" + }, + new + { + Id = new Guid("015a9f83-6e57-bc1e-8227-24a4e5248582"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Union of Myanmar", + Iso2 = "MM", + Iso3 = "MMR", + Name = "Myanmar", + NumericCode = "104" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Namibia", + Iso2 = "NA", + Iso3 = "NAM", + Name = "Namibia", + NumericCode = "516" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nauru", + Iso2 = "NR", + Iso3 = "NRU", + Name = "Nauru", + NumericCode = "520" + }, + new + { + Id = new Guid("e81c5db3-401a-e047-001e-045f39bef8ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Nepal", + Iso2 = "NP", + Iso3 = "NPL", + Name = "Nepal", + NumericCode = "524" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of the Netherlands", + Iso2 = "NL", + Iso3 = "NLD", + Name = "Netherlands", + NumericCode = "528" + }, + new + { + Id = new Guid("4b0729b6-f698-5730-767c-88e2d36691bb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Caledonia", + Iso2 = "NC", + Iso3 = "NCL", + Name = "New Caledonia", + NumericCode = "540" + }, + new + { + Id = new Guid("360e3c61-aaac-fa2f-d731-fc0824c05107"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Zealand", + Iso2 = "NZ", + Iso3 = "NZL", + Name = "New Zealand", + NumericCode = "554" + }, + new + { + Id = new Guid("cd0e8275-3def-1de4-8858-61aab36851c4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nicaragua", + Iso2 = "NI", + Iso3 = "NIC", + Name = "Nicaragua", + NumericCode = "558" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Niger", + Iso2 = "NE", + Iso3 = "NER", + Name = "Niger", + NumericCode = "562" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Nigeria", + Iso2 = "NG", + Iso3 = "NGA", + Name = "Nigeria", + NumericCode = "566" + }, + new + { + Id = new Guid("3eea06f4-c085-f619-6d52-b76a5f6fd2b6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Niue", + Iso2 = "NU", + Iso3 = "NIU", + Name = "Niue", + NumericCode = "570" + }, + new + { + Id = new Guid("47804b6a-e705-b925-f4fd-4adf6500180b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Norfolk Island", + Iso2 = "NF", + Iso3 = "NFK", + Name = "Norfolk Island", + NumericCode = "574" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of North Macedonia", + Iso2 = "MK", + Iso3 = "MKD", + Name = "North Macedonia", + NumericCode = "807" + }, + new + { + Id = new Guid("6ac64a20-5688-ccd0-4eca-88d8a2560079"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Northern Mariana Islands", + Iso2 = "MP", + Iso3 = "MNP", + Name = "Northern Mariana Islands", + NumericCode = "580" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Norway", + Iso2 = "NO", + Iso3 = "NOR", + Name = "Norway", + NumericCode = "578" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sultanate of Oman", + Iso2 = "OM", + Iso3 = "OMN", + Name = "Oman", + NumericCode = "512" + }, + new + { + Id = new Guid("cc7fabfc-4c2b-d9ff-bb45-003bfc2e468a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Pakistan", + Iso2 = "PK", + Iso3 = "PAK", + Name = "Pakistan", + NumericCode = "586" + }, + new + { + Id = new Guid("057884bc-3c2e-dea9-6522-b003c9297f7a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Palau", + Iso2 = "PW", + Iso3 = "PLW", + Name = "Palau", + NumericCode = "585" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Palestine", + Iso2 = "PS", + Iso3 = "PSE", + Name = "Palestine", + NumericCode = "275" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Panama", + Iso2 = "PA", + Iso3 = "PAN", + Name = "Panama", + NumericCode = "591" + }, + new + { + Id = new Guid("c926f091-fe96-35b3-56b5-d418d17e0159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Papua New Guinea", + Iso2 = "PG", + Iso3 = "PNG", + Name = "Papua New Guinea", + NumericCode = "598" + }, + new + { + Id = new Guid("db6ce903-ab43-3793-960c-659529bae6df"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Paraguay", + Iso2 = "PY", + Iso3 = "PRY", + Name = "Paraguay", + NumericCode = "600" + }, + new + { + Id = new Guid("75634729-8e4a-4cfd-739d-9f679bfca3ab"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Peru", + Iso2 = "PE", + Iso3 = "PER", + Name = "Peru", + NumericCode = "604" + }, + new + { + Id = new Guid("c93bccaf-1835-3c02-e2ee-c113ced19e43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Philippines", + Iso2 = "PH", + Iso3 = "PHL", + Name = "Philippines", + NumericCode = "608" + }, + new + { + Id = new Guid("a5d0c9af-2022-2b43-9332-eb6a2ce4305d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Pitcairn Islands", + Iso2 = "PN", + Iso3 = "PCN", + Name = "Pitcairn Islands", + NumericCode = "612" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Poland", + Iso2 = "PL", + Iso3 = "POL", + Name = "Poland", + NumericCode = "616" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Portuguese Republic", + Iso2 = "PT", + Iso3 = "PRT", + Name = "Portugal", + NumericCode = "620" + }, + new + { + Id = new Guid("cd2c97c3-5473-0719-3803-fcacedfe2ea2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Puerto Rico", + Iso2 = "PR", + Iso3 = "PRI", + Name = "Puerto Rico", + NumericCode = "630" + }, + new + { + Id = new Guid("067c9448-9ad0-2c21-a1dc-fbdf5a63d18d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Qatar", + Iso2 = "QA", + Iso3 = "QAT", + Name = "Qatar", + NumericCode = "634" + }, + new + { + Id = new Guid("881b4bb8-b6da-c73e-55c0-c9f31c02aaef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Réunion", + Iso2 = "RE", + Iso3 = "REU", + Name = "Réunion", + NumericCode = "638" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Romania", + Iso2 = "RO", + Iso3 = "ROU", + Name = "Romania", + NumericCode = "642" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Russian Federation", + Iso2 = "RU", + Iso3 = "RUS", + Name = "Russian Federation", + NumericCode = "643" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Rwanda", + Iso2 = "RW", + Iso3 = "RWA", + Name = "Rwanda", + NumericCode = "646" + }, + new + { + Id = new Guid("77f6f69b-ec41-8818-9395-8d39bf09e653"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Barthélemy", + Iso2 = "BL", + Iso3 = "BLM", + Name = "Saint Barthélemy", + NumericCode = "652" + }, + new + { + Id = new Guid("6a76d068-49e1-da80-ddb4-9ef3d11191e6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Helena, Ascension and Tristan da Cunha", + Iso2 = "SH", + Iso3 = "SHN", + Name = "Saint Helena, Ascension and Tristan da Cunha", + NumericCode = "654" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federation of Saint Kitts and Nevis", + Iso2 = "KN", + Iso3 = "KNA", + Name = "Saint Kitts and Nevis", + NumericCode = "659" + }, + new + { + Id = new Guid("220e980a-7363-0150-c250-89e83b967fb4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Lucia", + Iso2 = "LC", + Iso3 = "LCA", + Name = "Saint Lucia", + NumericCode = "662" + }, + new + { + Id = new Guid("899c2a9f-f35d-5a49-a6cd-f92531bb2266"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Martin (French part)", + Iso2 = "MF", + Iso3 = "MAF", + Name = "Saint Martin", + NumericCode = "663" + }, + new + { + Id = new Guid("5476986b-11a4-8463-9bd7-0f7354ec7a20"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Pierre and Miquelon", + Iso2 = "PM", + Iso3 = "SPM", + Name = "Saint Pierre and Miquelon", + NumericCode = "666" + }, + new + { + Id = new Guid("2f49855b-ff93-c399-d72a-121f2bf28bc9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Vincent and the Grenadines", + Iso2 = "VC", + Iso3 = "VCT", + Name = "Saint Vincent and the Grenadines", + NumericCode = "670" + }, + new + { + Id = new Guid("a7c4c9db-8fe4-7d43-e830-1a70954970c3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Samoa", + Iso2 = "WS", + Iso3 = "WSM", + Name = "Samoa", + NumericCode = "882" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of San Marino", + Iso2 = "SM", + Iso3 = "SMR", + Name = "San Marino", + NumericCode = "674" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Sao Tome and Principe", + Iso2 = "ST", + Iso3 = "STP", + Name = "Sao Tome and Principe", + NumericCode = "678" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Saudi Arabia", + Iso2 = "SA", + Iso3 = "SAU", + Name = "Saudi Arabia", + NumericCode = "682" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Senegal", + Iso2 = "SN", + Iso3 = "SEN", + Name = "Senegal", + NumericCode = "686" + }, + new + { + Id = new Guid("971c7e66-c6e3-71f4-580a-5caf2852f9f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Serbia", + Iso2 = "RS", + Iso3 = "SRB", + Name = "Serbia", + NumericCode = "688" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Seychelles", + Iso2 = "SC", + Iso3 = "SYC", + Name = "Seychelles", + NumericCode = "690" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sierra Leone", + Iso2 = "SL", + Iso3 = "SLE", + Name = "Sierra Leone", + NumericCode = "694" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Singapore", + Iso2 = "SG", + Iso3 = "SGP", + Name = "Singapore", + NumericCode = "702" + }, + new + { + Id = new Guid("141e589a-7046-a265-d2f6-b2f85e6eeadd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sint Maarten (Dutch part)", + Iso2 = "SX", + Iso3 = "SXM", + Name = "Sint Maarten (Dutch part)", + NumericCode = "534" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Slovakia (Slovak Republic)", + Iso2 = "SK", + Iso3 = "SVK", + Name = "Slovakia (Slovak Republic)", + NumericCode = "703" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Slovenia", + Iso2 = "SI", + Iso3 = "SVN", + Name = "Slovenia", + NumericCode = "705" + }, + new + { + Id = new Guid("7453c201-ecf1-d3dd-0409-e94d0733173b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Solomon Islands", + Iso2 = "SB", + Iso3 = "SLB", + Name = "Solomon Islands", + NumericCode = "090" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Somalia", + Iso2 = "SO", + Iso3 = "SOM", + Name = "Somalia", + NumericCode = "706" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Africa", + Iso2 = "ZA", + Iso3 = "ZAF", + Name = "South Africa", + NumericCode = "710" + }, + new + { + Id = new Guid("6af4d03e-edd0-d98a-bc7e-abc7df87d3dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "South Georgia and the South Sandwich Islands", + Iso2 = "GS", + Iso3 = "SGS", + Name = "South Georgia and the South Sandwich Islands", + NumericCode = "239" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Sudan", + Iso2 = "SS", + Iso3 = "SSD", + Name = "South Sudan", + NumericCode = "728" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Spain", + Iso2 = "ES", + Iso3 = "ESP", + Name = "Spain", + NumericCode = "724" + }, + new + { + Id = new Guid("687320c8-e841-c911-6d30-b14eb998feb6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Socialist Republic of Sri Lanka", + Iso2 = "LK", + Iso3 = "LKA", + Name = "Sri Lanka", + NumericCode = "144" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sudan", + Iso2 = "SD", + Iso3 = "SDN", + Name = "Sudan", + NumericCode = "729" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Suriname", + Iso2 = "SR", + Iso3 = "SUR", + Name = "Suriname", + NumericCode = "740" + }, + new + { + Id = new Guid("d525de3a-aecc-07de-0426-68f32af2968e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Svalbard & Jan Mayen Islands", + Iso2 = "SJ", + Iso3 = "SJM", + Name = "Svalbard & Jan Mayen Islands", + NumericCode = "744" + }, + new + { + Id = new Guid("a32a9fc2-677f-43e0-97aa-9e83943d785c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Eswatini", + Iso2 = "SZ", + Iso3 = "SWZ", + Name = "Eswatini", + NumericCode = "748" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Sweden", + Iso2 = "SE", + Iso3 = "SWE", + Name = "Sweden", + NumericCode = "752" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Swiss Confederation", + Iso2 = "CH", + Iso3 = "CHE", + Name = "Switzerland", + NumericCode = "756" + }, + new + { + Id = new Guid("c1a923f6-b9ec-78f7-cc1c-7025e3d69d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Syrian Arab Republic", + Iso2 = "SY", + Iso3 = "SYR", + Name = "Syrian Arab Republic", + NumericCode = "760" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Taiwan, Province of China", + Iso2 = "TW", + Iso3 = "TWN", + Name = "Taiwan", + NumericCode = "158" + }, + new + { + Id = new Guid("2a848549-9777-cf48-a0f2-b32c6f942096"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Tajikistan", + Iso2 = "TJ", + Iso3 = "TJK", + Name = "Tajikistan", + NumericCode = "762" + }, + new + { + Id = new Guid("4736c1ad-54bd-c8e8-d9ee-492a88268de8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Republic of Tanzania", + Iso2 = "TZ", + Iso3 = "TZA", + Name = "Tanzania", + NumericCode = "834" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Thailand", + Iso2 = "TH", + Iso3 = "THA", + Name = "Thailand", + NumericCode = "764" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Timor-Leste", + Iso2 = "TL", + Iso3 = "TLS", + Name = "Timor-Leste", + NumericCode = "626" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Togolese Republic", + Iso2 = "TG", + Iso3 = "TGO", + Name = "Togo", + NumericCode = "768" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tokelau", + Iso2 = "TK", + Iso3 = "TKL", + Name = "Tokelau", + NumericCode = "772" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Tonga", + Iso2 = "TO", + Iso3 = "TON", + Name = "Tonga", + NumericCode = "776" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Trinidad and Tobago", + Iso2 = "TT", + Iso3 = "TTO", + Name = "Trinidad and Tobago", + NumericCode = "780" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tunisian Republic", + Iso2 = "TN", + Iso3 = "TUN", + Name = "Tunisia", + NumericCode = "788" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Türkiye", + Iso2 = "TR", + Iso3 = "TUR", + Name = "Türkiye", + NumericCode = "792" + }, + new + { + Id = new Guid("550ca5df-3995-617c-c39d-437beb400a42"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turkmenistan", + Iso2 = "TM", + Iso3 = "TKM", + Name = "Turkmenistan", + NumericCode = "795" + }, + new + { + Id = new Guid("0e0fefd5-9a05-fde5-bee9-ef56db7748a1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turks and Caicos Islands", + Iso2 = "TC", + Iso3 = "TCA", + Name = "Turks and Caicos Islands", + NumericCode = "796" + }, + new + { + Id = new Guid("e0d562ca-f573-3c2f-eb83-f72d4d70d4fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tuvalu", + Iso2 = "TV", + Iso3 = "TUV", + Name = "Tuvalu", + NumericCode = "798" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uganda", + Iso2 = "UG", + Iso3 = "UGA", + Name = "Uganda", + NumericCode = "800" + }, + new + { + Id = new Guid("e087f51c-feba-19b6-5595-fcbdce170411"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ukraine", + Iso2 = "UA", + Iso3 = "UKR", + Name = "Ukraine", + NumericCode = "804" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Arab Emirates", + Iso2 = "AE", + Iso3 = "ARE", + Name = "United Arab Emirates", + NumericCode = "784" + }, + new + { + Id = new Guid("0b3b04b4-9782-79e3-bc55-9ab33b6ae9c7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Kingdom of Great Britain & Northern Ireland", + Iso2 = "GB", + Iso3 = "GBR", + Name = "United Kingdom of Great Britain and Northern Ireland", + NumericCode = "826" + }, + new + { + Id = new Guid("cb2e209b-d4c6-6d5c-8901-d989a9188783"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States of America", + Iso2 = "US", + Iso3 = "USA", + Name = "United States of America", + NumericCode = "840" + }, + new + { + Id = new Guid("0868cdd3-7f50-5a25-88d6-98c45f9157e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Minor Outlying Islands", + Iso2 = "UM", + Iso3 = "UMI", + Name = "United States Minor Outlying Islands", + NumericCode = "581" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Virgin Islands", + Iso2 = "VI", + Iso3 = "VIR", + Name = "United States Virgin Islands", + NumericCode = "850" + }, + new + { + Id = new Guid("8e787470-aae6-575a-fe0b-d65fc78b648a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Eastern Republic of Uruguay", + Iso2 = "UY", + Iso3 = "URY", + Name = "Uruguay", + NumericCode = "858" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uzbekistan", + Iso2 = "UZ", + Iso3 = "UZB", + Name = "Uzbekistan", + NumericCode = "860" + }, + new + { + Id = new Guid("c98174ef-8198-54ba-2ff1-b93f3c646db8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Vanuatu", + Iso2 = "VU", + Iso3 = "VUT", + Name = "Vanuatu", + NumericCode = "548" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bolivarian Republic of Venezuela", + Iso2 = "VE", + Iso3 = "VEN", + Name = "Venezuela", + NumericCode = "862" + }, + new + { + Id = new Guid("d7236157-d5a7-6b7a-3bc1-69802313fa30"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Socialist Republic of Vietnam", + Iso2 = "VN", + Iso3 = "VNM", + Name = "Vietnam", + NumericCode = "704" + }, + new + { + Id = new Guid("e186a953-7ab3-c009-501c-a754267b770b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Wallis and Futuna", + Iso2 = "WF", + Iso3 = "WLF", + Name = "Wallis and Futuna", + NumericCode = "876" + }, + new + { + Id = new Guid("2f4cc994-53f1-1763-8220-5d89e063804f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Western Sahara", + Iso2 = "EH", + Iso3 = "ESH", + Name = "Western Sahara", + NumericCode = "732" + }, + new + { + Id = new Guid("8c4441fd-8cd4-ff1e-928e-e46f9ca12552"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Yemen", + Iso2 = "YE", + Iso3 = "YEM", + Name = "Yemen", + NumericCode = "887" + }, + new + { + Id = new Guid("ab0b7e83-bf02-16e6-e5ae-46c4bd4c093b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zambia", + Iso2 = "ZM", + Iso3 = "ZMB", + Name = "Zambia", + NumericCode = "894" + }, + new + { + Id = new Guid("6984f722-6963-d067-d4d4-9fd3ef2edbf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zimbabwe", + Iso2 = "ZW", + Iso3 = "ZWE", + Name = "Zimbabwe", + NumericCode = "716" + }, + new + { + Id = new Guid("4b07d158-c1d0-8ab0-a28e-a56d64f910e1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kosovo", + Iso2 = "XK", + Iso3 = "XKX", + Name = "Kosovo", + NumericCode = "926" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("EnglishTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationsVersion") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoForCitizenReportingId") + .HasColumnType("uuid"); + + b.Property("PollingStationsVersion") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("MonitoringNgoForCitizenReportingId"); + + b.ToTable("ElectionRounds"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundFormTemplateAggregate.ElectionRoundFormTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormTemplateId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("FormTemplateId"); + + b.HasIndex("ElectionRoundId", "FormTemplateId") + .IsUnique(); + + b.ToTable("ElectionRoundFormTemplates"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Base64EncodedData") + .HasColumnType("text"); + + b.Property("CitizenReportsFilers") + .HasColumnType("jsonb"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ExportStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExportedDataType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilterConditions") + .HasColumnType("jsonb"); + + b.Property("FormSubmissionsFilters") + .HasColumnType("jsonb"); + + b.Property("IncidentReportsFilters") + .HasColumnType("jsonb"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("QuickReportsFilters") + .HasColumnType("jsonb"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("ExportedData", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property>("Metadata") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("TimeSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("UserFeedback") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("ObserverId"); + + b.ToTable("UserFeedback"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DisplayOrder") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("Forms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "FormId"); + + b.ToTable("FormSubmissions", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionCommentAggregate.FormSubmissionComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("SubmissionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("SubmissionId"); + + b.HasIndex("ElectionRoundId", "SubmissionId"); + + b.ToTable("FormSubmissionComments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormTemplateAggregate.FormTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FormTemplates"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ImportValidationErrorsAggregate.ImportValidationErrors", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImportType") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.ToTable("ImportValidationErrors"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationDescription") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("LocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("IncidentReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportCommentAggregate.IncidentReportComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("IncidentReportId"); + + b.HasIndex("ElectionRoundId", "IncidentReportId"); + + b.ToTable("IncidentReportComments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LanguageAggregate.Language", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Iso1") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NativeName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Iso1") + .IsUnique(); + + b.ToTable("Language"); + + b.HasData( + new + { + Id = new Guid("9c11bb58-5135-453a-1d24-dc20ef0e9031"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AA", + Name = "Afar", + NativeName = "Afaraf" + }, + new + { + Id = new Guid("bd4f1638-6017-733d-f696-b8b4d72664d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AB", + Name = "Abkhaz", + NativeName = "аҧсуа бызшәа" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AE", + Name = "Avestan", + NativeName = "avesta" + }, + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AF", + Name = "Afrikaans", + NativeName = "Afrikaans" + }, + new + { + Id = new Guid("ef584e3c-03f2-42b0-7139-69d15d21e5a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AK", + Name = "Akan", + NativeName = "Akan" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AM", + Name = "Amharic", + NativeName = "አማርኛ" + }, + new + { + Id = new Guid("d4d5c45a-d3c2-891e-6d7d-75569c3386ac"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AN", + Name = "Aragonese", + NativeName = "aragonés" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AR", + Name = "Arabic", + NativeName = "اَلْعَرَبِيَّةُ" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AS", + Name = "Assamese", + NativeName = "অসমীয়া" + }, + new + { + Id = new Guid("e43a2010-14fc-63a9-f9d3-0ab2a1d0e52f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AV", + Name = "Avaric", + NativeName = "авар мацӀ" + }, + new + { + Id = new Guid("78c6e8af-fcb4-c783-987c-7e1aca3aed64"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AY", + Name = "Aymara", + NativeName = "aymar aru" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AZ", + Name = "Azerbaijani", + NativeName = "azərbaycan dili" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BA", + Name = "Bashkir", + NativeName = "башҡорт теле" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BE", + Name = "Belarusian", + NativeName = "беларуская мова" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BG", + Name = "Bulgarian", + NativeName = "български език" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BI", + Name = "Bislama", + NativeName = "Bislama" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BM", + Name = "Bambara", + NativeName = "bamanankan" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BN", + Name = "Bengali", + NativeName = "বাংলা" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BO", + Name = "Tibetan", + NativeName = "བོད་ཡིག" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BR", + Name = "Breton", + NativeName = "brezhoneg" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BS", + Name = "Bosnian", + NativeName = "bosanski jezik" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CA", + Name = "Catalan", + NativeName = "Català" + }, + new + { + Id = new Guid("cd5689d6-7a06-73c7-650e-f6f94387fd88"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CE", + Name = "Chechen", + NativeName = "нохчийн мотт" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CH", + Name = "Chamorro", + NativeName = "Chamoru" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CO", + Name = "Corsican", + NativeName = "corsu" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CR", + Name = "Cree", + NativeName = "ᓀᐦᐃᔭᐍᐏᐣ" + }, + new + { + Id = new Guid("4def223a-9524-596d-cc29-ab7830c590de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CS", + Name = "Czech", + NativeName = "čeština" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CU", + Name = "Old Church Slavonic", + NativeName = "ѩзыкъ словѣньскъ" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CV", + Name = "Chuvash", + NativeName = "чӑваш чӗлхи" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CY", + Name = "Welsh", + NativeName = "Cymraeg" + }, + new + { + Id = new Guid("b356a541-1383-3c0a-9afd-6aebae3753cb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DA", + Name = "Danish", + NativeName = "dansk" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DE", + Name = "German", + NativeName = "Deutsch" + }, + new + { + Id = new Guid("d8d4f63d-fa65-63dd-a788-de2eec3d24ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DV", + Name = "Divehi", + NativeName = "ދިވެހި" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DZ", + Name = "Dzongkha", + NativeName = "རྫོང་ཁ" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EE", + Name = "Ewe", + NativeName = "Eʋegbe" + }, + new + { + Id = new Guid("b9da7f73-60cd-404c-18fb-1bc5bbfffb38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EL", + Name = "Greek", + NativeName = "Ελληνικά" + }, + new + { + Id = new Guid("094b3769-68b1-6211-ba2d-6bba92d6a167"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EN", + Name = "English", + NativeName = "English" + }, + new + { + Id = new Guid("1da84244-fa39-125e-06dc-3c0cb2342ce9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EO", + Name = "Esperanto", + NativeName = "Esperanto" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ES", + Name = "Spanish", + NativeName = "Español" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ET", + Name = "Estonian", + NativeName = "eesti" + }, + new + { + Id = new Guid("b2a87091-32fb-ba34-a721-bf8b3de5935d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EU", + Name = "Basque", + NativeName = "euskara" + }, + new + { + Id = new Guid("e9da8997-dee8-0c2d-79d3-05fafc45092e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FA", + Name = "Persian", + NativeName = "فارسی" + }, + new + { + Id = new Guid("51a86a09-0d0b-31c1-90f1-f237db8e29ad"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FF", + Name = "Fula", + NativeName = "Fulfulde" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FI", + Name = "Finnish", + NativeName = "suomi" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FJ", + Name = "Fijian", + NativeName = "vosa Vakaviti" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FO", + Name = "Faroese", + NativeName = "føroyskt" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FR", + Name = "French", + NativeName = "Français" + }, + new + { + Id = new Guid("fb429393-f994-0a16-37f9-edc0510fced5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FY", + Name = "Western Frisian", + NativeName = "Frysk" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GA", + Name = "Irish", + NativeName = "Gaeilge" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GD", + Name = "Scottish Gaelic", + NativeName = "Gàidhlig" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GL", + Name = "Galician", + NativeName = "galego" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GN", + Name = "Guaraní", + NativeName = "Avañe'ẽ" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GU", + Name = "Gujarati", + NativeName = "ગુજરાતી" + }, + new + { + Id = new Guid("849b5e66-dc68-a1ed-6ed3-e315fbd0a0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GV", + Name = "Manx", + NativeName = "Gaelg" + }, + new + { + Id = new Guid("2e9cb133-68a7-2f3b-49d1-0921cf42dfae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HA", + Name = "Hausa", + NativeName = "هَوُسَ" + }, + new + { + Id = new Guid("d685aa26-aee7-716b-9433-1b3411209f4b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HE", + Name = "Hebrew", + NativeName = "עברית" + }, + new + { + Id = new Guid("54686fcd-3f35-f468-7c9c-93217c5084bc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HI", + Name = "Hindi", + NativeName = "हिन्दी" + }, + new + { + Id = new Guid("87813ec7-4830-e4dc-5ab1-bd599057ede0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HO", + Name = "Hiri Motu", + NativeName = "Hiri Motu" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HR", + Name = "Croatian", + NativeName = "Hrvatski" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HT", + Name = "Haitian", + NativeName = "Kreyòl ayisyen" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HU", + Name = "Hungarian", + NativeName = "magyar" + }, + new + { + Id = new Guid("d832c50a-112e-4591-9432-4ada24bc85b2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HY", + Name = "Armenian", + NativeName = "Հայերեն" + }, + new + { + Id = new Guid("d5bffdfb-6a8e-6d9f-2e59-4ada912acdba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HZ", + Name = "Herero", + NativeName = "Otjiherero" + }, + new + { + Id = new Guid("7f065da7-4ba4-81ca-5126-dbf606a73907"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IA", + Name = "Interlingua", + NativeName = "Interlingua" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ID", + Name = "Indonesian", + NativeName = "Bahasa Indonesia" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IE", + Name = "Interlingue", + NativeName = "Interlingue" + }, + new + { + Id = new Guid("caddae27-283a-82b2-9365-76a3d6c49eee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IG", + Name = "Igbo", + NativeName = "Asụsụ Igbo" + }, + new + { + Id = new Guid("f21f562e-5c35-4806-4efc-416619b5b7f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "II", + Name = "Nuosu", + NativeName = "ꆈꌠ꒿ Nuosuhxop" + }, + new + { + Id = new Guid("23785991-fef4-e625-4d3b-b6ac364d0fa0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IK", + Name = "Inupiaq", + NativeName = "Iñupiaq" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IO", + Name = "Ido", + NativeName = "Ido" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IS", + Name = "Icelandic", + NativeName = "Íslenska" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IT", + Name = "Italian", + NativeName = "Italiano" + }, + new + { + Id = new Guid("899392d7-d54f-a1c6-407a-1bada9b85fdd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IU", + Name = "Inuktitut", + NativeName = "ᐃᓄᒃᑎᑐᑦ" + }, + new + { + Id = new Guid("6857242c-f772-38b5-b5a2-c8e8b9db551f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JA", + Name = "Japanese", + NativeName = "日本語" + }, + new + { + Id = new Guid("e7532b00-3b1b-ff2c-b7c0-26bd7e91af55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JV", + Name = "Javanese", + NativeName = "basa Jawa" + }, + new + { + Id = new Guid("9204928b-c569-ef6a-446e-4853aee439b0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KA", + Name = "Georgian", + NativeName = "ქართული" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KG", + Name = "Kongo", + NativeName = "Kikongo" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KI", + Name = "Kikuyu", + NativeName = "Gĩkũyũ" + }, + new + { + Id = new Guid("80ecea2c-8969-1929-0d4a-39ed2324abc6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KJ", + Name = "Kwanyama", + NativeName = "Kuanyama" + }, + new + { + Id = new Guid("b6b2351f-4f1e-c92f-0e9a-a915f4cc5fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KK", + Name = "Kazakh", + NativeName = "қазақ тілі" + }, + new + { + Id = new Guid("081a5fdb-445a-015a-1e36-f2e5014265ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KL", + Name = "Kalaallisut", + NativeName = "kalaallisut" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KM", + Name = "Khmer", + NativeName = "ខេមរភាសា" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KN", + Name = "Kannada", + NativeName = "ಕನ್ನಡ" + }, + new + { + Id = new Guid("74f19a84-b1c5-fa2d-8818-2220b80a3056"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KO", + Name = "Korean", + NativeName = "한국어" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KR", + Name = "Kanuri", + NativeName = "Kanuri" + }, + new + { + Id = new Guid("eace47f6-5499-f4f0-8f97-ed165b681d84"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KS", + Name = "Kashmiri", + NativeName = "कश्मीरी" + }, + new + { + Id = new Guid("7451108d-ad49-940a-d479-4d868b62a7c6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KU", + Name = "Kurdish", + NativeName = "Kurdî" + }, + new + { + Id = new Guid("78b7020d-8b82-3fae-2049-30e490ae1faf"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KV", + Name = "Komi", + NativeName = "коми кыв" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KW", + Name = "Cornish", + NativeName = "Kernewek" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KY", + Name = "Kyrgyz", + NativeName = "Кыргызча" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LA", + Name = "Latin", + NativeName = "latine" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LB", + Name = "Luxembourgish", + NativeName = "Lëtzebuergesch" + }, + new + { + Id = new Guid("80b770b8-4797-3d62-ef66-1ded7b0da0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LG", + Name = "Ganda", + NativeName = "Luganda" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LI", + Name = "Limburgish", + NativeName = "Limburgs" + }, + new + { + Id = new Guid("ca44a869-d3b6-052d-1e1a-ad4e3682a2ed"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LN", + Name = "Lingala", + NativeName = "Lingála" + }, + new + { + Id = new Guid("e9ad0bec-7dee-bd01-9528-1fc74d1d78dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LO", + Name = "Lao", + NativeName = "ພາສາລາວ" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LT", + Name = "Lithuanian", + NativeName = "lietuvių kalba" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LU", + Name = "Luba-Katanga", + NativeName = "Kiluba" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LV", + Name = "Latvian", + NativeName = "latviešu valoda" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MG", + Name = "Malagasy", + NativeName = "fiteny malagasy" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MH", + Name = "Marshallese", + NativeName = "Kajin M̧ajeļ" + }, + new + { + Id = new Guid("54726f17-03b8-8af3-0359-c42d8fe8459d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MI", + Name = "Māori", + NativeName = "te reo Māori" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MK", + Name = "Macedonian", + NativeName = "македонски јазик" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ML", + Name = "Malayalam", + NativeName = "മലയാളം" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MN", + Name = "Mongolian", + NativeName = "Монгол хэл" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MR", + Name = "Marathi", + NativeName = "मराठी" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MS", + Name = "Malay", + NativeName = "Bahasa Melayu" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MT", + Name = "Maltese", + NativeName = "Malti" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MY", + Name = "Burmese", + NativeName = "ဗမာစာ" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NA", + Name = "Nauru", + NativeName = "Dorerin Naoero" + }, + new + { + Id = new Guid("4a3aa5a4-473f-45cd-f054-fa0465c476a4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NB", + Name = "Norwegian Bokmål", + NativeName = "Norsk bokmål" + }, + new + { + Id = new Guid("b4292ad3-3ca8-eea5-f3e0-d1983db8f61e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ND", + Name = "Northern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NE", + Name = "Nepali", + NativeName = "नेपाली" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NG", + Name = "Ndonga", + NativeName = "Owambo" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NL", + Name = "Dutch", + NativeName = "Nederlands" + }, + new + { + Id = new Guid("df41c815-40f8-197a-7a8b-e456d43283d9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NN", + Name = "Norwegian Nynorsk", + NativeName = "Norsk nynorsk" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NO", + Name = "Norwegian", + NativeName = "Norsk" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NR", + Name = "Southern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("67729f87-ef47-dd3f-65f7-b0f6df0d6384"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NV", + Name = "Navajo", + NativeName = "Diné bizaad" + }, + new + { + Id = new Guid("720b4e12-b001-8d38-7c07-f43194b9645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NY", + Name = "Chichewa", + NativeName = "chiCheŵa" + }, + new + { + Id = new Guid("2b6d383a-9ab6-fcdf-bcfe-a4538faca407"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OC", + Name = "Occitan", + NativeName = "occitan" + }, + new + { + Id = new Guid("9ec46cb5-6c2b-0e22-07c5-eb2fe1b8d2ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OJ", + Name = "Ojibwe", + NativeName = "ᐊᓂᔑᓈᐯᒧᐎᓐ" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OM", + Name = "Oromo", + NativeName = "Afaan Oromoo" + }, + new + { + Id = new Guid("285b9e82-38af-33ab-79fd-0b4f3fd4f2f1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OR", + Name = "Oriya", + NativeName = "ଓଡ଼ିଆ" + }, + new + { + Id = new Guid("2d013d34-b258-8fe9-ef52-dd34e82a4672"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OS", + Name = "Ossetian", + NativeName = "ирон æвзаг" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PA", + Name = "Panjabi", + NativeName = "ਪੰਜਾਬੀ" + }, + new + { + Id = new Guid("d8ef067c-1087-4ff5-8e1f-2291df7ac958"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PI", + Name = "Pāli", + NativeName = "पाऴि" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PL", + Name = "Polish", + NativeName = "Polski" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PS", + Name = "Pashto", + NativeName = "پښتو" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PT", + Name = "Portuguese", + NativeName = "Português" + }, + new + { + Id = new Guid("93fb8ace-4156-12d5-218e-64b7d35129b1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "QU", + Name = "Quechua", + NativeName = "Runa Simi" + }, + new + { + Id = new Guid("136610e1-8115-9cf1-d671-7950c6483495"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RM", + Name = "Romansh", + NativeName = "rumantsch grischun" + }, + new + { + Id = new Guid("7a0725cf-311a-4f59-cff8-ad8b43dd226e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RN", + Name = "Kirundi", + NativeName = "Ikirundi" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RO", + Name = "Romanian", + NativeName = "Română" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RU", + Name = "Russian", + NativeName = "Русский" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RW", + Name = "Kinyarwanda", + NativeName = "Ikinyarwanda" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SA", + Name = "Sanskrit", + NativeName = "संस्कृतम्" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SC", + Name = "Sardinian", + NativeName = "sardu" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SD", + Name = "Sindhi", + NativeName = "सिन्धी" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SE", + Name = "Northern Sami", + NativeName = "Davvisámegiella" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SG", + Name = "Sango", + NativeName = "yângâ tî sängö" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SI", + Name = "Sinhala", + NativeName = "සිංහල" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SK", + Name = "Slovak", + NativeName = "slovenčina" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SL", + Name = "Slovenian", + NativeName = "slovenščina" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SM", + Name = "Samoan", + NativeName = "gagana fa'a Samoa" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SN", + Name = "Shona", + NativeName = "chiShona" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SO", + Name = "Somali", + NativeName = "Soomaaliga" + }, + new + { + Id = new Guid("fb1cce84-4a6c-1834-0ff2-6df002e3d56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SQ", + Name = "Albanian", + NativeName = "Shqip" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SR", + Name = "Serbian", + NativeName = "српски језик" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SS", + Name = "Swati", + NativeName = "SiSwati" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ST", + Name = "Southern Sotho", + NativeName = "Sesotho" + }, + new + { + Id = new Guid("ee1ace14-e945-4767-85ec-3d74be8b516b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SU", + Name = "Sundanese", + NativeName = "Basa Sunda" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SV", + Name = "Swedish", + NativeName = "Svenska" + }, + new + { + Id = new Guid("5f002f07-f2c3-9fa4-2e29-225d116c10a3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SW", + Name = "Swahili", + NativeName = "Kiswahili" + }, + new + { + Id = new Guid("8bc44f03-84a5-2afc-8b0b-40c727e4ce36"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TA", + Name = "Tamil", + NativeName = "தமிழ்" + }, + new + { + Id = new Guid("3bf5a74a-6d12-e971-16bc-c75e487f2615"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TE", + Name = "Telugu", + NativeName = "తెలుగు" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TG", + Name = "Tajik", + NativeName = "тоҷикӣ" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TH", + Name = "Thai", + NativeName = "ไทย" + }, + new + { + Id = new Guid("596e8283-10ce-d81d-2e6f-400fa259d717"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TI", + Name = "Tigrinya", + NativeName = "ትግርኛ" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TK", + Name = "Turkmen", + NativeName = "Türkmençe" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TL", + Name = "Tagalog", + NativeName = "Wikang Tagalog" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TN", + Name = "Tswana", + NativeName = "Setswana" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TO", + Name = "Tonga", + NativeName = "faka Tonga" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TR", + Name = "Turkish", + NativeName = "Türkçe" + }, + new + { + Id = new Guid("6200b376-9eae-d01b-de52-8674aaf5b013"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TS", + Name = "Tsonga", + NativeName = "Xitsonga" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TT", + Name = "Tatar", + NativeName = "татар теле" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TW", + Name = "Twi", + NativeName = "Twi" + }, + new + { + Id = new Guid("2299a74f-3ebc-f022-da1a-44ae59335b3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TY", + Name = "Tahitian", + NativeName = "Reo Tahiti" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UG", + Name = "Uyghur", + NativeName = "ئۇيغۇرچە‎" + }, + new + { + Id = new Guid("de29d5e7-2ecf-a4ff-5e40-5e83edd0d9b4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UK", + Name = "Ukrainian", + NativeName = "Українська" + }, + new + { + Id = new Guid("f1f09549-a9bb-da4a-9b98-8655a01235aa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UR", + Name = "Urdu", + NativeName = "اردو" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UZ", + Name = "Uzbek", + NativeName = "Ўзбек" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VE", + Name = "Venda", + NativeName = "Tshivenḓa" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VI", + Name = "Vietnamese", + NativeName = "Tiếng Việt" + }, + new + { + Id = new Guid("c2254fd9-159e-4064-0fbf-a7969cba06ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VO", + Name = "Volapük", + NativeName = "Volapük" + }, + new + { + Id = new Guid("629b68d8-1d71-d3ce-f13e-45048ffff017"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WA", + Name = "Walloon", + NativeName = "walon" + }, + new + { + Id = new Guid("ca6bfadf-4e87-0692-a6b3-20ea6a51555d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WO", + Name = "Wolof", + NativeName = "Wollof" + }, + new + { + Id = new Guid("0b9b4368-7ceb-e519-153d-2c58c983852b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "XH", + Name = "Xhosa", + NativeName = "isiXhosa" + }, + new + { + Id = new Guid("13016d0c-fbf0-9503-12f2-e0f8d27394ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YI", + Name = "Yiddish", + NativeName = "ייִדיש" + }, + new + { + Id = new Guid("d55a9eb2-48fc-2719-47bf-99e902c28e80"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YO", + Name = "Yoruba", + NativeName = "Yorùbá" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZA", + Name = "Zhuang", + NativeName = "Saɯ cueŋƅ" + }, + new + { + Id = new Guid("0ce6f5e0-0789-fa0e-b4b5-23a5b1f5e257"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZH", + Name = "Chinese", + NativeName = "中文" + }, + new + { + Id = new Guid("2c7b808d-7786-2deb-5318-56f7c238520e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZU", + Name = "Zulu", + NativeName = "isiZulu" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowMultipleFormSubmission") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormsVersion") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("NgoId"); + + b.ToTable("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.HasIndex("ObserverId"); + + b.HasIndex("ElectionRoundId", "Id") + .IsUnique(); + + b.ToTable("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.HasIndex("NgoId"); + + b.ToTable("NgoAdmins", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Ngos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoStaffAggregate.NgoStaff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.HasIndex("NgoId"); + + b.ToTable("NgoStaff", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("SubmissionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("Notes", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.HasKey("MonitoringObserverId", "NotificationId"); + + b.HasIndex("NotificationId"); + + b.ToTable("MonitoringObserverNotification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationStubAggregate.NotificationStub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("HasBeenProcessed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SerializedData") + .IsRequired() + .HasColumnType("text"); + + b.Property("StubType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("NotificationStubs"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ObserverId"); + + b.ToTable("NotificationTokens"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("Observers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("ObserversGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(2024) + .HasColumnType("character varying(2024)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Latitude") + .HasColumnType("double precision"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Longitude") + .HasColumnType("double precision"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'{}'::JSONB"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("PollingStations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationCorrectionAggregate.PollingStationCorrection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrectedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CorrectedBy") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FromPollingStationId") + .HasColumnType("uuid"); + + b.Property("RecordId") + .HasColumnType("uuid"); + + b.Property("RecordType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ToPollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FromPollingStationId"); + + b.HasIndex("ToPollingStationId"); + + b.HasIndex("ElectionRoundId", "RecordType", "RecordId"); + + b.ToTable("PollingStationCorrections", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArrivalTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Breaks") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'[]'::JSONB"); + + b.Property("DepartureTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("PollingStationInformationFormId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("PollingStationInformationFormId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "PollingStationInformationFormId") + .IsUnique(); + + b.ToTable("PollingStationInformation", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("PSI"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Published"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId") + .IsUnique(); + + b.ToTable("PollingStationInformationForms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IncidentCategory") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Other"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationDetails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuickReportLocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("QuickReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("QuickReportId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("QuickReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportCommentAggregate.QuickReportComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuickReportId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("QuickReportId"); + + b.HasIndex("ElectionRoundId", "QuickReportId"); + + b.ToTable("QuickReportComments", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.LocationAggregate.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Attachments") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportCommentAggregate.CitizenReportComment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany() + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Notes") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "Leader") + .WithMany() + .HasForeignKey("LeaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Leader"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("FormAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Form"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionGuideAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("GuideAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", "Guide") + .WithMany() + .HasForeignKey("GuideId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Guide"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("Memberships") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("Memberships") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgoForCitizenReporting") + .WithMany() + .HasForeignKey("MonitoringNgoForCitizenReportingId"); + + b.Navigation("Country"); + + b.Navigation("MonitoringNgoForCitizenReporting"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundFormTemplateAggregate.ElectionRoundFormTemplate", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormTemplateAggregate.FormTemplate", "FormTemplate") + .WithMany() + .HasForeignKey("FormTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("FormTemplate"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionCommentAggregate.FormSubmissionComment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", "Submission") + .WithMany() + .HasForeignKey("SubmissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Submission"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Attachments") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportCommentAggregate.IncidentReportComment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany() + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Notes") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany("MonitoringNgos") + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany() + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("MonitoringObservers") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany("MonitoringObservers") + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany("Admins") + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoStaffAggregate.NgoStaff", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany("Staff") + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + + b.Navigation("Notification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", null) + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationCorrectionAggregate.PollingStationCorrection", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "FromPollingStation") + .WithMany() + .HasForeignKey("FromPollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "ToPollingStation") + .WithMany() + .HasForeignKey("ToPollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("FromPollingStation"); + + b.Navigation("ToPollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "PollingStationInformationForm") + .WithMany() + .HasForeignKey("PollingStationInformationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + + b.Navigation("PollingStationInformationForm"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithOne() + .HasForeignKey("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportCommentAggregate.QuickReportComment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", "QuickReport") + .WithMany() + .HasForeignKey("QuickReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("QuickReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Navigation("FormAccess"); + + b.Navigation("GuideAccess"); + + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Navigation("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Navigation("Memberships"); + + b.Navigation("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Navigation("Admins"); + + b.Navigation("Staff"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Navigation("MonitoringObservers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20261017104516_AddLocationAggregates.cs b/api/src/Vote.Monitor.Domain/Migrations/20261017104516_AddLocationAggregates.cs new file mode 100644 index 000000000..7524fbf17 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20261017104516_AddLocationAggregates.cs @@ -0,0 +1,1707 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + /// + public partial class AddLocationAggregates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + -- Builds the answer aggregates of a form, keyed by question id, shaped like the BaseAnswerAggregate subtypes. + -- p_questions: the form's questions as stored. + -- p_submissions: [{ "submissionId", "responderId", "timeSubmitted", "answers", "pollingStationId" }] of the submissions to aggregate. + -- Single select, multi select and rating aggregates also count, per option / value, the distinct submissions + -- ("SubmissionsHistogram") and polling stations ("PollingStationsHistogram") that picked it. The polling stations + -- histogram is only built when the submissions carry "pollingStationId" (not for citizen reports). + CREATE OR REPLACE FUNCTION "AggregateAnswers"( + p_questions JSONB, + p_submissions JSONB + ) + RETURNS JSONB + AS + $$ + DECLARE + v_aggregates JSONB; + v_collected RECORD; + v_has_polling_stations BOOLEAN; + BEGIN + SELECT COALESCE(BOOL_OR(S ? 'PollingStationId'), FALSE) + INTO v_has_polling_stations + FROM JSONB_ARRAY_ELEMENTS(p_submissions) S; + + -- 1. an empty aggregate for every question, depending on its type + SELECT COALESCE(JSONB_OBJECT_AGG( + Q.question ->> 'Id', + JSONB_BUILD_OBJECT( + '$questionType', REPLACE(Q.question ->> '$questionType', 'Question', 'AnswerAggregate'), + 'QuestionId', Q.question -> 'Id', + 'DisplayOrder', Q.idx - 1, + 'Question', Q.question, + 'AnswersAggregated', 0 + ) || CASE + WHEN Q.question ->> '$questionType' = 'textQuestion' + THEN JSONB_BUILD_OBJECT('Answers', '[]'::JSONB) + WHEN Q.question ->> '$questionType' = 'numberQuestion' + THEN JSONB_BUILD_OBJECT('Min', 2147483647, 'Max', -2147483648, 'Average', 0) + WHEN Q.question ->> '$questionType' IN ('dateQuestion', 'timeQuestion', 'dateTimeQuestion') + THEN JSONB_BUILD_OBJECT('AnswersHistogram', '{}'::JSONB) + WHEN Q.question ->> '$questionType' = 'ratingQuestion' + THEN JSONB_BUILD_OBJECT( + 'Min', 2147483647, 'Max', -2147483648, 'Average', 0, + 'AnswersHistogram', ( + SELECT JSONB_OBJECT_AGG(V::TEXT, 0) + FROM GENERATE_SERIES(1, SUBSTRING(Q.question ->> 'Scale' FROM 'OneTo(\d+)')::INT) V)) + WHEN Q.question ->> '$questionType' IN ('singleSelectQuestion', 'multiSelectQuestion') + THEN JSONB_BUILD_OBJECT( + 'AnswersHistogram', ( + SELECT COALESCE(JSONB_OBJECT_AGG(O ->> 'Id', 0), '{}'::JSONB) + FROM JSONB_ARRAY_ELEMENTS(Q.question -> 'Options') O), + 'FreeTexts', '[]'::JSONB) + ELSE '{}'::JSONB + END + ), '{}'::JSONB) + INTO v_aggregates + FROM JSONB_ARRAY_ELEMENTS(p_questions) WITH ORDINALITY Q(question, idx); + + -- 2. collect data from the submissions' answers + WITH + SUBMISSION_ANSWERS AS ( + SELECT (S ->> 'SubmissionId')::UUID AS submission_id, + (S ->> 'ResponderId')::UUID AS responder_id, + (S ->> 'TimeSubmitted')::TIMESTAMPTZ AS time_submitted, + (S ->> 'PollingStationId')::UUID AS polling_station_id, + S -> 'Answers' AS answers + FROM JSONB_ARRAY_ELEMENTS(p_submissions) S + ), + FORM_QUESTIONS AS ( + SELECT Q ->> 'Id' AS question_id, + REPLACE(Q ->> '$questionType', 'Question', 'Answer') AS answer_type, + Q -> 'Options' AS options + FROM JSONB_ARRAY_ELEMENTS(p_questions) Q + ), + -- answers to the form's questions, of the type each question expects + ANSWERS AS ( + SELECT SA.submission_id, SA.responder_id, SA.time_submitted, SA.polling_station_id, + FQ.question_id, FQ.answer_type, FQ.options, A AS answer + FROM SUBMISSION_ANSWERS SA + CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(SA.answers) A + INNER JOIN FORM_QUESTIONS FQ ON FQ.question_id = A ->> 'QuestionId' + AND FQ.answer_type = A ->> '$answerType' + ), + -- one row per selected option of single and multi select answers + SELECTIONS AS ( + SELECT AN.*, SEL AS selection + FROM ANSWERS AN + CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS( + CASE + WHEN AN.answer_type = 'singleSelectAnswer' THEN JSONB_BUILD_ARRAY(AN.answer -> 'Selection') + ELSE AN.answer -> 'Selection' + END) SEL + WHERE AN.answer_type IN ('singleSelectAnswer', 'multiSelectAnswer') + ), + -- the rating value or selected option picked in each answer, for counting submissions and polling stations + PICKS AS ( + SELECT question_id, answer ->> 'Value' AS key, submission_id, polling_station_id + FROM ANSWERS + WHERE answer_type = 'ratingAnswer' + UNION ALL + SELECT question_id, selection ->> 'OptionId', submission_id, polling_station_id + FROM SELECTIONS + WHERE selection ->> 'OptionId' IS NOT NULL + ) + SELECT + -- per question: number of answers, and min/max/average for number and rating answers + (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, stats), '{}'::JSONB) + FROM (SELECT question_id, + JSONB_BUILD_OBJECT('AnswersAggregated', COUNT(*)) || + CASE + WHEN answer_type IN ('numberAnswer', 'ratingAnswer') THEN JSONB_BUILD_OBJECT( + 'Min', MIN((answer ->> 'Value')::INT), + 'Max', MAX((answer ->> 'Value')::INT), + 'Average', AVG((answer ->> 'Value')::INT)) + ELSE '{}'::JSONB + END AS stats + FROM ANSWERS + GROUP BY question_id, answer_type) X) AS stats, + + -- per question: rating values, hour buckets of dates and date times, minute buckets of times and selected options with their counts + (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, histogram), '{}'::JSONB) + FROM (SELECT question_id, JSONB_OBJECT_AGG(KEY, count) AS histogram + FROM (SELECT question_id, answer ->> 'Value' AS key, COUNT(*) AS count + FROM ANSWERS + WHERE answer_type = 'ratingAnswer' + GROUP BY 1, 2 + UNION ALL + SELECT question_id, + TO_CHAR(DATE_TRUNC('hour', (answer ->> 'Date')::TIMESTAMPTZ AT TIME ZONE 'UTC'), + 'YYYY-MM-DD"T"HH24":00:00.0000000Z"'), + COUNT(*) + FROM ANSWERS + WHERE answer_type = 'dateAnswer' + GROUP BY 1, 2 + UNION ALL + SELECT question_id, + TO_CHAR(DATE_TRUNC('hour', (answer ->> 'DateTime')::TIMESTAMPTZ AT TIME ZONE 'UTC'), + 'YYYY-MM-DD"T"HH24":00:00.0000000Z"'), + COUNT(*) + FROM ANSWERS + WHERE answer_type = 'dateTimeAnswer' + GROUP BY 1, 2 + UNION ALL + SELECT question_id, TO_CHAR((answer ->> 'Time')::TIME, 'HH24:MI'), COUNT(*) + FROM ANSWERS + WHERE answer_type = 'timeAnswer' + GROUP BY 1, 2 + UNION ALL + SELECT question_id, selection ->> 'OptionId', COUNT(*) + FROM SELECTIONS + WHERE selection ->> 'OptionId' IS NOT NULL + GROUP BY 1, 2) H + GROUP BY question_id) X) AS histograms, + + -- per question: distinct submissions per rating value / selected option + (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, histogram), '{}'::JSONB) + FROM (SELECT question_id, JSONB_OBJECT_AGG(key, count) AS histogram + FROM (SELECT question_id, key, COUNT(DISTINCT submission_id) AS count + FROM PICKS + GROUP BY 1, 2) H + GROUP BY question_id) X) AS submission_histograms, + + -- per question: distinct polling stations per rating value / selected option + (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, histogram), '{}'::JSONB) + FROM (SELECT question_id, JSONB_OBJECT_AGG(key, count) AS histogram + FROM (SELECT question_id, key, COUNT(DISTINCT polling_station_id) AS count + FROM PICKS + GROUP BY 1, 2) H + GROUP BY question_id) X) AS polling_station_histograms, + + -- per question: text answers, and free texts typed for free text options + (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, texts), '{}'::JSONB) + FROM (SELECT question_id, + JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', submission_id, + 'ResponderId', responder_id, + 'Value', text) ORDER BY time_submitted) AS texts + FROM (SELECT question_id, submission_id, responder_id, time_submitted, answer ->> 'Text' AS text + FROM ANSWERS + WHERE answer_type = 'textAnswer' + UNION ALL + SELECT SL.question_id, SL.submission_id, SL.responder_id, SL.time_submitted, + SL.selection ->> 'Text' + FROM SELECTIONS SL + WHERE NULLIF(BTRIM(SL.selection ->> 'Text'), '') IS NOT NULL + AND EXISTS (SELECT 1 + FROM JSONB_ARRAY_ELEMENTS(SL.options) O + WHERE O ->> 'Id' = SL.selection ->> 'OptionId' + AND (O ->> 'IsFreeText')::BOOLEAN)) T + GROUP BY question_id) X) AS texts + INTO v_collected; + + -- 3. merge the collected data into the empty aggregates + RETURN (SELECT COALESCE(JSONB_OBJECT_AGG( + E.key, + E.value + || COALESCE(v_collected.stats -> E.key, '{}'::JSONB) + || CASE + WHEN E.value ->> '$questionType' = 'textAnswerAggregate' + THEN JSONB_BUILD_OBJECT('Answers', COALESCE(v_collected.texts -> E.key, '[]'::JSONB)) + WHEN E.value ->> '$questionType' IN ('singleSelectAnswerAggregate', 'multiSelectAnswerAggregate') + THEN JSONB_BUILD_OBJECT( + 'AnswersHistogram', (E.value -> 'AnswersHistogram') || COALESCE(v_collected.histograms -> E.key, '{}'::JSONB), + 'FreeTexts', COALESCE(v_collected.texts -> E.key, '[]'::JSONB)) + WHEN E.value ->> '$questionType' IN ('ratingAnswerAggregate', 'dateAnswerAggregate', 'timeAnswerAggregate', 'dateTimeAnswerAggregate') + THEN JSONB_BUILD_OBJECT( + 'AnswersHistogram', (E.value -> 'AnswersHistogram') || COALESCE(v_collected.histograms -> E.key, '{}'::JSONB)) + ELSE '{}'::JSONB + END + || CASE + WHEN E.value ->> '$questionType' IN ('singleSelectAnswerAggregate', 'multiSelectAnswerAggregate', 'ratingAnswerAggregate') + THEN JSONB_BUILD_OBJECT( + 'SubmissionsHistogram', Z.zeros || COALESCE(v_collected.submission_histograms -> E.key, '{}'::JSONB)) + || CASE + WHEN v_has_polling_stations THEN JSONB_BUILD_OBJECT( + 'PollingStationsHistogram', Z.zeros || COALESCE(v_collected.polling_station_histograms -> E.key, '{}'::JSONB)) + ELSE '{}'::JSONB + END + ELSE '{}'::JSONB + END + ), '{}'::JSONB) + FROM JSONB_EACH(v_aggregates) E + -- every option / rating value with a count of 0 + CROSS JOIN LATERAL ( + SELECT COALESCE(JSONB_OBJECT_AGG(K, 0), '{}'::JSONB) AS zeros + FROM JSONB_OBJECT_KEYS(CASE WHEN JSONB_TYPEOF(E.value -> 'AnswersHistogram') = 'object' + THEN E.value -> 'AnswersHistogram' + ELSE '{}'::JSONB END) K + ) Z); + END; + $$ LANGUAGE plpgsql; + """); + + migrationBuilder.Sql( + """ + -- Adds a 1-based "Id" to every item of a JSON array. + CREATE OR REPLACE FUNCTION "NumberItems"( + p_items JSONB + ) + RETURNS JSONB + AS + $$ + SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT('Id', X.idx) || X.item ORDER BY X.idx), '[]'::JSONB) + FROM JSONB_ARRAY_ELEMENTS(COALESCE(p_items, '[]'::JSONB)) WITH ORDINALITY X(item, idx); + $$ LANGUAGE sql IMMUTABLE; + """); + + migrationBuilder.Sql( + """ + -- Key identifying a text answer or free text of a question: question id, submission id and a hash of the text. + CREATE OR REPLACE FUNCTION "TextAnswerKey"( + p_question_id TEXT, + p_text JSONB + ) + RETURNS TEXT + AS + $$ + SELECT p_question_id || '|' || (p_text ->> 'SubmissionId') || '|' || MD5(COALESCE(p_text ->> 'Value', '')); + $$ LANGUAGE sql IMMUTABLE; + """); + + migrationBuilder.Sql( + """ + -- Numbers the text answers ("Answers") and free texts ("FreeTexts") of answer aggregates. + -- Returns { "Texts": [{ "Id", "SubmissionId", "ResponderId", "Value" }], "Ids": { <"TextAnswerKey"> : } }. + CREATE OR REPLACE FUNCTION "ExtractTexts"( + p_aggregates JSONB + ) + RETURNS JSONB + AS + $$ + WITH + TEXTS AS ( + SELECT "TextAnswerKey"(A.key, T.text) AS key, + T.text, + ROW_NUMBER() OVER (ORDER BY A.key, T.kind, T.idx) AS position + FROM JSONB_EACH(p_aggregates) A + CROSS JOIN LATERAL ( + SELECT 1 AS kind, X.text, X.idx + FROM JSONB_ARRAY_ELEMENTS(COALESCE(A.value -> 'Answers', '[]'::JSONB)) WITH ORDINALITY X(text, idx) + UNION ALL + SELECT 2, Y.text, Y.idx + FROM JSONB_ARRAY_ELEMENTS(COALESCE(A.value -> 'FreeTexts', '[]'::JSONB)) WITH ORDINALITY Y(text, idx) + ) T + ), + -- one id per distinct text + UNIQUE_TEXTS AS ( + SELECT key, text, ROW_NUMBER() OVER (ORDER BY position) AS id + FROM (SELECT DISTINCT ON (key) key, text, position + FROM TEXTS + ORDER BY key, position) U + ) + SELECT JSONB_BUILD_OBJECT( + 'Texts', COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT('Id', id) || text ORDER BY id), '[]'::JSONB), + 'Ids', COALESCE(JSONB_OBJECT_AGG(key, id), '{}'::JSONB)) + FROM UNIQUE_TEXTS; + $$ LANGUAGE sql IMMUTABLE; + """); + + migrationBuilder.Sql( + """ + -- Makes answer aggregates reference texts, notes and attachments by id: text answers ("Answers") and free texts + -- ("FreeTexts") are replaced with "AnswerIds" / "FreeTextIds", and every aggregate gets the ids of its question's notes + -- ("NoteIds") and attachments ("AttachmentIds"). The question itself is dropped unless p_keep_question. + -- p_text_ids: "TextAnswerKey" -> text id (see "ExtractTexts"). + -- p_notes, p_attachments: the numbered notes / attachments to reference (see "NumberItems"). + CREATE OR REPLACE FUNCTION "ReferenceAnswers"( + p_aggregates JSONB, + p_text_ids JSONB, + p_notes JSONB, + p_attachments JSONB, + p_keep_question BOOLEAN + ) + RETURNS JSONB + AS + $$ + WITH + NOTE_IDS AS ( + SELECT N ->> 'QuestionId' AS question_id, JSONB_AGG(N -> 'Id' ORDER BY (N ->> 'Id')::INT) AS ids + FROM JSONB_ARRAY_ELEMENTS(p_notes) N + GROUP BY 1 + ), + ATTACHMENT_IDS AS ( + SELECT F ->> 'QuestionId' AS question_id, JSONB_AGG(F -> 'Id' ORDER BY (F ->> 'Id')::INT) AS ids + FROM JSONB_ARRAY_ELEMENTS(p_attachments) F + GROUP BY 1 + ) + SELECT COALESCE(JSONB_OBJECT_AGG( + A.key, + (CASE WHEN p_keep_question THEN A.value ELSE A.value - 'Question' END) - 'Answers' - 'FreeTexts' + || CASE + WHEN A.value ? 'Answers' THEN JSONB_BUILD_OBJECT( + 'AnswerIds', (SELECT COALESCE(JSONB_AGG(p_text_ids -> "TextAnswerKey"(A.key, T.text) ORDER BY T.idx), '[]'::JSONB) + FROM JSONB_ARRAY_ELEMENTS(A.value -> 'Answers') WITH ORDINALITY T(text, idx))) + WHEN A.value ? 'FreeTexts' THEN JSONB_BUILD_OBJECT( + 'FreeTextIds', (SELECT COALESCE(JSONB_AGG(p_text_ids -> "TextAnswerKey"(A.key, T.text) ORDER BY T.idx), '[]'::JSONB) + FROM JSONB_ARRAY_ELEMENTS(A.value -> 'FreeTexts') WITH ORDINALITY T(text, idx))) + ELSE '{}'::JSONB + END + || JSONB_BUILD_OBJECT( + 'NoteIds', COALESCE((SELECT ids FROM NOTE_IDS WHERE question_id = A.key), '[]'::JSONB), + 'AttachmentIds', COALESCE((SELECT ids FROM ATTACHMENT_IDS WHERE question_id = A.key), '[]'::JSONB)) + ), '{}'::JSONB) + FROM JSONB_EACH(p_aggregates) A; + $$ LANGUAGE sql IMMUTABLE; + """); + + migrationBuilder.Sql( + """ + -- Builds the entry of one location group: its submission count, totals and answer aggregates (see "ReferenceAnswers"), + -- referencing only the notes and attachments of the group's submissions. + -- p_submissions: the group's submissions, shaped as for "AggregateAnswersByLocation". + CREATE OR REPLACE FUNCTION "AggregateLocationGroup"( + p_questions JSONB, + p_submissions JSONB, + p_text_ids JSONB, + p_notes JSONB, + p_attachments JSONB + ) + RETURNS JSONB + AS + $$ + WITH + SUBMISSION_IDS AS ( + SELECT S ->> 'SubmissionId' AS submission_id + FROM JSONB_ARRAY_ELEMENTS(p_submissions) S + ) + SELECT JSONB_BUILD_OBJECT( + 'SubmissionCount', JSONB_ARRAY_LENGTH(p_submissions), + 'TotalNumberOfQuestionsAnswered', (SELECT COALESCE(SUM((S ->> 'NumberOfQuestionsAnswered')::INT), 0) + FROM JSONB_ARRAY_ELEMENTS(p_submissions) S), + 'TotalNumberOfFlaggedAnswers', (SELECT COALESCE(SUM((S ->> 'NumberOfFlaggedAnswers')::INT), 0) + FROM JSONB_ARRAY_ELEMENTS(p_submissions) S), + -- only for submissions made at polling stations (not citizen reports) + 'PollingStationsCount', (SELECT CASE WHEN BOOL_OR(S ? 'PollingStationId') + THEN COUNT(DISTINCT S ->> 'PollingStationId') END + FROM JSONB_ARRAY_ELEMENTS(p_submissions) S), + 'Aggregates', "ReferenceAnswers"( + "AggregateAnswers"(p_questions, p_submissions), + p_text_ids, + (SELECT COALESCE(JSONB_AGG(N), '[]'::JSONB) + FROM JSONB_ARRAY_ELEMENTS(p_notes) N + WHERE N ->> 'SubmissionId' IN (SELECT submission_id FROM SUBMISSION_IDS)), + (SELECT COALESCE(JSONB_AGG(F), '[]'::JSONB) + FROM JSONB_ARRAY_ELEMENTS(p_attachments) F + WHERE F ->> 'SubmissionId' IN (SELECT submission_id FROM SUBMISSION_IDS)), + FALSE) + ); + $$ LANGUAGE sql STABLE; + """); + + migrationBuilder.Sql( + """ + -- Breaks the answer aggregates of a form down by location: one location per Level1, Level1/Level2, ... Level1..Level5 path + -- (levels are keyed by their full path because names can repeat under different parents). Submissions without a value + -- for a level are left out of that level. + -- Returns { + -- "Locations": [{ "Id", "Level", "ParentId", "Level1".."Level5", "SubmissionCount", "TotalNumberOfQuestionsAnswered", + -- "TotalNumberOfFlaggedAnswers", "PollingStationsCount" }], + -- "ByQuestion": { : { : } } + -- } + -- p_submissions: [{ "SubmissionId", "ResponderId", "TimeSubmitted", "Answers", "NumberOfQuestionsAnswered", + -- "NumberOfFlaggedAnswers", "Level1".."Level5", "PollingStationId" (not for citizen reports) }] + -- p_text_ids: "TextAnswerKey" -> text id (see "ExtractTexts"). + -- p_notes, p_attachments: all numbered notes / attachments of p_submissions (see "NumberItems"). + CREATE OR REPLACE FUNCTION "AggregateAnswersByLocation"( + p_questions JSONB, + p_submissions JSONB, + p_text_ids JSONB, + p_notes JSONB, + p_attachments JSONB + ) + RETURNS JSONB + AS + $$ + WITH + S AS ( + SELECT SUB AS submission, + NULLIF(SUB ->> 'Level1', '') AS l1, + NULLIF(SUB ->> 'Level2', '') AS l2, + NULLIF(SUB ->> 'Level3', '') AS l3, + NULLIF(SUB ->> 'Level4', '') AS l4, + NULLIF(SUB ->> 'Level5', '') AS l5 + FROM JSONB_ARRAY_ELEMENTS(p_submissions) SUB + ), + GROUPS AS ( + SELECT 1 AS level, l1, NULL::TEXT AS l2, NULL::TEXT AS l3, NULL::TEXT AS l4, NULL::TEXT AS l5, JSONB_AGG(submission) AS submissions + FROM S + WHERE l1 IS NOT NULL + GROUP BY l1 + UNION ALL + SELECT 2 AS level, l1, l2, NULL::TEXT AS l3, NULL::TEXT AS l4, NULL::TEXT AS l5, JSONB_AGG(submission) AS submissions + FROM S + WHERE l2 IS NOT NULL + GROUP BY l1, l2 + UNION ALL + SELECT 3 AS level, l1, l2, l3, NULL::TEXT AS l4, NULL::TEXT AS l5, JSONB_AGG(submission) AS submissions + FROM S + WHERE l3 IS NOT NULL + GROUP BY l1, l2, l3 + UNION ALL + SELECT 4 AS level, l1, l2, l3, l4, NULL::TEXT AS l5, JSONB_AGG(submission) AS submissions + FROM S + WHERE l4 IS NOT NULL + GROUP BY l1, l2, l3, l4 + UNION ALL + SELECT 5 AS level, l1, l2, l3, l4, l5, JSONB_AGG(submission) AS submissions + FROM S + WHERE l5 IS NOT NULL + GROUP BY l1, l2, l3, l4, l5 + ), + LOCATIONS AS ( + SELECT ROW_NUMBER() OVER (ORDER BY level, l1, l2, l3, l4, l5) AS id, + G.level, G.l1, G.l2, G.l3, G.l4, G.l5, + "AggregateLocationGroup"(p_questions, G.submissions, p_text_ids, p_notes, p_attachments) AS stats + FROM GROUPS G + ), + -- the parent of a location is the location one level up with the same path + WITH_PARENTS AS ( + SELECT L.*, P.id AS parent_id + FROM LOCATIONS L + LEFT JOIN LOCATIONS P ON P.level = L.level - 1 + AND P.l1 IS NOT DISTINCT FROM L.l1 + AND (P.level < 2 OR P.l2 IS NOT DISTINCT FROM L.l2) + AND (P.level < 3 OR P.l3 IS NOT DISTINCT FROM L.l3) + AND (P.level < 4 OR P.l4 IS NOT DISTINCT FROM L.l4) + ) + SELECT JSONB_BUILD_OBJECT( + 'Locations', (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'Id', id, 'Level', level, 'ParentId', parent_id, + 'Level1', l1, 'Level2', l2, 'Level3', l3, 'Level4', l4, 'Level5', l5) + || (stats - 'Aggregates') ORDER BY id), '[]'::JSONB) + FROM WITH_PARENTS), + 'ByQuestion', (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, locations), '{}'::JSONB) + FROM (SELECT A.key AS question_id, JSONB_OBJECT_AGG(L.id, A.value ORDER BY L.id) AS locations + FROM LOCATIONS L + CROSS JOIN LATERAL JSONB_EACH(L.stats -> 'Aggregates') A + GROUP BY A.key) Q) + ); + $$ LANGUAGE sql STABLE; + """); + + migrationBuilder.Sql( + """ + -- Adds to every answer aggregate its breakdown per location id ("LocationAggregates"). + -- p_by_question: { : { : } } (see "AggregateAnswersByLocation"). + CREATE OR REPLACE FUNCTION "AttachLocationAggregates"( + p_aggregates JSONB, + p_by_question JSONB + ) + RETURNS JSONB + AS + $$ + SELECT COALESCE(JSONB_OBJECT_AGG( + A.key, + A.value || JSONB_BUILD_OBJECT('LocationAggregates', COALESCE(p_by_question -> A.key, '{}'::JSONB)) + ), '{}'::JSONB) + FROM JSONB_EACH(p_aggregates) A; + $$ LANGUAGE sql IMMUTABLE; + """); + + // the aggregate functions return new columns, so they have to be dropped and recreated + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "GetCitizenReportsAggregate"(uuid, uuid, uuid, text, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "GetIncidentReportsAggregate"(uuid, uuid, text, uuid, text, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "GetFormSubmissionsAggregate"(uuid, uuid, text, uuid, text, jsonb);"""); + + migrationBuilder.Sql( + """ + -- Aggregates the answers of a form's submissions. Returns one row shaped like FormSubmissionsAggregate, + -- or no rows when the form is not available to the ngo. + -- Texts, Notes and Attachments are numbered ("Id") and the question aggregates reference them by id + -- (see "ReferenceAnswers"). Locations lists the Level1..Level5 paths of the submissions and every question + -- aggregate holds its breakdown per location id in "LocationAggregates" (see "AggregateAnswersByLocation"). + -- + -- filter is a SQL predicate over "GetFormSubmissionEntries" aliased as s, built server-side by FilterSqlCompiler. + -- Its values are read from filterParameters as ($1 ->> 'name'), so they are never part of the SQL text. + CREATE OR REPLACE FUNCTION "GetFormSubmissionsAggregate"( + electionRoundId UUID, + ngoId UUID, + dataSource TEXT, + formId UUID, + filter TEXT DEFAULT 'TRUE', + filterParameters JSONB DEFAULT '{}'::JSONB + ) + RETURNS TABLE + ( + "FormId" UUID, + "FormCode" TEXT, + "FormType" TEXT, + "Name" JSONB, + "Description" JSONB, + "DefaultLanguage" TEXT, + "Languages" TEXT[], + "Responders" JSONB, + "SubmissionCount" INTEGER, + "TotalNumberOfQuestionsAnswered" INTEGER, + "TotalNumberOfFlaggedAnswers" INTEGER, + "PollingStationsCount" INTEGER, + "Aggregates" JSONB, + "Notes" JSONB, + "Attachments" JSONB, + "Texts" JSONB, + "Locations" JSONB + ) + AS + $$ + #variable_conflict use_column + DECLARE + v_form RECORD; + v_collected RECORD; + v_aggregates JSONB; + v_texts JSONB; + v_notes JSONB; + v_attachments JSONB; + v_locations JSONB; + BEGIN + -- load the form: a published form available to the ngo, or the election round's PSI form + SELECT F."Id" AS id, + F."Code"::TEXT AS code, + F."FormType" AS form_type, + F."Name" AS name, + F."Description" AS description, + F."DefaultLanguage"::TEXT AS default_language, + F."Languages" AS languages, + F."Questions" AS questions + INTO v_form + FROM "GetAvailableForms"(electionRoundId, ngoId, dataSource) AF + INNER JOIN "Forms" F ON F."Id" = AF."FormId" + WHERE F."Id" = formId + AND F."Status" = 'Published' + AND F."FormType" <> 'CitizenReporting'; + + IF NOT FOUND THEN + SELECT PSIF."Id" AS id, + 'PSI' AS code, + 'PSI' AS form_type, + L.psi_text AS name, + L.psi_text AS description, + PSIF."DefaultLanguage"::TEXT AS default_language, + PSIF."Languages" AS languages, + PSIF."Questions" AS questions + INTO v_form + FROM "PollingStationInformationForms" PSIF + CROSS JOIN LATERAL ( + SELECT COALESCE(JSONB_OBJECT_AGG(LANGUAGE, 'PSI'), '{}'::JSONB) AS psi_text + FROM UNNEST(PSIF."Languages") LANGUAGE + ) L + WHERE PSIF."ElectionRoundId" = electionRoundId + AND PSIF."Id" = formId; + + IF NOT FOUND THEN + RETURN; + END IF; + END IF; + + -- load the submissions matching the filter, with their notes and attachments + EXECUTE FORMAT( + $sql$ + WITH + SUBMISSIONS AS MATERIALIZED ( + SELECT S."SubmissionId", + S."TimeSubmitted", + S."MonitoringObserverId", + S."DisplayName", + S."Email", + S."PhoneNumber", + S."PollingStationId", + S."Level1", + S."Level2", + S."Level3", + S."Level4", + S."Level5", + S."NumberOfQuestionsAnswered", + S."NumberOfFlaggedAnswers", + S."Answers", + S."Notes", + S."Attachments" + FROM "GetFormSubmissionEntries"($2, $3, $4) S + WHERE S."FormId" = $5 + AND (%s) + ) + SELECT + (SELECT COUNT(*) FROM SUBMISSIONS)::INT AS submission_count, + + (SELECT COALESCE(SUM("NumberOfQuestionsAnswered"), 0) FROM SUBMISSIONS)::INT AS questions_answered, + + (SELECT COALESCE(SUM("NumberOfFlaggedAnswers"), 0) FROM SUBMISSIONS)::INT AS flagged_answers, + + (SELECT COUNT(DISTINCT "PollingStationId") FROM SUBMISSIONS)::INT AS polling_stations, + + (SELECT COALESCE(JSONB_AGG(DISTINCT JSONB_BUILD_OBJECT( + 'ResponderId', "MonitoringObserverId", + 'DisplayName', "DisplayName", + 'Email', "Email", + 'PhoneNumber', "PhoneNumber")), '[]'::JSONB) + FROM SUBMISSIONS) AS responders, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', "SubmissionId", + 'ResponderId', "MonitoringObserverId", + 'TimeSubmitted', "TimeSubmitted", + 'Answers', "Answers", + 'NumberOfQuestionsAnswered', "NumberOfQuestionsAnswered", + 'NumberOfFlaggedAnswers', "NumberOfFlaggedAnswers", + 'Level1', "Level1", + 'Level2', "Level2", + 'Level3', "Level3", + 'Level4', "Level4", + 'Level5', "Level5", + 'PollingStationId', "PollingStationId")), '[]'::JSONB) + FROM SUBMISSIONS) AS submissions, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'QuestionId', N -> 'QuestionId', + 'SubmissionId', S."SubmissionId", + 'Text', N -> 'Text', + 'TimeSubmitted', N -> 'TimeSubmitted', + 'MonitoringObserverId', S."MonitoringObserverId")), '[]'::JSONB) + FROM SUBMISSIONS S + CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(S."Notes") N) AS notes, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', S."SubmissionId", + 'QuestionId', F -> 'QuestionId', + 'MonitoringObserverId', S."MonitoringObserverId", + 'FilePath', F -> 'FilePath', + 'UploadedFileName', F -> 'UploadedFileName', + 'FileName', F -> 'FileName', + 'MimeType', F -> 'MimeType', + 'TimeSubmitted', F -> 'TimeSubmitted')), '[]'::JSONB) + FROM SUBMISSIONS S + CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(S."Attachments") F) AS attachments + $sql$, filter) + INTO v_collected + USING filterParameters, electionRoundId, ngoId, dataSource, formId; + + v_aggregates := "AggregateAnswers"(v_form.questions, v_collected.submissions); + v_texts := "ExtractTexts"(v_aggregates); + v_notes := "NumberItems"(v_collected.notes); + v_attachments := "NumberItems"(v_collected.attachments); + v_locations := "AggregateAnswersByLocation"(v_form.questions, v_collected.submissions, v_texts -> 'Ids', v_notes, v_attachments); + + RETURN QUERY + SELECT v_form.id, + v_form.code, + v_form.form_type, + v_form.name, + v_form.description, + v_form.default_language, + v_form.languages, + v_collected.responders, + v_collected.submission_count, + v_collected.questions_answered, + v_collected.flagged_answers, + v_collected.polling_stations, + "AttachLocationAggregates"("ReferenceAnswers"(v_aggregates, v_texts -> 'Ids', v_notes, v_attachments, TRUE), v_locations -> 'ByQuestion'), + v_notes, + v_attachments, + v_texts -> 'Texts', + v_locations -> 'Locations'; + END; + $$ LANGUAGE plpgsql; + """); + + migrationBuilder.Sql( + """ + -- Aggregates the answers of an incident reporting form. Returns one row shaped like FormSubmissionsAggregate, + -- or no rows when the form does not belong to the ngo. + -- Texts, Notes and Attachments are numbered ("Id") and the question aggregates reference them by id + -- (see "ReferenceAnswers"). Locations lists the Level1..Level5 paths of the submissions and every question + -- aggregate holds its breakdown per location id in "LocationAggregates" (see "AggregateAnswersByLocation"). + -- + -- filter is a SQL predicate over "GetIncidentReportEntries" aliased as s, built server-side by FilterSqlCompiler. + -- Its values are read from filterParameters as ($1 ->> 'name'), so they are never part of the SQL text. + CREATE OR REPLACE FUNCTION "GetIncidentReportsAggregate"( + electionRoundId UUID, + ngoId UUID, + dataSource TEXT, + formId UUID, + filter TEXT DEFAULT 'TRUE', + filterParameters JSONB DEFAULT '{}'::JSONB + ) + RETURNS TABLE + ( + "FormId" UUID, + "FormCode" TEXT, + "FormType" TEXT, + "Name" JSONB, + "Description" JSONB, + "DefaultLanguage" TEXT, + "Languages" TEXT[], + "Responders" JSONB, + "SubmissionCount" INTEGER, + "TotalNumberOfQuestionsAnswered" INTEGER, + "TotalNumberOfFlaggedAnswers" INTEGER, + "PollingStationsCount" INTEGER, + "Aggregates" JSONB, + "Notes" JSONB, + "Attachments" JSONB, + "Texts" JSONB, + "Locations" JSONB + ) + AS + $$ + #variable_conflict use_column + DECLARE + v_form RECORD; + v_collected RECORD; + v_aggregates JSONB; + v_texts JSONB; + v_notes JSONB; + v_attachments JSONB; + v_locations JSONB; + BEGIN + -- load the form: a form of the ngo in this election round + SELECT F."Id" AS id, + F."Code"::TEXT AS code, + F."FormType" AS form_type, + F."Name" AS name, + F."Description" AS description, + F."DefaultLanguage"::TEXT AS default_language, + F."Languages" AS languages, + F."Questions" AS questions + INTO v_form + FROM "Forms" F + INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" + WHERE F."Id" = formId + AND F."ElectionRoundId" = electionRoundId + AND MN."ElectionRoundId" = electionRoundId + AND MN."NgoId" = ngoId; + + IF NOT FOUND THEN + RETURN; + END IF; + + -- load the incident reports matching the filter, with their notes and attachments + EXECUTE FORMAT( + $sql$ + WITH + SUBMISSIONS AS MATERIALIZED ( + SELECT S."Id" AS "SubmissionId", + S."TimeSubmitted", + S."MonitoringObserverId", + S."DisplayName", + S."Email", + S."PhoneNumber", + S."PollingStationId", + S."Level1", + S."Level2", + S."Level3", + S."Level4", + S."Level5", + S."NumberOfQuestionsAnswered", + S."NumberOfFlaggedAnswers", + S."Answers" + FROM "GetIncidentReportEntries"($2, $3, $4) S + WHERE S."FormId" = $5 + AND (%s) + ) + SELECT + (SELECT COUNT(*) FROM SUBMISSIONS)::INT AS submission_count, + + (SELECT COALESCE(SUM("NumberOfQuestionsAnswered"), 0) FROM SUBMISSIONS)::INT AS questions_answered, + + (SELECT COALESCE(SUM("NumberOfFlaggedAnswers"), 0) FROM SUBMISSIONS)::INT AS flagged_answers, + + (SELECT COUNT(DISTINCT "PollingStationId") FROM SUBMISSIONS)::INT AS polling_stations, + + (SELECT COALESCE(JSONB_AGG(DISTINCT JSONB_BUILD_OBJECT( + 'ResponderId', "MonitoringObserverId", + 'DisplayName', "DisplayName", + 'Email', "Email", + 'PhoneNumber', "PhoneNumber")), '[]'::JSONB) + FROM SUBMISSIONS) AS responders, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', "SubmissionId", + 'ResponderId', "MonitoringObserverId", + 'TimeSubmitted', "TimeSubmitted", + 'Answers', "Answers", + 'NumberOfQuestionsAnswered', "NumberOfQuestionsAnswered", + 'NumberOfFlaggedAnswers', "NumberOfFlaggedAnswers", + 'Level1', "Level1", + 'Level2', "Level2", + 'Level3', "Level3", + 'Level4', "Level4", + 'Level5', "Level5", + 'PollingStationId', "PollingStationId")), '[]'::JSONB) + FROM SUBMISSIONS) AS submissions, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'QuestionId', N."QuestionId", + 'SubmissionId', S."SubmissionId", + 'Text', N."Text", + 'TimeSubmitted', COALESCE(N."LastModifiedOn", N."CreatedOn"), + 'MonitoringObserverId', S."MonitoringObserverId")), '[]'::JSONB) + FROM SUBMISSIONS S + INNER JOIN "IncidentReportNotes" N ON N."IncidentReportId" = S."SubmissionId") AS notes, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', S."SubmissionId", + 'QuestionId', A."QuestionId", + 'MonitoringObserverId', S."MonitoringObserverId", + 'FilePath', A."FilePath", + 'UploadedFileName', A."UploadedFileName", + 'FileName', A."FileName", + 'MimeType', A."MimeType", + 'TimeSubmitted', A."CreatedOn")), '[]'::JSONB) + FROM SUBMISSIONS S + INNER JOIN "IncidentReportAttachments" A ON A."IncidentReportId" = S."SubmissionId" + WHERE A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE) AS attachments + $sql$, filter) + INTO v_collected + USING filterParameters, electionRoundId, ngoId, dataSource, formId; + + v_aggregates := "AggregateAnswers"(v_form.questions, v_collected.submissions); + v_texts := "ExtractTexts"(v_aggregates); + v_notes := "NumberItems"(v_collected.notes); + v_attachments := "NumberItems"(v_collected.attachments); + v_locations := "AggregateAnswersByLocation"(v_form.questions, v_collected.submissions, v_texts -> 'Ids', v_notes, v_attachments); + + RETURN QUERY + SELECT v_form.id, + v_form.code, + v_form.form_type, + v_form.name, + v_form.description, + v_form.default_language, + v_form.languages, + v_collected.responders, + v_collected.submission_count, + v_collected.questions_answered, + v_collected.flagged_answers, + v_collected.polling_stations, + "AttachLocationAggregates"("ReferenceAnswers"(v_aggregates, v_texts -> 'Ids', v_notes, v_attachments, TRUE), v_locations -> 'ByQuestion'), + v_notes, + v_attachments, + v_texts -> 'Texts', + v_locations -> 'Locations'; + END; + $$ LANGUAGE plpgsql; + """); + + migrationBuilder.Sql( + """ + -- Aggregates the answers of a citizen reporting form. Returns one row shaped like CitizenReportFormSubmissionsAggregate, + -- or no rows when the form does not belong to the ngo. Citizen reports have no observer, so responder ids are empty. + -- Texts, Notes and Attachments are numbered ("Id") and the question aggregates reference them by id + -- (see "ReferenceAnswers"). Locations lists the Level1..Level5 paths of the submissions and every question + -- aggregate holds its breakdown per location id in "LocationAggregates" (see "AggregateAnswersByLocation"). + -- + -- filter is a SQL predicate over "GetCitizenReportEntries" aliased as s, built server-side by FilterSqlCompiler. + -- Its values are read from filterParameters as ($1 ->> 'name'), so they are never part of the SQL text. + CREATE OR REPLACE FUNCTION "GetCitizenReportsAggregate"( + electionRoundId UUID, + ngoId UUID, + formId UUID, + filter TEXT DEFAULT 'TRUE', + filterParameters JSONB DEFAULT '{}'::JSONB + ) + RETURNS TABLE + ( + "ElectionRoundId" UUID, + "MonitoringNgoId" UUID, + "FormId" UUID, + "FormCode" TEXT, + "FormType" TEXT, + "Name" JSONB, + "Description" JSONB, + "DefaultLanguage" TEXT, + "Languages" TEXT[], + "SubmissionCount" INTEGER, + "TotalNumberOfQuestionsAnswered" INTEGER, + "TotalNumberOfFlaggedAnswers" INTEGER, + "Aggregates" JSONB, + "Notes" JSONB, + "Attachments" JSONB, + "Texts" JSONB, + "Locations" JSONB + ) + AS + $$ + #variable_conflict use_column + DECLARE + v_form RECORD; + v_collected RECORD; + v_aggregates JSONB; + v_texts JSONB; + v_notes JSONB; + v_attachments JSONB; + v_locations JSONB; + BEGIN + -- load the form: a form of the ngo in this election round + SELECT F."Id" AS id, + F."ElectionRoundId" AS election_round_id, + F."MonitoringNgoId" AS monitoring_ngo_id, + F."Code"::TEXT AS code, + F."FormType" AS form_type, + F."Name" AS name, + F."Description" AS description, + F."DefaultLanguage"::TEXT AS default_language, + F."Languages" AS languages, + F."Questions" AS questions + INTO v_form + FROM "Forms" F + INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" + WHERE F."Id" = formId + AND F."ElectionRoundId" = electionRoundId + AND MN."ElectionRoundId" = electionRoundId + AND MN."NgoId" = ngoId; + + IF NOT FOUND THEN + RETURN; + END IF; + + -- load the citizen reports matching the filter, with their notes and attachments + EXECUTE FORMAT( + $sql$ + WITH + SUBMISSIONS AS MATERIALIZED ( + SELECT S."Id" AS "SubmissionId", + S."TimeSubmitted", + S."Level1", + S."Level2", + S."Level3", + S."Level4", + S."Level5", + S."NumberOfQuestionsAnswered", + S."NumberOfFlaggedAnswers", + S."Answers" + FROM "GetCitizenReportEntries"($2, $3) S + WHERE S."FormId" = $4 + AND (%s) + ) + SELECT + (SELECT COUNT(*) FROM SUBMISSIONS)::INT AS submission_count, + + (SELECT COALESCE(SUM("NumberOfQuestionsAnswered"), 0) FROM SUBMISSIONS)::INT AS questions_answered, + + (SELECT COALESCE(SUM("NumberOfFlaggedAnswers"), 0) FROM SUBMISSIONS)::INT AS flagged_answers, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', "SubmissionId", + 'ResponderId', '00000000-0000-0000-0000-000000000000'::UUID, + 'TimeSubmitted', "TimeSubmitted", + 'Answers', "Answers", + 'NumberOfQuestionsAnswered', "NumberOfQuestionsAnswered", + 'NumberOfFlaggedAnswers', "NumberOfFlaggedAnswers", + 'Level1', "Level1", + 'Level2', "Level2", + 'Level3', "Level3", + 'Level4', "Level4", + 'Level5', "Level5")), '[]'::JSONB) + FROM SUBMISSIONS) AS submissions, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'QuestionId', N."QuestionId", + 'SubmissionId', S."SubmissionId", + 'Text', N."Text", + 'TimeSubmitted', COALESCE(N."LastModifiedOn", N."CreatedOn"))), '[]'::JSONB) + FROM SUBMISSIONS S + INNER JOIN "CitizenReportNotes" N ON N."CitizenReportId" = S."SubmissionId") AS notes, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', S."SubmissionId", + 'QuestionId', A."QuestionId", + 'FilePath', A."FilePath", + 'UploadedFileName', A."UploadedFileName", + 'FileName', A."FileName", + 'MimeType', A."MimeType", + 'TimeSubmitted', A."CreatedOn")), '[]'::JSONB) + FROM SUBMISSIONS S + INNER JOIN "CitizenReportAttachments" A ON A."CitizenReportId" = S."SubmissionId" + WHERE A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE) AS attachments + $sql$, filter) + INTO v_collected + USING filterParameters, electionRoundId, ngoId, formId; + + v_aggregates := "AggregateAnswers"(v_form.questions, v_collected.submissions); + v_texts := "ExtractTexts"(v_aggregates); + v_notes := "NumberItems"(v_collected.notes); + v_attachments := "NumberItems"(v_collected.attachments); + v_locations := "AggregateAnswersByLocation"(v_form.questions, v_collected.submissions, v_texts -> 'Ids', v_notes, v_attachments); + + RETURN QUERY + SELECT v_form.election_round_id, + v_form.monitoring_ngo_id, + v_form.id, + v_form.code, + v_form.form_type, + v_form.name, + v_form.description, + v_form.default_language, + v_form.languages, + v_collected.submission_count, + v_collected.questions_answered, + v_collected.flagged_answers, + "AttachLocationAggregates"("ReferenceAnswers"(v_aggregates, v_texts -> 'Ids', v_notes, v_attachments, TRUE), v_locations -> 'ByQuestion'), + v_notes, + v_attachments, + v_texts -> 'Texts', + v_locations -> 'Locations'; + END; + $$ LANGUAGE plpgsql; + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "GetCitizenReportsAggregate"(uuid, uuid, uuid, text, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "GetIncidentReportsAggregate"(uuid, uuid, text, uuid, text, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "GetFormSubmissionsAggregate"(uuid, uuid, text, uuid, text, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "AttachLocationAggregates"(jsonb, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "AggregateAnswersByLocation"(jsonb, jsonb, jsonb, jsonb, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "AggregateLocationGroup"(jsonb, jsonb, jsonb, jsonb, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "ReferenceAnswers"(jsonb, jsonb, jsonb, jsonb, boolean);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "ExtractTexts"(jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "TextAnswerKey"(text, jsonb);"""); + migrationBuilder.Sql("""DROP FUNCTION IF EXISTS "NumberItems"(jsonb);"""); + + migrationBuilder.Sql( + """ + -- Builds the answer aggregates of a form, keyed by question id, shaped like the BaseAnswerAggregate subtypes. + -- p_questions: the form's questions as stored. + -- p_submissions: [{ "submissionId", "responderId", "timeSubmitted", "answers" }] of the submissions to aggregate. + CREATE OR REPLACE FUNCTION "AggregateAnswers"( + p_questions JSONB, + p_submissions JSONB + ) + RETURNS JSONB + AS + $$ + DECLARE + v_aggregates JSONB; + v_collected RECORD; + BEGIN + -- 1. an empty aggregate for every question, depending on its type + SELECT COALESCE(JSONB_OBJECT_AGG( + Q.question ->> 'Id', + JSONB_BUILD_OBJECT( + '$questionType', REPLACE(Q.question ->> '$questionType', 'Question', 'AnswerAggregate'), + 'QuestionId', Q.question -> 'Id', + 'DisplayOrder', Q.idx - 1, + 'Question', Q.question, + 'AnswersAggregated', 0 + ) || CASE + WHEN Q.question ->> '$questionType' = 'textQuestion' + THEN JSONB_BUILD_OBJECT('Answers', '[]'::JSONB) + WHEN Q.question ->> '$questionType' = 'numberQuestion' + THEN JSONB_BUILD_OBJECT('Min', 2147483647, 'Max', -2147483648, 'Average', 0) + WHEN Q.question ->> '$questionType' IN ('dateQuestion', 'timeQuestion', 'dateTimeQuestion') + THEN JSONB_BUILD_OBJECT('AnswersHistogram', '{}'::JSONB) + WHEN Q.question ->> '$questionType' = 'ratingQuestion' + THEN JSONB_BUILD_OBJECT( + 'Min', 2147483647, 'Max', -2147483648, 'Average', 0, + 'AnswersHistogram', ( + SELECT JSONB_OBJECT_AGG(V::TEXT, 0) + FROM GENERATE_SERIES(1, SUBSTRING(Q.question ->> 'Scale' FROM 'OneTo(\d+)')::INT) V)) + WHEN Q.question ->> '$questionType' IN ('singleSelectQuestion', 'multiSelectQuestion') + THEN JSONB_BUILD_OBJECT( + 'AnswersHistogram', ( + SELECT COALESCE(JSONB_OBJECT_AGG(O ->> 'Id', 0), '{}'::JSONB) + FROM JSONB_ARRAY_ELEMENTS(Q.question -> 'Options') O), + 'FreeTexts', '[]'::JSONB) + ELSE '{}'::JSONB + END + ), '{}'::JSONB) + INTO v_aggregates + FROM JSONB_ARRAY_ELEMENTS(p_questions) WITH ORDINALITY Q(question, idx); + + -- 2. collect data from the submissions' answers + WITH + SUBMISSION_ANSWERS AS ( + SELECT (S ->> 'SubmissionId')::UUID AS submission_id, + (S ->> 'ResponderId')::UUID AS responder_id, + (S ->> 'TimeSubmitted')::TIMESTAMPTZ AS time_submitted, + S -> 'Answers' AS answers + FROM JSONB_ARRAY_ELEMENTS(p_submissions) S + ), + FORM_QUESTIONS AS ( + SELECT Q ->> 'Id' AS question_id, + REPLACE(Q ->> '$questionType', 'Question', 'Answer') AS answer_type, + Q -> 'Options' AS options + FROM JSONB_ARRAY_ELEMENTS(p_questions) Q + ), + -- answers to the form's questions, of the type each question expects + ANSWERS AS ( + SELECT SA.submission_id, SA.responder_id, SA.time_submitted, + FQ.question_id, FQ.answer_type, FQ.options, A AS answer + FROM SUBMISSION_ANSWERS SA + CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(SA.answers) A + INNER JOIN FORM_QUESTIONS FQ ON FQ.question_id = A ->> 'QuestionId' + AND FQ.answer_type = A ->> '$answerType' + ), + -- one row per selected option of single and multi select answers + SELECTIONS AS ( + SELECT AN.*, SEL AS selection + FROM ANSWERS AN + CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS( + CASE + WHEN AN.answer_type = 'singleSelectAnswer' THEN JSONB_BUILD_ARRAY(AN.answer -> 'Selection') + ELSE AN.answer -> 'Selection' + END) SEL + WHERE AN.answer_type IN ('singleSelectAnswer', 'multiSelectAnswer') + ) + SELECT + -- per question: number of answers, and min/max/average for number and rating answers + (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, stats), '{}'::JSONB) + FROM (SELECT question_id, + JSONB_BUILD_OBJECT('AnswersAggregated', COUNT(*)) || + CASE + WHEN answer_type IN ('numberAnswer', 'ratingAnswer') THEN JSONB_BUILD_OBJECT( + 'Min', MIN((answer ->> 'Value')::INT), + 'Max', MAX((answer ->> 'Value')::INT), + 'Average', AVG((answer ->> 'Value')::INT)) + ELSE '{}'::JSONB + END AS stats + FROM ANSWERS + GROUP BY question_id, answer_type) X) AS stats, + + -- per question: rating values, hour buckets of dates and date times, minute buckets of times and selected options with their counts + (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, histogram), '{}'::JSONB) + FROM (SELECT question_id, JSONB_OBJECT_AGG(KEY, count) AS histogram + FROM (SELECT question_id, answer ->> 'Value' AS key, COUNT(*) AS count + FROM ANSWERS + WHERE answer_type = 'ratingAnswer' + GROUP BY 1, 2 + UNION ALL + SELECT question_id, + TO_CHAR(DATE_TRUNC('hour', (answer ->> 'Date')::TIMESTAMPTZ AT TIME ZONE 'UTC'), + 'YYYY-MM-DD"T"HH24":00:00.0000000Z"'), + COUNT(*) + FROM ANSWERS + WHERE answer_type = 'dateAnswer' + GROUP BY 1, 2 + UNION ALL + SELECT question_id, + TO_CHAR(DATE_TRUNC('hour', (answer ->> 'DateTime')::TIMESTAMPTZ AT TIME ZONE 'UTC'), + 'YYYY-MM-DD"T"HH24":00:00.0000000Z"'), + COUNT(*) + FROM ANSWERS + WHERE answer_type = 'dateTimeAnswer' + GROUP BY 1, 2 + UNION ALL + SELECT question_id, TO_CHAR((answer ->> 'Time')::TIME, 'HH24:MI'), COUNT(*) + FROM ANSWERS + WHERE answer_type = 'timeAnswer' + GROUP BY 1, 2 + UNION ALL + SELECT question_id, selection ->> 'OptionId', COUNT(*) + FROM SELECTIONS + WHERE selection ->> 'OptionId' IS NOT NULL + GROUP BY 1, 2) H + GROUP BY question_id) X) AS histograms, + + -- per question: text answers, and free texts typed for free text options + (SELECT COALESCE(JSONB_OBJECT_AGG(question_id, texts), '{}'::JSONB) + FROM (SELECT question_id, + JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', submission_id, + 'ResponderId', responder_id, + 'Value', text) ORDER BY time_submitted) AS texts + FROM (SELECT question_id, submission_id, responder_id, time_submitted, answer ->> 'Text' AS text + FROM ANSWERS + WHERE answer_type = 'textAnswer' + UNION ALL + SELECT SL.question_id, SL.submission_id, SL.responder_id, SL.time_submitted, + SL.selection ->> 'Text' + FROM SELECTIONS SL + WHERE NULLIF(BTRIM(SL.selection ->> 'Text'), '') IS NOT NULL + AND EXISTS (SELECT 1 + FROM JSONB_ARRAY_ELEMENTS(SL.options) O + WHERE O ->> 'Id' = SL.selection ->> 'OptionId' + AND (O ->> 'IsFreeText')::BOOLEAN)) T + GROUP BY question_id) X) AS texts + INTO v_collected; + + -- 3. merge the collected data into the empty aggregates + RETURN (SELECT COALESCE(JSONB_OBJECT_AGG( + E.key, + E.value + || COALESCE(v_collected.stats -> E.key, '{}'::JSONB) + || CASE + WHEN E.value ->> '$questionType' = 'textAnswerAggregate' + THEN JSONB_BUILD_OBJECT('Answers', COALESCE(v_collected.texts -> E.key, '[]'::JSONB)) + WHEN E.value ->> '$questionType' IN ('singleSelectAnswerAggregate', 'multiSelectAnswerAggregate') + THEN JSONB_BUILD_OBJECT( + 'AnswersHistogram', (E.value -> 'AnswersHistogram') || COALESCE(v_collected.histograms -> E.key, '{}'::JSONB), + 'FreeTexts', COALESCE(v_collected.texts -> E.key, '[]'::JSONB)) + WHEN E.value ->> '$questionType' IN ('ratingAnswerAggregate', 'dateAnswerAggregate', 'timeAnswerAggregate', 'dateTimeAnswerAggregate') + THEN JSONB_BUILD_OBJECT( + 'AnswersHistogram', (E.value -> 'AnswersHistogram') || COALESCE(v_collected.histograms -> E.key, '{}'::JSONB)) + ELSE '{}'::JSONB + END + ), '{}'::JSONB) + FROM JSONB_EACH(v_aggregates) E); + END; + $$ LANGUAGE plpgsql; + """); + + migrationBuilder.Sql( + """ + -- Aggregates the answers of a form's submissions. Returns one row shaped like FormSubmissionsAggregate, + -- or no rows when the form is not available to the ngo. + -- + -- filter is a SQL predicate over "GetFormSubmissionEntries" aliased as s, built server-side by FilterSqlCompiler. + -- Its values are read from filterParameters as ($1 ->> 'name'), so they are never part of the SQL text. + CREATE OR REPLACE FUNCTION "GetFormSubmissionsAggregate"( + electionRoundId UUID, + ngoId UUID, + dataSource TEXT, + formId UUID, + filter TEXT DEFAULT 'TRUE', + filterParameters JSONB DEFAULT '{}'::JSONB + ) + RETURNS TABLE + ( + "FormId" UUID, + "FormCode" TEXT, + "FormType" TEXT, + "Name" JSONB, + "Description" JSONB, + "DefaultLanguage" TEXT, + "Languages" TEXT[], + "Responders" JSONB, + "SubmissionCount" INTEGER, + "TotalNumberOfQuestionsAnswered" INTEGER, + "TotalNumberOfFlaggedAnswers" INTEGER, + "PollingStationsCount" INTEGER, + "Aggregates" JSONB, + "Notes" JSONB, + "Attachments" JSONB + ) + AS + $$ + #variable_conflict use_column + DECLARE + v_form RECORD; + v_collected RECORD; + BEGIN + -- load the form: a published form available to the ngo, or the election round's PSI form + SELECT F."Id" AS id, + F."Code"::TEXT AS code, + F."FormType" AS form_type, + F."Name" AS name, + F."Description" AS description, + F."DefaultLanguage"::TEXT AS default_language, + F."Languages" AS languages, + F."Questions" AS questions + INTO v_form + FROM "GetAvailableForms"(electionRoundId, ngoId, dataSource) AF + INNER JOIN "Forms" F ON F."Id" = AF."FormId" + WHERE F."Id" = formId + AND F."Status" = 'Published' + AND F."FormType" <> 'CitizenReporting'; + + IF NOT FOUND THEN + SELECT PSIF."Id" AS id, + 'PSI' AS code, + 'PSI' AS form_type, + L.psi_text AS name, + L.psi_text AS description, + PSIF."DefaultLanguage"::TEXT AS default_language, + PSIF."Languages" AS languages, + PSIF."Questions" AS questions + INTO v_form + FROM "PollingStationInformationForms" PSIF + CROSS JOIN LATERAL ( + SELECT COALESCE(JSONB_OBJECT_AGG(LANGUAGE, 'PSI'), '{}'::JSONB) AS psi_text + FROM UNNEST(PSIF."Languages") LANGUAGE + ) L + WHERE PSIF."ElectionRoundId" = electionRoundId + AND PSIF."Id" = formId; + + IF NOT FOUND THEN + RETURN; + END IF; + END IF; + + -- load the submissions matching the filter, with their notes and attachments + EXECUTE FORMAT( + $sql$ + WITH + SUBMISSIONS AS MATERIALIZED ( + SELECT S."SubmissionId", + S."TimeSubmitted", + S."MonitoringObserverId", + S."DisplayName", + S."Email", + S."PhoneNumber", + S."PollingStationId", + S."NumberOfQuestionsAnswered", + S."NumberOfFlaggedAnswers", + S."Answers", + S."Notes", + S."Attachments" + FROM "GetFormSubmissionEntries"($2, $3, $4) S + WHERE S."FormId" = $5 + AND (%s) + ) + SELECT + (SELECT COUNT(*) FROM SUBMISSIONS)::INT AS submission_count, + + (SELECT COALESCE(SUM("NumberOfQuestionsAnswered"), 0) FROM SUBMISSIONS)::INT AS questions_answered, + + (SELECT COALESCE(SUM("NumberOfFlaggedAnswers"), 0) FROM SUBMISSIONS)::INT AS flagged_answers, + + (SELECT COUNT(DISTINCT "PollingStationId") FROM SUBMISSIONS)::INT AS polling_stations, + + (SELECT COALESCE(JSONB_AGG(DISTINCT JSONB_BUILD_OBJECT( + 'ResponderId', "MonitoringObserverId", + 'DisplayName', "DisplayName", + 'Email', "Email", + 'PhoneNumber', "PhoneNumber")), '[]'::JSONB) + FROM SUBMISSIONS) AS responders, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', "SubmissionId", + 'ResponderId', "MonitoringObserverId", + 'TimeSubmitted', "TimeSubmitted", + 'Answers', "Answers")), '[]'::JSONB) + FROM SUBMISSIONS) AS submissions, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'QuestionId', N -> 'QuestionId', + 'SubmissionId', S."SubmissionId", + 'Text', N -> 'Text', + 'TimeSubmitted', N -> 'TimeSubmitted', + 'MonitoringObserverId', S."MonitoringObserverId")), '[]'::JSONB) + FROM SUBMISSIONS S + CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(S."Notes") N) AS notes, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', S."SubmissionId", + 'QuestionId', F -> 'QuestionId', + 'MonitoringObserverId', S."MonitoringObserverId", + 'FilePath', F -> 'FilePath', + 'UploadedFileName', F -> 'UploadedFileName', + 'FileName', F -> 'FileName', + 'MimeType', F -> 'MimeType', + 'TimeSubmitted', F -> 'TimeSubmitted')), '[]'::JSONB) + FROM SUBMISSIONS S + CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(S."Attachments") F) AS attachments + $sql$, filter) + INTO v_collected + USING filterParameters, electionRoundId, ngoId, dataSource, formId; + + RETURN QUERY + SELECT v_form.id, + v_form.code, + v_form.form_type, + v_form.name, + v_form.description, + v_form.default_language, + v_form.languages, + v_collected.responders, + v_collected.submission_count, + v_collected.questions_answered, + v_collected.flagged_answers, + v_collected.polling_stations, + "AggregateAnswers"(v_form.questions, v_collected.submissions), + v_collected.notes, + v_collected.attachments; + END; + $$ LANGUAGE plpgsql; + """); + + migrationBuilder.Sql( + """ + -- Aggregates the answers of an incident reporting form. Returns one row shaped like FormSubmissionsAggregate, + -- or no rows when the form does not belong to the ngo. + -- + -- filter is a SQL predicate over "GetIncidentReportEntries" aliased as s, built server-side by FilterSqlCompiler. + -- Its values are read from filterParameters as ($1 ->> 'name'), so they are never part of the SQL text. + CREATE OR REPLACE FUNCTION "GetIncidentReportsAggregate"( + electionRoundId UUID, + ngoId UUID, + dataSource TEXT, + formId UUID, + filter TEXT DEFAULT 'TRUE', + filterParameters JSONB DEFAULT '{}'::JSONB + ) + RETURNS TABLE + ( + "FormId" UUID, + "FormCode" TEXT, + "FormType" TEXT, + "Name" JSONB, + "Description" JSONB, + "DefaultLanguage" TEXT, + "Languages" TEXT[], + "Responders" JSONB, + "SubmissionCount" INTEGER, + "TotalNumberOfQuestionsAnswered" INTEGER, + "TotalNumberOfFlaggedAnswers" INTEGER, + "PollingStationsCount" INTEGER, + "Aggregates" JSONB, + "Notes" JSONB, + "Attachments" JSONB + ) + AS + $$ + #variable_conflict use_column + DECLARE + v_form RECORD; + v_collected RECORD; + BEGIN + -- load the form: a form of the ngo in this election round + SELECT F."Id" AS id, + F."Code"::TEXT AS code, + F."FormType" AS form_type, + F."Name" AS name, + F."Description" AS description, + F."DefaultLanguage"::TEXT AS default_language, + F."Languages" AS languages, + F."Questions" AS questions + INTO v_form + FROM "Forms" F + INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" + WHERE F."Id" = formId + AND F."ElectionRoundId" = electionRoundId + AND MN."ElectionRoundId" = electionRoundId + AND MN."NgoId" = ngoId; + + IF NOT FOUND THEN + RETURN; + END IF; + + -- load the incident reports matching the filter, with their notes and attachments + EXECUTE FORMAT( + $sql$ + WITH + SUBMISSIONS AS MATERIALIZED ( + SELECT S."Id" AS "SubmissionId", + S."TimeSubmitted", + S."MonitoringObserverId", + S."DisplayName", + S."Email", + S."PhoneNumber", + S."PollingStationId", + S."NumberOfQuestionsAnswered", + S."NumberOfFlaggedAnswers", + S."Answers" + FROM "GetIncidentReportEntries"($2, $3, $4) S + WHERE S."FormId" = $5 + AND (%s) + ) + SELECT + (SELECT COUNT(*) FROM SUBMISSIONS)::INT AS submission_count, + + (SELECT COALESCE(SUM("NumberOfQuestionsAnswered"), 0) FROM SUBMISSIONS)::INT AS questions_answered, + + (SELECT COALESCE(SUM("NumberOfFlaggedAnswers"), 0) FROM SUBMISSIONS)::INT AS flagged_answers, + + (SELECT COUNT(DISTINCT "PollingStationId") FROM SUBMISSIONS)::INT AS polling_stations, + + (SELECT COALESCE(JSONB_AGG(DISTINCT JSONB_BUILD_OBJECT( + 'ResponderId', "MonitoringObserverId", + 'DisplayName', "DisplayName", + 'Email', "Email", + 'PhoneNumber', "PhoneNumber")), '[]'::JSONB) + FROM SUBMISSIONS) AS responders, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', "SubmissionId", + 'ResponderId', "MonitoringObserverId", + 'TimeSubmitted', "TimeSubmitted", + 'Answers', "Answers")), '[]'::JSONB) + FROM SUBMISSIONS) AS submissions, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'QuestionId', N."QuestionId", + 'SubmissionId', S."SubmissionId", + 'Text', N."Text", + 'TimeSubmitted', COALESCE(N."LastModifiedOn", N."CreatedOn"), + 'MonitoringObserverId', S."MonitoringObserverId")), '[]'::JSONB) + FROM SUBMISSIONS S + INNER JOIN "IncidentReportNotes" N ON N."IncidentReportId" = S."SubmissionId") AS notes, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', S."SubmissionId", + 'QuestionId', A."QuestionId", + 'MonitoringObserverId', S."MonitoringObserverId", + 'FilePath', A."FilePath", + 'UploadedFileName', A."UploadedFileName", + 'FileName', A."FileName", + 'MimeType', A."MimeType", + 'TimeSubmitted', A."CreatedOn")), '[]'::JSONB) + FROM SUBMISSIONS S + INNER JOIN "IncidentReportAttachments" A ON A."IncidentReportId" = S."SubmissionId" + WHERE A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE) AS attachments + $sql$, filter) + INTO v_collected + USING filterParameters, electionRoundId, ngoId, dataSource, formId; + + RETURN QUERY + SELECT v_form.id, + v_form.code, + v_form.form_type, + v_form.name, + v_form.description, + v_form.default_language, + v_form.languages, + v_collected.responders, + v_collected.submission_count, + v_collected.questions_answered, + v_collected.flagged_answers, + v_collected.polling_stations, + "AggregateAnswers"(v_form.questions, v_collected.submissions), + v_collected.notes, + v_collected.attachments; + END; + $$ LANGUAGE plpgsql; + """); + + migrationBuilder.Sql( + """ + -- Aggregates the answers of a citizen reporting form. Returns one row shaped like CitizenReportFormSubmissionsAggregate, + -- or no rows when the form does not belong to the ngo. Citizen reports have no observer, so responder ids are empty. + -- + -- filter is a SQL predicate over "GetCitizenReportEntries" aliased as s, built server-side by FilterSqlCompiler. + -- Its values are read from filterParameters as ($1 ->> 'name'), so they are never part of the SQL text. + CREATE OR REPLACE FUNCTION "GetCitizenReportsAggregate"( + electionRoundId UUID, + ngoId UUID, + formId UUID, + filter TEXT DEFAULT 'TRUE', + filterParameters JSONB DEFAULT '{}'::JSONB + ) + RETURNS TABLE + ( + "ElectionRoundId" UUID, + "MonitoringNgoId" UUID, + "FormId" UUID, + "FormCode" TEXT, + "FormType" TEXT, + "Name" JSONB, + "Description" JSONB, + "DefaultLanguage" TEXT, + "Languages" TEXT[], + "SubmissionCount" INTEGER, + "TotalNumberOfQuestionsAnswered" INTEGER, + "TotalNumberOfFlaggedAnswers" INTEGER, + "Aggregates" JSONB, + "Notes" JSONB, + "Attachments" JSONB + ) + AS + $$ + #variable_conflict use_column + DECLARE + v_form RECORD; + v_collected RECORD; + BEGIN + -- load the form: a form of the ngo in this election round + SELECT F."Id" AS id, + F."ElectionRoundId" AS election_round_id, + F."MonitoringNgoId" AS monitoring_ngo_id, + F."Code"::TEXT AS code, + F."FormType" AS form_type, + F."Name" AS name, + F."Description" AS description, + F."DefaultLanguage"::TEXT AS default_language, + F."Languages" AS languages, + F."Questions" AS questions + INTO v_form + FROM "Forms" F + INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" + WHERE F."Id" = formId + AND F."ElectionRoundId" = electionRoundId + AND MN."ElectionRoundId" = electionRoundId + AND MN."NgoId" = ngoId; + + IF NOT FOUND THEN + RETURN; + END IF; + + -- load the citizen reports matching the filter, with their notes and attachments + EXECUTE FORMAT( + $sql$ + WITH + SUBMISSIONS AS MATERIALIZED ( + SELECT S."Id" AS "SubmissionId", + S."TimeSubmitted", + S."NumberOfQuestionsAnswered", + S."NumberOfFlaggedAnswers", + S."Answers" + FROM "GetCitizenReportEntries"($2, $3) S + WHERE S."FormId" = $4 + AND (%s) + ) + SELECT + (SELECT COUNT(*) FROM SUBMISSIONS)::INT AS submission_count, + + (SELECT COALESCE(SUM("NumberOfQuestionsAnswered"), 0) FROM SUBMISSIONS)::INT AS questions_answered, + + (SELECT COALESCE(SUM("NumberOfFlaggedAnswers"), 0) FROM SUBMISSIONS)::INT AS flagged_answers, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', "SubmissionId", + 'ResponderId', '00000000-0000-0000-0000-000000000000'::UUID, + 'TimeSubmitted', "TimeSubmitted", + 'Answers', "Answers")), '[]'::JSONB) + FROM SUBMISSIONS) AS submissions, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'QuestionId', N."QuestionId", + 'SubmissionId', S."SubmissionId", + 'Text', N."Text", + 'TimeSubmitted', COALESCE(N."LastModifiedOn", N."CreatedOn"))), '[]'::JSONB) + FROM SUBMISSIONS S + INNER JOIN "CitizenReportNotes" N ON N."CitizenReportId" = S."SubmissionId") AS notes, + + (SELECT COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT( + 'SubmissionId', S."SubmissionId", + 'QuestionId', A."QuestionId", + 'FilePath', A."FilePath", + 'UploadedFileName', A."UploadedFileName", + 'FileName', A."FileName", + 'MimeType', A."MimeType", + 'TimeSubmitted', A."CreatedOn")), '[]'::JSONB) + FROM SUBMISSIONS S + INNER JOIN "CitizenReportAttachments" A ON A."CitizenReportId" = S."SubmissionId" + WHERE A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE) AS attachments + $sql$, filter) + INTO v_collected + USING filterParameters, electionRoundId, ngoId, formId; + + RETURN QUERY + SELECT v_form.election_round_id, + v_form.monitoring_ngo_id, + v_form.id, + v_form.code, + v_form.form_type, + v_form.name, + v_form.description, + v_form.default_language, + v_form.languages, + v_collected.submission_count, + v_collected.questions_answered, + v_collected.flagged_answers, + "AggregateAnswers"(v_form.questions, v_collected.submissions), + v_collected.notes, + v_collected.attachments; + END; + $$ LANGUAGE plpgsql; + """); + } + } +} diff --git a/api/tests/Feature.CitizenReports.UnitTests/Endpoints/GetSubmissionsAggregatedV2RequestTests.cs b/api/tests/Feature.CitizenReports.UnitTests/Endpoints/GetSubmissionsAggregatedV2RequestTests.cs new file mode 100644 index 000000000..ea6541cbf --- /dev/null +++ b/api/tests/Feature.CitizenReports.UnitTests/Endpoints/GetSubmissionsAggregatedV2RequestTests.cs @@ -0,0 +1,58 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Feature.CitizenReports.GetSubmissionsAggregatedV2; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Core.RulesEngine; +using Vote.Monitor.Core.RulesEngine.Rules; +using Vote.Monitor.Domain.Entities.CitizenReportAggregate; +using Vote.Monitor.Domain.Queries; + +namespace Feature.CitizenReports.UnitTests.Endpoints; + +public class GetSubmissionsAggregatedV2RequestTests +{ + private static CompiledFilter Compile(Request request) => + new FilterSqlCompiler(V2ReportFilterFields.CitizenReports, parameterPrefixer: FilterSqlCompiler.FunctionParameter) + .Build(request.ToFilterRule()); + + [Fact] + public void ToFilterRule_WithoutFilterOrQueryParams_ShouldNotFilter() + { + Compile(new Request()).Sql.Should().Be("1 = 1"); + } + + [Fact] + public void ToFilterRule_ShouldApplyQueryParamsAlongsideFilter() + { + var locationId = Guid.NewGuid(); + var request = new Request + { + Filter = JsonSerializer.Deserialize("""{ "op": "eq", "field": "hasComments", "value": true }"""), + Level1Filter = "North", + Level3Filter = "City", + LocationId = locationId, + HasFlaggedAnswers = true, + FollowUpStatus = CitizenReportFollowUpStatus.Resolved, + HasNotes = false, + HasAttachments = true, + QuestionsAnswered = QuestionsAnsweredFilter.Some + }; + + var filter = Compile(request); + var parameters = JsonNode.Parse(filter.ToJsonParameters())!.AsObject() + .Select(p => p.Value!.ToJsonString()); + + foreach (var column in new[] + { + "s.\"HasComments\"", "s.\"Level1\"", "s.\"Level3\"", "s.\"LocationId\"", + "(s.\"NumberOfFlaggedAnswers\" > 0)", "s.\"FollowUpStatus\"", "s.\"HasNotes\"", + "s.\"HasAttachments\"", "s.\"QuestionsAnswered\"" + }) + { + filter.Sql.Should().Contain(column); + } + + parameters.Should().BeEquivalentTo( + "true", "\"North\"", "\"City\"", $"\"{locationId}\"", "true", "\"Resolved\"", "false", "true", "\"Some\""); + } +} diff --git a/api/tests/Feature.IncidentReports.UnitTests/Endpoints/GetSubmissionsAggregatedV2RequestTests.cs b/api/tests/Feature.IncidentReports.UnitTests/Endpoints/GetSubmissionsAggregatedV2RequestTests.cs new file mode 100644 index 000000000..28b4af55e --- /dev/null +++ b/api/tests/Feature.IncidentReports.UnitTests/Endpoints/GetSubmissionsAggregatedV2RequestTests.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using FluentAssertions; +using Feature.IncidentReports.GetSubmissionsAggregatedV2; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Core.RulesEngine; +using Vote.Monitor.Core.RulesEngine.Rules; +using Vote.Monitor.Domain.Entities.IncidentReportAggregate; +using Vote.Monitor.Domain.Queries; +using Xunit; + +namespace Feature.IncidentReports.UnitTests.Endpoints; + +public class GetSubmissionsAggregatedV2RequestTests +{ + private static CompiledFilter Compile(Request request) => + new FilterSqlCompiler(V2ReportFilterFields.IncidentReports, parameterPrefixer: FilterSqlCompiler.FunctionParameter) + .Build(request.ToFilterRule()); + + [Fact] + public void ToFilterRule_WithoutFilterOrQueryParams_ShouldNotFilter() + { + Compile(new Request()).Sql.Should().Be("1 = 1"); + } + + [Fact] + public void ToFilterRule_ShouldApplyQueryParamsAlongsideFilter() + { + var pollingStationId = Guid.NewGuid(); + var request = new Request + { + Filter = JsonSerializer.Deserialize("""{ "op": "eq", "field": "hasComments", "value": true }"""), + Level1Filter = "North", + Level2Filter = " ", + PollingStationNumberFilter = "12", + PollingStationId = pollingStationId, + HasFlaggedAnswers = false, + FollowUpStatusFilter = IncidentReportFollowUpStatus.NeedsFollowUp, + LocationTypeFilter = IncidentReportLocationType.PollingStation, + HasNotes = true, + HasAttachments = false, + QuestionsAnswered = QuestionsAnsweredFilter.All, + IsCompletedFilter = true + }; + + var filter = Compile(request); + var parameters = JsonNode.Parse(filter.ToJsonParameters())!.AsObject() + .Select(p => p.Value!.ToJsonString()); + + foreach (var column in new[] + { + "s.\"HasComments\"", "s.\"Level1\"", "s.\"Number\"", "s.\"PollingStationId\"", + "(s.\"NumberOfFlaggedAnswers\" > 0)", "s.\"FollowUpStatus\"", "s.\"LocationType\"", "s.\"HasNotes\"", + "s.\"HasAttachments\"", "s.\"QuestionsAnswered\"", "s.\"IsCompleted\"" + }) + { + filter.Sql.Should().Contain(column); + } + + // blank query params add no condition + filter.Sql.Should().NotContain("s.\"Level2\""); + parameters.Should().BeEquivalentTo( + "true", "\"North\"", "\"12\"", $"\"{pollingStationId}\"", "false", "\"NeedsFollowUp\"", "\"PollingStation\"", + "true", "false", "\"All\"", "true"); + } +} diff --git a/api/tests/Vote.Monitor.Core.UnitTests/RulesEngine/FilterRulesTests.cs b/api/tests/Vote.Monitor.Core.UnitTests/RulesEngine/FilterRulesTests.cs new file mode 100644 index 000000000..b43ba9649 --- /dev/null +++ b/api/tests/Vote.Monitor.Core.UnitTests/RulesEngine/FilterRulesTests.cs @@ -0,0 +1,63 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Vote.Monitor.Core.RulesEngine; +using Vote.Monitor.Core.RulesEngine.Rules; + +namespace Vote.Monitor.Core.UnitTests.RulesEngine; + +public class FilterRulesTests +{ + private static readonly IReadOnlyDictionary Fields = new Dictionary + { + ["level1"] = new("s.\"Level1\"", "text", FilterOperators.Set, FilterValue.String), + ["isCompleted"] = new("s.\"IsCompleted\"", "boolean", FilterOperators.Boolean, FilterValue.Boolean) + }; + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Eq_WithoutValue_ShouldReturnNull(string? value) + { + FilterRules.Eq("level1", value).Should().BeNull(); + } + + [Fact] + public void Eq_WithValue_ShouldReturnEqRule() + { + var rule = FilterRules.Eq("isCompleted", true); + + rule.Should().BeOfType(); + var eq = (EqFilter)rule!; + eq.Field.Should().Be("isCompleted"); + eq.Value!.Value.GetBoolean().Should().BeTrue(); + } + + [Fact] + public void And_WithoutRules_ShouldReturnNull() + { + FilterRules.And(null, null).Should().BeNull(); + } + + [Fact] + public void And_WithOneRule_ShouldReturnThatRule() + { + var rule = FilterRules.Eq("level1", "North"); + + FilterRules.And(null, rule, null).Should().BeSameAs(rule); + } + + [Fact] + public void And_WithSeveralRules_ShouldCompileToAllOfThem() + { + var requestFilter = JsonSerializer.Deserialize("""{ "op": "eq", "field": "isCompleted", "value": true }"""); + + var rule = FilterRules.And(requestFilter, FilterRules.Eq("level1", "North"), FilterRules.Eq("level1", null)); + var filter = new FilterSqlCompiler(Fields, parameterPrefixer: FilterSqlCompiler.FunctionParameter).Build(rule); + var parameters = JsonNode.Parse(filter.ToJsonParameters())!.AsObject(); + + rule.Should().BeOfType().Which.Values.Should().HaveCount(2); + filter.Sql.Should().StartWith("(").And.Contain(" AND ").And.Contain("s.\"IsCompleted\"").And.Contain("s.\"Level1\""); + parameters.Select(p => p.Value!.ToJsonString()).Should().BeEquivalentTo("true", "\"North\""); + } +}