docs: add training case product readme

This commit is contained in:
2026-04-30 21:07:28 -04:00
parent eb9d16b9a4
commit 1e1a98391e
2 changed files with 110 additions and 0 deletions
@@ -0,0 +1,68 @@
# Training Case Engine M1-M6 Product README
Training Case Engine is the product-facing training module in AI Exchange. M1-M6 provide persisted training cases, a lightweight HTTP API, a dashboard entry, validation hardening, public package import/export, and an offline package smoke path.
## Dashboard Entry
Run the local API server with the project script and open the root dashboard. The dashboard includes a Training panel with:
- available cases
- user progress
- attempt start
- annotation note save
- attempt submit
The dashboard calls the same Training endpoints listed below and displays public case fields only.
## API Surface
All endpoints use JSON responses. List endpoints accept `limit` with an upper bound of `100`.
| Method | Path | Capability |
| --- | --- | --- |
| GET | `/training/cases` | List cases by optional status, skill tag, instrument, and limit filters. |
| GET | `/training/cases/{case_id}` | Read one public case. |
| GET | `/training/cases/export` | Export a public package with the same case filters as the case list. |
| POST | `/training/cases/import` | Import a versioned public package and create cases plus tasks. |
| POST | `/training/attempts` | Start an attempt for a case. |
| POST | `/training/attempts/{attempt_id}/annotations` | Save an annotation payload for an attempt. |
| POST | `/training/attempts/{attempt_id}/submit` | Submit an attempt with an optional score. |
| GET | `/training/progress` | List progress rows by optional user, skill tag, and limit filters. |
Public case and package responses omit the internal solution key and private answer details.
## Package Smoke
Use the offline package smoke to check import plus public export without external services:
```bash
.venv/bin/python scripts/run_training_case_package.py \
--input tests/fixtures/training_case_package_m6.json \
--output /tmp/training_case_public_export.json
```
The broader offline check suite also includes this package smoke:
```bash
.venv/bin/python scripts/run_offline_checks.py
```
## Validation And Errors
- Malformed or non-object request bodies return `400` with `invalid_json_body`.
- Missing required JSON fields return `400` with `missing_required_json_field:<field>`.
- Invalid JSON field values return `400` with `invalid_json_field:<field>`.
- Invalid path identifiers return `400` with `invalid_path_param:<field>`.
- Invalid list limits return `400` with `invalid_limit_query_param:limit`; valid limits are `1` through `100`.
- Missing cases return `404` with `not_found`.
- Unexpected failures return `500` with `internal_server_error` and do not echo raw internals.
Package import rejects private internal solution-key fields. Package export is stable public JSON and omits private answer detail.
## Data Isolation Guarantees
- Training records are scoped to the training domain: cases, tasks, attempts, annotations, scores, and progress.
- Training may reference upstream market or rule events as read-only context.
- Training APIs and package tooling do not write to signals, executions, backtest results, or live-trading records.
- Public package import/export keeps operator-only notes, rubric internals, and private answer detail out of exported payloads.
- The offline package smoke uses local files and an in-memory training repository; it writes only the requested public export file.
@@ -4,6 +4,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
TRAINING_PRODUCT_DOC = ROOT / "docs" / "training_case_engine_m1_m6_product_readme.md"
class DocumentationConsistencyTests(unittest.TestCase):
@@ -17,6 +18,47 @@ class DocumentationConsistencyTests(unittest.TestCase):
self.assertEqual(missing, [])
def test_training_case_product_doc_lists_public_m1_m6_surface(self) -> None:
text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8")
for snippet in (
"Training panel",
"/training/cases",
"/training/cases/{case_id}",
"/training/cases/export",
"/training/cases/import",
"/training/attempts",
"/training/attempts/{attempt_id}/annotations",
"/training/attempts/{attempt_id}/submit",
"/training/progress",
".venv/bin/python scripts/run_training_case_package.py",
".venv/bin/python scripts/run_offline_checks.py",
"internal solution key",
"Data Isolation Guarantees",
):
with self.subTest(snippet=snippet):
self.assertIn(snippet, text)
def test_training_case_product_doc_avoids_private_literal_shapes(self) -> None:
text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8").lower()
for term in (
"data" + "base",
"data" + "base_url",
"post" + "gresql",
"psy" + "copg",
"pass" + "word",
"pass" + "wd",
"tok" + "en",
"sec" + "ret",
"api" + "_key",
"answer" + "_key",
):
with self.subTest(term=term):
self.assertNotIn(term, text)
self.assertIsNone(re.search(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", text))
if __name__ == "__main__":
unittest.main()