}                                                                                                                                                               // string.IsNullOrEmpty ensures response is not null

        /// <summary>
        /// Computes a summary status report based on <see cref="Properties"/>, <see cref="NatureOfSystem"/>, and <see cref="Children"/>, when a node-level report is not available.
        /// </summary>
        /// <param name="includeHtmlTag">Whether or not to include the html and body tags.</param>
        /// <param name="ignoreRatingsBetterThan">A value indicating which reports to completely ignore.</param>
        /// <param name="ignorePendingRatings">Whether or not to ignore pending ratings.</param>
        /// <param name="notificationTimeZone">An optional <see cref="TimeZoneInfo"/> that will be used to convert the notification time.  If not specified, UTC will be used.</param>
        /// <returns>A <see cref="StatusAuditAlert"/> summarizing the overall state of the system.</returns>
        public StatusAuditAlert GetSummaryAlerts(bool includeHtmlTag, float ignoreRatingsBetterThan, bool ignorePendingRatings, TimeZoneInfo?notificationTimeZone = null)
        {
            DateTime start = AmbientClock.UtcNow;
            StatusResultsOrganizer organized = new StatusResultsOrganizer(this);

            organized.ComputeOverallRatingAndSort();

            DateTime notificationTime       = TimeZoneInfo.ConvertTimeFromUtc(organized.MostRecentTime, notificationTimeZone ?? TimeZoneInfo.Utc);
            StatusNotificationWriter writer = new StatusNotificationWriter(notificationTime);

            // build HTML style and header for the indicated rating and rating range
            float overallRating = organized.SortRating;

            if (includeHtmlTag)
            {
                writer.EnterHtmlAndBody(overallRating);
            }
            writer.EnterStatusRange(overallRating);
            StatusRatingRange ratingRange = StatusRating.FindRange(overallRating);

            // filter irrelevant top-level reports
            AggregatedAlert?aggregatedAlert = null;

            foreach (StatusResultsOrganizer child in organized.Children)
            {
                // use the specified child rating, or okay if one is not specified
                float childRating = child.SortRating;
                // is this one better than the cutoff?  stop now because all the subsequent reports are better than this one! (because they're sorted by rating)
                if (childRating > ignoreRatingsBetterThan || (ignorePendingRatings && float.IsNaN(childRating)))
                {
                    break;
                }
                StatusRatingRange childRatingRange = StatusRating.FindRange(childRating);
                if (childRatingRange != ratingRange)
                {
                    if (aggregatedAlert != null)
                    {
                        writer.WriteAggregatedAlert(aggregatedAlert);
                        aggregatedAlert = null;
                    }
                    writer.LeaveStatusRange();
                    writer.EnterStatusRange(childRating);
                    ratingRange = childRatingRange;
                }
                Aggregate(ref aggregatedAlert, writer, start, child, ignoreRatingsBetterThan);
            }
            if (aggregatedAlert != null)
            {
                writer.WriteAggregatedAlert(aggregatedAlert);
                aggregatedAlert = null;
            }
            writer.LeaveStatusRange();
            if (includeHtmlTag)
            {
                writer.LeaveBodyAndHtml();
            }
            StatusAuditAlert alert = new StatusAuditAlert(overallRating, string.Empty, writer.Terse, writer.Details);

            return(alert);
        }