Skip to content

Assays

Generate snail assays.

Assay

Bases: BaseModel

A single assay.

Parameters:

Name Type Description Default
ident str

unique identifier

required
specimen str

which specimen

required
person str

who did the assay

required
machine str

machine ID

required
performed date

date assay was performed

required
readings Grid[float]

assay readings

required
treatments Grid[str]

samples or controls

required
Source code in src/snailz/assays.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Assay(BaseModel):
    """A single assay."""

    ident: str = Field(description="unique identifier")
    specimen: str = Field(description="which specimen")
    person: str = Field(description="who did the assay")
    machine: str = Field(description="machine ID")
    performed: date = Field(description="date assay was performed")
    readings: Grid[float] = Field(description="assay readings")
    treatments: Grid[str] = Field(description="samples or controls")

    model_config = {"extra": "forbid"}

    @model_validator(mode="after")
    def show_fields(self):
        return self

    def to_csv(self, kind: str) -> str:
        """Return a CSV string representation of the assay data.

        Parameters:
            kind: Either "readings" or "treatments"

        Returns:
            A CSV-formatted string with the assay data.

        Raises:
            ValueError: If 'kind' is not "readings" or "treatments"
        """
        if kind not in ["readings", "treatments"]:
            raise ValueError("data_type must be 'readings' or 'treatments'")

        # Get the appropriate data based on data_type
        data = self.readings if kind == "readings" else self.treatments
        assert isinstance(data, Grid)

        # Generate column headers (A, B, C, etc.) and calculate metadata padding
        column_headers = [""] + [chr(ord("A") + i) for i in range(data.width)]
        max_columns = len(column_headers)
        padding = [""] * (max_columns - 2)

        # Write data
        output = io.StringIO()
        writer = csv.writer(output, lineterminator="\n")
        pre = [
            ["id", self.ident] + padding,
            ["specimen", self.specimen] + padding,
            ["date", self.performed.isoformat()] + padding,
            ["by", self.person] + padding,
            ["machine", self.machine] + padding,
            column_headers,
        ]
        for row in pre:
            writer.writerow(row)

        for i, y in enumerate(range(data.height - 1, -1, -1)):
            row = [i + 1] + [data[x, y] for x in range(data.width)]
            writer.writerow(row)

        return output.getvalue()

to_csv(kind)

Return a CSV string representation of the assay data.

Parameters:

Name Type Description Default
kind str

Either "readings" or "treatments"

required

Returns:

Type Description
str

A CSV-formatted string with the assay data.

Raises:

Type Description
ValueError

If 'kind' is not "readings" or "treatments"

Source code in src/snailz/assays.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def to_csv(self, kind: str) -> str:
    """Return a CSV string representation of the assay data.

    Parameters:
        kind: Either "readings" or "treatments"

    Returns:
        A CSV-formatted string with the assay data.

    Raises:
        ValueError: If 'kind' is not "readings" or "treatments"
    """
    if kind not in ["readings", "treatments"]:
        raise ValueError("data_type must be 'readings' or 'treatments'")

    # Get the appropriate data based on data_type
    data = self.readings if kind == "readings" else self.treatments
    assert isinstance(data, Grid)

    # Generate column headers (A, B, C, etc.) and calculate metadata padding
    column_headers = [""] + [chr(ord("A") + i) for i in range(data.width)]
    max_columns = len(column_headers)
    padding = [""] * (max_columns - 2)

    # Write data
    output = io.StringIO()
    writer = csv.writer(output, lineterminator="\n")
    pre = [
        ["id", self.ident] + padding,
        ["specimen", self.specimen] + padding,
        ["date", self.performed.isoformat()] + padding,
        ["by", self.person] + padding,
        ["machine", self.machine] + padding,
        column_headers,
    ]
    for row in pre:
        writer.writerow(row)

    for i, y in enumerate(range(data.height - 1, -1, -1)):
        row = [i + 1] + [data[x, y] for x in range(data.width)]
        writer.writerow(row)

    return output.getvalue()

AllAssays

Bases: BaseModel

All generated assays.

Parameters:

Name Type Description Default
items list[Assay]

actual assays

