Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-21 08:46:27

0001 """Publish the testbed datataking state projection to Snapper."""
0002 
0003 from dataclasses import dataclass
0004 from datetime import datetime, timezone as datetime_timezone
0005 from typing import Optional
0006 
0007 from django.db import transaction
0008 from django.db.models import OuterRef, Subquery
0009 from django.db.models.fields.json import KeyTextTransform
0010 from django.utils import timezone
0011 
0012 from snapper_ai.services import (
0013     ComponentUpdate,
0014     publish_component,
0015     register_component,
0016 )
0017 
0018 from .models import RunState
0019 from .workflow_models import WorkflowExecution
0020 
0021 
0022 PUBLISHER_IDENTITY = "swf-monitor:run-state"
0023 ASSESSMENT_POLICY_VERSION = "swf-datataking-state-v1"
0024 EVENT_RESOLVER = "swf-testbed-system-state-events"
0025 MAX_NAMESPACES = 128
0026 MAX_SERIALIZED_BYTES = 64 * 1024
0027 
0028 DATATAKING_REGISTRATION = {
0029     "title": "Testbed datataking state by namespace",
0030     "description": (
0031         "The latest run state for each namespace sharing the testbed platform, "
0032         "forming its datataking lanes in the E0-E1 global state at an instant."
0033     ),
0034     "visibility": "public",
0035     "owning_subsystem": "SWF testbed datataking state",
0036     "assessment_policy": ASSESSMENT_POLICY_VERSION,
0037     "max_serialized_bytes": MAX_SERIALIZED_BYTES,
0038     "quantities": {
0039         "namespaces": {
0040             "path": "namespaces",
0041             "type": "object",
0042             "required": True,
0043             "kind": "bounded_map",
0044             "max_items": MAX_NAMESPACES,
0045             "description": (
0046                 "Map keyed by testbed namespace; each value contains the "
0047                 "current run number, phase, state, optional substate, and last "
0048                 "transition time for that independently operated testbed."
0049             ),
0050         },
0051     },
0052     "event_sources": [
0053         {
0054             "name": "datataking-state-transitions",
0055             "resolver": EVENT_RESOLVER,
0056             "owner": "SWF testbed datataking state",
0057             "event_kind": "datataking-state-transition",
0058             "event_time_field": "timestamp",
0059             "visibility": "public",
0060         }
0061     ],
0062 }
0063 
0064 
0065 @dataclass(frozen=True)
0066 class DatatakingPublication:
0067     registration_update: ComponentUpdate
0068     update: ComponentUpdate
0069     projection: dict
0070     run_numbers: dict[str, int]
0071 
0072 
0073 def _timestamp(value: datetime) -> str:
0074     return (
0075         value.astimezone(datetime_timezone.utc)
0076         .isoformat()
0077         .replace("+00:00", "Z")
0078     )
0079 
0080 
0081 def _latest_run_states_by_namespace() -> list[RunState]:
0082     execution_key = KeyTextTransform("execution_id", "metadata")
0083     namespace = Subquery(
0084         WorkflowExecution.objects.filter(
0085             execution_id=OuterRef("execution_key")
0086         ).values("namespace")[:1]
0087     )
0088     rows = list(
0089         RunState.objects.annotate(execution_key=execution_key)
0090         .annotate(testbed_namespace=namespace)
0091         .exclude(testbed_namespace__isnull=True)
0092         .exclude(testbed_namespace="")
0093         .order_by("testbed_namespace", "-run_number")
0094         .distinct("testbed_namespace")[: MAX_NAMESPACES + 1]
0095     )
0096     if len(rows) > MAX_NAMESPACES:
0097         raise ValueError(
0098             f"datataking projection exceeds {MAX_NAMESPACES} namespaces"
0099         )
0100     return rows
0101 
0102 
0103 def datataking_projection() -> tuple[dict, dict[str, int]]:
0104     """Return the latest bounded run state for each testbed namespace."""
0105     run_states = _latest_run_states_by_namespace()
0106     if not run_states:
0107         raise ValueError(
0108             "cannot publish datataking state without a namespaced RunState row"
0109         )
0110 
0111     namespaces = {}
0112     run_numbers = {}
0113     for run_state in run_states:
0114         namespace = run_state.testbed_namespace
0115         state = {
0116             "run_number": run_state.run_number,
0117             "phase": run_state.phase,
0118             "state": run_state.state,
0119             "last_transition_at": _timestamp(run_state.state_changed_at),
0120         }
0121         if run_state.substate is not None:
0122             state["substate"] = run_state.substate
0123         namespaces[namespace] = state
0124         run_numbers[namespace] = run_state.run_number
0125     return {"namespaces": namespaces}, run_numbers
0126 
0127 
0128 @transaction.atomic
0129 def publish_datataking_state(
0130     assessed_at: Optional[datetime] = None,
0131 ) -> DatatakingPublication:
0132     """Register and publish current datataking state across namespaces."""
0133     projection, run_numbers = datataking_projection()
0134     assessed_at = assessed_at or timezone.now()
0135     registration_update = register_component(
0136         scope="testbed",
0137         name="datataking",
0138         publisher_identity=PUBLISHER_IDENTITY,
0139         registration=DATATAKING_REGISTRATION,
0140         component_schema_version=2,
0141     )
0142     update = publish_component(
0143         scope="testbed",
0144         name="datataking",
0145         publisher_identity=PUBLISHER_IDENTITY,
0146         data=projection,
0147         assessed_at=assessed_at,
0148         assessment_policy_version=ASSESSMENT_POLICY_VERSION,
0149     )
0150     return DatatakingPublication(
0151         registration_update=registration_update,
0152         update=update,
0153         projection=projection,
0154         run_numbers=run_numbers,
0155     )