{"name":"collections","intro":"Exact list and record logic — the operations a model starts quietly dropping items from somewhere past twenty entries. Compare two lists and get both differences named, group records with real aggregates computed as decimals, sort on several keys, find duplicates, paginate, chunk, and flatten or rebuild nested JSON — plus the table arithmetic a spreadsheet would do: filter rows, pivot, running totals, IQR outliers, a per-field summary and CSV out. Records may arrive as JSON objects or as CSV text: the delimiter is sniffed, the header row detected (the first row when it has no numeric, date or boolean cell; `has_header` overrides), every field typed as number, ISO date, boolean or text, and blank rows and `N/A`-style cells skipped and counted — each reading stated in `assumptions`. Numbers loaded from CSV are exact decimals returned as strings. Tables above 5,000 rows are refused. The list modes address fields with dotted paths (`rep.name`, `items[0].sku`); the table modes — `filter`, `pivot`, `running`, `outliers`, `summarize`, `to_csv` — work on flat records, top-level fields only.","when_to_use":["“What is in list A but not list B?” — and the reverse, in the same answer.","Grouping records by a field with sum/avg/min/max that must be exact.","Multi-key sorting, deduplication and duplicate hunting over records.","A CSV export pasted in: summarise every column, filter rows, pivot, running totals, spot outliers.","Reshaping JSON: flatten for a spreadsheet, unflatten from a form payload, records to CSV.","Pagination and chunking before handing work to another system."],"network":false,"related":"[`text`](/docs/tools/text) `sort`/`dedupe` for plain strings · [`numbers`](/docs/tools/numbers) for formatting the aggregates · [`validate`](/docs/tools/validate) `assert` to check the records themselves.","docs_url":"/docs/tools/collections","modes":[{"name":"set_ops","purpose":"Compare two lists: union, intersection, differences.","description":"Compares two lists and always returns the full picture — `only_in_a`, `only_in_b`, `in_both`, counts, and whether the two are equal as sets — regardless of which `op` you asked for. `op` additionally puts one specific result in `result`. Objects are compared structurally, or on one field via `key`, and always by type as well as value: Python treats `True == 1`, so `[1, true]` used to collapse to one element. Duplicates inside a list are collapsed, and that is stated in `assumptions`. Either side may be CSV text, read as records; how each was read is stated with an `a:`/`b:` prefix.","parameters":[{"name":"a","type":"array \\| string","required":true,"doc":"First list, or CSV text.","default":null},{"name":"b","type":"array \\| string","required":true,"doc":"Second list, or CSV text.","default":null},{"name":"op","type":"string","required":false,"doc":"Which result to highlight.","default":"`compare`"},{"name":"key","type":"string","required":false,"doc":"Dotted path to compare on, for objects.","default":null},{"name":"case_insensitive","type":"boolean","required":false,"doc":"Fold case on string comparisons.","default":"`false`"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when a side is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"set_ops","a":["A1","B2","C3","D4"],"b":["B2","D4","E5"]},{"mode":"set_ops","a":["x","y","y"],"b":["y","z"],"op":"union"},{"mode":"set_ops","a":[{"sku":"A1","qty":2},{"sku":"B2","qty":1}],"b":[{"sku":"B2","qty":9}],"op":"difference","key":"sku"},{"mode":"set_ops","a":["x"],"b":{"y":1}},{"mode":"set_ops","a":["x"],"b":["y"],"op":"xor"}]},{"name":"group_by","purpose":"Group records by a field, with aggregates.","description":"Groups records on a dotted path and counts each group. Add `agg_field` and `agg` to compute aggregates per group — `count`, `count_distinct`, `sum`, `avg`, `min`, `max`, `first`, `last`, `list` — as exact decimals returned as strings, so currency totals do not drift. Members are included unless `include_items` is false.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text.","default":null},{"name":"key","type":"string","required":true,"doc":"Dotted path to group on.","default":null},{"name":"agg_field","type":"string","required":false,"doc":"Dotted path the aggregates are computed over.","default":null},{"name":"agg","type":"string[] \\| string","required":false,"doc":"Which aggregates to compute.","default":"`[count]`"},{"name":"include_items","type":"boolean","required":false,"doc":"Include each group's members.","default":"`true`"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"group_by","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"key":"region","agg_field":"amount","agg":["sum","avg","count"],"include_items":false},{"mode":"group_by","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"key":"rep.name","include_items":true},{"mode":"group_by","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","key":"region","agg_field":"amount","agg":["sum","count"],"include_items":false},{"mode":"group_by","items":{"a":1},"key":"a"},{"mode":"group_by","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"key":"region","agg_field":"amount","agg":["median"]}]},{"name":"aggregate","purpose":"Aggregate one field across every record.","description":"The whole-list version of `group_by`'s aggregates: `count`, `count_distinct`, `sum`, `avg`, `median`, `min`, `max`, `first`, `last`, `list`. Numeric aggregates are computed as decimals and returned as strings; non-numeric values are ignored for them and that is stated in `assumptions`. Omit `field` to aggregate the items themselves. For every numeric field at once, see `summarize`.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records or values, or CSV text.","default":null},{"name":"field","type":"string","required":false,"doc":"Dotted path to aggregate; omit to use the items themselves.","default":null},{"name":"ops","type":"string[]","required":false,"doc":"Which aggregates to compute.","default":"`[count, sum, avg, min, max]`"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"aggregate","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"field":"amount"},{"mode":"aggregate","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"field":"region","ops":["count","count_distinct","list"]},{"mode":"aggregate","items":{"a":1}},{"mode":"aggregate","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"field":"amount","ops":["stdev"]}]},{"name":"pick_fields","purpose":"Project records down to the fields you need.","description":"Builds a narrower record from each input, pulling values with dotted paths. Missing paths become `null` rather than raising. `rename` maps a path to an output name, and `short_names` uses the last path segment as the key. Two fields that would land on the same output name are refused — one of them used to be silently dropped.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text.","default":null},{"name":"fields","type":"string[]","required":true,"doc":"Dotted paths to keep.","default":null},{"name":"rename","type":"object","required":false,"doc":"Map of path to output name.","default":null},{"name":"short_names","type":"boolean","required":false,"doc":"Use the last path segment as the key.","default":"`false`"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"pick_fields","items":[{"a":1,"b":2}],"fields":["a","b"],"rename":{"a":"z","b":"z"}},{"mode":"pick_fields","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"fields":["id","rep.name","amount"],"short_names":true},{"mode":"pick_fields","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"fields":["id","rep.email"],"rename":{"rep.email":"contact"}},{"mode":"pick_fields","items":{"id":1},"fields":["id"]}]},{"name":"flatten","purpose":"Flatten nested JSON — or nested lists.","description":"Given an object, produces a single-level map whose keys are dotted paths (`rep.name`, `tags[0]`) — the shape a CSV or a form encoder wants. Given a list, flattens nested lists instead. `depth` limits how far it descends; `separator` changes the joining character. CSV text is read as a list of records first.","parameters":[{"name":"data","type":"string \\| number \\| integer \\| boolean \\| array \\| object","required":true,"doc":"The structure to flatten, or CSV text.","default":null},{"name":"depth","type":"integer","required":false,"doc":"Maximum levels to descend.","default":"unlimited"},{"name":"separator","type":"string","required":false,"doc":"Key separator.","default":"`.`"},{"name":"flatten_lists","type":"boolean","required":false,"doc":"Index into lists as well as objects.","default":"`true`"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `data` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"flatten","data":{"order":{"id":"A-1","rep":{"name":"Asha"}},"tags":["rush","gift"]}},{"mode":"flatten","data":{"order":{"id":"A-1","rep":{"name":"Asha"}}},"depth":2},{"mode":"flatten","data":[1,[2,[3,4]],5]},{"mode":"flatten"}]},{"name":"unflatten","purpose":"Rebuild nested JSON from dotted keys.","description":"The inverse of `flatten`: turns a map of dotted keys back into nested objects and arrays. Bracketed indices (`items[0].sku`) create lists, and gaps are filled with nulls rather than shifting entries.","parameters":[{"name":"data","type":"string \\| number \\| integer \\| boolean \\| array \\| object","required":true,"doc":"A flat object with dotted keys.","default":null},{"name":"separator","type":"string","required":false,"doc":"Key separator.","default":"`.`"}],"examples":[{"mode":"unflatten","data":{"order.id":"A-1","order.rep.name":"Asha","order.total":1200.5}},{"mode":"unflatten","data":{"items[0].sku":"A1","items[1].sku":"B2","items[1].qty":3}},{"mode":"unflatten"}]},{"name":"paginate","purpose":"Slice a list into a page, with navigation flags.","description":"Returns one page of a list together with everything a caller needs to move around it: total items, total pages, `has_next`, `has_prev` and the 1-based `range` covered. A page beyond the end returns an empty slice, not an error — the flags say what happened.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The full list, or CSV text.","default":null},{"name":"page","type":"integer","required":false,"doc":"1-based page number.","default":"1"},{"name":"per_page","type":"integer","required":false,"doc":"Items per page.","default":"20"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"paginate","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"page":2,"per_page":3},{"mode":"paginate","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"page":9,"per_page":3},{"mode":"paginate","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"page":0},{"mode":"paginate","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"per_page":0},{"mode":"paginate","items":{"a":1}}]},{"name":"find_duplicates","purpose":"Find repeats and where they are.","description":"Reports every value that occurs more than once, with all of its indices and its count — so a duplicate can be located, not just detected. `key` looks at one field of each record; `case_insensitive` folds case on strings. A record that has no such key is left out of the comparison and counted in `skipped_missing_key`: rows that all lack a field are not duplicates of one another. Values keep their type, so `1`, `1.0`, `\"1\"` and `true` are four different values rather than one.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The list to inspect, or CSV text.","default":null},{"name":"key","type":"string","required":false,"doc":"Dotted path to compare, for objects.","default":null},{"name":"case_insensitive","type":"boolean","required":false,"doc":"Fold case on string comparisons.","default":"`false`"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"find_duplicates","items":["a@x.com","b@x.com","A@X.com","c@x.com","b@x.com"],"case_insensitive":true},{"mode":"find_duplicates","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"key":"rep.name"},{"mode":"find_duplicates","items":{"a":1}}]},{"name":"sort_by","purpose":"Stable multi-key sort over records.","description":"Sorts records by several keys at once, each with its own direction: `keys: [{field: region}, {field: amount, order: desc}]`. The sort is stable, nulls sort last, JSON numbers compare as numbers, and strings — numeric-looking ones included — compare case-insensitively as text. Fields loaded from CSV text are typed first, so a numeric column sorts numerically. `changed` says whether the order actually moved.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text.","default":null},{"name":"keys","type":"array","required":false,"doc":"Sort keys: `{field, order}` or bare field names.","default":null},{"name":"key","type":"string","required":false,"doc":"A single sort field, as a shorthand for `keys`.","default":null},{"name":"order","type":"string","required":false,"doc":"Direction for the `key` shorthand.","default":"`asc`"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"sort_by","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"keys":[{"field":"region"},{"field":"amount","order":"desc"}]},{"mode":"sort_by","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"key":"amount","order":"desc"},{"mode":"sort_by","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}]},{"mode":"sort_by","items":{"a":1},"key":"id"}]},{"name":"chunk","purpose":"Split a list into batches.","description":"Splits a list either into chunks of a fixed `size` — the last one may be shorter — or into exactly `n` chunks whose sizes differ by at most one. Returns the chunks and their sizes, which is what a batching loop actually needs.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The list to split, or CSV text.","default":null},{"name":"size","type":"integer","required":false,"doc":"Maximum items per chunk.","default":null},{"name":"n","type":"integer","required":false,"doc":"Number of chunks; sizes differ by at most 1.","default":null},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"chunk","items":[1,2,3,4,5,6,7],"size":3},{"mode":"chunk","items":[1,2,3,4,5,6,7],"n":3},{"mode":"chunk","items":[1,2,3]},{"mode":"chunk","items":{"a":1},"size":2}]},{"name":"filter","purpose":"Keep the rows that satisfy every predicate.","description":"Keeps the records for which every `where` predicate holds (AND). Each predicate is `{field, op, value}` with the `validate.assert` vocabulary — `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `not_in`, `contains`, `starts_with`, `ends_with`, `empty`, `not_empty` — and the comparison is made in the field's inferred type: `500` against a numeric field is a numeric comparison, `2026-01-07` against a date field a date one. Text comparisons are case-sensitive. Returns the kept `items`, their `count` and how many were `removed`; more than 500 rows are echoed in part, with a warning.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text.","default":null},{"name":"where","type":"object[]","required":true,"doc":"Predicates `{field, op, value}`, all of which must hold.","default":null},{"name":"columns","type":"string[]","required":false,"doc":"Fields to echo.","default":"all"},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"filter","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","where":[{"field":"amount","op":"gte","value":500}]},{"mode":"filter","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"where":[{"field":"region","op":"in","value":["north","south"]},{"field":"amount","op":"lt","value":"1000"}],"columns":["id","amount"]},{"mode":"filter","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","where":[{"field":"rep","op":"like","value":"A%"}]}]},{"name":"pivot","purpose":"Cross-tabulate: row keys × one field's values, one aggregate each.","description":"A pivot table: `by` names the row key(s), `pivot_columns` the field whose distinct values become the columns, and each cell holds one aggregate (`agg`: `sum` by default, or `avg`, `min`, `max`, `median`, `count`) of `column`. Without `column` the cells count rows. A combination with no rows is `null`, not zero. Every row carries a `total`, and `totals` holds the column totals and the grand total — the same aggregate over every underlying value, so an `avg` total is the true mean, not a mean of means. `pivot_columns` with more than 200 distinct values is refused with `too_large`: the table would have that many columns.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text.","default":null},{"name":"by","type":"string[] \\| string","required":true,"doc":"Field(s) forming the row keys.","default":null},{"name":"pivot_columns","type":"string","required":true,"doc":"Field whose values become the columns.","default":null},{"name":"column","type":"string","required":false,"doc":"Numeric field aggregated into each cell; omit to count rows.","default":null},{"name":"agg","type":"string[] \\| string","required":false,"doc":"The single aggregate to use.","default":"`sum`, or `count` without `column`"},{"name":"decimals","type":"integer","required":false,"doc":"Round computed values to this many places, half-up.","default":null},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"pivot","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","by":"region","pivot_columns":"rep","column":"amount"},{"mode":"pivot","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","by":"rep","pivot_columns":"region","agg":"count"},{"mode":"pivot","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","by":"region","column":"amount"}]},{"name":"running","purpose":"Cumulative total down the rows.","description":"Adds a `running` field to every record: the cumulative total of `column` in the order given. With `by`, the total restarts for each group and `totals` reports where each group ended. A blank cell adds nothing. When the table has exactly one numeric field it is used and that is stated; with several the call is `ambiguous` and lists them.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text.","default":null},{"name":"column","type":"string","required":false,"doc":"Numeric field to accumulate.","default":"the only numeric field"},{"name":"by","type":"string[] \\| string","required":false,"doc":"Field(s) whose change restarts the total.","default":null},{"name":"columns","type":"string[]","required":false,"doc":"Fields to echo alongside `running`.","default":"all"},{"name":"decimals","type":"integer","required":false,"doc":"Round the running values to this many places, half-up.","default":null},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"running","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","column":"amount","columns":["date","amount"]},{"mode":"running","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","column":"amount","by":"region","columns":["region","amount"]},{"mode":"running","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","column":"rep"}]},{"name":"outliers","purpose":"Flag values outside the 1.5×IQR fences.","description":"Tukey's rule: Q1 and Q3 are his hinges — the medians of the lower and upper halves of the sorted values, the middle value included in both when the count is odd — and anything below `Q1 − 1.5×IQR` or above `Q3 + 1.5×IQR` is an outlier. Reports `q1`, `q3`, `iqr`, both fences and each flagged row with its 1-based `row`, `value` and `side`. Needs at least four numeric values. `column` defaults to the only numeric field, if there is one.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text.","default":null},{"name":"column","type":"string","required":false,"doc":"Numeric field to inspect.","default":"the only numeric field"},{"name":"decimals","type":"integer","required":false,"doc":"Round the quartiles and fences to this many places, half-up.","default":null},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"outliers","items":"day,sales\nd1,12\nd2,15\nd3,11\nd4,14\nd5,13\nd6,16\nd7,12\nd8,15\nd9,14\nd10,95\n","column":"sales"},{"mode":"outliers","items":"v\n1.5\n2.25\n2.5\n3.75\n4\n40\n","decimals":2},{"mode":"outliers","items":"v\n1\n2\n3\n","column":"v"}]},{"name":"summarize","purpose":"Every field at once: counts, totals, ranges.","description":"One entry per field with its inferred `type`, the `count` of filled cells and the `nulls`. Numeric fields add `sum`, `avg`, `min`, `max` and `median` as exact decimals; date fields their `min` and `max`; boolean fields `true`/`false` counts; text fields the number of `distinct` values. This is `aggregate` for the whole table in one call.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text.","default":null},{"name":"columns","type":"string[]","required":false,"doc":"Fields to summarise.","default":"all"},{"name":"decimals","type":"integer","required":false,"doc":"Round computed values to this many places, half-up.","default":null},{"name":"delimiter","type":"string","required":false,"doc":"CSV delimiter, when `items` is CSV text.","default":"sniffed"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"}],"examples":[{"mode":"summarize","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n"},{"mode":"summarize","items":"name,score\nAnn,10\n,\nBob,N/A\n\nCid,30\n"},{"mode":"summarize","items":"region,rep,amount,date\nnorth,Asha,\"1,200.50\",2026-01-05\nsouth,Bo,890.00,2026-01-06\nnorth,Asha,430.25,2026-01-07\neast,Chen,2100.00,2026-01-08\nsouth,Bo,75.75,2026-01-09\n","columns":["amount","total"]}]},{"name":"to_csv","purpose":"Records out as CSV text.","description":"Writes the records as CSV text with a header row: numbers in plain decimal form (no thousands separators or symbols), booleans as `true`/`false`, blanks as empty cells, and quoting only where the delimiter or a quote appears in a value. `columns` chooses and orders the fields. Every row is written — this mode is exempt from the 500-row echo cap.\n\nA cell beginning `=`, `+`, `-`, `@`, tab or carriage return is what a spreadsheet reads as a **formula**, so `=cmd|' /C calc'!A0` in a record becomes code when someone opens the file. Those cells are prefixed with an apostrophe — which Excel, LibreOffice and Sheets all read as “text follows” — and the count is reported in `assumptions` and in `escaped_cells`. A negative number is left alone: `-12.5` is data, and escaping it would corrupt a column of deltas. Pass `escape_formulas: false` to write cells verbatim, which is warned about.","parameters":[{"name":"items","type":"array \\| string","required":true,"doc":"The records, or CSV text to re-shape.","default":null},{"name":"columns","type":"string[]","required":false,"doc":"Fields to write, in order.","default":"all"},{"name":"delimiter","type":"string","required":false,"doc":"Delimiter to write with — and to read `items` with, when it is CSV text.","default":"`,`"},{"name":"has_header","type":"boolean","required":false,"doc":"Whether CSV text starts with a header row.","default":"detected"},{"name":"escape_formulas","type":"boolean","required":false,"doc":"Neutralise cells a spreadsheet would run as a formula.","default":"`true`"}],"examples":[{"mode":"to_csv","items":[{"name":"Asha","note":"=cmd|' /C calc'!A0"},{"name":"Ravi","note":"fine"}]},{"mode":"to_csv","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"columns":["id","region","amount"]},{"mode":"to_csv","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"columns":["id","amount"],"delimiter":";"},{"mode":"to_csv","items":[{"id":"A-1","region":"north","amount":"1200.50","rep":{"name":"Asha"}},{"id":"A-2","region":"south","amount":"890.00","rep":{"name":"Bo"}},{"id":"A-3","region":"north","amount":"430.25","rep":{"name":"Asha"}},{"id":"A-4","region":"east","amount":"2100.00","rep":{"name":"Chen"}},{"id":"A-5","region":"south","amount":"75.75","rep":{"name":"Bo"}}],"columns":["id","total"]}]}]}