required
Source code in src/snailz/assays.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
class AllAssays(BaseModel):
    """All generated assays."""

    items: list[Assay] = Field(description="actual assays")

    def to_csv(self) -> str:
        """Return a CSV string representation of the assay summary data.

        Returns:
            A CSV-formatted string containing a summary of all assays
        """
        return utils.to_csv(
            self.items,
            ["ident", "specimen", "person", "performed", "machine"],
            lambda r: [
                r.ident,
                r.specimen,
                r.person,
                r.performed.isoformat(),
                r.machine,
            ],
        )

    @staticmethod
    def generate(
        params: AssayParams,
        persons: AllPersons,
        machines: AllMachines,
        specimens: AllSpecimens,
    ) -> "AllAssays":
        """Generate an assay for each specimen.

        Parameters:
            params: assay generation parameters
            persons: all staff members
            machines: all laboratory equipment
            specimens: specimens to generate assays for

        Returns:
            Assay list object
        """
        # Duplicate a few specimens and randomize order.
        subjects = model.assay_specimens(params, specimens)

        gen = utils.unique_id("assays", lambda: f"{random.randint(0, 999999):06d}")
        items = []
        for spec in subjects:
            performed = spec.collected + model.assay_performed(params)
            person = random.choice(persons.items)
            machine = random.choice(machines.items)
            treatments = _make_treatments(params)
            readings = _make_readings(params, spec, performed, machine, treatments)
            ident = next(gen)
            assert isinstance(ident, str)  # to satisfy type checking
            items.append(
                Assay(
                    ident=ident,
                    performed=performed,
                    specimen=spec.ident,
                    person=person.ident,
                    machine=machine.ident,
                    treatments=treatments,
                    readings=readings,
                )
            )

        return AllAssays(items=items)

to_csv()

Return a CSV string representation of the assay summary data.

Returns:

Type Description
str

A CSV-formatted string containing a summary of all assays

Source code in src/snailz/assays.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def to_csv(self) -> str:
    """Return a CSV string representation of the assay summary data.

    Returns:
        A CSV-formatted string containing a summary of all assays
    """
    return utils.to_csv(
        self.items,
        ["ident", "specimen", "person", "performed", "machine"],
        lambda r: [
            r.ident,
            r.specimen,
            r.person,
            r.performed.isoformat(),
            r.machine,
        ],
    )

generate(params, persons, machines, specimens) staticmethod

Generate an assay for each specimen.

Parameters:

Name Type Description Default
params AssayParams

assay generation parameters

required
persons AllPersons

all staff members

required
machines AllMachines

all laboratory equipment

required
specimens AllSpecimens

specimens to generate assays for

required

Returns:

Type Description
AllAssays

Assay list object

Source code in src/snailz/assays.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
@staticmethod
def generate(
    params: AssayParams,
    persons: AllPersons,
    machines: AllMachines,
    specimens: AllSpecimens,
) -> "AllAssays":
    """Generate an assay for each specimen.

    Parameters:
        params: assay generation parameters
        persons: all staff members
        machines: all laboratory equipment
        specimens: specimens to generate assays for

    Returns:
        Assay list object
    """
    # Duplicate a few specimens and randomize order.
    subjects = model.assay_specimens(params, specimens)

    gen = utils.unique_id("assays", lambda: f"{random.randint(0, 999999):06d}")
    items = []
    for spec in subjects:
        performed = spec.collected + model.assay_performed(params)
        person = random.choice(persons.items)
        machine = random.choice(machines.items)
        treatments = _make_treatments(params)
        readings = _make_readings(params, spec, performed, machine, treatments)
        ident = next(gen)
        assert isinstance(ident, str)  # to satisfy type checking
        items.append(
            Assay(
                ident=ident,
                performed=performed,
                specimen=spec.ident,
                person=person.ident,
                machine=machine.ident,
                treatments=treatments,
                readings=readings,
            )
        )

    return AllAssays(items=items)

_make_readings(params, specimen, performed, machine, treatments)

Make a single assay.

Source code in src/snailz/assays.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def _make_readings(
    params: AssayParams,
    specimen: Specimen,
    performed: date,
    machine: Machine,
    treatments: Grid[str],
) -> Grid[float]:
    """Make a single assay."""
    readings = Grid(width=params.plate_size, height=params.plate_size, default=0.0)
    for x in range(params.plate_size):
        for y in range(params.plate_size):
            readings[x, y] = round(
                model.assay_reading(params, specimen, treatments[x, y], performed),
                utils.PRECISION,
            )
    return readings

_make_treatments(params)

Generate random treatments.

Source code in src/snailz/assays.py
170
171
172
173
174
175
176
177
178
179
180
181
def _make_treatments(params: AssayParams) -> Grid[str]:
    """Generate random treatments."""
    size = params.plate_size
    size_sq = size**2
    half = size_sq // 2
    available = list(("S" * half) + ("C" * (size_sq - half)))
    random.shuffle(available)
    treatments = Grid(width=size, height=size, default="")
    for x in range(size):
        for y in range(size):
            treatments[x, y] = available.pop()
    return treatments