From 718ef21fb2bfb9ae20cc460622190fdffabf2e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAnaPcode=E2=80=9D?= <“anastasiapupo@gmail.com”> Date: Sat, 8 Aug 2026 16:12:26 -0700 Subject: [PATCH 1/5] Hoist level emoji map to LEVEL_EMOJI constant Was duplicated in render_playbook_card (line 159) and generate_playbook (line 267). Moved to a LEVEL_EMOJI constant, placed after PlaybookEntry, and pointed both call sites at it. Part of #1512 cleanup of tooling file (c) --- toolchain/mfc/gen_case_constraints_docs.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/toolchain/mfc/gen_case_constraints_docs.py b/toolchain/mfc/gen_case_constraints_docs.py index 2c73038783..b4c21f7212 100644 --- a/toolchain/mfc/gen_case_constraints_docs.py +++ b/toolchain/mfc/gen_case_constraints_docs.py @@ -50,6 +50,9 @@ class PlaybookEntry: tags: List[str] +LEVEL_EMOJI = {"Beginner": "🟢", "Intermediate": "🟡", "Advanced": "🔴"} + + # Curated list of hero examples PLAYBOOK_EXAMPLES = [ PlaybookEntry( @@ -156,7 +159,7 @@ def render_playbook_card(entry: PlaybookEntry, summary: Dict[str, Any]) -> str: lines = [] tags_str = " · ".join(entry.tags) - level_emoji = {"Beginner": "🟢", "Intermediate": "🟡", "Advanced": "🔴"}.get(entry.level, "") + level_emoji = LEVEL_EMOJI.get(entry.level, "") lines.append("
") lines.append(f"{entry.title} {level_emoji} {entry.level} · {entry.case_dir}\n") @@ -264,7 +267,7 @@ def generate_playbook() -> str: if not level_entries: continue - level_emoji = {"Beginner": "🟢", "Intermediate": "🟡", "Advanced": "🔴"}.get(level, "") + level_emoji = LEVEL_EMOJI.get(level, "") lines.append(f"\n### {level_emoji} {level} Examples\n") for entry in level_entries: From 212f2de1b68d980f0c029bf2795ba19bb52f769d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAnaPcode=E2=80=9D?= <“anastasiapupo@gmail.com”> Date: Sat, 8 Aug 2026 17:12:42 -0700 Subject: [PATCH 2/5] Collapse schema-name getters into _named helper get_model_name, get_riemann_solver_name, and get_time_stepper_name (lines 133-151) had byte-identical bodies modulo the schema key string. Replaced all three with a single _named(param, value) helper and updated the three call sites: line 170 (model_eqns), line 222 (riemann_solver), and line 226 (time_stepper). Part of issue 1512 cleanup of tooling file (c) --- toolchain/mfc/gen_case_constraints_docs.py | 28 ++++++---------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/toolchain/mfc/gen_case_constraints_docs.py b/toolchain/mfc/gen_case_constraints_docs.py index b4c21f7212..1ab7151f1e 100644 --- a/toolchain/mfc/gen_case_constraints_docs.py +++ b/toolchain/mfc/gen_case_constraints_docs.py @@ -133,25 +133,11 @@ def summarize_case_params(params: Dict[str, Any]) -> Dict[str, Any]: } -def get_model_name(model_eqns: int | None) -> str: - """Get human-friendly model name from schema.""" - if model_eqns is None: +def _named(param: str, value: int | None) -> str: + """Get the name from a schema (i.e. model name, Riemann solver name, time stepper name).""" + if value is None: return "Not specified" - return get_value_label("model_eqns", model_eqns) or "Not specified" - - -def get_riemann_solver_name(solver: int | None) -> str: - """Get Riemann solver name from schema.""" - if solver is None: - return "Not specified" - return get_value_label("riemann_solver", solver) or "Not specified" - - -def get_time_stepper_name(stepper: int | None) -> str: - """Get time stepper name from schema.""" - if stepper is None: - return "Not specified" - return get_value_label("time_stepper", stepper) or "Not specified" + return get_value_label(param, value) or "Not specified" def render_playbook_card(entry: PlaybookEntry, summary: Dict[str, Any]) -> str: @@ -167,7 +153,7 @@ def render_playbook_card(entry: PlaybookEntry, summary: Dict[str, Any]) -> str: lines.append(f"**Tags:** {tags_str}\n") lines.append("**Physics Configuration:**\n") - lines.append(f"- **Model:** {get_model_name(summary['model_eqns'])} (`model_eqns = {summary['model_eqns']}`)") + lines.append(f"- **Model:** {_named('model_eqns', summary['model_eqns'])} (`model_eqns = {summary['model_eqns']}`)") if summary["num_fluids"] is not None: lines.append(f"- **Number of fluids:** {summary['num_fluids']}") @@ -219,11 +205,11 @@ def render_playbook_card(entry: PlaybookEntry, summary: Dict[str, Any]) -> str: lines.append(f"- **Reconstruction:** MUSCL (order {summary['muscl_order']})") if summary["riemann_solver"]: - solver_name = get_riemann_solver_name(summary["riemann_solver"]) + solver_name = _named("riemann_solver", summary["riemann_solver"]) lines.append(f"- **Riemann solver:** {solver_name} (`riemann_solver = {summary['riemann_solver']}`)") if summary["time_stepper"]: - stepper_name = get_time_stepper_name(summary["time_stepper"]) + stepper_name = _named("time_stepper", summary["time_stepper"]) lines.append(f"- **Time stepping:** {stepper_name}") # Links From 1d5406eec4e750d75a6c7198fc8460a8b044eeb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAnaPcode=E2=80=9D?= <“anastasiapupo@gmail.com”> Date: Sun, 9 Aug 2026 12:58:17 -0700 Subject: [PATCH 3/5] Read threshold seconds from HEADLESS_THRESHOLDS The seconds were hardcoded at each comparison in notify_long_running_threads, while only the message text indexed the HEADLESS_THRESHOLDS table. Read the seconds from this table too, so they live in one place. Part of issue 1512 cleanup of tooling file (d) --- toolchain/mfc/sched.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/toolchain/mfc/sched.py b/toolchain/mfc/sched.py index c7158bbbd8..8e7b186d26 100644 --- a/toolchain/mfc/sched.py +++ b/toolchain/mfc/sched.py @@ -140,17 +140,17 @@ def notify_long_running_threads(progress: rich.progress.Progress, running_tracke # headless: milestone notifications at 2, 10, 30 minutes else: # 2 minutes - if (not holder.notified_2m) and elapsed >= 2 * 60: + if (not holder.notified_2m) and elapsed >= HEADLESS_THRESHOLDS[0][0]: cons.print(f" {HEADLESS_THRESHOLDS[0][1]} [bold magenta]{case_uuid}[/bold magenta] {case_trace}") holder.notified_2m = True # 10 minutes - if (not holder.notified_10m) and elapsed >= 10 * 60: + if (not holder.notified_10m) and elapsed >= HEADLESS_THRESHOLDS[1][0]: cons.print(f" {HEADLESS_THRESHOLDS[1][1]} [bold magenta]{case_uuid}[/bold magenta] {case_trace}") holder.notified_10m = True # 30 minutes - if (not holder.notified_30m) and elapsed >= 30 * 60: + if (not holder.notified_30m) and elapsed >= HEADLESS_THRESHOLDS[2][0]: cons.print(f" {HEADLESS_THRESHOLDS[2][1]} [bold magenta]{case_uuid}[/bold magenta] {case_trace}") holder.notified_30m = True From 065e86f28280e13c90dc05e2e519541f1be84269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAnaPcode=E2=80=9D?= <“anastasiapupo@gmail.com”> Date: Sun, 9 Aug 2026 15:34:10 -0700 Subject: [PATCH 4/5] Delete ORG_COLORS and inline "yellow" All 8 entries in ORG_COLORS mapped to "yellow" and the lookup defaulted to "yellow", so every org rendered yellow regardless. Deleted the dictionary and inlined yellow. Part of issue 1512 cleanup of tooling file (e) --- toolchain/mfc/user_guide.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/toolchain/mfc/user_guide.py b/toolchain/mfc/user_guide.py index 6dd608bc88..dd701ec005 100644 --- a/toolchain/mfc/user_guide.py +++ b/toolchain/mfc/user_guide.py @@ -51,18 +51,8 @@ "h": "HiPerGator", # Proper capitalization } -# Display order and colors for organizations +# Display order for organizations ORG_ORDER = ["ORNL", "LLNL", "ACCESS", "Georgia Tech", "Caltech", "Brown", "DoD", "Florida", "CSCS"] -ORG_COLORS = { - "ORNL": "yellow", - "LLNL": "yellow", - "ACCESS": "yellow", - "Georgia Tech": "yellow", - "Caltech": "yellow", - "Brown": "yellow", - "DoD": "yellow", - "Florida": "yellow", -} def _parse_modules_file(): @@ -142,8 +132,7 @@ def _generate_clusters_content(): continue # Format: " [yellow]ORG:[/yellow] [cyan]slug[/cyan]=Name [cyan]slug2[/cyan]=Name2" entries = [f"[cyan]{slug}[/cyan]={_get_cluster_short_name(slug, name)}" for slug, name in org_clusters[org]] - color = ORG_COLORS.get(org, "yellow") - cluster_lines.append(f" [{color}]{org}:[/{color}] " + " ".join(entries)) + cluster_lines.append(f" [yellow]{org}:[/yellow] " + " ".join(entries)) # Handle "Other" if any if org_clusters.get("Other"): From 93a8c7c28f3e400f863298ceed16712a887dc38b Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Tue, 18 Aug 2026 11:34:34 -0400 Subject: [PATCH 5/5] Address review: name the helper for its default, drive the level list and headless milestones from their tables --- toolchain/mfc/gen_case_constraints_docs.py | 15 +++++++------- toolchain/mfc/sched.py | 24 ++++++---------------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/toolchain/mfc/gen_case_constraints_docs.py b/toolchain/mfc/gen_case_constraints_docs.py index 1ab7151f1e..4b4d549502 100644 --- a/toolchain/mfc/gen_case_constraints_docs.py +++ b/toolchain/mfc/gen_case_constraints_docs.py @@ -133,8 +133,8 @@ def summarize_case_params(params: Dict[str, Any]) -> Dict[str, Any]: } -def _named(param: str, value: int | None) -> str: - """Get the name from a schema (i.e. model name, Riemann solver name, time stepper name).""" +def _value_label_or_default(param: str, value: int | None) -> str: + """Get a parameter value's human-friendly name from the schema, or "Not specified".""" if value is None: return "Not specified" return get_value_label(param, value) or "Not specified" @@ -153,7 +153,7 @@ def render_playbook_card(entry: PlaybookEntry, summary: Dict[str, Any]) -> str: lines.append(f"**Tags:** {tags_str}\n") lines.append("**Physics Configuration:**\n") - lines.append(f"- **Model:** {_named('model_eqns', summary['model_eqns'])} (`model_eqns = {summary['model_eqns']}`)") + lines.append(f"- **Model:** {_value_label_or_default('model_eqns', summary['model_eqns'])} (`model_eqns = {summary['model_eqns']}`)") if summary["num_fluids"] is not None: lines.append(f"- **Number of fluids:** {summary['num_fluids']}") @@ -205,11 +205,11 @@ def render_playbook_card(entry: PlaybookEntry, summary: Dict[str, Any]) -> str: lines.append(f"- **Reconstruction:** MUSCL (order {summary['muscl_order']})") if summary["riemann_solver"]: - solver_name = _named("riemann_solver", summary["riemann_solver"]) + solver_name = _value_label_or_default("riemann_solver", summary["riemann_solver"]) lines.append(f"- **Riemann solver:** {solver_name} (`riemann_solver = {summary['riemann_solver']}`)") if summary["time_stepper"]: - stepper_name = _named("time_stepper", summary["time_stepper"]) + stepper_name = _value_label_or_default("time_stepper", summary["time_stepper"]) lines.append(f"- **Time stepping:** {stepper_name}") # Links @@ -248,13 +248,12 @@ def generate_playbook() -> str: ) # Group by level - for level in ["Beginner", "Intermediate", "Advanced"]: + for level in LEVEL_EMOJI: level_entries = [e for e in PLAYBOOK_EXAMPLES if e.level == level] if not level_entries: continue - level_emoji = LEVEL_EMOJI.get(level, "") - lines.append(f"\n### {level_emoji} {level} Examples\n") + lines.append(f"\n### {LEVEL_EMOJI[level]} {level} Examples\n") for entry in level_entries: try: diff --git a/toolchain/mfc/sched.py b/toolchain/mfc/sched.py index 8e7b186d26..95a803c9c0 100644 --- a/toolchain/mfc/sched.py +++ b/toolchain/mfc/sched.py @@ -54,9 +54,7 @@ class WorkerThreadHolder: start: float = 0.0 # Track which milestones we've already logged notified_interactive: bool = False # First notification in interactive mode (time varies by dimensionality) - notified_2m: bool = False # Headless mode: 2 minute milestone - notified_10m: bool = False # Headless mode: 10 minute milestone - notified_30m: bool = False # Headless mode: 30 minute milestone + notified_headless: typing.Set[int] = dataclasses.field(default_factory=set) # Headless mode: HEADLESS_THRESHOLDS indices already logged @dataclasses.dataclass @@ -137,22 +135,12 @@ def notify_long_running_threads(progress: rich.progress.Progress, running_tracke cons.print(f" [italic yellow]Still running[/italic yellow] ({dim_label}, >{time_label}) [bold magenta]{case_uuid}[/bold magenta] {case_trace}") holder.notified_interactive = True - # headless: milestone notifications at 2, 10, 30 minutes + # headless: one notification per HEADLESS_THRESHOLDS milestone else: - # 2 minutes - if (not holder.notified_2m) and elapsed >= HEADLESS_THRESHOLDS[0][0]: - cons.print(f" {HEADLESS_THRESHOLDS[0][1]} [bold magenta]{case_uuid}[/bold magenta] {case_trace}") - holder.notified_2m = True - - # 10 minutes - if (not holder.notified_10m) and elapsed >= HEADLESS_THRESHOLDS[1][0]: - cons.print(f" {HEADLESS_THRESHOLDS[1][1]} [bold magenta]{case_uuid}[/bold magenta] {case_trace}") - holder.notified_10m = True - - # 30 minutes - if (not holder.notified_30m) and elapsed >= HEADLESS_THRESHOLDS[2][0]: - cons.print(f" {HEADLESS_THRESHOLDS[2][1]} [bold magenta]{case_uuid}[/bold magenta] {case_trace}") - holder.notified_30m = True + for i, (secs, message) in enumerate(HEADLESS_THRESHOLDS): + if i not in holder.notified_headless and elapsed >= secs: + cons.print(f" {message} [bold magenta]{case_uuid}[/bold magenta] {case_trace}") + holder.notified_headless.add(i) # update the interactive "Running" row if interactive and running_tracker is not None: