Files
Jungfraujoch/receiver/RugnuxLiveStatus.cpp
T
leonarski_f 15ca4b329f broker: serve the placed model and its maps of the last rugnux live run (/rugnux/model.cif, /rugnux/map.ccp4)
A rugnux live run that placed a model keeps it, with a copy of its map coefficients, beside the live
status (RugnuxStructure), for as long as /rugnux/status keeps the run. Made in memory on the first
request and cached, never written to disk:
- GET /rugnux/model.cif: the placed model as mmCIF, labelled as WritePlacedModel labels _model.cif
  (PlacedModelMmcif, the labelling factored out of WritePlacedModel; the file output is unchanged).
- GET /rugnux/map.ccp4?type=2fofc|fofc: the 2mFo-DFc / mFo-DFc map as a CCP4 map, FFT of the
  FWT/PHWT or DELFWT/PHDELWT coefficients (get_f_phi_on_grid + MapFromFPhi, sampled as ModelValidation
  samples), cut to the box around the model plus 5 A, with the whole cell's mean and RMS in the header.
- 404 without a placed model or maps, 400 for an unknown map type; both routes protected by the dataset
  bearer token like /rugnux/status. rugnux_live_status gets placed_model.

C++ model, python client docs, TypeScript client and redoc regenerated.

Tests: a point's coefficients give a map peaking at the atom in a cropped box; the mmCIF; the structure
is cleared at the next start; the auth test covers the new routes. End to end (myoglobin 2M replay with
an AlphaFold model): the maps equal gemmi's FFT of the written _maps.mtz to 2.5e-6 rms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 33124c3288ba112def3f51c0d91f5206886f3495)
2026-10-22 20:18:50 +02:00

139 lines
4.3 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "RugnuxLiveStatus.h"
double RugnuxLiveStatus::Since(std::chrono::steady_clock::time_point t) const {
return std::chrono::duration<double>(t - start).count();
}
double RugnuxLiveStatus::Now() const {
return Since(std::chrono::steady_clock::now());
}
void RugnuxLiveStatus::CloseStep() {
if (!s.steps.empty() && !s.steps.back().duration_s)
s.steps.back().duration_s = Now() - s.steps.back().started_s;
}
void RugnuxLiveStatus::Start(int64_t images_expected, std::optional<double> expected_collection_s,
std::chrono::steady_clock::time_point start_received) {
std::unique_lock ul(m);
s = RugnuxLiveSnapshot{};
s.state = RugnuxLiveState::Running;
s.images_expected = images_expected;
s.expected_collection_s = expected_collection_s;
start = start_received;
last_image.reset();
}
void RugnuxLiveStatus::ImageReceived(std::chrono::steady_clock::time_point received) {
std::unique_lock ul(m);
++s.images_received;
// Several forward threads report, so the images need not come here in the order they arrived.
const double t = Since(received);
if (!s.start_to_first_image_s || t < s.start_to_first_image_s.value())
s.start_to_first_image_s = t;
if (!last_image || received > last_image.value())
last_image = received;
}
void RugnuxLiveStatus::Step(const std::string &title) {
std::unique_lock ul(m);
CloseStep();
s.steps.push_back(RugnuxLiveStep{.title = title, .started_s = Now()});
s.step_progress.reset();
}
void RugnuxLiveStatus::Note(const std::string &text) {
std::unique_lock ul(m);
if (s.steps.empty())
s.steps.push_back(RugnuxLiveStep{.title = "", .started_s = Now()});
s.steps.back().notes.push_back(text);
}
void RugnuxLiveStatus::Progress(uint64_t done, uint64_t total) {
std::unique_lock ul(m);
if (total > 0)
s.step_progress = static_cast<float>(done) / static_cast<float>(total);
}
void RugnuxLiveStatus::Finish(RugnuxLiveState state, const std::string &message) {
std::unique_lock ul(m);
CloseStep();
s.elapsed_s = Now();
s.state = state;
s.message = message;
s.step_progress.reset();
}
void RugnuxLiveStatus::Result(const std::string &verdict, const std::string &verdict_text,
const std::vector<std::pair<std::string, std::string>> &summary,
const std::vector<std::string> &warnings) {
std::unique_lock ul(m);
s.verdict = verdict;
s.verdict_text = verdict_text;
s.summary = summary;
s.warnings = warnings;
}
void RugnuxLiveStatus::Latency(double last_image_to_written_s) {
std::unique_lock ul(m);
s.last_image_to_written_s = last_image_to_written_s;
}
std::optional<std::chrono::steady_clock::time_point> RugnuxLiveStatus::LastImage() {
std::unique_lock ul(m);
if (last_image)
s.last_image_s = Since(last_image.value());
return last_image;
}
void RugnuxLiveStatus::Cut(const std::string &reason) {
std::unique_lock ul(m);
s.cut = reason;
s.cut_s = Now();
}
void RugnuxLiveStatus::Probe(const std::string &text) {
std::unique_lock ul(m);
s.probe = text;
s.probe_s = Now();
}
void RugnuxLiveStatus::Warning(const std::string &text) {
std::unique_lock ul(m);
s.warnings.push_back(text);
}
void RugnuxLiveStatus::Structure(std::shared_ptr<const RugnuxStructure> structure) {
std::unique_lock ul(m);
s.structure = std::move(structure);
}
void RugnuxLiveStatus::Clear() {
std::unique_lock ul(m);
s = RugnuxLiveSnapshot{};
last_image.reset();
}
RugnuxLiveSnapshot RugnuxLiveStatus::Get() const {
std::unique_lock ul(m);
RugnuxLiveSnapshot ret = s;
if (s.state == RugnuxLiveState::Running)
ret.elapsed_s = Now();
// Held since START, reported since the first image - or, before it has come, since now.
const double zero = s.start_to_first_image_s.value_or(s.state == RugnuxLiveState::Running ? ret.elapsed_s : 0.0);
ret.elapsed_s -= zero;
for (auto &step: ret.steps)
step.started_s -= zero;
if (ret.probe_s)
*ret.probe_s -= zero;
if (ret.last_image_s)
*ret.last_image_s -= zero;
if (ret.cut_s)
*ret.cut_s -= zero;
return ret;
}