GCMD_Keyword_Classifier_MVP / MVP_PROGRESS.md
igerasimov's picture
MVP Milestone 12
9f2f534
|
Raw
History Blame Contribute Delete
54.2 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade

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.