Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 50 additions & 7 deletions design/SP-3225/linktablestats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ New Module-Level Constants
``science``, ``fwhm``, ``mean_norm_teff``, ``visit_rate``, ``teff_rate``,
``targets``.

``RSS_PRENIGHT_DESC_FORMAT``
A format string template used to populate RSS item descriptions for
``lsstcam`` ``prenight`` entries. Uses distinct labels from
``RSS_DESC_FORMAT`` to make it clear the data comes from a simulation
rather than actual observations:

- "Simulated (science) visits" instead of "Total visits" (all prenight
visits are science, so no separate "Science visits" line is needed).
- Metric labels omit the "(science visits)" qualifier (redundant).
- "Mean visit rate" omits "(all visits on sky)" (redundant).

Contains placeholders for: ``total``, ``fwhm``, ``visit_rate``,
``mean_norm_teff``, ``teff_rate``, ``targets``.

``EFF_TIME_BREAKDOWN_COLS``
A tuple of the three ``compute_tinysum`` output column names that carry
the effective-time breakdown factors:
Expand Down Expand Up @@ -308,10 +322,33 @@ Implementation Steps
5. **Indent** the XML tree and optionally write to file.


Helper: ``_format_summary_desc`` – effective-time breakdown
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Helper: ``_format_summary_desc``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

def _format_summary_desc(
tinysum: pd.DataFrame,
dayobs: int,
report: str,
instrument: str,
night: datetime.date,
desc_format: str = None,
) -> str:

``desc_format`` : ``str`` or ``None``
The format string to use for the description. ``None`` defaults to
``RSS_DESC_FORMAT``. Pass ``RSS_PRENIGHT_DESC_FORMAT`` for prenight
items.

The function passes all computed keyword arguments (``report``,
``instrument``, ``night``, ``total``, ``science``, ``fwhm``,
``visit_rate``, ``mean_norm_teff``, ``teff_rate``, ``targets``) to
``desc_format.format()``. Format strings that use only a subset of these
keywords are valid — unused keywords are silently ignored by
``str.format()``.

Within ``_format_summary_desc``, the ``{mean_norm_teff}`` placeholder is
**Effective-time breakdown**: The ``{mean_norm_teff}`` placeholder is
populated conditionally:

- If all three columns in ``EFF_TIME_BREAKDOWN_COLS`` are present in the
Expand Down Expand Up @@ -430,7 +467,7 @@ visits use ``t_eff`` and ``visitExposureTime`` and are passed unmodified.
... prenight_visits=prenight_visits,
... )
>>> desc = tree.getroot().find("channel/item/description").text
>>> "Total visits:" in desc
>>> "Simulated (science) visits:" in desc
True


Expand Down Expand Up @@ -466,8 +503,8 @@ Changes from ``main``
description when no simulation visits match the night.
5. **Removed** ``"preprogress"`` from the default ``report_columns`` tuple
in ``make_report_link_table``.
6. **New constants**: ``RSS_DESC_FORMAT``, ``INT_SUMMARY_COLUMNS``,
``FLOAT_SUMMARY_COLUMNS``, ``SUMMARY_COLUMNS``,
6. **New constants**: ``RSS_DESC_FORMAT``, ``RSS_PRENIGHT_DESC_FORMAT``,
``INT_SUMMARY_COLUMNS``, ``FLOAT_SUMMARY_COLUMNS``, ``SUMMARY_COLUMNS``,
``EFF_TIME_BREAKDOWN_COLS``.
7. **New imports**: ``hashlib``, ``numpy``,
``rubin_scheduler.site_models.Almanac``,
Expand All @@ -489,6 +526,11 @@ Changes from ``main``
line includes a bracketed breakdown of PSF, transparency, and sky
background factors. When any column is absent, the line retains its
original format.
12. **Distinct prenight RSS format**: ``_format_summary_desc`` now accepts a
``desc_format`` parameter (default ``None`` → ``RSS_DESC_FORMAT``). The
prenight branch passes ``RSS_PRENIGHT_DESC_FORMAT``, which uses
"Simulated (science) visits" instead of "Total visits" and omits the
separate "Science visits" line and redundant qualifier text.


Dependencies
Expand Down Expand Up @@ -522,7 +564,8 @@ Tests are in ``tests/test_reports.py``.
Passes a synthetic ``prenight_visits`` DataFrame (using the simulator
``t_eff``/``visitExposureTime`` columns) whose dayObs match the report
fixtures, and verifies the ``lsstcam`` ``prenight`` item descriptions carry
a populated ``Total visits: N (...)`` summary with a band breakdown.
a populated ``Simulated (science) visits: N (...)`` summary with a band
breakdown (using the distinct ``RSS_PRENIGHT_DESC_FORMAT``).

