1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
81
82
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use crate;
/// `MIN(value1, ...)` — smallest numeric value in the arguments.
/// Direct args: Numbers, Dates, Bool (TRUE=1, FALSE=0), parseable text coerced
/// to number.
/// Array elements: Numbers and Dates; text/Bool → skip; errors propagate.
///
/// "No numbers" is *two* rules, not one:
///
/// - an **empty** array argument is `#REF!`: `=MIN({})`. Captured in Google
/// Sheets; the row lands separately, since it fails until this code exists.
/// - a **populated** array holding nothing numeric is 0. The in-repo evidence
/// is indirect but sufficient: statistical.tsv pins
/// `=IFERROR(MIN({"a","b","c"}),"no numbers")` to the *number* 0, so MIN
/// cannot have errored. A direct `=MIN({"a","b"})` row is captured and
/// lands with the others.
///
/// MIN needs no code for the second rule — it already falls through to 0.
/// An array holding only *blanks* — what `=MIN(A1:A3)` over an untouched
/// column materializes as — is 0 too, and is now captured rather than assumed:
/// seven range shapes, each with a populated control, are laid out on
/// [`stat_helpers::is_blank_only_array`] along with the note that those rows
/// are not in this repo yet. MIN was the one of the four already giving that
/// answer, so it needs no code for this rule either — the predicate is not
/// called from here at all; MAX, MAXA and MINA were brought to it.
///
/// [`stat_helpers::is_blank_only_array`]: super::stat_helpers::is_blank_only_array
///
/// **Dates participate as bare serials** and carry their type out. A date-only
/// range answers the earliest date, a date beside a plain number is compared on
/// the serial with no special casing (so a small plain number beats every
/// date), and the result is date-typed whenever a date took part — even when
/// the plain number won.
///
/// Captured and extrapolated are not the same thing here, so keep them apart:
///
/// - **Values** are captured in Google Sheets for every form — a date-only
/// column, a date/number column, array literals of both shapes, and dates
/// passed as direct arguments.
/// - **Typing** is captured for the *range* forms only, read back through the
/// cell that holds the result (`=MIN(<date-only range>)` and
/// `=MIN(<date/number range>)` both come back `date` — the second even
/// though a plain 5 won). The literal and direct-argument rows report
/// `number`, but that is an artifact of the capture harness reading them
/// through an `INDEX(...,1,1)` wrapper, which drops the cell's date format —
/// it is not a Sheets answer. So the date typing of
/// `=MIN({DATE(...),DATE(...)})` is **extrapolated** from the range forms,
/// not probed.
///
/// **None of those rows are in this repo yet.** They come off the
/// conformance-fixtures pipeline and land in a separate fixtures-only PR —
/// they fail until this code exists, and CI rejects a PR that mixes fixture
/// TSVs with code. Same arrangement as the blank-only rows described on
/// `stat_helpers::is_blank_only_array`. A reviewer working from this repo
/// alone can check the unit tests and this comment; the Sheets answers
/// themselves have to be taken from that pipeline.
///
/// Two consequences were filed separately and are now closed:
///
/// - `COUNT` did not count dates, so `=COUNT(MIN(<date range>))` answered 0
/// where it used to answer 1. Fixed in `count` against captured Sheets rows
/// (#780).
/// - `MAXA`/`MINA` silently dropped a `Zoned` sitting beside a `Date`, where
/// `MAX`/`MIN` route the same input through `zoned_extreme` and error. The
/// A-variants now consult the same helper, so all four agree; the rule is a
/// deliberate truecalc-only decision recorded on `maxa_fn` (#781).
/// Recursively fold a nested array's numbers into `result` for MIN's
/// array-context rules (Bool/Text/Empty skipped, errors propagate).
/// A `Date` folds in as its bare serial and raises `saw_date`, which types the
/// answer; every other variant keeps the arm it already had.