// Pregnancy episode start marker types: LMP: Last menstrual period date, GEST: Gestational age record, FERT: Assisted
        // conception procedure date, ULS: Nuchal ultrasound date, AFP: Alpha feto protein test date, AMEN: Amenorrhea record date,
        // URINE: Urine pregnancy test date
        private static IEnumerable <KeyValuePair <Event, Event> > FindPregnancEpisodes(
            Dictionary <string, List <Event> > validOutcomes, RawEvents raw,
            Dictionary <string, TermDurations> termDurations, Dictionary <string, GestEst> gestEst, string[] markers, bool hasDefault, bool desc)
        {
            var outcomes = new List <Event>();

            foreach (var outcomeCategory in validOutcomes.Keys)
            {
                outcomes.AddRange(validOutcomes[outcomeCategory]);
            }

            foreach (var outcome in outcomes)
            {
                var           duration         = termDurations[outcome.Category];
                var           ge               = gestEst[outcome.Category];
                var           priorOutcomeDate = DateTime.MinValue;
                var           priorOutcomes    = outcomes.Where(o => o.Date < outcome.Date).ToArray();
                TermDurations durationPrior    = null;
                if (priorOutcomes.Any())
                {
                    var po = priorOutcomes.OrderByDescending(o => o.Date).First();
                    priorOutcomeDate = po.Date;
                    durationPrior    = termDurations[po.Category];
                }

                foreach (var startEpisodeOutcome in FindPregnancyStartEvent(raw, duration, durationPrior, outcome, priorOutcomeDate,
                                                                            ge, markers, hasDefault, desc))
                {
                    yield return(new KeyValuePair <Event, Event>(outcome, startEpisodeOutcome));
                }
            }
        }
        private static IEnumerable <Event> FindPregnancyStartEvent(RawEvents raw, TermDurations duration,
                                                                   TermDurations durationPrior, Event outcome, DateTime priorOutcomeDate, GestEst g, string[] markers,
                                                                   bool hasDefault, bool desc)
        {
            List <Event> starts = new List <Event>();

            foreach (var startMarker in markers)
            {
                if (!raw.PregnancyEvents.ContainsKey(startMarker))
                {
                    continue;
                }

                foreach (var pe in raw.PregnancyEvents[startMarker]
                         .OrderBy(e => e.Date))
                {
                    var e = FindStart(outcome, pe, duration, priorOutcomeDate);
                    if (e == null)
                    {
                        continue;
                    }

                    e.OriginalDate = pe.Date;
                    starts.Add(e);
                }
            }

            if (starts.Any())
            {
                foreach (var group in starts.GroupBy(s => s.Category))
                {
                    if (desc)
                    {
                        var maxDate = group.Max(e => e.OriginalDate);
                        yield return(group.Where(e => e.OriginalDate == maxDate).OrderBy(e => e.EventId).First());
                    }
                    else
                    {
                        var minDate = group.Min(e => e.OriginalDate);
                        yield return(group.Where(e => e.OriginalDate == minDate).OrderBy(e => e.EventId).First());
                    }
                }
            }

            if (hasDefault)
            {
                var wStart = outcome.Date.Date.AddDays(-1 * duration.MaxTerm);
                var wEnd   = outcome.Date.AddDays(30);

                if (outcome.Date.Between(priorOutcomeDate > wStart ? priorOutcomeDate : wStart,
                                         wEnd))
                {
                    var episodeStartDate = outcome.Date;
                    var category         = "DEFAULT";

                    if (raw.PregnancyEvents.ContainsKey("PREM") && raw.PregnancyEvents["PREM"].Count(e =>
                                                                                                     e.Date.Between(priorOutcomeDate > wStart ? priorOutcomeDate : wStart,
                                                                                                                    wEnd)) > 0)
                    {
                        episodeStartDate = episodeStartDate.Date.AddDays(-1 * g.PreTerm);
                        category         = "PREM";
                    }
                    else if (raw.PregnancyEvents.ContainsKey("FT") && raw.PregnancyEvents["FT"].Count(e =>
                                                                                                      e.Date.Between(priorOutcomeDate > wStart ? priorOutcomeDate : wStart,
                                                                                                                     wEnd)) > 0)
                    {
                        episodeStartDate = episodeStartDate.Date.AddDays(-1 * g.FullTerm);
                    }
                    else
                    {
                        episodeStartDate = episodeStartDate.Date.AddDays(-1 * g.NoData);
                    }

                    if (durationPrior != null && priorOutcomeDate.AddDays(durationPrior.Retry) > episodeStartDate)
                    {
                        episodeStartDate = priorOutcomeDate.AddDays(durationPrior.Retry);
                    }

                    yield return(new Event {
                        EventId = outcome.EventId, Category = category, Date = episodeStartDate
                    });
                }
            }
        }
        public IEnumerable <ConditionEra> GetPregnancyEpisodes(IVocabulary vocab, Person person, ObservationPeriod[] observationPeriods,
                                                               ConditionOccurrence[] conditionOccurrences,
                                                               ProcedureOccurrence[] procedureOccurrences, Observation[] observations, Measurement[] measurements,
                                                               DrugExposure[] drugExposures)
        {
            if (person.GenderConceptId != 8532)
            {
                yield break;
            }

            var raw = new RawEvents();

            raw.Fill(vocab, conditionOccurrences, procedureOccurrences, observations, measurements, drugExposures);

            if (raw.PregnancyEvents.Count == 0)
            {
                yield break;
            }

            var outcomeLimits = new Dictionary <string, Dictionary <string, OutcomeLimit> >();

            foreach (var ol in _outcomeLimits)
            {
                if (!outcomeLimits.ContainsKey(ol.FirstPregCategory))
                {
                    outcomeLimits.Add(ol.FirstPregCategory, new Dictionary <string, OutcomeLimit>());
                }

                outcomeLimits[ol.FirstPregCategory].Add(ol.OutcomePregCategory, ol);
            }

            var termDurations = _termDurations.ToDictionary(termDuration => termDuration.Category);
            var gestEsts      = _gestEsts.ToDictionary(ge => ge.Category);

            var validOutcomes = new Dictionary <string, List <Event> >();

            foreach (var outcomeType in _outcomeTypes)
            {
                if (!raw.PregnancyEvents.ContainsKey(outcomeType))
                {
                    continue;
                }

                foreach (var g in raw.PregnancyEvents[outcomeType].OrderBy(pe => pe.Date).GroupBy(pe => pe.Date))
                {
                    var e = g.First();
                    if (outcomeType == "AB" || outcomeType == "SA")
                    {
                        // Reassign abortion outcome date to last abortion date within 2 weeks after
                        var revisedDates = new List <DateTime>(2)
                        {
                            GetRevisedDate("SA", 14, e, raw.PregnancyEvents),
                            GetRevisedDate("AB", 14, e, raw.PregnancyEvents)
                        };
                        var revisedDate = revisedDates.Max();
                        if (revisedDate != DateTime.MinValue)
                        {
                            e.Date = revisedDate;
                        }
                    }
                    else if (outcomeType == "ECT")
                    {
                        // Reassign ectopic pregnancy outcome date to last treatment date within 2 weeks after
                        var revisedDates = new List <DateTime>(2)
                        {
                            GetRevisedDate("ECT_SURG1", 14, e, raw.PregnancyEvents),
                            GetRevisedDate("MTX", 14, e, raw.PregnancyEvents)
                        };

                        var revisedDate = revisedDates.Max();
                        if (revisedDate != DateTime.MinValue)
                        {
                            e.Date = revisedDate;
                        }
                    }

                    if (!HasInvalidWindow(e, outcomeLimits, validOutcomes) && IsValid(e, raw.PregnancyEvents))
                    {
                        if (!validOutcomes.ContainsKey(e.Category))
                        {
                            validOutcomes.Add(e.Category, new List <Event>());
                        }

                        validOutcomes[e.Category].Add(e);
                    }
                }
            }


            if (validOutcomes.Count > 0)
            {
                var allEpisodes = FindPregnancEpisodes(validOutcomes, raw, termDurations, gestEsts,
                                                       new[] { "OVUL", "OVUL2", "NULS", "AFP", "AMEN", "UP" }, true, false).ToList();

                allEpisodes.AddRange(FindPregnancEpisodes(validOutcomes, raw, termDurations, gestEsts,
                                                          new[] { "LMP", "GEST" }, false, true));

                var pconf = FindPregnancEpisodes(validOutcomes, raw, termDurations, gestEsts,
                                                 new[] { "PCONF", "AGP", "PCOMP", "TA" }, false, false).ToArray();
                var pconfEpisodes = new List <KeyValuePair <Event, Event> >();
                if (pconf.Any())
                {
                    pconfEpisodes.AddRange(pconf.GroupBy(p => p.Key.Guid)
                                           .Select(g => g.OrderBy(e => e.Value.Date).First()));
                }

                var contraEpisodes = FindPregnancEpisodes(validOutcomes, raw, termDurations, gestEsts,
                                                          new[] { "CONTRA" }, false, true).ToArray();

                var all = GetEpisodesAllStarts(person, allEpisodes, pconfEpisodes, contraEpisodes).ToArray();
                foreach (var episodes in all.GroupBy(e => e.EndDate))
                {
                    var pe        = episodes.OrderBy(e => e.TypeConceptId).ThenBy(e => e.StartDate).First();
                    var startDate = pe.StartDate;
                    var endDate   = pe.EndDate.Value;
                    if (startDate.Year - person.YearOfBirth >= 12 && startDate.Year - person.YearOfBirth <= 55 &&
                        observationPeriods.Any(op =>
                                               startDate >= op.StartDate && endDate.Between(op.StartDate, op.EndDate.Value)))
                    {
                        int cnt = 0;

                        foreach (var c in raw.PregnancyEvents.Keys)
                        {
                            foreach (var e in raw.PregnancyEvents[c])
                            {
                                if (e.Date.Between(
                                        endDate.AddDays(-1 * termDurations[pe.SourceValue].MaxTerm + 1), endDate))
                                {
                                    cnt++;
                                }
                            }
                        }

                        if (cnt >= 2)
                        {
                            pe.ConceptId = GetConceptId(pe);
                            yield return(pe);
                        }
                    }
                }
            }
        }