feat: expose physical-sample-level results via the SDK - #98
Conversation
|
| Filename | Overview |
|---|---|
| src/atomscale/client.py | Adds the public fetch method and default aggregate enrichment, but the optional extra request can now abort existing sample and project fetches. |
| src/atomscale/timeseries/physical_sample.py | Adds long-form parsing, filtering, validation, dtypes, and provenance metadata with no established current blocking defect. |
| src/atomscale/results/group.py | Adds a backward-compatible optional sample_metrics attribute to PhysicalSampleResult. |
| docs/guides/analysis-results.rst | Documents sample-level results, but incorrectly says no metrics produce None rather than an empty DataFrame. |
| tests/test_physical_sample_timeseries.py | Covers primary parser and client behavior but does not exercise non-404 failures of the default aggregate enrichment. |
Sequence Diagram
sequenceDiagram
participant Caller
participant Client
participant Samples as Physical Samples API
participant Metrics as Sample Metrics API
Caller->>Client: get_physical_sample(id)
Client->>Samples: GET physical_samples/
Samples-->>Client: sample metadata
Client->>Metrics: "GET physical_samples/{id}/timeseries/"
alt successful response
Metrics-->>Client: properties
Client-->>Caller: PhysicalSampleResult + sample_metrics
else 404
Metrics-->>Client: 404
Client-->>Caller: PhysicalSampleResult + None
else other HTTP or transport failure
Metrics--xClient: error
Client--xCaller: exception
end
Prompt To Fix All With AI
### Issue 1
src/atomscale/client.py:1117-1121
**Optional enrichment aborts sample fetches**
When the additional metrics request receives a non-404 HTTP error or transport failure, the error propagates from the default-enabled enrichment, causing an otherwise successful `get_physical_sample` call—and an entire `get_project` aggregation—to fail.
### Issue 2
docs/guides/analysis-results.rst:132
**Empty metrics representation is misstated**
The example says `sample_metrics` is `None` when a sample has no computed metrics, but a successful empty response produces an empty DataFrame; callers following this guidance can incorrectly treat that frame as populated data.
```suggestion
print(sample.sample_metrics) # Empty DataFrame if fetched but no metrics exist
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "expose physical sample level results" | Re-trigger Greptile
| sample_metrics: DataFrame | None = None | ||
| if include_sample_metrics: | ||
| raw_metrics = self._get( | ||
| sub_url=f"physical_samples/{physical_sample_id}/timeseries/" | ||
| ) |
There was a problem hiding this comment.
Optional enrichment aborts sample fetches
When the additional metrics request receives a non-404 HTTP error or transport failure, the error propagates from the default-enabled enrichment, causing an otherwise successful get_physical_sample call—and an entire get_project aggregation—to fail.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/atomscale/client.py
Line: 1117-1121
Comment:
**Optional enrichment aborts sample fetches**
When the additional metrics request receives a non-404 HTTP error or transport failure, the error propagates from the default-enabled enrichment, causing an otherwise successful `get_physical_sample` call—and an entire `get_project` aggregation—to fail.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| .. code-block:: python | ||
|
|
||
| sample = client.get_physical_sample(physical_sample_id) | ||
| print(sample.sample_metrics) # None if the sample has no computed metrics |
There was a problem hiding this comment.
Empty metrics representation is misstated
The example says sample_metrics is None when a sample has no computed metrics, but a successful empty response produces an empty DataFrame; callers following this guidance can incorrectly treat that frame as populated data.
| print(sample.sample_metrics) # None if the sample has no computed metrics | |
| print(sample.sample_metrics) # Empty DataFrame if fetched but no metrics exist |
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/guides/analysis-results.rst
Line: 132
Comment:
**Empty metrics representation is misstated**
The example says `sample_metrics` is `None` when a sample has no computed metrics, but a successful empty response produces an empty DataFrame; callers following this guidance can incorrectly treat that frame as populated data.
```suggestion
print(sample.sample_metrics) # Empty DataFrame if fetched but no metrics exist
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
The default-enabled sample_metrics fetch only swallowed 404s; any other HTTP error (500/502/…) or transport failure propagated, aborting an otherwise successful get_physical_sample call and cascading through get_project's per-sample loop. Wrap the enrichment fetch/parse in a try/except for ClientError and RequestException, warn, and leave sample_metrics=None. The primary get_physical_sample_timeseries method still raises as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What
Adds SDK access to sample-scoped computed timeseries results — the
physical_sample_timeseries_resultsrows the backend already produces per physical sample (rheed_quality,composition_metric). These are the headline "sample result" and were previously unreachable from the SDK.Why
get_physical_sample(psid, align=True)only re-joins each constituent data item's own curated RHEED series — it never touches the sample-scoped table. Reproducing sample-level analyses (e.g. the BTO combined quality × lateral-uniformity ranking) was blocked on this and required an out-of-band production RDS query. This closes that gap. Read-only; the backend endpoint already exists and is org-scoped like the routes the SDK already calls.Changes
Client.get_physical_sample_timeseries(psid, *, property_names=None)— hitsGET /physical_samples/{id}/timeseries/and returns a long-form DataFrame (property_name,real_time_seconds,value,result_id,last_updated,generating_dbos_workflow_id). Long (not wide) because distinct properties can carry different axes, so a wide join onreal_time_secondswould mis-align them.property_namesfilters client-side. Per-propertyconstituent_data_idslive indf.attrs.timeseries/physical_sample.py::physical_sample_timeseries_to_dataframe()(exported from thetimeseriespackage). Maps JSONnull→NaN, validates per-property length, and returns an empty frame for samples with no metrics.PhysicalSampleResult.sample_metricsis now populated byget_physical_sample()(newinclude_sample_metrics=Truekwarg to opt out). Fetched resiliently — a 404 leaves itNonerather than newly raising, soget_physical_sample's lenient behavior is preserved.analysis-results.rst._get: null→NaN, filtering, empty, 404→ClientError, length mismatch,sample_metricspopulate/skip) + 1 integration test that probes real BTO samples forrheed_qualityand skips gracefully.Error / edge behavior
ClientError(status_code=404).ValueError(chosen over a bareassert, which-Ostrips).Reviewer notes / deviations
unix_timesforward-compat: the parser passes aunix_timescolumn through if the payload includes it (the planned backend follow-up). Today's responses omit it, so the default output matches the current documented shape and needs no SDK change when that lands.unix_timesfor absolute-time alignment, and a server-sideproperty_namesfilter param. The SDK ships and works without them.physical_sample_id, matching the repo's existing skip-gracefully convention.Verification
test_rheed_timeseries,test_align) green; full suite (196 tests) collects cleanly.rheed_qualityover a growth window; trimmed-meanQspot-check) require running against prod withAS_API_KEY.🤖 Generated with Claude Code