``test_make_report_rss_feed_prenight_blank_when_no_visits``
Passes ``prenight_visits`` whose dayObs match no report night, and verifies
Expand Down
35 changes: 31 additions & 4 deletions schedview/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
Science targets: {targets}
"""

RSS_PRENIGHT_DESC_FORMAT = """
Simulated (science) visits: {total};
Median FWHM: {fwhm};
Mean visit rate: {visit_rate} visits/hour;
Total eff_time / total exp_time: {mean_norm_teff};
Total eff_time / total night time: {teff_rate};
Science targets: {targets}
"""


def find_reports(
report_dir: str = "/sdf/data/rubin/shared/scheduler/reports",
Expand Down Expand Up @@ -183,7 +192,14 @@ def make_report_link_table(
return report_table_html


def _format_summary_desc(tinysum: pd.DataFrame, dayobs: int, report: str, instrument: str, night) -> str:
def _format_summary_desc(
tinysum: pd.DataFrame,
dayobs: int,
report: str,
instrument: str,
night: datetime.date,
desc_format: str = None,
) -> str:
"""Format an RSS item description from a ``compute_tinysum`` row.
Parameters
Expand All @@ -199,11 +215,14 @@ def _format_summary_desc(tinysum: pd.DataFrame, dayobs: int, report: str, instru
The instrument name.
night : `datetime.date`
The local calendar date of the night start.
desc_format: `str` or `None`
Format for the description field. None defaults
to ``RSS_DESC_FORMAT``.
Returns
-------
desc : `str`
The formatted ``RSS_DESC_FORMAT`` text for the night.
The formatted text for the night.
"""
try:
teff_rate = np.round(tinysum.loc[dayobs, "teff/night duration"], 2)
Expand All @@ -225,7 +244,10 @@ def _format_summary_desc(tinysum: pd.DataFrame, dayobs: int, report: str, instru
else:
mean_norm_teff_str = f"{norm_teff}"

return RSS_DESC_FORMAT.format(
if desc_format is None:
desc_format = RSS_DESC_FORMAT

return desc_format.format(
report=report,
instrument=instrument,
night=night,
Expand Down Expand Up @@ -359,7 +381,12 @@ def make_report_rss_feed(
# visits gets a completely blank description (no fallback text).
if dayobs in prenight_tinysum.index:
desc.text = _format_summary_desc(
prenight_tinysum, dayobs, report_row.report, instrument, report_row.night
prenight_tinysum,
dayobs,
report_row.report,
instrument,
report_row.night,
desc_format=RSS_PRENIGHT_DESC_FORMAT,
)
else:
desc.text = ""
Expand Down
8 changes: 8 additions & 0 deletions tests/test_design_docs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for RST design documents under design/."""

import os
import subprocess
import sys
from pathlib import Path
Expand All @@ -9,6 +10,12 @@
DESIGN_DIR = Path(__file__).parent.parent / "design"
RST_FILES = sorted(DESIGN_DIR.rglob("*.rst"))

# rst2html calls locale.setlocale(locale.LC_ALL, '') on startup, which fails
# if the environment's locale (e.g. en_US.UTF-8) isn't installed on the host.
# Force a locale that is always available so the test doesn't depend on the
# host's locale configuration.
_RST_ENV = {**os.environ, "LC_ALL": "C.UTF-8", "LANG": "C.UTF-8"}


def _rst_id(rst_path):
"""Return a short test ID for a design-doc path."""
Expand All @@ -23,6 +30,7 @@ def test_rst_valid(rst_file):
["rst2html", "--halt=warning", str(rst_file), "/dev/null"],
capture_output=True,
text=True,
env=_RST_ENV,
)
assert result.returncode == 0, f"RST validation failed for {rst_file.name}:\n{result.stderr}"

Expand Down
9 changes: 4 additions & 5 deletions tests/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,10 @@ def test_make_report_rss_feed_prenight_description(self):
if category == "lsstcam_prenight":
prenight_descs.append(item.findtext("description") or "")
joined = "\n".join(prenight_descs)
# The prenight items must carry a populated summary description.
assert re.search(r"Total visits: \d+ \(\d+[ugrizy]", joined)
# All prenight visits count as science, so the science line is
# populated with its own band breakdown (not zero).
assert re.search(r"Science visits: \d+ \(\d+[ugrizy]", joined)
# The prenight items must carry a populated summary description
# using the distinct prenight format (no separate "Science visits"
# line since all prenight visits are science).
assert re.search(r"Simulated \(science\) visits: \d+ \(\d+[ugrizy]", joined)

def test_make_report_rss_feed_prenight_blank_when_no_visits(self):
rng = np.random.default_rng(0)
Expand Down
Loading