Spaces:
Running on Zero
A newer version of the Gradio SDK is available: 6.20.0
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
.gitignorefor 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 formattedpython -m pytestpassed: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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged 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, andVariable_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:
CanonicalConceptRecordinsrc/gcmd_classifier/models.pyVocabularyIndexinsrc/gcmd_classifier/vocabulary/index.pyload_vocabulary,build_vocabulary_index, andcalculate_file_hashinsrc/gcmd_classifier/vocabulary/loader.py
Hierarchy assumptions discovered from the current real file:
- The root node is the non-UUID
CategorynamedEARTH SCIENCE. - The current file contains only the
EARTH SCIENCECategory 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,456Variable_Level_2, 553Variable_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
levelandname - 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 formattedpython -m pytestpassed: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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged 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, andAbstract - 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:
ArticleRecordinsrc/gcmd_classifier/models.pyArticleValidationIssueandArticleLoadResultinsrc/gcmd_classifier/models.pyread_article_json,validate_article_record,validate_article_records, andload_articlesinsrc/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.errorswhile all remaining records continue to be validated. - Duplicate DOI records are reported with
DUPLICATE_DOIand excluded fromArticleLoadResult.articlesso returned records have unique DOI values.
Extra-field policy:
- Unexpected fields are rejected for the MVP with
UNEXPECTED_FIELDbecause neitherPROJECT_SPEC.mdnorAGENTS.mddefines an allow-list extension policy for source article records.
Exact-preservation protections:
- Successful
ArticleRecordvalues are returned unchanged. - DOI values are not normalized or replaced.
- Titles and abstracts are not stripped, lowercased, rewritten, summarized, or supplemented.
- Empty
Abstractvalues are valid and preserved exactly. - Whitespace-only strings are preserved; non-empty checks do not trim source values.
Yearmust be a strict integer and Boolean values are rejected.
Current full-data findings:
data/articles.jsoncontains 468 source records.- 467 records have valid non-empty unique DOI values.
- Exactly one record, index 467, is invalid because
DOIis empty. - 14 records have empty
Abstractvalues 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, orAbstract - Empty
DOIandTitle - Non-string
DOI,Title, orAbstract - 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 formattedpython -m pytestpassed: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.jsonunchanged by this milestone.data/gcmd_hierarchy.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged by this milestone.
Deviations from approved plan:
IMPLEMENTATION_PLAN.mdwas revised to reflect the clarified Milestone 3 rule that emptyAbstractvalues 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, andstop_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:
CandidateDecisionTopicResponseTermResponseVariableResponse
Result and status models implemented:
SupportTypeArticleProcessingStatusArticleClassificationOutcomeClassificationFinalStatusReviewStatusOutputWarningOutputErrorDeterministicValidationResultConfidenceMetadataOriginalCandidateReferenceSemanticValidationResultClassificationRecordProcessingMetadataArticleResultRunSummary
Schema validation rules and invariants implemented:
- Candidate IDs must be non-empty strings.
- Candidate confidence, when present, must be between
0.0and1.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: truerequires an emptyselectedlist.- Non-empty
selectedis represented only withstop_at_parent: false. stop_at_parent: truerequires a non-emptystop_reason.- Accepted and reduced classification records require successful deterministic validation.
- Completed not-classified article results require an empty
classificationslist and a non-emptyno_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, andAbstract. - Empty
Abstractis valid in article result output. - Boolean
Yearis 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.jsonschemas/run_summary.schema.json
Dependencies:
- No dependency was added.
jsonschemawas 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 formattedpython -m pytestpassed: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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged 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:
ModelClientprotocolModelStageModelRequestModelResponseTokenUsageRetryPolicygenerate_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_promptbuild_term_promptbuild_variable_promptPromptCandidateParentContext
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_idvalues. - 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
Abstractis an empty string. - Source article text is inserted without trimming, normalization, or rewriting.
Retry behavior implemented:
- Retry only
RetryableModelErrorfailures. - Do not retry
NonRetryableModelErrorfailures. - Configurable
max_retriesthroughRetryPolicyandModelSettings. - Successful responses include the retry count.
- Exhausted retries raise
ModelRetriesExhaustedErrorwith the retry count. - Unknown candidate IDs are not retried or validated in this layer;
UnknownCandidateIDErroris available for later routing stages.
Configuration fields added:
MODEL_PROVIDERMODEL_NAMEMODEL_TEMPERATUREMODEL_TIMEOUT_SECONDSMODEL_MAX_RETRIESPROMPT_VERSION_TOPICPROMPT_VERSION_TERMPROMPT_VERSION_VARIABLEMODEL_API_KEY_ENV_VARMODEL_INCLUDE_COST_METADATA
OpenAI provider status:
OpenAIModelClientis 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 formattedpython -m pytestpassed: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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged 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
TopicResponserequest and parsing via the Milestone 5 model layer - Selected
candidate_idvalidation 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_candidatesreads Topic records fromVocabularyIndex.topics().- Only UUID-bearing
Topicrecords are converted into candidates. - The non-UUID root
Categoryis 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_topicsbuilds Topic candidates, creates a Topic prompt, and callsgenerate_with_retriesusing the neutralModelClientinterface.- The router requests the
TopicResponseschema. - Selected candidate IDs are mapped back to authoritative Topic records from the vocabulary index.
- Each selected Topic produces a
TopicBranchSeedcontaining 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
TopicRoutingResultwith no branches. no_selection_reasonis preserved when supplied by the model.- No fallback Topic is created.
- No fake
FALLBACKbehavior 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
Abstractprompt pass-through - No live provider usage
Current full-data smoke assertion:
- The current
data/gcmd_hierarchy.jsonbuilds 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 formattedpython -m pytestpassed:143 passed in 0.43s
Live model/API status:
- No live model API calls were made.
- Unit tests use
FakeModelClientonly.
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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged by this milestone.
Deviations from approved plan:
- Tests were added in
tests/test_topic_routing.pyrather thantests/test_pipeline.pyto 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
TermResponserequest and parsing via the Milestone 5 model layer - Selected Term
candidate_idvalidation 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_candidatesaccepts a selected Topic UUID and reads onlyVocabularyIndex.terms_for_topic(topic_uuid).- Only UUID-bearing direct
Termchildren 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_termsconsumes a Milestone 6TopicBranchSeed.- 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_retriesusing the neutralModelClientinterface. - The router requests the
TermResponseschema. - Selected candidate IDs are mapped back to authoritative Term records from the vocabulary index.
- Each selected Term produces a
TermBranchSeedcontaining 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: truereturns a successfulTopicStopResult.stop_reasonis 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_relationshipverifies 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
Abstractprompt pass-through - No live provider usage
Current full-data smoke assertion:
- The current
data/gcmd_hierarchy.jsonbuilds 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 formattedpython -m pytestpassed:168 passed in 0.58s
Live model/API status:
- No live model API calls were made.
- Unit tests use
FakeModelClientonly.
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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged by this milestone.
Deviations from approved plan:
- Tests were added in
tests/test_term_routing.pyrather thantests/test_pipeline.pyto keep this milestone focused on Term routing instead of pipeline behavior. - No
classification/traversal.pymodule 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, andVariable_Level_2parents - Recursive controlled descent through Variable levels using only direct children
- Structured
VariableResponserequest and parsing via the Milestone 5 model layer - Selected Variable
candidate_idvalidation 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_candidatesaccepts a current parent UUID and reads onlyVocabularyIndex.variables_for_parent(parent_uuid).- Supported parents are
Term,Variable_Level_1, andVariable_Level_2records. - 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_variablesstarts from a Milestone 7TermBranchSeed.- Each parent evaluation calls
generate_with_retriesusing the neutralModelClientinterface and requestsVariableResponse. - 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: truereturns a successful terminal outcome at the current parent and preservesstop_reason.Variable_Level_3selections stop naturally as leaf-node terminal outcomes.- Term,
Variable_Level_1, andVariable_Level_2parents 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
VariableBranchErrorentries and do not discard successful sibling terminal outcomes.
Invalid candidate behavior implemented:
- Unknown selected Variable candidate IDs raise
UnknownCandidateIDErrorat the current branch. - Duplicate selected Variable candidate IDs raise
UnknownCandidateIDErrorat 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_relationshiprejects candidates that are not direct children of the current parent.
Branch provenance behavior implemented:
VariableTerminalOutcomeincludes 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.EvidenceSteppreserves 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_1branches - 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
Abstractprompt pass-through - No live provider usage
Current full-data smoke assertion:
- The current
data/gcmd_hierarchy.jsonbuilds direct Variable candidates matching current counts: 1,368Variable_Level_1, 1,456Variable_Level_2, and 553Variable_Level_3records. - 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 formattedpython -m pytestpassed:197 passed in 0.58s
Live model/API status:
- No live model API calls were made.
- Unit tests use
FakeModelClientonly.
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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged by this milestone.
Deviations from approved plan:
- Tests were added in
tests/test_variable_traversal.pyrather thantests/test_pipeline.pyto keep this milestone focused on Variable descent instead of pipeline behavior. classification/traversal.pywas 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
ClassificationRecordobjects - 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:
ClassificationCandidaterepresents a terminal candidate awaiting deterministic vocabulary validation.validate_candidateconfirms 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
VocabularyIndexrelationships. - Topic membership and Term membership are checked with UUID ancestry rather than model-supplied paths.
- Root
Categorycandidates 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
ClassificationRecordonly 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_classificationsremoves 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
OutputErrordiagnostics with error code, stage, field, expected value, and actual value where useful. - Redundancy actions produce structured
OutputWarningdiagnostics for duplicate UUID removal, duplicate canonical-path removal, same-branch ancestor removal, independent ancestor preservation, and insufficient-provenance preservation.
Tests added:
tests/test_validation.pytests/test_redundancy.py
Verification:
python -m ruff check .passed:All checks passed!python -m ruff format --check .passed:36 files already formattedpython -m pytestpassed: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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged by this milestone.
Deviations from approved plan:
- Tests were added in focused
tests/test_validation.pyandtests/test_redundancy.pyfiles, matching the approved likely file list. src/gcmd_classifier/models.pyandsrc/gcmd_classifier/errors.pydid 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
ClassificationRecordin 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
ArticleResultgeneration 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_articlecoordinates 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: partialwith the accepted classifications retained. - Failed articles retain source fields exactly and include structured errors.
Batch runner behavior implemented:
run_batchaccepts anArticleLoadResult, 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_reprocessto 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:
CacheIdentityincludes 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_eventrecords structured events through standard logging.sanitize_log_detailsredacts 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
OutputErrorandOutputWarningmodels are used for article, branch, validation, redundancy, and invalid-source diagnostics.
Run summary behavior implemented:
RunSummarynow includes explicitvalid_article_records,invalid_source_records,processed_articles,cache_hits,cache_misses,total_warnings,total_errors, andduration_secondsfields.schemas/run_summary.schema.jsonwas regenerated from the updated Pydantic model.- Summary output remains machine-readable and validates against the draft JSON Schema.
Tests added:
tests/test_cache.pytests/test_persistence.pytests/test_pipeline.py
Verification:
python -m ruff check .passed:All checks passed!python -m ruff format --check .passed:46 files already formattedpython -m pytestpassed:261 passed in 0.77s
Live model/API status:
- No live model API calls were made.
- Unit tests use
FakeModelClientonly.
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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged by this milestone.
Deviations from approved plan:
src/gcmd_classifier/errors.pydid not require changes because existing typed exceptions and structuredOutputErrordiagnostics were sufficient.src/gcmd_classifier/config.pydid not require changes because existing model settings already covered the cache identity inputs needed for this milestone.RunSummaryandschemas/run_summary.schema.jsonwere 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 GradioInterface- 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
Abstracthandling, root launcher import behavior, and frozen prototype immutability
create_demo() behavior implemented:
- Builds a Gradio interface with
Title,Abstract, optionalDOI, and optionalYearinputs. - Returns summary Markdown, detailed
ArticleResultJSON, 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_demofromgcmd_classifier.ui.gradio_app. - Creates
demo = create_demo()for Hugging Face Spaces compatibility. - Calls
demo.launch()only underif __name__ == "__main__". - Contains no routing, validation, model-provider, or classification business logic.
How the UI calls the pipeline service:
run_demo_classificationconstructs anArticleRecordfrom 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
ArticleResultJSON payload. - Empty
Abstractvalues 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 formattedpython -m pytestpassed: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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged 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.pyrequires 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
integrationand 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.pyincludes an optionalpytest.mark.integrationlive smoke test.- It is skipped unless
GCMD_RUN_LIVE_INTEGRATION=1is set. - It is skipped unless
MODEL_PROVIDER=openaiis configured. - It is skipped unless the configured API key environment variable is present.
- Normal
python -m pytestdoes 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.pybytes 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 formattedpython -m pytestpassed: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.jsonunchanged by this milestone.data/articles.jsonunchanged by this milestone.prototype/app_hf_poc.pyunchanged 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.tomlwas updated only to register theintegrationpytest 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.