# v1.9.9 - Sampler Reader and Writer Module Extraction (2025-12-29)

## What Changed?

This release completes Phase 2 decomposition by extracting reader and writer functionality from the Sampler class into dedicated modules.
The `_reader.py` module handles all event reading operations, while `_writer.py` manages CSV writing and high-level acquisition methods.
Combined with previously extracted helpers and iterators, the Sampler is now fully modularized following the Single Responsibility Principle.

---

## What's New

### Event Reader Module (`_reader.py`)

**What it does**:
Extracts all event reading functionality into a dedicated EventReader class. Handles low-level device I/O and event parsing with three core methods for different use cases.

**Methods extracted**:

1. **`read_event() -> RawEvent | None`**: Read one measurement from device with timestamp
   - Blocks until detector sends data
   - Records exact timestamp (to microsecond precision)
   - Returns RawEvent or None if line is invalid
   - Handles corrupted data gracefully

2. **`stream_events(iterator) -> Iterator[RawEvent]`**: Generator yielding events one-at-a-time
   - Memory-efficient for large datasets
   - Automatically skips invalid lines with warnings
   - Yields only valid RawEvent objects
   - Pauses until caller asks for next event

3. **`collect_events(iterator) -> list[RawEvent]`**: Collect all events into a list
   - Reads all measurements first, returns complete list
   - Uses stream_events internally
   - Simple interface for all-at-once processing
   - Loads everything into memory at once

### Event Writer Module (`_writer.py`)

**What it does**:
Extracts all event writing functionality into a dedicated EventWriter class. Handles CSV file creation with both streaming and buffered modes, plus high-level acquisition methods.

**Methods extracted**:

1. **`save_events(file_path, source)`**: Write measurements to CSV file
   - Accepts Iterator or list[RawEvent] as source
   - Supports streaming mode (write as you go)
   - Supports buffered mode (collect first, then write)
   - Proper CSV formatting with timestamps and sensor values

2. **`acquire_by_count(file_path, event_count)`**: Collect and save fixed number of events
   - Reads exactly N measurements from detector
   - Shows progress bar (if enabled)
   - High-level API for fixed-count acquisition
   - Recommended for most use cases

3. **`acquire_by_time(file_path, duration, sleep_interval)`**: Collect and save for fixed duration
   - Collects measurements for exactly N seconds
   - Configurable polling interval
   - Shows progress bar (if enabled)
   - Used extensively in threshold scanning

**Code examples**:

```python
from haniwers.v1.daq.sampler._reader import EventReader
from haniwers.v1.daq.sampler._writer import EventWriter
from haniwers.v1.daq.sampler._iterators import count_based_iterator
from pathlib import Path

# Create reader and writer
reader = EventReader(device, logger)
writer = EventWriter(reader, Path("./data"), stream_mode=True)

# High-level API (recommended)
writer.acquire_by_count(Path("./data/run.csv"), 1000)
writer.acquire_by_time(Path("./data/scan.csv"), duration=10.0, sleep_interval=0.1)

# Low-level API
event = reader.read_event()
iterator = count_based_iterator(100)
for event in reader.stream_events(iterator):
    process(event)
```

---

## Installation

### Quick Start

```bash
# Get the release
git checkout v1.9.9

# Setup
task env:setup

# Run CLI
haniwers-v1 --help
```

---

## What's Different from the Last Version?

### ✅ Added

- New `src/haniwers/v1/daq/sampler/_reader.py` module with EventReader class (read_event, stream_events, collect_events)
- New `src/haniwers/v1/daq/sampler/_writer.py` module with EventWriter class (save_events, acquire_by_count, acquire_by_time)
- Comprehensive docstrings with usage examples for both new modules
- Clean separation of reading and writing concerns following Single Responsibility Principle

### 🔧 Changed

- Reorganized `src/haniwers/v1/daq/sampler/_base.py` to coordinate with new reader/writer modules
- No changes to public API or CLI functionality

### 🐛 Fixed

- No bug fixes in this release

---

## Is It Safe to Upgrade?

**Backward Compatible**: ✅ Yes

All existing imports and APIs continue to work without modification. The extraction of reader and writer functionality into dedicated modules is a pure refactoring with zero breaking changes. Sampler API remains identical.

---

## Tests Passed

- ✅ Builds without errors
- ✅ All 133 unit tests passing (DAQ module)
- ✅ Sampler functionality tests passing (10 tests covering count/time/run modes)
- ✅ MAC address tests passing (3 essential tests)
- ✅ Reader module functionality verified
- ✅ Writer module functionality verified
- ✅ Pre-commit hooks passing (ruff format, YAML, etc.)

---

## Release Details

- **Date**: 2025-12-29
- **Version**: v1.9.9
- **Files Changed**: 2 (created `_reader.py`, `_writer.py`)
- **Commits**:
  - 03779ce: refactor(daq/sampler): extract event reading methods to _reader.py
  - 9345343: refactor(daq/sampler): extract event writing methods to _writer.py
  - bd9440b: bump: version 1.9.8 → 1.9.9

---

## Next Steps

### Phase 2 Complete! Sampler Fully Modularized

```text
src/haniwers/v1/daq/sampler/
├── __init__.py           # Re-exports all classes
├── _base.py              # Sampler orchestration layer (1,591 lines → pending simplification)
├── _reader.py            # EventReader (read_event, stream_events, collect_events) ✓
├── _writer.py            # EventWriter (save_events, acquire_by_count, acquire_by_time) ✓
├── _iterators.py         # Iterators (count_based_iterator, time_based_iterator) ✓
└── _helpers.py           # Static helpers (sanitize, tqdm_wrapper, mock_sample) ✓
```

**Phase 2 Summary**:
- v1.9.7: Package structure created
- v1.9.8: Helpers and iterators extracted
- v1.9.9: Reader and writer extracted ✓

### Phase 3 (v1.10.0 planned): Simplify Orchestration Layer

The final step is simplifying _base.py from 1,591 lines to ~200-300 lines as a pure orchestration layer:

- Remove all reading logic (now in _reader.py)
- Remove all writing logic (now in _writer.py)
- Keep only: initialization, configuration management, public API coordination
- Delegate to specialized modules via composition

### Phase 4 (v1.10.0+ planned): Technical Debt Resolution

- Complete removal of deprecated DaqConfig and ScanConfig models
- Removal of TODO comments with implementations
- Integration test stabilization
- Performance optimization if needed

### v1 → v2 Transition

This refactoring prepares v1 for stable maintenance mode while providing:

- Clean, modular codebase for v2 migration
- Reusable reader/writer/helper modules for v2
- Clear architectural patterns to follow
- Foundation for subsystem integration in v2

---

## Architecture Achievements

This modularization follows the **Single Responsibility Principle (SRP)**:

| Module | Lines | Responsibility | Status |
|--------|-------|-----------------|--------|
| _helpers.py | 217 | Type conversion, progress display | ✓ |
| _iterators.py | 162 | Count/time-based iteration | ✓ |
| _reader.py | 269 | Event reading from device | ✓ |
| _writer.py | 370 | Event writing to CSV | ✓ |
| _base.py | 1,591 | Orchestration layer | Phase 3 pending |

**Target**: Reduce _base.py to ~250 lines by delegating to specialized modules.

See [project constitution](/.specify/memory/constitution.md) for more details on architectural principles.
