flip.flower.progress ==================== .. py:module:: flip.flower.progress .. autoapi-nested-parse:: Flower round-progress emission — the Flower twin of the NVFLARE round relay. Emits the typed round facts (``FLLogEvent``) that back the hub's round-aware activity feed. Facts only: the hub composes display text at serve time, so wording changes never require rebuilding FL images. Every helper is best-effort — a telemetry failure is logged and must never break the strategy loop (matching ``flip.flower.metrics``). Only the fl-server should import from this module; fl-clients hold no hub credentials. ``flip.flower.strategy.FlipFedAvg`` wires these into the FedAvg hooks; app templates should subclass it rather than calling these directly. Classes ------- .. autoapisummary:: flip.flower.progress.RoundTelemetry Functions --------- .. autoapisummary:: flip.flower.progress.resolve_absent_site flip.flower.progress.report_round_started flip.flower.progress.report_client_result flip.flower.progress.report_round_aggregated Module Contents --------------- .. py:function:: resolve_absent_site(dispatched_nodes: set[int], responded_nodes: set[int], site_by_node: dict[int, str]) -> str | None Name the single client that was dispatched a task but never returned a healthy reply. Flower stamps a placeholder ``src_node_id`` on the error reply it synthesises for an unreachable node, so a dead client cannot be recognised from its own reply. It can be recognised by elimination: the strategy knows which nodes it dispatched to, which answered, and (from earlier healthy replies) each node's site name. Returns None when the absence is ambiguous (several nodes silent at once) or the absent node never identified itself — guessing would put a real trust's name on another trust's failure. :param dispatched_nodes: Node ids the round's tasks were sent to. :param responded_nodes: Node ids that returned a healthy reply this round. :param site_by_node: Node id to site name, learned from healthy replies. :returns: The absent client's site name, or None when unidentifiable. :rtype: str | None .. py:class:: RoundTelemetry Per-run reply bookkeeping: who was dispatched, who answered, who fell out. Extracted from ``FlipFedAvg`` (which needs ``flwr`` to import) so CI can pin the two invariants that make crashed-client attribution safe: - absent clients are named only from sites learned off healthy replies (accumulated across the whole run) — never guessed from the placeholder identity on a synthesised error reply; - each phase keeps its **own** dispatch roster — a train strategy's evaluate arm may sample a different cohort, and resolving evaluate absences against the train roster would put one trust's name on another trust's failure. .. py:attribute:: site_by_node :type: dict[int, str] .. py:attribute:: _dispatched_by_phase :type: dict[Phase, set[int]] .. py:method:: record_dispatch(phase: Phase, node_ids: set[int]) -> None Record the node ids a phase's tasks were just sent to (replaces the phase's previous roster — each round samples afresh). :param phase: Which strategy phase dispatched. :param node_ids: The ``dst_node_id`` of every dispatched message. .. py:method:: dispatched_count(phase: Phase) -> int | None How many clients the phase's current round was dispatched to. :param phase: Which strategy phase to count. :returns: The roster size, or None when the phase never dispatched. :rtype: int | None .. py:method:: forward_replies(replies: list[flwr.common.message.Message], phase: Phase, server_round: int, model_id: str, flip: flip.FLIP) -> int Forward every reply's metrics, exception and round event to the hub. Learns each healthy reply's site, then names the one client the phase dispatched to that never returned a healthy reply (see ``resolve_absent_site``) so its error is attributed to the right trust. fl-clients never reach the Central Hub directly — every forward happens here, server-side. :param replies: The round's reply Messages. :param phase: The strategy phase these replies belong to. :param server_round: Flower's 1-based round number. :param model_id: The FLIP model ID for the run. :param flip: The FLIP instance used to reach the Central Hub. :returns: How many replies were healthy. :rtype: int .. py:function:: report_round_started(flip: flip.FLIP, model_id: str, server_round: int, total_rounds: int | None) -> None Report that the server dispatched the global model for a round. :param flip: The FLIP instance used to reach the Central Hub. :param model_id: The FLIP model ID for the run. :param server_round: Flower's 1-based round number (already the wire contract). :param total_rounds: The run's round total, when known. .. py:function:: report_client_result(msg: flwr.common.message.Message, server_round: int, model_id: str, flip: flip.FLIP) -> bool Report one received client reply; returns whether the reply was healthy. The boolean feeds the strategy's returned-count, so a telemetry failure on a healthy reply still counts it as returned. :param msg: A Flower reply Message from a client. :param server_round: Flower's 1-based round number. :param model_id: The FLIP model ID for the run. :param flip: The FLIP instance used to reach the Central Hub. :returns: True when the reply is healthy (no error), regardless of whether the telemetry post succeeded. :rtype: bool .. py:function:: report_round_aggregated(flip: flip.FLIP, model_id: str, server_round: int, returned: int | None, expected: int | None) -> None Report that a round's replies were aggregated. :param flip: The FLIP instance used to reach the Central Hub. :param model_id: The FLIP model ID for the run. :param server_round: Flower's 1-based round number. :param returned: Healthy replies received this round, when known. :param expected: Clients the round was dispatched to, when known.