Skip to content

Commit a8c5ae1

Browse files
fix: Use immutable copy pattern for WellItem result attachment (#1166)
## Summary Resolves P0 TODO #2 from codebase audit by using `dataclasses.replace()` for immutable result attachment instead of mutating private `_result` field after construction. ## Problem Previously, `WellItem._result` was mutated inside `_create_measurement()` with a TODO comment: ```python # TODO: temp workaround for cal doc result well_item._result = result ``` This violated the intended immutability of the private field. The TODO was added during PR #546 refactor when Result changed from a public mutable field to a private field with property accessor. **Root cause**: WellItem and Result data come from different file sections parsed at different times, but calculated documents need access to result data through well items. ## Solution Use `dataclasses.replace()` to create immutable copies with results attached at the correct point in the data flow: 1. Parse wells (from "Sample Setup" section) 2. Parse results (from "Results" section) 3. **Create enriched copies** via `enrich_wells_with_results()` using `dataclasses.replace()` 4. Use enriched wells for calculated documents and measurements ## Changes - Added `enrich_wells_with_results()` that returns new Well/WellItem instances with results attached - Removed TODO and mutation from `_create_measurement()` - Parser now uses `enriched_wells` for both calculated documents and measurements - Clear architectural separation: "wells without results" → "wells with results" ## Testing - All 27 appbio_quantstudio tests pass - All 506 tests pass - No golden file changes - Linting clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.1 <noreply@anthropic.com>
1 parent e24a200 commit a8c5ae1

2 files changed

Lines changed: 30 additions & 4 deletions

File tree

src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_data_creator.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,6 @@ def _create_measurement(
241241
) -> Measurement | None:
242242
if not result:
243243
return None
244-
# TODO: temp workaround for cal doc result
245-
well_item._result = result
246244

247245
(
248246
reporter_dye_data_cube,
@@ -332,6 +330,30 @@ def get_well_item_results(
332330
)
333331

334332

333+
def enrich_wells_with_results(
334+
wells: list[Well],
335+
results_data: dict[int, dict[str, Result]],
336+
) -> list[Well]:
337+
"""Return new Wells with results attached to well items via immutable copy.
338+
339+
This creates new WellItem instances with _result properly set at construction time
340+
(via dataclasses.replace), avoiding mutation of the private field after creation.
341+
"""
342+
from dataclasses import replace
343+
344+
enriched_wells = []
345+
for well in wells:
346+
enriched_items = []
347+
for well_item in well.items:
348+
result = get_well_item_results(well_item, results_data)
349+
if result:
350+
enriched_items.append(replace(well_item, _result=result))
351+
else:
352+
enriched_items.append(well_item)
353+
enriched_wells.append(replace(well, items=enriched_items))
354+
return enriched_wells
355+
356+
335357
def get_well_item_amp_data(
336358
well_item: WellItem, amp_data: dict[int, dict[str, AmplificationData]]
337359
) -> AmplificationData | None:

src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_parser.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
create_calculated_data,
99
create_measurement_groups,
1010
create_metadata,
11+
enrich_wells_with_results,
1112
)
1213
from allotropy.parsers.appbio_quantstudio.appbio_quantstudio_reader import (
1314
AppBioQuantStudioReader,
@@ -41,8 +42,11 @@ def parse_data(
4142
results_data, result_metadata = Result.create(reader, header.experiment_type)
4243
melt_data = MeltCurveRawData.create(reader)
4344

45+
# Create immutable copies of wells with results attached
46+
enriched_wells = enrich_wells_with_results(wells, results_data)
47+
4448
calculated_data_documents = iter_calculated_data_documents(
45-
[well_item for well in wells for well_item in well.items],
49+
[well_item for well in enriched_wells for well_item in well.items],
4650
header.experiment_type,
4751
result_metadata.reference_sample_description,
4852
result_metadata.reference_dna_description,
@@ -52,7 +56,7 @@ def parse_data(
5256
metadata=create_metadata(header, original_file_path),
5357
measurement_groups=create_measurement_groups(
5458
header,
55-
wells,
59+
enriched_wells,
5660
amp_data,
5761
multi_data,
5862
results_data,

0 commit comments

Comments
 (0)