File size: 54,150 Bytes
f70d732 0f2ecac 413d5a1 0539596 d840c10 531d9de 5d05dd0 19d779e 57b6fe2 e726170 7dab768 9f2f534 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 | # MVP Progress
## Milestone 1: Repository Setup and Tooling
Status: completed
Scope completed:
- Packaging and pytest configuration
- Pydantic deterministic-core dependency
- Ruff configuration
- Optional dependency groups for OpenAI and Gradio
- Environment example file
- `.gitignore` for local, Python, test, build, and generated-output artifacts
- Import smoke test
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `2 files already formatted`
- `python -m pytest` passed: `1 passed in 0.00s`
Out of scope, not started:
- Vocabulary loading
- Article loading
- Model integration
- Classification logic
- Persistence
- UI and root `app.py`
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
## Milestone 2: Hierarchy Loader and Vocabulary Index
Status: completed
Scope completed:
- Deterministic loader for `data/gcmd_hierarchy.json`
- Recursive traversal of the GCMD hierarchy through `Category`, `Topic`, `Term`, `Variable_Level_1`, `Variable_Level_2`, and `Variable_Level_3`
- Canonical UUID-bearing concept records
- Vocabulary file SHA-256 version hash
- Canonical paths excluding the root `Category`
- UUID, canonical path, parent, direct-child, Topic, Term, Variable, ancestor, and descendant lookups
- Typed vocabulary errors for malformed hierarchy content
- Focused small hierarchy fixture and full-data smoke tests
Implemented models and lookup structures:
- `CanonicalConceptRecord` in `src/gcmd_classifier/models.py`
- `VocabularyIndex` in `src/gcmd_classifier/vocabulary/index.py`
- `load_vocabulary`, `build_vocabulary_index`, and `calculate_file_hash` in `src/gcmd_classifier/vocabulary/loader.py`
Hierarchy assumptions discovered from the current real file:
- The root node is the non-UUID `Category` named `EARTH SCIENCE`.
- The current file contains only the `EARTH SCIENCE` Category represented by that root.
- Every current Topic and Term has a UUID.
- The only UUID-less node in the current file is the root Category.
- Current UUID-bearing record counts are: 14 Topics, 144 Terms, 1,368 `Variable_Level_1`, 1,456 `Variable_Level_2`, 553 `Variable_Level_3`, and 3,535 total UUID-bearing records.
- Current observed transitions are adjacent only: `Category -> Topic -> Term -> Variable_Level_1 -> Variable_Level_2 -> Variable_Level_3`.
Error cases implemented:
- Invalid JSON
- Non-object root or child nodes
- Missing or empty required `level` and `name`
- Unsupported hierarchy levels
- UUID-bearing root Category
- UUID-less non-root nodes
- Invalid parent-child level transitions
- Children under `Variable_Level_3`
- Non-list `children`
- Non-string `definition`
- Duplicate UUIDs
- Same canonical path assigned to different UUIDs
- Lookup requests for unknown UUIDs or invalid lookup scopes
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `8 files already formatted`
- `python -m pytest` passed: `23 passed in 0.14s`
Out of scope, not started:
- Article loading
- Model integration
- Topic routing
- Term routing
- Variable-level classification
- Deterministic final classification validation beyond vocabulary lookup integrity
- Redundancy removal
- Persistence, caching, and output generation
- UI and root `app.py`
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- None.
Remaining risks or assumptions:
- The loader remains fully dynamic and does not hard-code vocabulary names, UUIDs, paths, branches, or record counts, but full-data smoke tests intentionally assert current-file counts.
- Future approved vocabulary rules would be needed before accepting any UUID-less non-root hierarchy node.
## Milestone 3: Article Loading and Validation
Status: completed
Scope completed:
- Deterministic loader for `data/articles.json`
- Strict source article model preserving exact serialized fields: `DOI`, `Title`, `Year`, and `Abstract`
- Read/decode, individual record validation, duplicate DOI detection, and aggregate result return paths
- Record-level validation errors with source index, code, field, message, and DOI when available
- Valid and invalid article fixtures
- Full-data smoke test for the current `data/articles.json`
Implemented article model and loader APIs:
- `ArticleRecord` in `src/gcmd_classifier/models.py`
- `ArticleValidationIssue` and `ArticleLoadResult` in `src/gcmd_classifier/models.py`
- `read_article_json`, `validate_article_record`, `validate_article_records`, and `load_articles` in `src/gcmd_classifier/articles/loader.py`
Validation and error-collection policy:
- File-level conditions raise typed exceptions: missing file, invalid JSON, and top-level JSON value that is not a list.
- Record-level conditions are collected in `ArticleLoadResult.errors` while all remaining records continue to be validated.
- Duplicate DOI records are reported with `DUPLICATE_DOI` and excluded from `ArticleLoadResult.articles` so returned records have unique DOI values.
Extra-field policy:
- Unexpected fields are rejected for the MVP with `UNEXPECTED_FIELD` because neither `PROJECT_SPEC.md` nor `AGENTS.md` defines an allow-list extension policy for source article records.
Exact-preservation protections:
- Successful `ArticleRecord` values are returned unchanged.
- DOI values are not normalized or replaced.
- Titles and abstracts are not stripped, lowercased, rewritten, summarized, or supplemented.
- Empty `Abstract` values are valid and preserved exactly.
- Whitespace-only strings are preserved; non-empty checks do not trim source values.
- `Year` must be a strict integer and Boolean values are rejected.
Current full-data findings:
- `data/articles.json` contains 468 source records.
- 467 records have valid non-empty unique DOI values.
- Exactly one record, index 467, is invalid because `DOI` is empty.
- 14 records have empty `Abstract` values and otherwise load successfully.
Error cases covered:
- File not found
- Invalid JSON
- Top-level JSON value not a list
- Article entry not an object
- Missing `DOI`, `Title`, `Year`, or `Abstract`
- Empty `DOI` and `Title`
- Non-string `DOI`, `Title`, or `Abstract`
- String, floating-point, and Boolean `Year`
- Null field values
- Duplicate DOI
- Unexpected extra fields
- Multiple invalid records reported in one aggregate result
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `11 files already formatted`
- `python -m pytest` passed: `51 passed in 0.18s`
Out of scope, not started:
- Model prompts
- Model-provider integration
- Topic routing
- Term routing
- Variable-level classification
- Persistence
- Caching
- Batch processing
- UI and root `app.py`
Source-file status:
- `data/articles.json` unchanged by this milestone.
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- `IMPLEMENTATION_PLAN.md` was revised to reflect the clarified Milestone 3 rule that empty `Abstract` values are valid.
Remaining risks or assumptions:
- The current source file remains partially invalid until the empty DOI at index 467 is corrected.
- Whitespace-only DOI or Title values are non-empty and therefore preserved as valid under the explicit no-trimming rule; this can be tightened later only with an approved source-data rule.
## Milestone 4: Shared Models, Output Schema Draft, and Structured Response Schemas
Status: completed
Scope completed:
- Structured Pydantic response schemas for Topic, Term, and Variable decisions
- Shared candidate decision schema with selected candidate ID, optional uncalibrated confidence, evidence, support type, and reason
- Term and Variable response invariants for `stop_at_parent`, `selected`, and `stop_reason`
- Separate status enums for article processing status, article classification outcome, classification final status, and review status
- Article result, classification record, deterministic validation result, warning/error, processing metadata, and run summary models
- Draft JSON Schema files for article classification results and run summaries
- Tests for structured response validation, output model validation, status-scope separation, confidence ranges, and JSON Schema validation
Structured response models implemented:
- `CandidateDecision`
- `TopicResponse`
- `TermResponse`
- `VariableResponse`
Result and status models implemented:
- `SupportType`
- `ArticleProcessingStatus`
- `ArticleClassificationOutcome`
- `ClassificationFinalStatus`
- `ReviewStatus`
- `OutputWarning`
- `OutputError`
- `DeterministicValidationResult`
- `ConfidenceMetadata`
- `OriginalCandidateReference`
- `SemanticValidationResult`
- `ClassificationRecord`
- `ProcessingMetadata`
- `ArticleResult`
- `RunSummary`
Schema validation rules and invariants implemented:
- Candidate IDs must be non-empty strings.
- Candidate confidence, when present, must be between `0.0` and `1.0`.
- Candidate evidence is required and non-empty for selected candidates.
- Unknown fields are rejected in structured model responses and output models.
- `stop_at_parent: true` requires an empty `selected` list.
- Non-empty `selected` is represented only with `stop_at_parent: false`.
- `stop_at_parent: true` requires a non-empty `stop_reason`.
- Accepted and reduced classification records require successful deterministic validation.
- Completed not-classified article results require an empty `classifications` list and a non-empty `no_classification_reason`.
- Completed classified article results require at least one classification record.
- Failed and partial article results can be represented without classification records.
- Article result output preserves exact source field names: `DOI`, `Title`, `Year`, and `Abstract`.
- Empty `Abstract` is valid in article result output.
- Boolean `Year` is rejected by article result validation.
- Review-compatible enum values are representable, but no review-trigger logic was implemented.
JSON Schema files:
- `schemas/classification_result.schema.json`
- `schemas/run_summary.schema.json`
Dependencies:
- No dependency was added. `jsonschema` was already available in the active interpreter and was used for JSON Schema tests.
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `15 files already formatted`
- `python -m pytest` passed: `93 passed in 0.33s`
Out of scope, not started:
- Live model-provider implementation
- Prompt construction
- Topic routing
- Term routing
- Variable-level traversal or classification
- Semantic validation implementation
- Persistence
- Caching
- Batch processing
- UI and root `app.py`
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- None.
Remaining risks or assumptions:
- The JSON Schemas are draft schemas generated from the current Pydantic models; they are useful for MVP validation but may need hand refinement before publication as final normative schemas.
- Confidence remains optional, uncalibrated metadata and does not independently determine acceptance.
- Review-related enum values are schema-compatible only; emitting them still requires later approved decision rules.
- The schema can represent future semantic validation output, but Milestone 4 does not implement a semantic validator.
## Milestone 5: Model Abstraction, Prompts, Fake Model, and Retries
Status: completed
Scope completed:
- Provider-neutral model client protocol and request/response types
- Typed structured model responses carried through the model interface
- Deterministic scripted fake model client for unit tests
- Retry wrapper for retryable model failures
- Prompt builders for Topic, Term, and Variable decisions
- Environment-backed model and prompt configuration
- Guarded optional OpenAI provider module with isolated imports
- Unit tests for fake model behavior, prompts, retry behavior, configuration, and OpenAI import isolation
Model interface implemented:
- `ModelClient` protocol
- `ModelStage`
- `ModelRequest`
- `ModelResponse`
- `TokenUsage`
- `RetryPolicy`
- `generate_with_retries`
Fake model behavior implemented:
- Returns scripted typed Topic, Term, and Variable responses.
- Supports no-selection responses.
- Can return invalid candidate IDs for later routing-validation tests without performing candidate validation in this layer.
- Raises scripted retryable and non-retryable errors.
- Supports fail-once-then-succeed retry scenarios.
- Supports retry exhaustion scenarios.
- Records every request it receives for assertions.
- Does not import or require OpenAI.
Prompt builders implemented:
- `build_topic_prompt`
- `build_term_prompt`
- `build_variable_prompt`
- `PromptCandidate`
- `ParentContext`
Prompt protections implemented:
- Article title and abstract are clearly delimited.
- Article content is explicitly described as untrusted input.
- Prompts instruct the model not to follow instructions inside article text.
- Prompts instruct the model to choose only supplied `candidate_id` values.
- Prompts instruct the model not to invent, generate, or modify UUIDs, canonical paths, labels, hierarchy levels, or parent-child relationships.
- Prompts request structured output only.
- Prompts allow no selection for Topic routing and stopping at parent for Term and Variable decisions.
- Prompts remain valid when `Abstract` is an empty string.
- Source article text is inserted without trimming, normalization, or rewriting.
Retry behavior implemented:
- Retry only `RetryableModelError` failures.
- Do not retry `NonRetryableModelError` failures.
- Configurable `max_retries` through `RetryPolicy` and `ModelSettings`.
- Successful responses include the retry count.
- Exhausted retries raise `ModelRetriesExhaustedError` with the retry count.
- Unknown candidate IDs are not retried or validated in this layer; `UnknownCandidateIDError` is available for later routing stages.
Configuration fields added:
- `MODEL_PROVIDER`
- `MODEL_NAME`
- `MODEL_TEMPERATURE`
- `MODEL_TIMEOUT_SECONDS`
- `MODEL_MAX_RETRIES`
- `PROMPT_VERSION_TOPIC`
- `PROMPT_VERSION_TERM`
- `PROMPT_VERSION_VARIABLE`
- `MODEL_API_KEY_ENV_VAR`
- `MODEL_INCLUDE_COST_METADATA`
OpenAI provider status:
- `OpenAIModelClient` is implemented as a minimal guarded optional provider behind the shared interface.
- OpenAI imports occur only inside provider construction, not at module import time.
- API keys are read from environment variables only.
- Unit tests do not instantiate a live configured provider and make no live API calls.
- The provider is intentionally lightly tested in this milestone; fake-model and interface behavior are the deterministic core.
Dependencies:
- No dependency was added.
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `25 files already formatted`
- `python -m pytest` passed: `124 passed in 0.37s`
Live model/API status:
- No live model API calls were made.
Out of scope, not started:
- Topic routing implementation
- Term routing implementation
- Variable traversal or end-to-end classification
- Deterministic candidate validation in routing components
- Semantic validation implementation
- Persistence
- Caching
- Batch processing
- Gradio UI
- Root `app.py`
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- The OpenAI provider is minimal and guarded rather than fully integration-tested, because live provider behavior and SDK details are intentionally outside deterministic unit tests for this milestone.
Remaining risks or assumptions:
- Provider-specific retry classification for OpenAI SDK exception classes may need refinement when live integration tests are explicitly approved.
- Prompt wording is covered by unit tests, but prompt quality still needs later evaluation with routing components.
- Candidate ID validation belongs to later routing logic and was not implemented here.
## Milestone 6: Topic Routing
Status: completed
Scope completed:
- Topic candidate construction from the loaded vocabulary index
- Topic routing through the provider-neutral model interface
- Structured `TopicResponse` request and parsing via the Milestone 5 model layer
- Selected `candidate_id` validation against the current prompt candidate set
- Topic branch seed output for later Term routing
- Valid no-topic routing result without fallback behavior
- Unit tests for candidate construction, Topic routing, model interaction, invalid candidates, and current full-data Topic count
Topic candidate builder behavior implemented:
- `build_topic_candidates` reads Topic records from `VocabularyIndex.topics()`.
- Only UUID-bearing `Topic` records are converted into candidates.
- The non-UUID root `Category` is never included as a candidate.
- Candidate IDs are application-supplied deterministic IDs such as `topic_0001`, not authoritative UUIDs.
- Candidate IDs map back to authoritative Topic UUIDs in application code.
- Prompt candidates include candidate ID, Topic name, level, optional definition, and canonical path context.
- No Topic names, UUIDs, paths, or counts are hard-coded in application behavior.
Topic router behavior implemented:
- `route_topics` builds Topic candidates, creates a Topic prompt, and calls `generate_with_retries` using the neutral `ModelClient` interface.
- The router requests the `TopicResponse` schema.
- Selected candidate IDs are mapped back to authoritative Topic records from the vocabulary index.
- Each selected Topic produces a `TopicBranchSeed` containing branch ID, Topic UUID, Topic name, level, canonical path, evidence, support type, confidence, model reason, original candidate ID, prompt version, provider, model name, and retry count.
- The router performs Topic routing only and does not route Terms or Variables.
No-topic behavior implemented:
- Empty Topic selections return a successful `TopicRoutingResult` with no branches.
- `no_selection_reason` is preserved when supplied by the model.
- No fallback Topic is created.
- No fake `FALLBACK` behavior from the proof of concept is used.
Invalid candidate behavior implemented:
- Unknown selected candidate IDs raise `UnknownCandidateIDError`.
- Duplicate selected candidate IDs raise `UnknownCandidateIDError`.
- Empty or malformed candidate IDs fail structured model response validation before routing accepts them.
- Invalid Topic candidate IDs are not mapped to vocabulary records and are not silently dropped.
Fake-model tests added:
- Single Topic selection
- Multiple Topic selection
- No Topic selection
- Invalid candidate ID selection
- Empty/malformed candidate ID rejection
- Provider-neutral request assertions
- Empty `Abstract` prompt pass-through
- No live provider usage
Current full-data smoke assertion:
- The current `data/gcmd_hierarchy.json` builds 14 Topic candidates for the EARTH SCIENCE vocabulary.
- All current full-data Topic candidates map to UUID-bearing Topic records.
- This count is a current-file smoke assertion only and is not hard-coded into application behavior.
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `29 files already formatted`
- `python -m pytest` passed: `143 passed in 0.43s`
Live model/API status:
- No live model API calls were made.
- Unit tests use `FakeModelClient` only.
Out of scope, not started:
- Term routing
- Variable traversal
- End-to-end classification
- Deterministic final classification validation
- Redundancy removal
- Persistence
- Caching
- Batch processing
- Gradio UI
- Root `app.py`
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- Tests were added in `tests/test_topic_routing.py` rather than `tests/test_pipeline.py` to keep this milestone focused on Topic routing instead of pipeline behavior.
Remaining risks or assumptions:
- Candidate IDs are stable for deterministic vocabulary order; if a future vocabulary inserts or reorders Topics, candidate IDs may shift for that prompt, but each prompt still carries its own candidate-to-record mapping.
- Topic routing currently raises explicit errors for unknown or duplicate selected IDs rather than returning partial valid selections with warnings.
- Confidence is preserved as optional uncalibrated metadata and is not used to independently accept a Topic.
## Milestone 7: Term Routing
Status: completed
Scope completed:
- Direct Term candidate construction beneath one selected Topic branch
- Term routing through the provider-neutral model interface
- Structured `TermResponse` request and parsing via the Milestone 5 model layer
- Selected Term `candidate_id` validation against the current prompt candidate set
- Topic-to-Term direct-child relationship validation
- Term branch seed output for later Variable routing
- Successful stop-at-Topic routing result when no Term is adequately supported
- Unit tests for Term candidate construction, Term routing, stop behavior, invalid candidates, relationship validation, model interaction, and current full-data Term count
Term candidate builder behavior implemented:
- `build_term_candidates` accepts a selected Topic UUID and reads only `VocabularyIndex.terms_for_topic(topic_uuid)`.
- Only UUID-bearing direct `Term` children of the selected Topic are converted into candidates.
- Terms from sibling Topics are not included.
- Candidate IDs are application-supplied deterministic IDs such as `term_0001`, scoped to the current Term prompt.
- Candidate IDs map back to authoritative Term UUIDs in application code.
- Prompt candidates include candidate ID, Term name, level, optional definition, canonical path context, and parent Topic context.
- No Term names, UUIDs, paths, counts, or Topic-to-Term relationships are hard-coded in application behavior.
Term router behavior implemented:
- `route_terms` consumes a Milestone 6 `TopicBranchSeed`.
- The router builds direct Term candidates only for the selected Topic.
- The router creates a Term prompt with selected parent Topic context and direct-child Term candidates.
- The router calls `generate_with_retries` using the neutral `ModelClient` interface.
- The router requests the `TermResponse` schema.
- Selected candidate IDs are mapped back to authoritative Term records from the vocabulary index.
- Each selected Term produces a `TermBranchSeed` containing branch ID, parent Topic UUID/name, Term UUID, Term name, level, canonical path, evidence, support type, confidence, model reason, original candidate ID, parent branch ID, prompt version, provider, model name, and retry count.
- Multiple selected Terms produce independent branch seeds.
- The router performs Term routing only and does not route Variables.
Stop-at-Topic behavior implemented:
- `stop_at_parent: true` returns a successful `TopicStopResult`.
- `stop_reason` is preserved.
- Parent Topic UUID, name, level, canonical path, evidence, support type, confidence, and candidate ID are preserved.
- No Term branch seeds are created for stop-at-Topic results.
- Stopping at the Topic is not treated as a system failure.
- No fake or fallback Terms are created.
Invalid candidate behavior implemented:
- Unknown selected Term candidate IDs raise `UnknownCandidateIDError`.
- Duplicate selected Term candidate IDs raise `UnknownCandidateIDError`.
- Empty or malformed candidate IDs fail structured model response validation before routing accepts them.
- Invalid Term candidate IDs are not mapped to vocabulary records and are not silently dropped.
Topic-to-Term relationship validation implemented:
- `validate_term_candidate_relationship` verifies that each Term candidate belongs to the selected Topic.
- It checks both the candidate's stored Topic UUID and the vocabulary index parent relationship.
- A manually corrupted candidate-to-record mapping is rejected.
- A sibling Topic Term cannot be accepted because it is absent from the selected Topic prompt candidate set.
Fake-model tests added:
- One Term selected under a selected Topic
- Multiple Terms under one selected Topic
- Stop at Topic with `stop_at_parent: true`
- Topic with no direct/supported Term returning a stop result
- Invalid Term candidate ID selection
- Duplicate Term candidate ID selection
- Empty/malformed candidate ID rejection
- Provider-neutral request assertions
- Empty `Abstract` prompt pass-through
- No live provider usage
Current full-data smoke assertion:
- The current `data/gcmd_hierarchy.json` builds 144 direct Term candidates across all current Topics.
- All current full-data Term candidates map to UUID-bearing Term records.
- Current Term UUIDs are unique across Topic candidate lists.
- This count is a current-file smoke assertion only and is not hard-coded into application behavior.
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `30 files already formatted`
- `python -m pytest` passed: `168 passed in 0.58s`
Live model/API status:
- No live model API calls were made.
- Unit tests use `FakeModelClient` only.
Out of scope, not started:
- Variable traversal
- End-to-end classification
- Final deterministic classification validation
- Redundancy removal
- Persistence
- Caching
- Batch processing
- Gradio UI
- Root `app.py`
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- Tests were added in `tests/test_term_routing.py` rather than `tests/test_pipeline.py` to keep this milestone focused on Term routing instead of pipeline behavior.
- No `classification/traversal.py` module was added because the necessary branch helpers fit cleanly in the existing candidate/router modules without introducing a premature abstraction.
Remaining risks or assumptions:
- Term candidate IDs are stable for deterministic direct-child order within a prompt; if vocabulary order changes, candidate IDs may shift, but each prompt still carries its own candidate-to-record mapping.
- Term routing raises explicit errors for unknown or duplicate selected IDs rather than returning partial valid selections with warnings.
- Confidence is preserved as optional uncalibrated metadata and is not used to independently accept a Term.
## Milestone 8: Variable-Level Controlled Descent
Status: completed
Scope completed:
- Direct Variable candidate construction beneath Term, `Variable_Level_1`, and `Variable_Level_2` parents
- Recursive controlled descent through Variable levels using only direct children
- Structured `VariableResponse` request and parsing via the Milestone 5 model layer
- Selected Variable `candidate_id` validation against the current prompt candidate set
- Direct parent-to-child relationship validation for selected Variables
- Terminal branch outcomes for stop-at-parent, no-child parent, and leaf-node cases
- Branch provenance and evidence trail tracking
- Branch-level error reporting that preserves successful sibling outcomes
- Unit tests for Variable candidate construction, controlled descent, stopping, leaf handling, multi-branch behavior, invalid candidates, provenance, model interaction, and current full-data Variable counts
Variable candidate builder behavior implemented:
- `build_variable_candidates` accepts a current parent UUID and reads only `VocabularyIndex.variables_for_parent(parent_uuid)`.
- Supported parents are `Term`, `Variable_Level_1`, and `Variable_Level_2` records.
- Only UUID-bearing direct Variable children are converted into candidates.
- Grandchildren, sibling branches, and global Variables are not included.
- Candidate IDs are application-supplied deterministic IDs such as `variable_0001`, scoped to the current Variable prompt.
- Candidate IDs map back to authoritative Variable UUIDs in application code.
- Prompt candidates include candidate ID, Variable name, level, optional definition, canonical path context, and parent context.
- No Variable names, UUIDs, paths, counts, or parent-child relationships are hard-coded in application behavior.
Controlled descent behavior implemented:
- `descend_variables` starts from a Milestone 7 `TermBranchSeed`.
- Each parent evaluation calls `generate_with_retries` using the neutral `ModelClient` interface and requests `VariableResponse`.
- Each model call receives only direct Variable children of the current parent.
- Selected children create independent child branches with distinct branch IDs.
- Descent continues recursively for selected child branches only.
- Grandchildren are not evaluated before their parent is selected.
- The traversal does not skip hierarchy levels and does not evaluate Variables globally.
Stop-at-parent behavior implemented:
- A parent with no direct Variable children returns a successful terminal outcome at the current parent.
- `stop_at_parent: true` returns a successful terminal outcome at the current parent and preserves `stop_reason`.
- `Variable_Level_3` selections stop naturally as leaf-node terminal outcomes.
- Term, `Variable_Level_1`, and `Variable_Level_2` parents can all become terminal outcomes when descent is unsupported.
- No fake or fallback Variable is created.
- Descent is not forced to a leaf.
Multi-branch behavior implemented:
- Multiple selected child Variables create independent branch outcomes.
- Child branch IDs preserve the parent branch lineage.
- Sibling branches continue independently when another sibling stops.
- Branch-level failures are recorded as `VariableBranchError` entries and do not discard successful sibling terminal outcomes.
Invalid candidate behavior implemented:
- Unknown selected Variable candidate IDs raise `UnknownCandidateIDError` at the current branch.
- Duplicate selected Variable candidate IDs raise `UnknownCandidateIDError` at the current branch.
- Empty or malformed candidate IDs fail structured model response validation before traversal accepts them.
- Invalid Variable candidate IDs are not mapped to vocabulary records and are not silently dropped.
- `validate_variable_candidate_relationship` rejects candidates that are not direct children of the current parent.
Branch provenance behavior implemented:
- `VariableTerminalOutcome` includes branch ID, parent branch ID, Topic UUID/name, Term UUID/name, final UUID/name/level/canonical path/path components, evidence, support type, confidence, reason, stop reason, candidate ID, prompt/model metadata, and retry count when available.
- `EvidenceStep` preserves the branch lineage and evidence trail from Term through selected Variable stages.
- Confidence remains optional, uncalibrated metadata and does not independently determine acceptance.
Fake-model tests added:
- Stop at Term
- Stop at `Variable_Level_1`
- Stop at `Variable_Level_2`
- Select `Variable_Level_3`
- Natural leaf-node handling
- Multiple independent `Variable_Level_1` branches
- Multiple selected descendants under one branch
- Sibling continuation after one branch stops
- Sibling continuation after one branch fails
- Unknown, duplicate, empty, and malformed candidate IDs
- Provider-neutral request assertions
- Empty `Abstract` prompt pass-through
- No live provider usage
Current full-data smoke assertion:
- The current `data/gcmd_hierarchy.json` builds direct Variable candidates matching current counts: 1,368 `Variable_Level_1`, 1,456 `Variable_Level_2`, and 553 `Variable_Level_3` records.
- Every current full-data Variable candidate maps to a UUID-bearing Variable record.
- Every current full-data Variable candidate is a direct child of the parent used to build it.
- These counts are current-file smoke assertions only and are not hard-coded into application behavior.
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `32 files already formatted`
- `python -m pytest` passed: `197 passed in 0.58s`
Live model/API status:
- No live model API calls were made.
- Unit tests use `FakeModelClient` only.
Out of scope, not started:
- Redundancy removal
- Final deterministic classification validation
- Final article-level output construction
- Persistence
- Caching
- Batch processing
- Gradio UI
- Root `app.py`
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- Tests were added in `tests/test_variable_traversal.py` rather than `tests/test_pipeline.py` to keep this milestone focused on Variable descent instead of pipeline behavior.
- `classification/traversal.py` was added as planned for the reusable descent engine.
- Branch-level partial behavior was implemented only for selected child branches; an initial Term-parent model failure still propagates because there are no sibling Variable branches yet at that point.
Remaining risks or assumptions:
- Variable candidate IDs are stable for deterministic direct-child order within a prompt; if vocabulary order changes, candidate IDs may shift, but each prompt still carries its own candidate-to-record mapping.
- Branch-level errors are represented for traversal continuity but are not yet converted into final article-level statuses.
- Terminal outcomes are routing/descent outcomes only; Milestone 9 still needs final deterministic validation and redundancy removal.
## Milestone 9: Deterministic Validation and Redundancy Removal
Status: completed
Scope completed:
- Deterministic validation of terminal classification candidates against `VocabularyIndex`
- Narrow conversion from validated routing/traversal candidates into final `ClassificationRecord` objects
- Structural diagnostics for rejected invalid candidates
- Redundancy removal for duplicate UUIDs, duplicate canonical paths, and same-lineage ancestor classifications
- Branch-provenance-aware preservation of independent ancestor classifications
- Unit tests for validation, conversion, diagnostics, redundancy removal, and full-data representative validation
Deterministic validation behavior implemented:
- `ClassificationCandidate` represents a terminal candidate awaiting deterministic vocabulary validation.
- `validate_candidate` confirms that the final UUID exists in the loaded vocabulary and is assignable.
- Supplied names, levels, canonical paths, path components, Topic context, Term context, and expected parent UUIDs are verified against authoritative vocabulary records when present.
- Parent-child ancestry is checked through `VocabularyIndex` relationships.
- Topic membership and Term membership are checked with UUID ancestry rather than model-supplied paths.
- Root `Category` candidates are rejected explicitly and never accepted as final classifications.
- Invented UUIDs, labels, levels, paths, and hierarchy relationships produce structured validation errors.
- Validation is structural only and does not make independent semantic-support decisions.
Final classification record conversion behavior implemented:
- Valid candidates are converted to `ClassificationRecord` only after deterministic validation succeeds.
- Accepted records use authoritative UUID, name, level, canonical path, path components, Topic, Term, and parent UUID from the vocabulary index.
- Routing/traversal metadata is preserved where available, including branch ID, evidence, support type, optional uncalibrated confidence, reason or stop reason, and validation result.
- Invalid candidates are returned as rejected validation results and do not appear in accepted `classifications`.
Redundancy removal behavior implemented:
- `remove_redundant_classifications` removes exact duplicate UUIDs.
- Exact duplicate canonical paths are removed even when the UUID differs.
- Ancestors are removed when a deeper descendant exists in the same branch lineage.
- Ancestor classifications are preserved when branch provenance indicates an independent decision.
- When provenance is insufficient, both ancestor and descendant are preserved with a warning.
- Sibling classifications are preserved.
- Same concept names in different branches are preserved when UUIDs and paths differ.
- Redundancy uses `VocabularyIndex.is_ancestor`, not string-prefix path matching, as the primary ancestry test.
Diagnostic behavior implemented:
- Rejected validation candidates include structured `OutputError` diagnostics with error code, stage, field, expected value, and actual value where useful.
- Redundancy actions produce structured `OutputWarning` diagnostics for duplicate UUID removal, duplicate canonical-path removal, same-branch ancestor removal, independent ancestor preservation, and insufficient-provenance preservation.
Tests added:
- `tests/test_validation.py`
- `tests/test_redundancy.py`
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `36 files already formatted`
- `python -m pytest` passed: `230 passed in 0.69s`
Live model/API status:
- No model calls were made.
- No live API calls were made.
Out of scope, not started:
- Independent semantic validation
- Persistence
- Caching
- Batch processing
- Gradio UI
- Root `app.py`
- End-to-end article-level orchestration
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- Tests were added in focused `tests/test_validation.py` and `tests/test_redundancy.py` files, matching the approved likely file list.
- `src/gcmd_classifier/models.py` and `src/gcmd_classifier/errors.py` did not require changes because existing structured output and error models were sufficient.
Remaining risks or assumptions:
- Candidate ID provenance is retained as structured warning metadata on converted records rather than adding a new generated output field to `ClassificationRecord` in this milestone.
- Non-assignable concept rejection is implemented, but the current vocabulary fixture contains only assignable UUID-bearing records.
- Redundancy cleanup is collection-level only; integration into final article result assembly remains deferred to later milestones.
## Milestone 10: Batch Runner, Incremental Persistence, Caching, and Logging
Status: completed
Scope completed:
- Single-article orchestration through Topic routing, Term routing, Variable descent, deterministic validation, and redundancy removal
- Article-level `ArticleResult` generation for classified, not-classified, partial, and failed outcomes
- Batch processing over validated article records with continuation after article failures
- Invalid source article diagnostics without fallback DOI generation
- Incremental JSON checkpoint persistence after each article result
- Final consolidated machine-readable JSON output
- File-backed cache identity and cache reuse
- Structured logging helpers with secret redaction
- Run summary counters for valid/invalid records, processed articles, cache hits/misses, warnings, errors, and accepted classifications
Single-article pipeline behavior implemented:
- `classify_article` coordinates existing Milestone 6-9 components.
- No-topic results become `processing_status: completed`, `classification_outcome: not_classified`, empty classifications, and a no-classification reason.
- Stop-at-Topic, stop-at-Term, and Variable terminal outcomes are converted only after deterministic validation succeeds.
- Branch-level Variable descent errors are retained as structured errors while successful sibling terminal outcomes are preserved.
- Articles with accepted classifications and branch errors become `processing_status: partial` with the accepted classifications retained.
- Failed articles retain source fields exactly and include structured errors.
Batch runner behavior implemented:
- `run_batch` accepts an `ArticleLoadResult`, processes only valid records in source order, and reports invalid source records in the summary.
- One article failure does not stop later valid articles.
- Each article result is saved promptly after completion or failure.
- Batch output avoids duplicate article files for the same DOI-safe key by overwriting the DOI checkpoint path.
- The runner supports `force_reprocess` to bypass cache reuse.
Persistence strategy selected and implemented:
- The MVP uses one JSON checkpoint file per DOI-safe SHA-256 key plus an optional consolidated `results.json`.
- This append/checkpoint-style representation avoids rewriting a single large output document after every article.
- Individual article checkpoint writes and consolidated output writes use temp-file-plus-atomic-replace.
- Temporary and destination files are created in the same destination directory, so atomic replace occurs on the same filesystem.
- No database infrastructure was introduced.
Cache identity and reuse behavior implemented:
- `CacheIdentity` includes DOI, exact article fingerprint, vocabulary version/hash, model provider, model name, model temperature, timeout, max retries, Topic/Term/Variable prompt versions, application version, and relevant configuration hash.
- Cache keys change when article content, vocabulary version, model settings, prompt versions, application version, or relevant configuration changes.
- Cache hits return article results with `processing_metadata.cache_used: true`.
- Cache hits remain completed work and are never marked `skipped`.
- Force reprocessing bypasses cache reuse.
- Stale cache entries are not reused when identity inputs differ.
Logging and diagnostics behavior implemented:
- `log_event` records structured events through standard logging.
- `sanitize_log_details` redacts keys that look like API keys, tokens, authorization headers, secrets, passwords, or sensitive headers.
- Pipeline and batch events include DOI, stage, branch/cache context, status, classification counts, and error counts when available.
- Structured `OutputError` and `OutputWarning` models are used for article, branch, validation, redundancy, and invalid-source diagnostics.
Run summary behavior implemented:
- `RunSummary` now includes explicit `valid_article_records`, `invalid_source_records`, `processed_articles`, `cache_hits`, `cache_misses`, `total_warnings`, `total_errors`, and `duration_seconds` fields.
- `schemas/run_summary.schema.json` was regenerated from the updated Pydantic model.
- Summary output remains machine-readable and validates against the draft JSON Schema.
Tests added:
- `tests/test_cache.py`
- `tests/test_persistence.py`
- `tests/test_pipeline.py`
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `46 files already formatted`
- `python -m pytest` passed: `261 passed in 0.77s`
Live model/API status:
- No live model API calls were made.
- Unit tests use `FakeModelClient` only.
Out of scope, not started:
- Gradio UI
- Hugging Face launcher
- Root `app.py`
- Live-model smoke tests
- Independent semantic validation
- Production deployment infrastructure
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- `src/gcmd_classifier/errors.py` did not require changes because existing typed exceptions and structured `OutputError` diagnostics were sufficient.
- `src/gcmd_classifier/config.py` did not require changes because existing model settings already covered the cache identity inputs needed for this milestone.
- `RunSummary` and `schemas/run_summary.schema.json` were updated to represent required Milestone 10 counters explicitly.
Remaining risks or assumptions:
- The checkpoint store uses DOI-safe SHA-256 filenames, so humans inspect consolidated JSON rather than filename-readable DOI values.
- Cached failed and partial results can be reused when the cache identity matches exactly; future policy may choose to cache only completed results.
- Batch processing is sequential for the MVP. Parallel execution remains deferred.
- Run summary model-call totals are available for fake clients and providers that expose request counts or metadata; richer token/cost aggregation remains provider-dependent.
## Milestone 11: Gradio Integration and Hugging Face Launcher
Status: completed
Scope completed:
- Lightweight Gradio demonstration UI package under `src/gcmd_classifier/ui/`
- `create_demo()` factory for a Gradio `Interface`
- Root-level thin Hugging Face launcher in `app.py`
- UI formatting for classifications, UUIDs, canonical paths, hierarchy levels, evidence, support type, confidence metadata, stopping reason, deterministic validation status, no-classification reason, warnings, and errors
- UI tests for importability, construction with a fake Gradio module, pipeline delegation, formatting helpers, empty `Abstract` handling, root launcher import behavior, and frozen prototype immutability
`create_demo()` behavior implemented:
- Builds a Gradio interface with `Title`, `Abstract`, optional `DOI`, and optional `Year` inputs.
- Returns summary Markdown, detailed `ArticleResult` JSON, and structured diagnostics JSON outputs.
- Defers all classification work until the user submits input.
- Uses `ModelSettings.from_environment()` for provider/model configuration.
- Uses a guarded Gradio import so non-UI modules and deterministic tests can run without Gradio import success.
Root `app.py` launcher behavior implemented:
- Imports only `create_demo` from `gcmd_classifier.ui.gradio_app`.
- Creates `demo = create_demo()` for Hugging Face Spaces compatibility.
- Calls `demo.launch()` only under `if __name__ == "__main__"`.
- Contains no routing, validation, model-provider, or classification business logic.
How the UI calls the pipeline service:
- `run_demo_classification` constructs an `ArticleRecord` from UI inputs.
- It loads the configured vocabulary when one is not injected.
- It creates a model client through a factory and calls `pipeline_service.classify_article`.
- It does not call Topic routing, Term routing, Variable descent, deterministic validation, redundancy removal, OpenAI, or any model provider directly.
Display behavior implemented:
- Classified results render a Markdown table with level, name, UUID, canonical path, evidence, support type, confidence metadata, stopping reason, and deterministic validation status.
- No-classification results display the no-classification reason.
- Warnings and errors appear in both the summary panel and structured diagnostics JSON.
- Detailed output returns the full `ArticleResult` JSON payload.
- Empty `Abstract` values are accepted and passed through to the pipeline input path.
Dependency handling:
- No dependency changes were made.
- Gradio remains an optional dependency group in `pyproject.toml`.
- In the active interpreter, importing Gradio fails because an optional audio dependency is unavailable (`pyaudioop`), so tests use a fake Gradio module for construction checks while keeping the real UI import guarded.
- No API keys or secrets are hard-coded or printed.
Tests added:
- `tests/test_ui.py`
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `50 files already formatted`
- `python -m pytest` passed: `272 passed in 0.78s`
Live model/API status:
- No live model API calls were made in tests.
- UI tests use injected fake Gradio and fake pipeline/model behavior only.
Out of scope, not started:
- Production review UI
- Live-model smoke testing
- External deployment automation
- Milestone 12 evaluation work
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- Tests use a fake Gradio module for `create_demo()` construction because the active interpreter cannot import Gradio successfully. The UI still constructs a real Gradio interface when the optional dependency imports correctly.
- The UI supports a deterministic fake/demo model mode by default via `FakeModelClient`, but the fake response still runs through the pipeline service and is clearly represented as fake demo behavior.
- When DOI is omitted in manual UI input, the UI creates a deterministic `demo-ui:` DOI and emits a UI warning; source JSON article loading behavior is unchanged and still never invents identifiers.
Remaining risks or assumptions:
- Manual live-provider use depends on optional provider dependencies and environment variables being available in the runtime environment.
- The demo UI is intentionally simple and does not implement production human-review workflows.
- Root `app.py` requires a working Gradio optional dependency in the deployment environment, as expected for Hugging Face Spaces.
## Milestone 12: MVP Smoke/Evaluation Harness
Status: completed
Scope completed:
- Pytest-based MVP smoke/evaluation harness
- Fake-model end-to-end smoke run over a small current-data subset
- Optional live-model integration test marked `integration` and skipped by default
- Pytest marker registration for integration tests
- Baseline preservation checks for `prototype/app_hf_poc.py`
- Source-file immutability checks for current data files
Smoke/evaluation harness behavior implemented:
- The smoke command is `python -m pytest tests/test_mvp_smoke.py`.
- The smoke test loads the current full vocabulary from `data/gcmd_hierarchy.json`.
- The smoke test loads and validates the current full article source from `data/articles.json`.
- It verifies the current invalid source record is reported in diagnostics rather than silently ignored.
- It processes only two valid articles in routine tests to avoid full-data model processing.
- It uses the existing batch runner, cache, JSON store, pipeline service, routing, traversal, deterministic validation, redundancy removal, and output models.
- It validates article results and run summary output against the existing JSON Schemas.
Fake-model smoke behavior implemented:
- The fake smoke script dynamically selects a real Topic, Term, and Variable branch from the loaded vocabulary without hard-coding names, UUIDs, paths, or counts into application behavior.
- One fake article produces an accepted classification through Topic routing, Term routing, Variable descent, deterministic validation, redundancy removal, article result creation, and persistence.
- One fake article produces a completed no-classification result.
- Accepted classifications are asserted to have `deterministic_validation.valid: true`.
- The fake smoke run makes no live API calls.
Optional live integration behavior:
- `tests/test_mvp_smoke.py` includes an optional `pytest.mark.integration` live smoke test.
- It is skipped unless `GCMD_RUN_LIVE_INTEGRATION=1` is set.
- It is skipped unless `MODEL_PROVIDER=openai` is configured.
- It is skipped unless the configured API key environment variable is present.
- Normal `python -m pytest` does not run live model calls.
- If explicitly enabled, the live test processes only one article and asserts structural validity only.
Metrics recorded:
- Total source records considered
- Valid article records
- Invalid source records
- Articles processed in the smoke run
- Classified articles
- Not-classified articles
- Partial articles
- Failed articles
- Accepted classifications
- Deterministic validation failures through accepted-record assertions
- Warnings and errors
- Model calls
- Cache hits and misses
- Processing start time
- Processing end time
- Duration
- Token usage and estimated cost remain available when providers supply them
Baseline preservation behavior:
- The smoke harness reads `prototype/app_hf_poc.py` bytes before and after the fake smoke run and confirms they are unchanged.
- The smoke harness asserts the prototype module is not imported during the fake smoke run.
- No prototype execution, refactor, or comparison study was added.
Verification:
- `python -m ruff check .` passed: `All checks passed!`
- `python -m ruff format --check .` passed: `51 files already formatted`
- `python -m pytest` passed: `273 passed, 1 skipped in 0.89s`
Live model/API status:
- Normal tests made no live model API calls.
- The single skipped test is the optional live integration smoke test.
Out of scope, not started:
- Full expert-reviewed evaluation dashboard
- Publication benchmark
- Production monitoring system
- Model-comparison framework
- Full proof-of-concept comparison study
Source-file status:
- `data/gcmd_hierarchy.json` unchanged by this milestone.
- `data/articles.json` unchanged by this milestone.
- `prototype/app_hf_poc.py` unchanged by this milestone.
Deviations from approved plan:
- No standalone script was added because a pytest smoke harness is the simplest maintainable command and was explicitly allowed.
- `pyproject.toml` was updated only to register the `integration` pytest marker.
Remaining risks or assumptions:
- Fake-model smoke validates structural pipeline behavior, not scientific correctness.
- Live integration remains opt-in and depends on optional dependencies, credentials, and provider availability.
- Future baseline comparison with the frozen proof of concept remains deferred.
|