mostlyright-data 0.31.6__tar.gz → 0.31.7__tar.gz
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.
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/PKG-INFO +1 -1
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/SKILL.md +10 -1
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +2 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/commands.md +2 -2
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/enriching-with-small-models.md +5 -1
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/preparing-table-search.md +7 -0
- mostlyright_data-0.31.7/src/mostlyright/data_harness/thin/delivery.py +261 -0
- mostlyright_data-0.31.7/src/mostlyright/data_harness/thin/description_shape.py +120 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_datasets.py +6 -6
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_runs.py +135 -27
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_stream.py +23 -3
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/vocabulary.py +4 -2
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/render.py +25 -1
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/.gitignore +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/README.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/pyproject.toml +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/scripts/hatch_build.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/agents/openai.yaml +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/agent-protocol.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/boundaries.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/installation-parity.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/live-run.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/making-a-table-incremental.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/narrating-the-run.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/one-install.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/patterns.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/prediction-labels.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/promote.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/readers.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/receipts.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/recipe-test.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/reference-pages.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/required-protocol.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/source-credentials.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/sources.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/transforms.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/user-communication-contract.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/__init__.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/agent_protocol.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/canonical.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/formats.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/key_seam.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/page_coverage.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/session_probes.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/skill_assets.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/table_manifest.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/__init__.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/acquire.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/activity.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/approvals.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/backoff.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/categories.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/commands.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/download.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/enrichment.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/matching_benchmark.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/narrative.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/parity.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/probe.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/propose.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/recipe.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/recipe_explain.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/release_check.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/research.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/router.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/runs.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/session.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/stream.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/support.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/table_search_contract.json +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/transport.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_matching.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_search.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/__init__.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/attendance.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/clarification.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/credentials.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/login.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/ux/remediation.py +0 -0
|
@@ -270,6 +270,14 @@ merely to obtain a green run. Read [recovery details](references/agent-protocol.
|
|
|
270
270
|
Then run `mr-data models suggest RUN_ID --json`. It reads the run's preview and names where
|
|
271
271
|
the table could use a deterministic derivation or a model call, and whether this deployment
|
|
272
272
|
serves that model. Confirm each lead against the rows before you offer it.
|
|
273
|
+
A succeeded full run's `mr-data status` and `mr-data watch` output carries a `delivery` block.
|
|
274
|
+
It names a missing cover, a description that is missing or out of the skill's shape,
|
|
275
|
+
natural-language search when the run's table has none, and always the enrichment offer.
|
|
276
|
+
Complete every step before you report the build as done. Studio marks no main table, so you
|
|
277
|
+
judge it: skip the search step for a supporting table and say so in one line. A scratch table
|
|
278
|
+
you test and demote owes none of these steps.
|
|
279
|
+
Offering enrichment is mandatory. Accepting it is the person's choice, and a no is a complete
|
|
280
|
+
answer.
|
|
273
281
|
9. Any truncated source means a preview, not the requested complete dataset. Inspect coverage
|
|
274
282
|
even when the run succeeded. Untruncated acquisition does not prove publisher pagination, nested
|
|
275
283
|
records or population coverage is complete. A null coverage window does not mean all history was acquired.
|
|
@@ -285,7 +293,8 @@ merely to obtain a green run. Read [recovery details](references/agent-protocol.
|
|
|
285
293
|
repair, which is why the decision belongs in step 5. A table left not refresh-ready never
|
|
286
294
|
refreshes unless it declares `rebuild`; it does not become ready later.
|
|
287
295
|
Download and verify artifacts when the user requested bytes.
|
|
288
|
-
10.
|
|
296
|
+
10. Search is on by default for the dataset's main table. Supporting tables such as lookups and
|
|
297
|
+
code lists do not need it. Enable it, inspect search readiness and test meaningful queries against
|
|
289
298
|
the observed version as [search preparation](references/preparing-table-search.md) describes.
|
|
290
299
|
Search preparation runs independently: pending or failed indexing never means the data build
|
|
291
300
|
failed, and declared metadata alone never proves observed search quality. Before delivery,
|
|
@@ -300,6 +300,8 @@ uses must follow from the actual fields and coverage, without promising an outco
|
|
|
300
300
|
no bold field labels, em dashes,
|
|
301
301
|
semicolons, fake quotations or unsupported claims
|
|
302
302
|
- every `##` heading in DESCRIPTION.md is one of the five, in that relative order, none repeated
|
|
303
|
+
- Coverage and Columns are present. A succeeded full run's `delivery` block checks the opening,
|
|
304
|
+
the headings and the 4000-character bound, and names the description until it passes
|
|
303
305
|
|
|
304
306
|
**Every dataset gets a cover before its first run is presented.** Once the description is written,
|
|
305
307
|
generate one with `mr-data cover DATASET_ID "what the image should depict" --json`, describing the
|
{mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/skills/mr-data-build/references/commands.md
RENAMED
|
@@ -26,9 +26,9 @@ run in full.
|
|
|
26
26
|
| `mr-data recipe` | Register one recipe document (dataset, question, table plan, sources, transform, checks and units) in one call, and print the identifiers the server derived. The document is read first and every fault comes back in one refusal, each with the JSON pointer that names it. `--no-lint` sends it as written instead. A recipe that misses a feature that sets how fast a run fetches (a stated `pages.concurrency` under 64, a per-row request open to `batch`, a page walk open to `pagination.parallel`, or a Parquet file open to a ranged read) is refused `THIN_RECIPE_SLOW`, with each feature's pointer and fix, before anything is sent. Apply the fixes, or pass `--accept-slow "REASON"` with one sentence on why the slow path is right for this source. The receipt prints the reason and the features it covered. Registration refuses `THIN_BRIEF_MISSING` when the dataset's record carries no answered question and no delegation, and `THIN_RECIPE_DATASET_UNBOUND` when the document names no `dataset.id`. `--delegated "their words"` writes the delegation onto the dataset and registers in the same command, and clears the first of those two and never the second. `mr-data recipe show ID` reads one back. `mr-data recipe explain FILE` (or a recipe identifier) lists every reason its table can or cannot refresh, each with a rule code and Studio's suggested repair, and exits 2 when one blocks it. `mr-data recipe forecast RECIPE_ID` says what one run would ask of each source before it runs, and exits 3 when the run would overrun today's platform quota or one attempt. `--digest` is optional there, and without it the digest the recipe was registered with is forecast. `mr-data recipe readiness --table TABLE_ID --current-only` reads the revision one table runs now. The explanation's `fast_features` names each fast feature the recipe uses or misses, with a one-line fix. [Making a table incremental](making-a-table-incremental.md#the-loop-explain-forecast-run-diagnose) says when to use each. |
|
|
27
27
|
| `mr-data cover` | Generate and attach one branded 1200×630 dataset cover. Give it the dataset ID and what the image should depict; Studio fixes the model, single-color style, curated random palette, dimensions and storage. |
|
|
28
28
|
| `mr-data run` | Start one run against a registered recipe, named as `--recipe RECIPE_ID`, with `--digest RECIPE_DIGEST` when you want the run to assert which bytes it means. Neither is positional. The digest is the bare hex the registration receipt printed. Left out, the run reads it back from the registered recipe, which is the same value, because a recipe identifier names one revision. A full of a recipe that misses a fetch-speed feature is refused `THIN_RECIPE_SLOW`, as registration is, unless `--accept-slow "REASON"` is passed. Other modes print the missed features and are not refused. The mode is one of six: `sample`, `full`, `refresh`, `backfill`, `compact` and `replay`. Four have a shorthand flag (`--sample`, `--full`, `--refresh`, `--backfill`) and two do not, so write `--mode MODE`, which is accepted for every one of them. A refresh executes only Studio's persisted source-action plan, and a plan shape it does not refresh returns `RESYNC_REQUIRED` before acquisition. [Which plans refresh](making-a-table-incremental.md#which-plans-refresh) lists them, and `mr-data recipe explain` says which one a table is. Use `mr-data table resync TABLE_ID --request-id UUID` for an explicit full source reread, retaining that UUID if the response is uncertain. `--backfill` states the exact window with `--window START END`. `--mode replay --sources-from RUN_ID` asks Studio to run the registered revision against the RETAINED RAW INPUTS of one named successful run of the same table: nothing is fetched from this computer, nothing is acquired again, the result is compared against that run and never becomes the live version, and it neither asks for nor records an approval. Where Studio has replay switched off, or the named run is not successful, not the same table, or no longer retains its inputs, it answers a typed refusal. Report the code rather than retrying in another mode. `--sources-from` on any other mode is refused `THIN_ARGUMENT_INVALID` before anything is sent. `--max-rows` and `--max-source-bytes` bound ONE SOURCE rather than the finished table. `--max-rows` cuts rows after a source is fetched and decoded, so it does not shorten a collection walk. A sample with no clamp is refused: `CLAMPS_REQUIRED` from Studio, and `THIN_REQUEST_INVALID` from this client first. A table's first succeeded run goes live, truncated or not, so test on a scratch table as [the probe rule](3-probe-read-a-source-before-committing-to-it.md#the-probe-rule-test-on-a-scratch-table-never-on-the-real-table) says. A large run is held for a size confirmation (not a charge), which `--confirm` settles and whose printed `confirm_command` re-states every argument the request carried. `--approve-full RUN_ID` releases the full an existing legacy sample-first pair is holding once its preview has succeeded, naming either half of the pair. Where the deployment still wants a person at a browser it answers `THIN_INTERACTIVE_HUMAN_REQUIRED` and names the run page. `--retry RUN_ID` resumes a failed or cancelled progressive full from the pages it saved (`resume_capture_run_id` and the ceilings it carried; `retry_of_run_id` as well only when Studio admits a named retry). A failure that says the recipe must change is refused before anything is posted. Any other failed run is tried again when its triple carries `room_fault: true`, re-stating that run's own coordinate and ceilings and naming the failed run so Studio starts a new attempt, and is refused with the triple and the next step Studio's recovery category names when it is not. `room_fault` is Studio's `recovery.room_fault` where Studio states one. A source that kept refusing (`SOURCE_RATE_LIMITED`, `SOURCE_HTTP_5XX`, `SOURCE_PROXY_BUSY`) is sent anyway: Studio admits it 120 seconds after the failure and refuses `RUN_RETRY_TOO_SOON` with a Retry-After before then, so wait that long and run `--retry` again. `--cancel RUN` stops one. |
|
|
29
|
-
| `mr-data status` | Report which of the seven states one run is in, what it delivered and whether a ceiling cut it short, and — on a failure — what failed, where, and whether the fault was the execution room's rather than the recipe's. Exits non-zero on a failed run. `--diagnose` adds everything else Studio can say about the run: its progress, a per-row source's plan counts, its checkpoints, and how each attempt ended, with a lost attempt's cause marked as an inference. |
|
|
29
|
+
| `mr-data status` | Report which of the seven states one run is in, what it delivered and whether a ceiling cut it short, and — on a failure — what failed, where, and whether the fault was the execution room's rather than the recipe's. Exits non-zero on a failed run. `--diagnose` adds everything else Studio can say about the run: its progress, a per-row source's plan counts, its checkpoints, and how each attempt ended, with a lost attempt's cause marked as an inference. A succeeded full run carries `delivery`: the steps still owed before the build is done (a description missing or out of the skill's shape, a missing cover, natural-language search when the dataset's main table has none, and always the enrichment offer), each with its command. It never changes the exit code. |
|
|
30
30
|
| `mr-data runs` | Report this workspace's own runs: identifier, mode, state and creation time, plus the failure triple of any that failed and whether that failure was the execution room's. `--dataset DATASET_ID` narrows it to one dataset's runs, so "what have I run for this dataset?" is one scoped read, and a value that is not an identifier is refused `THIN_ARGUMENT_INVALID`. `--status` and `--mode` narrow it too and are refused `THIN_ARGUMENT_INVALID` for a word outside the seven states or the six modes; the three combine. Studio narrows before it cuts a page, so a filtered page holds the newest runs that match however many runs the workspace has. `--limit` says how many, and pages are followed to reach it; a listing that does not end says how many were read and suggests a smaller `--limit` or a narrower filter. The answer prints `next_cursor`, and `--cursor` continues from it. |
|
|
31
|
-
| `mr-data watch` | Stream one run's live progress under the durable event type of each stage, resuming across stream cuts. Exits non-zero when the run failed. `mr-data watch RUN_ID RUN_ID ...` follows up to 16 runs at once, prints one line as each ends, and returns once all have finished with one receipt per run (`failed_run_ids`, `refused_run_ids`); it exits 2 when any run failed and 1 when a run could not be watched. `--from-seq` and `--last-event-id` resume one run only. |
|
|
31
|
+
| `mr-data watch` | Stream one run's live progress under the durable event type of each stage, resuming across stream cuts. Exits non-zero when the run failed. `mr-data watch RUN_ID RUN_ID ...` follows up to 16 runs at once, prints one line as each ends, and returns once all have finished with one receipt per run (`failed_run_ids`, `refused_run_ids`); it exits 2 when any run failed and 1 when a run could not be watched. `--from-seq` and `--last-event-id` resume one run only. A full run that succeeds carries the same `delivery` block as `mr-data status`, and the multi-run receipt merges every run's steps. |
|
|
32
32
|
| `mr-data query` | Run one bounded read-only SQL statement over a finished run's own table and print the rows. Name the table `run_table` in the SQL, whatever the recipe called it. Bounded on rows, on answer bytes and on time, and refused by name when a bound is crossed. When no worker can answer, the refusal names `mr-data download RUN_ID` as `fallback_command`, which brings the table here to read locally. The question is asked through Mostly Right Cloud; while its usage paywall is on and the workspace is unsubscribed, each answered query counts toward the person's monthly allowance, and a spent allowance refuses `THIN_USAGE_LIMIT`. A refusal after the question was queued names its `query_id`; running the same command again reads that query back. |
|
|
33
33
|
| `mr-data search` | `search VERB` prepares and tests versioned search for a new table through Studio; no model or index runs on this computer. The verbs are `inspect`, `sample`, `values`, `propose`, `configure`, `validate`, `add-cases`, `explain`, `preview`, `evaluate`, `estimate`, `build`, `status`, `activate`, `cancel`, `rollback`, `locate` and `try`. `inspect`, `propose` and `validate` also take `--recipe FILE` before registration. `validate --spec` checks a spec and its evaluation cases against the pinned contract without sending anything. `locate` resolves evaluation-case rows to locators, and `try` runs one query against the live search. A build returns promptly with job coordinates, and an operation Studio does not serve answers a typed capability error. [Prepare search](preparing-table-search.md) says how it fits a build. |
|
|
34
34
|
| `mr-data peek` | Print the columns, their logical types and the first rows of one run's table. The type comes off the run's own column profile where the preview carries none, so `type` is null only for a run that sealed no profile. |
|
|
@@ -88,7 +88,11 @@ also keep the rows it settled from being sent at all: see [Needs Studio 4.14](#n
|
|
|
88
88
|
5. **After the run (step 8).** Run `mr-data models suggest RUN_ID --json`. It reads the run's
|
|
89
89
|
preview and names, column by column, a deterministic derivation or a model call that would
|
|
90
90
|
improve the table, each with `model_status` for the model it needs. It spends no call. Read
|
|
91
|
-
it as leads: confirm each against the rows before offering it.
|
|
91
|
+
it as leads: confirm each against the rows before offering it. The run's `delivery` block on
|
|
92
|
+
`mr-data status` and `mr-data watch` lists this offer as required, beside a missing
|
|
93
|
+
description, cover or main-table search. Search is its own required step, not part of this
|
|
94
|
+
offer. Make the offer before you report the build as done. Offering is
|
|
95
|
+
mandatory. Accepting is the person's choice. If nothing is worth adding, say so in one line.
|
|
92
96
|
6. **Deliver (step 10).** End the report with what would make the table better: the remaining
|
|
93
97
|
offers, deterministic ones first, each with the column it reads and what it would add. One
|
|
94
98
|
concrete sentence each, not a menu of everything possible.
|
|
@@ -57,6 +57,13 @@ missing search support only when `error` begins with
|
|
|
57
57
|
`THIN_SEARCH_CAPABILITY_UNAVAILABLE` as a capability limit too. Any other refusal is not
|
|
58
58
|
capability evidence. That includes 401, 403, 429, timeouts and invalid responses. Resolve it.
|
|
59
59
|
|
|
60
|
+
Search is on by default for the dataset's main table, whether or not the brief mentions it.
|
|
61
|
+
Supporting tables such as lookups, code lists and dimension tables do not need it. A succeeded
|
|
62
|
+
full run's `delivery` block names the search step whenever the run's table has no search, a
|
|
63
|
+
search whose build failed included. Studio marks no main table, so you judge which one it is.
|
|
64
|
+
Skip the step for a supporting table, while the capability is missing, or when the table has
|
|
65
|
+
nothing searchable, and say why in one line.
|
|
66
|
+
|
|
60
67
|
When the brief does not mention search, search is optional only while the capability is missing.
|
|
61
68
|
Then omit `table.search`, keep the drafted intent beside the recipe, and record the limit in the
|
|
62
69
|
dataset's Sources and decisions note. Never put that limit in the description, title or topics.
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""The steps a succeeded full run still owes before the build is reported as done.
|
|
2
|
+
|
|
3
|
+
A full run that succeeded has data on the page. The page is not finished until it has a real
|
|
4
|
+
description and a cover, and the person has been offered enrichment. This module names what is
|
|
5
|
+
missing, reading the dataset once, and always names the enrichment offer. It never raises: a
|
|
6
|
+
dataset read that fails degrades to the enrichment step and a note.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Iterable, Mapping
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from mostlyright.data_harness.thin import description_shape
|
|
15
|
+
from mostlyright.data_harness.thin.session import StudioSession
|
|
16
|
+
from mostlyright.data_harness.thin.transport import ThinLaneError, Transport
|
|
17
|
+
from mostlyright.data_harness.thin.v4 import identifier
|
|
18
|
+
from mostlyright.data_harness.thin.v4_datasets import StudioV4DatasetClient
|
|
19
|
+
|
|
20
|
+
DELIVERY_VERSION = 1
|
|
21
|
+
|
|
22
|
+
#: Where the description's shape is written down.
|
|
23
|
+
SKILL_REFERENCE = "the build skill's references/5-draft-one-recipe-document-one-call.md"
|
|
24
|
+
|
|
25
|
+
#: What each conformance failure tells the person to fix.
|
|
26
|
+
_FAILURE_SAYS: dict[str, str] = {
|
|
27
|
+
"too_long": "it is over 4000 characters",
|
|
28
|
+
"missing_bold_opening": "it does not open with a bold first sentence",
|
|
29
|
+
"unknown_heading": "a ## heading is not one of the five",
|
|
30
|
+
"repeated_heading": "a ## heading is repeated",
|
|
31
|
+
"out_of_order_heading": "the ## headings are out of order",
|
|
32
|
+
"missing_coverage": "it has no ## Coverage section",
|
|
33
|
+
"missing_columns": "it has no ## Columns section",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#: The step names, in the order a person does them.
|
|
37
|
+
STEPS: tuple[str, ...] = ("description", "cover", "search", "enrichment_offer")
|
|
38
|
+
|
|
39
|
+
_ENRICHMENT_WHY = (
|
|
40
|
+
"Offering enrichment is required on every full build. Confirm each lead against the rows, "
|
|
41
|
+
"then offer the worthwhile classification or extraction in one question. If nothing is worth "
|
|
42
|
+
"adding, say so in one line. A no is a complete answer."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_SEARCH_WHY = (
|
|
46
|
+
"Enable natural-language search on the dataset's main table: the one people query "
|
|
47
|
+
"directly. It is on by default, not an offer. Follow the skill's search flow in "
|
|
48
|
+
"references/preparing-table-search.md: search inspect, propose, validate, configure, build, "
|
|
49
|
+
"activate. Skip it, saying why in one line, when this is a supporting table (a lookup, a "
|
|
50
|
+
"code list, a dimension), when search inspect shows this deployment does not serve search "
|
|
51
|
+
"authoring, or when the table has nothing searchable."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
#: Every block says this: a scratch table is a test, not the delivered dataset.
|
|
55
|
+
SCOPE = "A scratch table you test and demote owes none of these steps."
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def applies(run: Mapping[str, Any], *, succeeded: bool) -> bool:
|
|
59
|
+
"""Only a full run that succeeded owes delivery.
|
|
60
|
+
|
|
61
|
+
A sample is a test, and a refresh or a backfill adds rows to a dataset delivered before. The
|
|
62
|
+
caller folds a recipe test into ``succeeded``.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
return succeeded and run.get("mode") == "full"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _step(step: str, command: str, why: str) -> dict[str, str]:
|
|
69
|
+
return {"step": step, "command": command, "why": why}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _enrichment_step(run_id: str) -> dict[str, str]:
|
|
73
|
+
return _step("enrichment_offer", f"mr-data models suggest {run_id} --json", _ENRICHMENT_WHY)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _description_why(description: Any) -> str | None:
|
|
77
|
+
"""Why the description step is owed, or ``None`` when the description conforms."""
|
|
78
|
+
|
|
79
|
+
verdict = description_shape.check(description)
|
|
80
|
+
if verdict["conforms"]:
|
|
81
|
+
return None
|
|
82
|
+
sections = ", ".join(description_shape.HEADINGS)
|
|
83
|
+
if verdict["failures"] == ["empty"]:
|
|
84
|
+
return (
|
|
85
|
+
"The dataset has no description. Write one in the skill's shape: a bold first "
|
|
86
|
+
f"sentence, then ## sections from {sections}, in that order. See {SKILL_REFERENCE}."
|
|
87
|
+
)
|
|
88
|
+
said = ", ".join(_FAILURE_SAYS[code] for code in verdict["failures"])
|
|
89
|
+
return (
|
|
90
|
+
f"The description does not follow the skill's shape: {said}. Rewrite it with a bold first "
|
|
91
|
+
f"sentence, then ## sections from {sections}, in that order, Coverage and Columns always "
|
|
92
|
+
f"present. See {SKILL_REFERENCE}."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def required_steps(
|
|
97
|
+
record: Mapping[str, Any], *, dataset_id: str, run_id: str
|
|
98
|
+
) -> list[dict[str, str]]:
|
|
99
|
+
"""The steps one dataset record still needs, the enrichment offer always last."""
|
|
100
|
+
|
|
101
|
+
steps: list[dict[str, str]] = []
|
|
102
|
+
why = _description_why(record.get("description"))
|
|
103
|
+
if why is not None:
|
|
104
|
+
steps.append(
|
|
105
|
+
_step(
|
|
106
|
+
"description",
|
|
107
|
+
f'mr-data dataset set {dataset_id} --name "TITLE" --topics "TOPIC,TOPIC" '
|
|
108
|
+
"--description-file DESCRIPTION.md",
|
|
109
|
+
why,
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
if not record.get("cover"):
|
|
113
|
+
steps.append(
|
|
114
|
+
_step(
|
|
115
|
+
"cover",
|
|
116
|
+
f'mr-data cover {dataset_id} "WHAT THE IMAGE SHOWS"',
|
|
117
|
+
"The dataset has no cover. Generate one.",
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
steps.append(_enrichment_step(run_id))
|
|
121
|
+
return steps
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _search_state(answer: Mapping[str, Any], table_id: str) -> str:
|
|
125
|
+
"""``active``, ``building`` (a search job is running for this version) or ``none``."""
|
|
126
|
+
|
|
127
|
+
if answer.get("schema_version") != "table-search-metadata.v1":
|
|
128
|
+
raise ValueError("not a table-search metadata answer")
|
|
129
|
+
if answer.get("table_id") != table_id:
|
|
130
|
+
raise ValueError("table-search metadata for another table")
|
|
131
|
+
if isinstance(answer.get("generation_id"), str) and answer["generation_id"]:
|
|
132
|
+
return "active"
|
|
133
|
+
# Only a running job counts: a revision whose build failed, or a spec never built, is owed.
|
|
134
|
+
if answer.get("preparation_state") == "pending":
|
|
135
|
+
return "building"
|
|
136
|
+
return "none"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _search_step(
|
|
140
|
+
client: StudioV4DatasetClient, record: Mapping[str, Any], run: Mapping[str, Any]
|
|
141
|
+
) -> tuple[dict[str, str] | None, str | None]:
|
|
142
|
+
"""The search step when the run's table has no search, and any note. Never raises.
|
|
143
|
+
|
|
144
|
+
Studio marks no main table, so the step names the rule and the agent skips it, saying why,
|
|
145
|
+
for a supporting table.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
table_id = identifier(run.get("table_id"), "table identifier")
|
|
150
|
+
except Exception: # a record without a usable table identifier is still a delivered run
|
|
151
|
+
return None, "Search could not be checked: the run names no table."
|
|
152
|
+
try:
|
|
153
|
+
# Imported here: the search module's imports reach the run module, which imports this one.
|
|
154
|
+
from mostlyright.data_harness.thin.v4_search import TABLE_SEARCH_PATH
|
|
155
|
+
|
|
156
|
+
answer = client._call(
|
|
157
|
+
"GET",
|
|
158
|
+
TABLE_SEARCH_PATH.format(table_id=table_id),
|
|
159
|
+
query={"links": "false"},
|
|
160
|
+
expected=(200,),
|
|
161
|
+
)
|
|
162
|
+
state = _search_state(answer, table_id)
|
|
163
|
+
except ThinLaneError as refusal:
|
|
164
|
+
return None, _search_unchecked(table_id, f" ({refusal.code})")
|
|
165
|
+
except Exception: # a failed read must never fail the command
|
|
166
|
+
return None, _search_unchecked(table_id, "")
|
|
167
|
+
if state == "active":
|
|
168
|
+
return None, None
|
|
169
|
+
if state == "building":
|
|
170
|
+
return None, (
|
|
171
|
+
f"Search is building for this version. Read mr-data search status {table_id} "
|
|
172
|
+
"until it is active."
|
|
173
|
+
)
|
|
174
|
+
why = f"This run's table, {table_id}, has no search. {_SEARCH_WHY}"
|
|
175
|
+
return _step("search", "mr-data search inspect --recipe RECIPE.json --json", why), None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _search_unchecked(table_id: str, why: str) -> str:
|
|
179
|
+
return f"Search could not be checked{why}. Read mr-data search status {table_id}."
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _unchecked(run_id: str, note: str) -> dict[str, Any]:
|
|
183
|
+
return {
|
|
184
|
+
"version": DELIVERY_VERSION,
|
|
185
|
+
"scope": SCOPE,
|
|
186
|
+
"required": [_enrichment_step(run_id)],
|
|
187
|
+
"note": note,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def block(client: StudioV4DatasetClient, run: Mapping[str, Any], run_id: str) -> dict[str, Any]:
|
|
192
|
+
"""The ``delivery`` member for one succeeded full run. Never raises."""
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
dataset_id = identifier(run.get("dataset_id"), "dataset identifier")
|
|
196
|
+
except Exception: # a record without a usable dataset identifier is still a delivered run
|
|
197
|
+
return _unchecked(run_id, "Presentation could not be checked: the run names no dataset.")
|
|
198
|
+
try:
|
|
199
|
+
record = client.dataset(dataset_id)
|
|
200
|
+
except ThinLaneError as refusal:
|
|
201
|
+
record = f" ({refusal.code})"
|
|
202
|
+
except Exception: # a failed read must never fail the command
|
|
203
|
+
record = ""
|
|
204
|
+
if not isinstance(record, Mapping):
|
|
205
|
+
why = record if isinstance(record, str) else ""
|
|
206
|
+
return _unchecked(
|
|
207
|
+
run_id,
|
|
208
|
+
f"Presentation could not be checked{why}. Read mr-data dataset show {dataset_id}.",
|
|
209
|
+
)
|
|
210
|
+
steps = required_steps(record, dataset_id=dataset_id, run_id=run_id)
|
|
211
|
+
search, note = _search_step(client, record, run)
|
|
212
|
+
if search is not None:
|
|
213
|
+
steps.insert(len(steps) - 1, search)
|
|
214
|
+
delivered: dict[str, Any] = {"version": DELIVERY_VERSION, "scope": SCOPE, "required": steps}
|
|
215
|
+
if note is not None:
|
|
216
|
+
delivered["note"] = note
|
|
217
|
+
return delivered
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def for_run(
|
|
221
|
+
session: StudioSession,
|
|
222
|
+
transport: Transport,
|
|
223
|
+
run: Mapping[str, Any],
|
|
224
|
+
run_id: str,
|
|
225
|
+
*,
|
|
226
|
+
succeeded: bool,
|
|
227
|
+
) -> dict[str, Any] | None:
|
|
228
|
+
"""The ``delivery`` member, or ``None`` when the run owes none."""
|
|
229
|
+
|
|
230
|
+
if not applies(run, succeeded=succeeded):
|
|
231
|
+
return None
|
|
232
|
+
try:
|
|
233
|
+
return block(StudioV4DatasetClient(session, transport=transport), run, run_id)
|
|
234
|
+
except Exception: # the block must never fail the command
|
|
235
|
+
return _unchecked(run_id, "Presentation could not be checked.")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def merged(blocks: Iterable[Mapping[str, Any] | None]) -> dict[str, Any] | None:
|
|
239
|
+
"""One block for several runs: every step once, in run order."""
|
|
240
|
+
|
|
241
|
+
steps: list[dict[str, str]] = []
|
|
242
|
+
notes: list[str] = []
|
|
243
|
+
seen: set[tuple[Any, Any]] = set()
|
|
244
|
+
for one in blocks:
|
|
245
|
+
if not isinstance(one, Mapping):
|
|
246
|
+
continue
|
|
247
|
+
for step in one.get("required") or ():
|
|
248
|
+
key = (step.get("command"), step.get("why"))
|
|
249
|
+
if key in seen:
|
|
250
|
+
continue
|
|
251
|
+
seen.add(key)
|
|
252
|
+
steps.append(dict(step))
|
|
253
|
+
note = one.get("note")
|
|
254
|
+
if isinstance(note, str) and note not in notes:
|
|
255
|
+
notes.append(note)
|
|
256
|
+
if not steps:
|
|
257
|
+
return None
|
|
258
|
+
delivered: dict[str, Any] = {"version": DELIVERY_VERSION, "scope": SCOPE, "required": steps}
|
|
259
|
+
if notes:
|
|
260
|
+
delivered["note"] = " ".join(notes)
|
|
261
|
+
return delivered
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Whether a dataset description follows the build skill's shape.
|
|
2
|
+
|
|
3
|
+
One pure function over the text, held to ``tests/fixtures/description/conformance.json`` so Cloud
|
|
4
|
+
can mirror it case for case. The rules a mirror must copy exactly:
|
|
5
|
+
|
|
6
|
+
* One leading U+FEFF is dropped. Whitespace is the ASCII set space, tab, LF, CR, FF and VT only.
|
|
7
|
+
* Lengths count Unicode code points, not UTF-16 units.
|
|
8
|
+
* Lines are split on LF after CRLF becomes LF.
|
|
9
|
+
* A heading is a line that starts at column one with ``## ``. Its text is the rest of the line
|
|
10
|
+
with ASCII whitespace trimmed. Lines inside a fence (a line starting with ``` or ~~~ after up
|
|
11
|
+
to three spaces opens or closes one) are body text.
|
|
12
|
+
* The opening is the text before the first heading. Its first paragraph runs to the first blank
|
|
13
|
+
line, and it must start with ``**`` and close a non-blank bold span inside that paragraph.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
CHECK = "description-conformance.v1"
|
|
21
|
+
|
|
22
|
+
MAX_DESCRIPTION_CHARS = 4000
|
|
23
|
+
|
|
24
|
+
#: The five headings the skill allows, in the order they must appear.
|
|
25
|
+
HEADINGS: tuple[str, ...] = (
|
|
26
|
+
"Coverage",
|
|
27
|
+
"Columns",
|
|
28
|
+
"Missing values",
|
|
29
|
+
"Suitable for",
|
|
30
|
+
"Source and rights",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
#: The headings every built table has something to say under.
|
|
34
|
+
REQUIRED_HEADINGS: tuple[str, ...] = ("Coverage", "Columns")
|
|
35
|
+
|
|
36
|
+
#: Every failure code, in the order they are reported.
|
|
37
|
+
FAILURES: tuple[str, ...] = (
|
|
38
|
+
"empty",
|
|
39
|
+
"too_long",
|
|
40
|
+
"missing_bold_opening",
|
|
41
|
+
"unknown_heading",
|
|
42
|
+
"repeated_heading",
|
|
43
|
+
"out_of_order_heading",
|
|
44
|
+
"missing_coverage",
|
|
45
|
+
"missing_columns",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
_WHITESPACE = " \t\n\r\f\v"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _fence(line: str) -> bool:
|
|
52
|
+
stripped = line.lstrip(" ")
|
|
53
|
+
return len(line) - len(stripped) <= 3 and stripped.startswith(("```", "~~~"))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _bold_opening(opening: str) -> bool:
|
|
57
|
+
paragraph = opening.split("\n\n", 1)[0]
|
|
58
|
+
if not paragraph.startswith("**"):
|
|
59
|
+
return False
|
|
60
|
+
close = paragraph.find("**", 2)
|
|
61
|
+
return close > 2 and bool(paragraph[2:close].strip(_WHITESPACE))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def check(description: Any) -> dict[str, Any]:
|
|
65
|
+
"""``{"conforms": bool, "failures": [code, ...]}`` for one description."""
|
|
66
|
+
|
|
67
|
+
text = description if isinstance(description, str) else ""
|
|
68
|
+
if text.startswith("\ufeff"):
|
|
69
|
+
text = text[1:]
|
|
70
|
+
if not text.strip(_WHITESPACE):
|
|
71
|
+
return {"conforms": False, "failures": ["empty"]}
|
|
72
|
+
found: set[str] = set()
|
|
73
|
+
if len(text) > MAX_DESCRIPTION_CHARS:
|
|
74
|
+
found.add("too_long")
|
|
75
|
+
opening: list[str] = []
|
|
76
|
+
headings: list[str] = []
|
|
77
|
+
fenced = False
|
|
78
|
+
for line in text.replace("\r\n", "\n").split("\n"):
|
|
79
|
+
if _fence(line):
|
|
80
|
+
fenced = not fenced
|
|
81
|
+
elif not fenced and line.startswith("## "):
|
|
82
|
+
headings.append(line[3:].strip(_WHITESPACE))
|
|
83
|
+
continue
|
|
84
|
+
if not headings:
|
|
85
|
+
opening.append(line)
|
|
86
|
+
blank_normalised = "\n".join(
|
|
87
|
+
"" if not line.strip(_WHITESPACE) else line for line in opening
|
|
88
|
+
).strip(_WHITESPACE)
|
|
89
|
+
if not _bold_opening(blank_normalised):
|
|
90
|
+
found.add("missing_bold_opening")
|
|
91
|
+
seen: set[str] = set()
|
|
92
|
+
last = -1
|
|
93
|
+
for heading in headings:
|
|
94
|
+
if heading not in HEADINGS:
|
|
95
|
+
found.add("unknown_heading")
|
|
96
|
+
continue
|
|
97
|
+
if heading in seen:
|
|
98
|
+
found.add("repeated_heading")
|
|
99
|
+
continue
|
|
100
|
+
seen.add(heading)
|
|
101
|
+
position = HEADINGS.index(heading)
|
|
102
|
+
if position < last:
|
|
103
|
+
found.add("out_of_order_heading")
|
|
104
|
+
last = max(last, position)
|
|
105
|
+
if "Coverage" not in seen:
|
|
106
|
+
found.add("missing_coverage")
|
|
107
|
+
if "Columns" not in seen:
|
|
108
|
+
found.add("missing_columns")
|
|
109
|
+
failures = [code for code in FAILURES if code in found]
|
|
110
|
+
return {"conforms": not failures, "failures": failures}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
__all__ = [
|
|
114
|
+
"CHECK",
|
|
115
|
+
"FAILURES",
|
|
116
|
+
"HEADINGS",
|
|
117
|
+
"MAX_DESCRIPTION_CHARS",
|
|
118
|
+
"REQUIRED_HEADINGS",
|
|
119
|
+
"check",
|
|
120
|
+
]
|
{mostlyright_data-0.31.6 → mostlyright_data-0.31.7}/src/mostlyright/data_harness/thin/v4_datasets.py
RENAMED
|
@@ -771,8 +771,10 @@ def declare_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
771
771
|
)
|
|
772
772
|
parser.add_argument(
|
|
773
773
|
"--category",
|
|
774
|
-
|
|
775
|
-
|
|
774
|
+
help=(
|
|
775
|
+
"with set, the primary category; inspect definitions with dataset categories. "
|
|
776
|
+
"Offline reference: " + ", ".join(CATEGORY_IDS)
|
|
777
|
+
),
|
|
776
778
|
)
|
|
777
779
|
parser.add_argument(
|
|
778
780
|
"--topics",
|
|
@@ -1760,10 +1762,8 @@ def write(
|
|
|
1760
1762
|
name = checked_name(named, needed_by="set") if named is not None else None
|
|
1761
1763
|
description = read_description(args, stdin=stdin)
|
|
1762
1764
|
category = getattr(args, "category", None)
|
|
1763
|
-
if category is not None and category
|
|
1764
|
-
raise ThinLaneError(
|
|
1765
|
-
"THIN_REQUEST_INVALID", "--category must be one of: " + ", ".join(CATEGORY_IDS)
|
|
1766
|
-
)
|
|
1765
|
+
if category is not None and category.strip() == "":
|
|
1766
|
+
raise ThinLaneError("THIN_REQUEST_INVALID", "--category needs a value")
|
|
1767
1767
|
topics = read_topics(getattr(args, "topics", None))
|
|
1768
1768
|
named_license = getattr(args, "license", None)
|
|
1769
1769
|
if (
|