/// <summary>Initializes this object.</summary> private static void Init() { if (_needsInit) { IEnumerable <Type> localTypes = Assembly.GetExecutingAssembly().GetTypes() .Where(t => t.GetInterfaces().Contains(typeof(IReportingFormat))); foreach (Type localType in localTypes) { IReportingFormat format = (IReportingFormat)localType.GetProperty("Current").GetValue(null); _formatsByName.Add(format.Name, format); } _needsInit = false; } }
/// <summary>Gets a questionnaire.</summary> /// <exception cref="ArgumentNullException">Thrown when one or more required arguments are null.</exception> /// <param name="format">Describes the format to use.</param> /// <returns>The questionnaire.</returns> public static Questionnaire GetQuestionnaire(IReportingFormat format) { if (!_initialized) { Init(); } if (format == null) { throw new ArgumentNullException(nameof(format)); } if (!_questionnaires.ContainsKey(format.Name)) { return(null); } return(_questionnaires[format.Name]); }
/// <summary>Gets the markdown.</summary> /// <exception cref="ArgumentNullException">Thrown when one or more required arguments are null.</exception> /// <param name="format">Describes the format to use.</param> /// <returns>The markdown.</returns> public static string GetMarkdown(IReportingFormat format) { if (!_initialized) { Init(); } if (format == null) { throw new ArgumentNullException(nameof(format)); } if (!_markdowns.ContainsKey(format.Name)) { return(null); } return(_markdowns[format.Name]); }
/// <summary>Gets a bundle.</summary> /// <exception cref="ArgumentNullException">Thrown when one or more required arguments are null.</exception> /// <param name="format">Describes the format to use.</param> /// <returns>The bundle.</returns> public static Bundle GetBundle(IReportingFormat format) { if (!_initialized) { Init(); } if (format == null) { throw new ArgumentNullException(nameof(format)); } if (!_measures.ContainsKey(format.Name)) { return(null); } return(GetBundleForMeasure( _measures[format.Name], format.Name)); }
/// <summary>Builds a questionnaire.</summary> /// <exception cref="ArgumentNullException">Thrown when one or more required arguments are null.</exception> /// <param name="format">Describes the format to use.</param> /// <returns>A Questionnaire.</returns> private static Questionnaire BuildQuestionnaire( IReportingFormat format) { if (format == null) { throw new ArgumentNullException(nameof(format)); } if (string.IsNullOrEmpty(format.Name)) { throw new ArgumentNullException(nameof(format), $"Invalid IReportingFormat.Name: {format.Name}"); } if ((format.Fields == null) || (format.Fields.Count == 0)) { throw new ArgumentNullException(nameof(format), $"Invalid IReportingFormat.Fields: {format.Fields}"); } if ((format.QuestionnaireSections == null) || (format.QuestionnaireSections.Count == 0)) { throw new ArgumentNullException(nameof(format), $"Invalid IReportingFormat.QuestionnaireSections: {format.QuestionnaireSections}"); } Questionnaire questionnaire = new Questionnaire() { Meta = new Meta() { Profile = new string[] { FhirSystems.Questionnaire, }, }, Id = format.Name, Name = format.Name, Url = $"{CanonicalUrl}/Questionnaire/{format.Name}", Version = QuestionnaireVersion, Title = format.Title, Description = new Markdown(format.Description), Status = PublicationStatus.Draft, Date = PublicationDate, Publisher = Publisher, Jurisdiction = new List <CodeableConcept>() { FhirTriplet.UnitedStates.GetConcept(), }, UseContext = new List <UsageContext>() { new UsageContext() { Code = FhirTriplet.GetCode(FhirSystems.UsageContextType, CommonLiterals.ContextFocus), Value = FhirTriplet.SctCovid.GetConcept(), }, }, Item = new List <Questionnaire.ItemComponent>(), }; int sectionNumber = -1; int itemNumber = 0; foreach (QuestionnaireSection questionnaireSection in format.QuestionnaireSections) { sectionNumber++; itemNumber = 0; Questionnaire.ItemComponent section = new Questionnaire.ItemComponent() { LinkId = $"section_{sectionNumber}", Type = Questionnaire.QuestionnaireItemType.Group, Item = new List <Questionnaire.ItemComponent>(), Repeats = false, }; #if false // 2020.05.13 - Argonaut extensions aren't valid in R4 yet section.AddExtension( "http://fhir.org/guides/argonaut/questionnaire/StructureDefinition/extension-itemOrder", new FhirDecimal(sectionNumber)); #endif if (format.Fields.ContainsKey(questionnaireSection.Title)) { section.Text = $"{format.Fields[questionnaireSection.Title].Title}: {format.Fields[questionnaireSection.Title].Description}"; } else { section.Text = questionnaireSection.Title; } foreach (QuestionnaireQuestion question in questionnaireSection.Fields) { Questionnaire.ItemComponent component = ComponentFromQuestion( format, question, ref itemNumber); if (component == null) { continue; } section.Item.Add(component); } questionnaire.Item.Add(section); } return(questionnaire); }
/// <summary>Component from question.</summary> /// <param name="format"> Describes the format to use.</param> /// <param name="question"> The question.</param> /// <param name="itemOrder">[in,out] The item order.</param> /// <returns>A Questionnaire.ItemComponent.</returns> private static Questionnaire.ItemComponent ComponentFromQuestion( IReportingFormat format, QuestionnaireQuestion question, ref int itemOrder) { if (!format.Fields.ContainsKey(question.ValueFieldName)) { return(null); } FormatField valueField = format.Fields[question.ValueFieldName]; FormatField displayField; if (string.IsNullOrEmpty(question.DisplayFieldName) || (!format.Fields.ContainsKey(question.DisplayFieldName))) { displayField = valueField; } else { displayField = format.Fields[question.DisplayFieldName]; } Questionnaire.ItemComponent component = new Questionnaire.ItemComponent() { LinkId = valueField.Name, Required = valueField.IsRequired == true, Repeats = false, }; if (!string.IsNullOrEmpty(question.FieldSystem)) { component.Code = new List <Coding>() { new Coding(question.FieldSystem, valueField.Name), }; } #if false // 2020.05.13 - Argonaut extensions aren't valid in R4 yet component.AddExtension( "http://fhir.org/guides/argonaut/questionnaire/StructureDefinition/extension-itemOrder", new FhirDecimal(itemOrder++)); #endif if (question.UseTitleOnly) { component.Text = $"{displayField.Title}"; } else { component.Text = $"{displayField.Title}: {displayField.Description}"; } int optionOrder = 0; switch (valueField.Type) { case FormatField.FieldType.Date: component.Type = Questionnaire.QuestionnaireItemType.Date; break; case FormatField.FieldType.Count: component.Type = Questionnaire.QuestionnaireItemType.Integer; break; case FormatField.FieldType.Percentage: component.Type = Questionnaire.QuestionnaireItemType.Decimal; break; case FormatField.FieldType.Boolean: component.Type = Questionnaire.QuestionnaireItemType.Boolean; break; case FormatField.FieldType.Choice: component.Type = Questionnaire.QuestionnaireItemType.Choice; component.AnswerOption = new List <Questionnaire.AnswerOptionComponent>(); component.AddExtension( "http://hl7.org/fhir/StructureDefinition/questionnaire-optionExclusive", new FhirBoolean(true), true); foreach (FormatFieldOption option in valueField.Options) { Element element = new FhirString(option.Text); #if false // 2020.05.13 - Argonaut extensions aren't valid in R4 yet element.AddExtension( "http://fhir.org/guides/argonaut/questionnaire/StructureDefinition/extension-itemOrder", new FhirDecimal(optionOrder++)); #endif component.AnswerOption.Add(new Questionnaire.AnswerOptionComponent() { Value = element, }); } break; case FormatField.FieldType.MultiSelectChoice: component.Type = Questionnaire.QuestionnaireItemType.Choice; component.AnswerOption = new List <Questionnaire.AnswerOptionComponent>(); foreach (FormatFieldOption option in valueField.Options) { Element element = new FhirString(option.Text); #if false // 2020.05.13 - Argonaut extensions aren't valid in R4 yet element.AddExtension( "http://fhir.org/guides/argonaut/questionnaire/StructureDefinition/extension-itemOrder", new FhirDecimal(optionOrder++)); #endif component.AnswerOption.Add(new Questionnaire.AnswerOptionComponent() { Value = element, }); } break; case FormatField.FieldType.Text: component.Type = Questionnaire.QuestionnaireItemType.Text; break; case FormatField.FieldType.ShortString: component.Type = Questionnaire.QuestionnaireItemType.String; break; case FormatField.FieldType.Display: default: component.Type = Questionnaire.QuestionnaireItemType.Display; break; } return(component); }
/// <summary>Group component from nested.</summary> /// <param name="grouping">The grouping.</param> /// <param name="format"> Describes the format to use.</param> /// <returns>A Measure.GroupComponent.</returns> private static Measure.GroupComponent GroupComponentFromNested( MeasureGrouping grouping, IReportingFormat format) { string description = grouping.CodeText; if (string.IsNullOrEmpty(description)) { if (format.Fields.ContainsKey(grouping.FieldName)) { description = string.IsNullOrEmpty(format.Fields[grouping.FieldName].Description) ? format.Fields[grouping.FieldName].Title : format.Fields[grouping.FieldName].Description; } } Measure.GroupComponent groupComponent = new Measure.GroupComponent() { Code = grouping.CodeCoding.GetConcept(description), }; if ((grouping.GroupAttributes != null) && (grouping.GroupAttributes.Count > 0)) { Extension groupAttributes = new Extension() { Url = "http://hl7.org/fhir/us/saner/StructureDefinition/MeasureGroupAttributes", Extension = new List <Extension>(), }; foreach (MeasureGroupingExtension ext in grouping.GroupAttributes) { Extension attribute = new Extension() { Url = ext.Key, }; if ((ext.Properties != null) && (ext.Properties.Count > 0)) { CodeableConcept value = new CodeableConcept() { Coding = new List <Coding>(), }; foreach (FhirTriplet prop in ext.Properties) { value.Coding.Add(prop.GetCoding()); } if (!string.IsNullOrEmpty(ext.Text)) { value.Text = ext.Text; } attribute.Value = value; } if (!string.IsNullOrEmpty(ext.ValueString)) { attribute.Value = new FhirString(ext.ValueString); } groupAttributes.Extension.Add(attribute); } groupComponent.Extension = new List <Extension>() { groupAttributes, }; } if ((grouping.PopulationFields != null) && (grouping.PopulationFields.Count > 0)) { groupComponent.Population = new List <Measure.PopulationComponent>(); foreach (MeasureGroupingPopulation pop in grouping.PopulationFields) { if (!format.Fields.ContainsKey(pop.Name)) { continue; } FormatField field = format.Fields[pop.Name]; Measure.PopulationComponent populationComponent = new Measure.PopulationComponent() { Code = new FhirTriplet( FhirSystems.SanerPopulation, field.Name, field.Title).GetConcept(field.Description), Description = field.Description ?? field.Title, Criteria = new Expression() { Name = pop.Name, Description = field.Title, Language = "text/plain", Expression_ = field.Description ?? field.Title, }, }; if (pop.PopulationType != null) { populationComponent.Code.Coding.Add(pop.PopulationType.GetCoding()); } groupComponent.Population.Add(populationComponent); } } return(groupComponent); }
/// <summary>Builds a measure.</summary> /// <exception cref="ArgumentNullException">Thrown when one or more required arguments are null.</exception> /// <param name="format">Describes the format to use.</param> /// <returns>A Measure.</returns> private static Measure BuildMeasure( IReportingFormat format) { if (format == null) { throw new ArgumentNullException(nameof(format)); } if (string.IsNullOrEmpty(format.Name)) { throw new ArgumentNullException(nameof(format), $"Invalid IReportingFormat.Name: {format.Name}"); } if ((format.Fields == null) || (format.Fields.Count == 0)) { throw new ArgumentNullException(nameof(format), $"Invalid IReportingFormat.Fields: {format.Fields}"); } if ((format.MeasureGroupings == null) || (format.MeasureGroupings.Count == 0)) { throw new ArgumentNullException(nameof(format), $"Invalid IReportingFormat.MeasureGroupings: {format.MeasureGroupings}"); } Measure measure = new Measure() { Meta = new Meta() { Profile = new string[] { FhirSystems.Measure, "http://hl7.org/fhir/us/saner/StructureDefinition/PublicHealthMeasure", }, }, Id = format.Name, Name = format.Name, Url = $"{CanonicalUrl}/Measure/{format.Name}", Version = MeasureVersion, Title = format.Title, Description = new Markdown(format.Description), Status = PublicationStatus.Draft, Experimental = true, Subject = FhirTriplet.ResourceLocation.GetConcept(), Date = PublicationDate, Publisher = Publisher, Jurisdiction = new List <CodeableConcept>() { FhirTriplet.UnitedStates.GetConcept(), }, UseContext = new List <UsageContext>() { new UsageContext() { Code = FhirTriplet.GetCode(FhirSystems.UsageContextType, CommonLiterals.ContextFocus), Value = FhirTriplet.SctCovid.GetConcept(), }, }, Type = new List <CodeableConcept>() { FhirTriplet.MeasureTypeComposite.GetConcept(), }, Group = new List <Measure.GroupComponent>(), Contact = SanerCommon.Contacts, Author = format.Authors, }; if ((format.Definition != null) && (format.Definition.Count > 0)) { measure.Definition = new List <Markdown>(); foreach (string definition in format.Definition) { measure.Definition.Add(new Markdown(definition)); } } if (format.Artifacts != null) { measure.RelatedArtifact = new List <RelatedArtifact>(); measure.RelatedArtifact.AddRange(format.Artifacts); } foreach (MeasureGrouping grouping in format.MeasureGroupings) { if (grouping.CodeCoding != null) { measure.Group.Add(GroupComponentFromNested(grouping, format)); continue; } if ((!string.IsNullOrEmpty(grouping.FieldName)) && format.Fields.ContainsKey(grouping.FieldName)) { measure.Group.Add(GroupComponentFromFlat(grouping, format)); } } return(measure); }
/// <summary>Group component from flat.</summary> /// <param name="grouping">The grouping.</param> /// <param name="format"> Describes the format to use.</param> /// <returns>A Measure.GroupComponent.</returns> private static Measure.GroupComponent GroupComponentFromFlat( MeasureGrouping grouping, IReportingFormat format) { FormatField field = format.Fields[grouping.FieldName]; string title = string.IsNullOrEmpty(field.Title) ? field.Name : field.Title; string description = string.IsNullOrEmpty(field.Description) ? title : field.Description; Measure.GroupComponent groupComponent = new Measure.GroupComponent() { Code = new CodeableConcept( SanerCommon.CanonicalUrl, field.Name, description), }; if ((grouping.GroupAttributes != null) && (grouping.GroupAttributes.Count > 0)) { Extension groupAttributes = new Extension() { Url = "http://hl7.org/fhir/us/saner/StructureDefinition/MeasureGroupAttributes", Extension = new List <Extension>(), }; foreach (MeasureGroupingExtension ext in grouping.GroupAttributes) { Extension attribute = new Extension() { Url = ext.Key, }; if ((ext.Properties != null) && (ext.Properties.Count > 0)) { CodeableConcept value = new CodeableConcept() { Coding = new List <Coding>(), }; foreach (FhirTriplet prop in ext.Properties) { value.Coding.Add(prop.GetCoding()); } if (!string.IsNullOrEmpty(ext.Text)) { value.Text = ext.Text; } attribute.Value = value; } if (!string.IsNullOrEmpty(ext.ValueString)) { attribute.Value = new FhirString(ext.ValueString); } groupAttributes.Extension.Add(attribute); } groupComponent.Extension = new List <Extension>() { groupAttributes, }; } if ((grouping.PopulationFields != null) && (grouping.PopulationFields.Count > 0)) { groupComponent.Population = new List <Measure.PopulationComponent>(); foreach (MeasureGroupingPopulation pop in grouping.PopulationFields) { if (!format.Fields.ContainsKey(pop.Name)) { continue; } FormatField popField = format.Fields[pop.Name]; Measure.PopulationComponent populationComponent = new Measure.PopulationComponent() { Code = new FhirTriplet( FhirSystems.SanerPopulation, popField.Name, popField.Title).GetConcept(popField.Description), Description = string.IsNullOrEmpty(field.Description) ? field.Title : field.Description, Criteria = new Expression() { Name = pop.Name, Description = popField.Title, Language = "text/plain", Expression_ = string.IsNullOrEmpty(popField.Description) ? field.Title : popField.Description, }, }; if (pop.PopulationType != null) { populationComponent.Code.Coding.Add(pop.PopulationType.GetCoding()); } groupComponent.Population.Add(populationComponent); } } else if (field.Type == FormatField.FieldType.Boolean) { string desc = string.IsNullOrEmpty(field.Description) ? field.Title : field.Description; groupComponent.Population = new List <Measure.PopulationComponent>(); Measure.PopulationComponent trueComponent = new Measure.PopulationComponent() { Code = new FhirTriplet( FhirSystems.SanerAggregateBool, "true", "Count of 'Yes' or 'True' responses for this field").GetConcept(), Description = $"YES - {desc}", Criteria = new Expression() { Name = $"{field.Name}True", Description = field.Title, Language = "text/plain", Expression_ = "true", }, }; Measure.PopulationComponent falseComponent = new Measure.PopulationComponent() { Code = new FhirTriplet( FhirSystems.SanerAggregateBool, "false", "Count of 'No' or 'False' responses for this field").GetConcept(), Description = $"NO - {desc}", Criteria = new Expression() { Name = $"{field.Name}False", Description = field.Title, Language = "text/plain", Expression_ = "false", }, }; groupComponent.Population.Add(trueComponent); groupComponent.Population.Add(falseComponent); } else if (field.Type == FormatField.FieldType.Choice) { groupComponent.Population = new List <Measure.PopulationComponent>(); int choiceNumber = 0; foreach (FormatFieldOption populationChoice in field.Options) { Measure.PopulationComponent choiceComponent = new Measure.PopulationComponent() { Code = new FhirTriplet( FhirSystems.SanerAggregateChoice, populationChoice.Text, $"Aggregate count of respondents selecting the option: {populationChoice.Text}").GetConcept(), Description = $"{field.Title}:{populationChoice.Text}", Criteria = new Expression() { Name = $"{field.Name}Choice{choiceNumber++}", Description = field.Title, Language = "text/plain", Expression_ = populationChoice.Text, }, }; groupComponent.Population.Add(choiceComponent); } } return(groupComponent); }
/// <summary>Builds a markdown.</summary> /// <param name="format">Describes the format to use.</param> /// <returns>A string.</returns> private static string BuildMarkdown(IReportingFormat format) { StringBuilder sb = new StringBuilder(); sb.AppendLine($"# {format.Name}"); sb.AppendLine(); sb.AppendLine($"{format.Title}"); sb.AppendLine(); sb.AppendLine($"{format.Description}"); if (format.Artifacts != null) { sb.AppendLine("## Related artifacts"); foreach (RelatedArtifact artifact in format.Artifacts) { sb.AppendLine($"* [{artifact.Display}]({artifact.Url})"); } sb.AppendLine(); sb.AppendLine(); } if ((format.Definition != null) && (format.Definition.Count > 0)) { sb.AppendLine("## Definitions"); foreach (string definition in format.Definition) { sb.AppendLine($"* {definition}"); } } sb.AppendLine("## Group Definitions"); sb.AppendLine("Group Code|Population Code|System"); sb.AppendLine("----------|---------------|------"); List <string> fields = new List <string>(); foreach (MeasureGrouping grouping in format.MeasureGroupings) { Measure.GroupComponent component = null; if (grouping.CodeCoding != null) { component = GroupComponentFromNested(grouping, format); } else if ((!string.IsNullOrEmpty(grouping.FieldName)) && format.Fields.ContainsKey(grouping.FieldName)) { component = GroupComponentFromFlat(grouping, format); } if (component == null) { continue; } if ((!string.IsNullOrEmpty(grouping.FieldName)) && format.Fields.ContainsKey(grouping.FieldName)) { fields.Add(grouping.FieldName); } sb.AppendLine( $"{component.Code.Coding[0].Code}" + $"|<nobr/>" + $"|{component.Code.Coding[0].System}"); foreach (Measure.PopulationComponent pop in component.Population) { string popSys = string.Empty; string popCode = string.Empty; foreach (Coding popCoding in pop.Code.Coding) { if (string.IsNullOrEmpty(popSys)) { popSys = popCoding.System; } else { popSys += $"<br/>{popCoding.System}"; } if (string.IsNullOrEmpty(popCode)) { popCode = popCoding.Code; } else { popCode += $"<br/>{popCoding.Code}"; } if (format.Fields.ContainsKey(popCoding.Code)) { fields.Add(popCoding.Code); } } sb.AppendLine( $"<nobr/>" + $"|{popCode}" + $"|{popSys}"); } } sb.AppendLine(); sb.AppendLine("## Field Definitions"); sb.AppendLine("Field Name|Title|Description"); sb.AppendLine("----------|-----|-----------"); fields.Sort(); string lastField = string.Empty; foreach (string field in fields) { if (field == lastField) { continue; } lastField = field; FormatField formatField = format.Fields[field]; string title = formatField.Title; string description = string.IsNullOrEmpty(formatField.Description) ? formatField.Title : formatField.Description; if ((!string.IsNullOrEmpty(formatField.AdditionalFieldDescription)) && format.Fields.ContainsKey(formatField.AdditionalFieldDescription)) { FormatField additional = format.Fields[formatField.AdditionalFieldDescription]; title += $" - {additional.Title}"; description += $" - {additional.Description}"; } sb.AppendLine( $"{field}" + $"|{title}" + $"|{description}"); } return(sb.ToString()); }