@anthusai/papyrus 2.0.0-next.88 → 2.0.0-next.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -99,6 +99,11 @@ MAXIMUM_BODY = 50_000
|
|
|
99
99
|
MAXIMUM_REASON = 500
|
|
100
100
|
MAXIMUM_EVIDENCE_SOURCE = 200
|
|
101
101
|
MAXIMUM_EVIDENCE_SIGNALS = 1_000_000
|
|
102
|
+
#: An optional figure spec kept in ``evidence.figure``: a small chart of cited series (no image), approved with the copy.
|
|
103
|
+
FIGURE_TYPES = ("line", "bar")
|
|
104
|
+
MAXIMUM_FIGURE_POINTS = 60
|
|
105
|
+
MAXIMUM_FIGURE_SERIES = 6
|
|
106
|
+
MAXIMUM_FIGURE_BYTES = 8_000
|
|
102
107
|
|
|
103
108
|
|
|
104
109
|
@dataclass
|
|
@@ -190,18 +195,53 @@ def clean_targets(kind: str, raw: Any) -> list[dict[str, str]] | Outcome:
|
|
|
190
195
|
return targets
|
|
191
196
|
|
|
192
197
|
|
|
198
|
+
def clean_figure(raw: Any) -> dict[str, Any] | None:
|
|
199
|
+
"""A chart spec ``{type, title, caption, unit, source, x, series: [{signal, label, values}]}``, or None when it is malformed.
|
|
200
|
+
|
|
201
|
+
Unknown keys are dropped. Every series has exactly one value per ``x`` label; values are whole or decimal numbers, never text."""
|
|
202
|
+
if not isinstance(raw, dict) or raw.get("type") not in FIGURE_TYPES:
|
|
203
|
+
return None
|
|
204
|
+
title, caption, unit, source = (clean_text(raw.get(key), limit) for key, limit in (("title", 200), ("caption", 300), ("unit", 60), ("source", MAXIMUM_EVIDENCE_SOURCE)))
|
|
205
|
+
x, series = raw.get("x"), raw.get("series")
|
|
206
|
+
if None in (title, caption, unit, source) or not isinstance(x, list) or not isinstance(series, list):
|
|
207
|
+
return None
|
|
208
|
+
if not 2 <= len(x) <= MAXIMUM_FIGURE_POINTS or not 1 <= len(series) <= MAXIMUM_FIGURE_SERIES:
|
|
209
|
+
return None
|
|
210
|
+
labels = [clean_text(label, 20) for label in x]
|
|
211
|
+
if None in labels:
|
|
212
|
+
return None
|
|
213
|
+
cleaned = []
|
|
214
|
+
for entry in series:
|
|
215
|
+
if not isinstance(entry, dict):
|
|
216
|
+
return None
|
|
217
|
+
signal, label, values = clean_text(entry.get("signal"), 130), clean_text(entry.get("label"), 120), entry.get("values")
|
|
218
|
+
if signal is None or label is None or not isinstance(values, list) or len(values) != len(labels):
|
|
219
|
+
return None
|
|
220
|
+
if any(isinstance(value, bool) or not isinstance(value, (int, float)) or not 0 <= value <= 1_000_000_000 for value in values):
|
|
221
|
+
return None
|
|
222
|
+
cleaned.append({"signal": signal, "label": label, "values": list(values)})
|
|
223
|
+
figure = {"type": raw["type"], "title": title, "caption": caption, "unit": unit, "source": source, "x": labels, "series": cleaned}
|
|
224
|
+
return figure if len(json.dumps(figure)) <= MAXIMUM_FIGURE_BYTES else None
|
|
225
|
+
|
|
226
|
+
|
|
193
227
|
def clean_evidence(raw: Any) -> dict[str, Any] | None | Outcome:
|
|
194
228
|
parsed = parse_json(raw) if raw is not None else None
|
|
195
229
|
if parsed is None:
|
|
196
230
|
return None
|
|
197
231
|
if not isinstance(parsed, dict):
|
|
198
|
-
return refused("invalid_evidence", "evidence must be {signals, source}.")
|
|
232
|
+
return refused("invalid_evidence", "evidence must be {signals, source} with an optional figure.")
|
|
199
233
|
signals, source = parsed.get("signals"), parsed.get("source")
|
|
200
234
|
if not isinstance(signals, int) or isinstance(signals, bool) or not 0 <= signals <= MAXIMUM_EVIDENCE_SIGNALS:
|
|
201
235
|
return refused("invalid_evidence", "evidence.signals must be a whole number of signals.")
|
|
202
236
|
if source is not None and clean_text(source, MAXIMUM_EVIDENCE_SOURCE) is None:
|
|
203
237
|
return refused("invalid_evidence", "evidence.source must be short text.")
|
|
204
|
-
|
|
238
|
+
evidence = {"signals": signals, "source": source.strip() if isinstance(source, str) else None}
|
|
239
|
+
if parsed.get("figure") is not None:
|
|
240
|
+
figure = clean_figure(parsed["figure"])
|
|
241
|
+
if figure is None:
|
|
242
|
+
return refused("invalid_evidence", "evidence.figure must be a small chart spec: type line or bar, title, caption, unit, source, x labels and series with one value per label.")
|
|
243
|
+
evidence["figure"] = figure
|
|
244
|
+
return evidence
|
|
205
245
|
|
|
206
246
|
|
|
207
247
|
def file_proposal(arguments: dict[str, Any], *, actor_sub: str, now_iso: str, proposal_id: str | None = None) -> tuple[dict[str, Any] | None, Outcome | None]:
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED