linkml-reference-validator 0.2.0rc1__tar.gz → 0.2.1rc1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- linkml_reference_validator-0.2.1rc1/.claude/settings.json +2 -0
- linkml_reference_validator-0.2.1rc1/.devcontainer/devcontainer.json +27 -0
- linkml_reference_validator-0.2.1rc1/.devcontainer/post_create.sh +6 -0
- linkml_reference_validator-0.2.1rc1/.github/workflows/claude-code-review.yml +61 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/workflows/claude-ontology-review.yml +13 -27
- linkml_reference_validator-0.2.1rc1/.github/workflows/claude.yml +62 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/workflows/dragon-ai.yml +2 -2
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.gitignore +1 -0
- linkml_reference_validator-0.2.1rc1/.goosehints +1 -0
- linkml_reference_validator-0.2.1rc1/AGENTS.md +1 -0
- linkml_reference_validator-0.2.1rc1/BUG_GEO_ACCESSION_TO_UID.md +137 -0
- linkml_reference_validator-0.2.1rc1/BUG_ZENODO_DOI_AND_URL_HANDLING.md +258 -0
- linkml_reference_validator-0.2.1rc1/CLI-ISSUES.md +31 -0
- linkml_reference_validator-0.2.1rc1/FEATURE_REQUEST_REPAIR.md +255 -0
- linkml_reference_validator-0.2.0rc1/README.md → linkml_reference_validator-0.2.1rc1/PKG-INFO +54 -2
- linkml_reference_validator-0.2.0rc1/PKG-INFO → linkml_reference_validator-0.2.1rc1/README.md +33 -22
- linkml_reference_validator-0.2.1rc1/copilot-instructions.md +1 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/concepts/content-types.md +61 -2
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/concepts/editorial-conventions.md +5 -1
- linkml_reference_validator-0.2.1rc1/docs/how-to/fetch-full-text-and-pdfs.md +139 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/repair-validation-errors.md +1 -1
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/use-local-files-and-urls.md +6 -2
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/validate-dois.md +95 -7
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/validate-urls.md +13 -7
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/notebooks/01_getting_started.ipynb +3 -2
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/notebooks/02_advanced_usage.ipynb +3 -2
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/notebooks/04_obo_validation.ipynb +4 -3
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/notebooks/05_geo_validation.ipynb +5 -4
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/reference/cli.md +243 -21
- linkml_reference_validator-0.2.1rc1/docs/setup-guide.md +699 -0
- linkml_reference_validator-0.2.1rc1/docs/superpowers/plans/2026-06-12-modular-pdf-fulltext-fetching.md +2969 -0
- linkml_reference_validator-0.2.1rc1/docs/superpowers/specs/2026-06-12-modular-pdf-fulltext-fetching-design.md +260 -0
- linkml_reference_validator-0.2.1rc1/docs/troubleshooting.md +706 -0
- linkml_reference_validator-0.2.1rc1/docs/tutorials/complete-workflow.md +900 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/mkdocs.yml +4 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/pyproject.toml +1 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/cli/lookup.py +38 -1
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/cli/shared.py +8 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/cli/validate.py +66 -33
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/acquire.py +134 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/extract/__init__.py +16 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/extract/base.py +64 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/extract/html.py +46 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/extract/pdf.py +67 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/extract/xml.py +46 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/fulltext/__init__.py +19 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/fulltext/base.py +70 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/fulltext/json_api.py +135 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/fulltext/loader.py +94 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/fulltext/openalex.py +70 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/fulltext/pmc.py +124 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/fulltext/unpaywall.py +69 -0
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/identifiers.py +58 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/reference_fetcher.py +324 -1
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/clinicaltrials.py +15 -2
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/sources/doi.py +492 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/entrez.py +29 -4
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/pmid.py +110 -21
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/url.py +43 -12
- linkml_reference_validator-0.2.1rc1/src/linkml_reference_validator/etl/sources/utils.py +106 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/field_detection.py +43 -2
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/models.py +195 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/plugins/reference_validation_plugin.py +26 -12
- linkml_reference_validator-0.2.1rc1/tests/data/test_data_deep_nesting_invalid.yaml +17 -0
- linkml_reference_validator-0.2.1rc1/tests/data/test_data_deep_nesting_valid.yaml +23 -0
- linkml_reference_validator-0.2.1rc1/tests/data/test_schema_deep_nesting.yaml +74 -0
- linkml_reference_validator-0.2.1rc1/tests/test_acquire.py +116 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_cli.py +266 -0
- linkml_reference_validator-0.2.1rc1/tests/test_deep_nesting.py +401 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_e2e_integration.py +58 -0
- linkml_reference_validator-0.2.1rc1/tests/test_extractors.py +104 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_field_detection.py +47 -0
- linkml_reference_validator-0.2.1rc1/tests/test_fulltext_loader.py +97 -0
- linkml_reference_validator-0.2.1rc1/tests/test_fulltext_providers.py +175 -0
- linkml_reference_validator-0.2.1rc1/tests/test_identifiers.py +31 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_models.py +38 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_optional_linkml_dependency.py +31 -3
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_plugin_integration.py +2 -1
- linkml_reference_validator-0.2.1rc1/tests/test_reference_fetcher.py +995 -0
- linkml_reference_validator-0.2.1rc1/tests/test_source_utils.py +193 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_sources.py +149 -33
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_supporting_text_validator.py +20 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_title_validation.py +2 -0
- linkml_reference_validator-0.2.1rc1/tests/test_unknown_slot_traversal.py +131 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_validation_config.py +17 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/uv.lock +14 -0
- linkml_reference_validator-0.2.0rc1/.claude/settings.json +0 -14
- linkml_reference_validator-0.2.0rc1/.github/workflows/claude-code-review.yml +0 -78
- linkml_reference_validator-0.2.0rc1/.github/workflows/claude.yml +0 -90
- linkml_reference_validator-0.2.0rc1/src/linkml_reference_validator/etl/sources/doi.py +0 -182
- linkml_reference_validator-0.2.0rc1/tests/test_reference_fetcher.py +0 -312
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.claude/.mcp.json +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.copier-answers.yml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.editorconfig +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/actions/claude-code-action/action.yml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/actions/claude-issue-summarize-action/action.yml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/actions/claude-issue-triage-action/action.yml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/ai-controllers.json +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/copilot-instructions.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/copilot-setup-steps.yml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/dependabot.yml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/workflows/claude-issue-summarize.yml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/workflows/claude-issue-triage.yml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/workflows/deploy-docs.yaml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/workflows/main.yaml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.github/workflows/pypi-publish.yaml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.pre-commit-config.yaml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/.yamllint.yaml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/CLAUDE.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/CODE_OF_CONDUCT.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/CONTRIBUTING.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/LICENSE +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/REVIEW2.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/REVIEW3.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/ai.just +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/about.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/concepts/how-it-works.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/elements/.gitkeep +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/add-reference-source.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/skip-unsupported-references.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/validate-clinical-trials.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/validate-entrez.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/validate-obo-files.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/how-to/validate-titles.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/index.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/notebooks/03_python_api.ipynb +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/quickstart.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/templates-linkml/README.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/docs/todo.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/examples/README.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/justfile +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/mypy.ini +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/notebooks/01_getting_started.ipynb +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/notebooks/02_advanced_usage.ipynb +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/notebooks/03_python_api.ipynb +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/notebooks/README.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/project/README.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/project.justfile +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/pytest.ini +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/__init__.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/_version.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/cli/__init__.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/cli/cache.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/cli/repair.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/__init__.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/__init__.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/base.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/file.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/json_api.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/sources/loader.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/etl/text_extractor.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/plugins/REVIEW.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/plugins/__init__.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/validation/__init__.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/validation/fuzzy_text_utils.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/validation/repairer.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/src/linkml_reference_validator/validation/supporting_text_validator.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/__init__.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/conftest.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/data/test_data_invalid.yaml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/data/test_data_valid.yaml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/data/test_schema.yaml +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/CLINICALTRIALS_NCT00000001.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/GEO_GSE12345.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMC_TEST001.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMC_TEST001.txt +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMC_TEST002.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMC_TEST002.txt +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMID_TEST001.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMID_TEST001.txt +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMID_TEST002.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMID_TEST002.txt +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMID_TEST003.md +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/PMID_TEST003.txt +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/fixtures/sample.obo +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_cli_lookup.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_cli_repair.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_clinicaltrials_integration.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_fuzzy_suggestions.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_geo_integration.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_json_api_source.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_pmc_fulltext.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_real_validation.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_repair.py +0 -0
- {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc1}/tests/test_text_extractor.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "odkfull-codespace",
|
|
3
|
+
// consider also: Codespaces Prebuilds
|
|
4
|
+
"image": "obolibrary/odkfull:latest",
|
|
5
|
+
|
|
6
|
+
// Mount your repo where ODK expects it
|
|
7
|
+
"workspaceMount": "source=${localWorkspaceFolder},target=/work,type=bind,consistency=cached",
|
|
8
|
+
"workspaceFolder": "/work",
|
|
9
|
+
|
|
10
|
+
// Keep VS Code in control of the container command (safer than using image ENTRYPOINT)
|
|
11
|
+
"overrideCommand": true,
|
|
12
|
+
|
|
13
|
+
// Be root so you can add small tools if you need them
|
|
14
|
+
"remoteUser": "root",
|
|
15
|
+
//"features": {
|
|
16
|
+
// "ghcr.io/devcontainers/features/node:1": { "version": "lts" }
|
|
17
|
+
//},
|
|
18
|
+
"extensions": [
|
|
19
|
+
"anthropic.claude-code",
|
|
20
|
+
"ms-python.python",
|
|
21
|
+
"ms-azuretools.vscode-docker"
|
|
22
|
+
],
|
|
23
|
+
"remoteEnv": {
|
|
24
|
+
"TEST_ENV": "123"
|
|
25
|
+
},
|
|
26
|
+
"postCreateCommand": "bash .devcontainer/post_create.sh"
|
|
27
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Claude Code Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize]
|
|
6
|
+
# Optional: Only run on specific file changes
|
|
7
|
+
# paths:
|
|
8
|
+
# - "src/**/*.ts"
|
|
9
|
+
# - "src/**/*.tsx"
|
|
10
|
+
# - "src/**/*.js"
|
|
11
|
+
# - "src/**/*.jsx"
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude-review:
|
|
15
|
+
# Optional: Filter by PR author
|
|
16
|
+
# if: |
|
|
17
|
+
# github.event.pull_request.user.login == 'external-contributor' ||
|
|
18
|
+
# github.event.pull_request.user.login == 'new-developer' ||
|
|
19
|
+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
20
|
+
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pull-requests: read
|
|
25
|
+
issues: read
|
|
26
|
+
id-token: write
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout repository
|
|
30
|
+
uses: actions/checkout@v6
|
|
31
|
+
with:
|
|
32
|
+
fetch-depth: 1
|
|
33
|
+
|
|
34
|
+
- name: Run Claude Code Review
|
|
35
|
+
id: claude-review
|
|
36
|
+
uses: anthropics/claude-code-action@v1
|
|
37
|
+
with:
|
|
38
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
39
|
+
|
|
40
|
+
# In v1, providing a `prompt` runs in automation mode, which does NOT
|
|
41
|
+
# post a comment by default. track_progress restores the v0.x
|
|
42
|
+
# "post the review as a PR comment" behavior and auto-injects PR context.
|
|
43
|
+
track_progress: true
|
|
44
|
+
|
|
45
|
+
# Prompt for the automated review (no @claude mention needed).
|
|
46
|
+
prompt: |
|
|
47
|
+
Please review this pull request and provide feedback on:
|
|
48
|
+
- Code quality and best practices
|
|
49
|
+
- Potential bugs or issues
|
|
50
|
+
- Performance considerations
|
|
51
|
+
- Security concerns
|
|
52
|
+
- Test coverage
|
|
53
|
+
|
|
54
|
+
Be constructive and helpful in your feedback.
|
|
55
|
+
|
|
56
|
+
# Args passed through to the Claude CLI. Use the `opus` alias rather
|
|
57
|
+
# than a dated model id so the workflow tracks the current Opus and
|
|
58
|
+
# won't 404 when a specific version is retired.
|
|
59
|
+
claude_args: |
|
|
60
|
+
--model opus
|
|
61
|
+
|
|
@@ -35,15 +35,23 @@ jobs:
|
|
|
35
35
|
|
|
36
36
|
- name: Run Claude Code Ontology Review
|
|
37
37
|
id: claude-review
|
|
38
|
-
uses: anthropics/claude-code-action@
|
|
38
|
+
uses: anthropics/claude-code-action@v1
|
|
39
39
|
with:
|
|
40
40
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
|
|
42
|
+
# In v1, providing a `prompt` runs in automation mode, which does NOT
|
|
43
|
+
# post a comment by default. track_progress restores the v0.x
|
|
44
|
+
# "post the review as a PR comment" behavior and auto-injects PR context.
|
|
45
|
+
track_progress: true
|
|
44
46
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
+
# Model is passed through to the Claude CLI via claude_args. Use the
|
|
48
|
+
# `opus` alias so the workflow tracks the current Opus and won't 404
|
|
49
|
+
# when a specific dated model id is retired.
|
|
50
|
+
claude_args: |
|
|
51
|
+
--model opus
|
|
52
|
+
|
|
53
|
+
# Prompt for the automated review (no @claude mention needed)
|
|
54
|
+
prompt: |
|
|
47
55
|
REPO: ${{ github.repository }}
|
|
48
56
|
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
49
57
|
|
|
@@ -90,26 +98,4 @@ jobs:
|
|
|
90
98
|
|
|
91
99
|
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
|
|
92
100
|
use_sticky_comment: true
|
|
93
|
-
|
|
94
|
-
# Optional: Customize review based on file types
|
|
95
|
-
# direct_prompt: |
|
|
96
|
-
# Review this PR focusing on:
|
|
97
|
-
# - For TypeScript files: Type safety and proper interface usage
|
|
98
|
-
# - For API endpoints: Security, input validation, and error handling
|
|
99
|
-
# - For React components: Performance, accessibility, and best practices
|
|
100
|
-
# - For tests: Coverage, edge cases, and test quality
|
|
101
|
-
|
|
102
|
-
# Optional: Different prompts for different authors
|
|
103
|
-
# direct_prompt: |
|
|
104
|
-
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
|
|
105
|
-
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
|
|
106
|
-
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
|
|
107
|
-
|
|
108
|
-
# Optional: Add specific tools for running tests or linting
|
|
109
|
-
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
|
|
110
|
-
|
|
111
|
-
# Optional: Skip review for certain conditions
|
|
112
|
-
# if: |
|
|
113
|
-
# !contains(github.event.pull_request.title, '[skip-review]') &&
|
|
114
|
-
# !contains(github.event.pull_request.title, '[WIP]')
|
|
115
101
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
pull_request_review_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
issues:
|
|
9
|
+
types: [opened, assigned]
|
|
10
|
+
pull_request_review:
|
|
11
|
+
types: [submitted]
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude:
|
|
15
|
+
if: |
|
|
16
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
17
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
18
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
19
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: write
|
|
23
|
+
pull-requests: write
|
|
24
|
+
issues: write
|
|
25
|
+
id-token: write
|
|
26
|
+
actions: read # Required for Claude to read CI results on PRs
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout repository
|
|
29
|
+
uses: actions/checkout@v6
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 1
|
|
32
|
+
|
|
33
|
+
- name: Install uv
|
|
34
|
+
uses: astral-sh/setup-uv@v7
|
|
35
|
+
|
|
36
|
+
- name: Install python tools
|
|
37
|
+
run: |
|
|
38
|
+
uv sync
|
|
39
|
+
|
|
40
|
+
- name: Run Claude Code
|
|
41
|
+
id: claude
|
|
42
|
+
uses: anthropics/claude-code-action@v1
|
|
43
|
+
|
|
44
|
+
with:
|
|
45
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
46
|
+
|
|
47
|
+
# This is an optional setting that allows Claude to read CI results on PRs
|
|
48
|
+
additional_permissions: |
|
|
49
|
+
actions: read
|
|
50
|
+
|
|
51
|
+
# In v1 the model, MCP servers, and allowed tools are passed through to
|
|
52
|
+
# the Claude CLI via claude_args (the v0.x model / mcp_config /
|
|
53
|
+
# allowed_tools inputs were removed). The `opus` alias tracks the
|
|
54
|
+
# current Opus so this won't 404 when a dated model id is retired.
|
|
55
|
+
claude_args: |
|
|
56
|
+
--model opus
|
|
57
|
+
--mcp-config '{"mcpServers":{"ols":{"command":"uvx","args":["ols-mcp"]},"sequential-thinking":{"command":"npx","args":["-y","@modelcontextprotocol/server-sequential-thinking"]}}}'
|
|
58
|
+
--allowedTools "Bash(*),FileEdit,Edit,MultiEdit,WebSearch,WebFetch,mcp__ols_mcp__search_all_ontologies,mcp__ols_mcp__get_terms_from_ontology"
|
|
59
|
+
|
|
60
|
+
# Optional: Customize the trigger phrase (default: @claude)
|
|
61
|
+
# trigger_phrase: "/claude"
|
|
62
|
+
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
|
|
30
30
|
- name: Check for qualifying mention
|
|
31
31
|
id: check
|
|
32
|
-
uses: actions/github-script@
|
|
32
|
+
uses: actions/github-script@v9
|
|
33
33
|
with:
|
|
34
34
|
github-token: ${{ secrets.PAT_FOR_PR }} # Use PAT instead of default token
|
|
35
35
|
script: |
|
|
@@ -131,7 +131,7 @@ jobs:
|
|
|
131
131
|
ls -alt ${{ env.TOOLS_DIR }}
|
|
132
132
|
|
|
133
133
|
- name: Install uv
|
|
134
|
-
uses: astral-sh/setup-uv@
|
|
134
|
+
uses: astral-sh/setup-uv@v7
|
|
135
135
|
|
|
136
136
|
- name: Install python tools
|
|
137
137
|
run: |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
CLAUDE.md
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Bug: GEOSource fails to fetch GEO datasets
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
The `GEOSource` class in `linkml_reference_validator/etl/sources/entrez.py` cannot fetch GEO dataset metadata because it passes GSE accessions directly to `esummary`, but the GDS Entrez database requires numeric UIDs.
|
|
6
|
+
|
|
7
|
+
## Error Observed
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
WARNING:linkml_reference_validator.etl.sources.entrez:Failed to fetch Entrez summary for GEO:GSE67472: Invalid uid GSE67472 at position= 0
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Root Cause
|
|
14
|
+
|
|
15
|
+
The `EntrezSummarySource.fetch()` method calls:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
handle = Entrez.esummary(db=self.ENTREZ_DB, id=identifier)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For GEO, this becomes `esummary(db='gds', id='GSE67472')`, but the GDS database doesn't accept accession numbers as IDs - it requires numeric UIDs like `200067472`.
|
|
22
|
+
|
|
23
|
+
## Proof of Concept
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from Bio import Entrez
|
|
27
|
+
Entrez.email = 'test@example.com'
|
|
28
|
+
|
|
29
|
+
# This FAILS - accession not accepted as UID
|
|
30
|
+
handle = Entrez.esummary(db='gds', id='GSE67472')
|
|
31
|
+
# Error: Invalid uid GSE67472 at position=0
|
|
32
|
+
|
|
33
|
+
# This WORKS - use esearch first to get UID
|
|
34
|
+
handle = Entrez.esearch(db='gds', term='GSE67472[Accession]')
|
|
35
|
+
result = Entrez.read(handle)
|
|
36
|
+
handle.close()
|
|
37
|
+
# result['IdList'] = ['200067472', ...]
|
|
38
|
+
|
|
39
|
+
uid = result['IdList'][0] # '200067472'
|
|
40
|
+
|
|
41
|
+
handle = Entrez.esummary(db='gds', id=uid)
|
|
42
|
+
summary = Entrez.read(handle)
|
|
43
|
+
handle.close()
|
|
44
|
+
print(summary[0].get('title'))
|
|
45
|
+
# Output: "Airway epithelial gene expression in asthma versus healthy controls"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Proposed Fix
|
|
49
|
+
|
|
50
|
+
Override `fetch()` in `GEOSource` to add an `esearch` step that converts accessions to UIDs:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
@ReferenceSourceRegistry.register
|
|
54
|
+
class GEOSource(EntrezSummarySource):
|
|
55
|
+
"""Fetch GEO series and dataset summaries from Entrez."""
|
|
56
|
+
|
|
57
|
+
PREFIX = "GEO"
|
|
58
|
+
ENTREZ_DB = "gds"
|
|
59
|
+
TITLE_FIELDS = ("title", "description", "summary")
|
|
60
|
+
CONTENT_FIELDS = ("summary", "description", "title")
|
|
61
|
+
ID_PATTERNS = (r"^GSE\d+$", r"^GDS\d+$")
|
|
62
|
+
|
|
63
|
+
def fetch(
|
|
64
|
+
self, identifier: str, config: ReferenceValidationConfig
|
|
65
|
+
) -> Optional[ReferenceContent]:
|
|
66
|
+
"""Fetch GEO dataset metadata, converting accession to UID first."""
|
|
67
|
+
Entrez.email = config.email
|
|
68
|
+
time.sleep(config.rate_limit_delay)
|
|
69
|
+
|
|
70
|
+
# Convert accession to UID via esearch
|
|
71
|
+
uid = self._accession_to_uid(identifier)
|
|
72
|
+
if not uid:
|
|
73
|
+
logger.warning(f"Could not find GDS UID for {identifier}")
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
# Now fetch summary with numeric UID
|
|
77
|
+
handle = None
|
|
78
|
+
try:
|
|
79
|
+
handle = Entrez.esummary(db=self.ENTREZ_DB, id=uid)
|
|
80
|
+
records = Entrez.read(handle)
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
logger.warning(f"Failed to fetch Entrez summary for {self.prefix()}:{identifier}: {exc}")
|
|
83
|
+
return None
|
|
84
|
+
finally:
|
|
85
|
+
if handle is not None:
|
|
86
|
+
handle.close()
|
|
87
|
+
|
|
88
|
+
record = self._extract_record(records)
|
|
89
|
+
if not record:
|
|
90
|
+
logger.warning(f"No Entrez summary found for {self.prefix()}:{identifier}")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
title = self._get_first_field_value(record, self.TITLE_FIELDS)
|
|
94
|
+
content = self._get_first_field_value(record, self.CONTENT_FIELDS)
|
|
95
|
+
content_type = "summary" if content else "unavailable"
|
|
96
|
+
|
|
97
|
+
return ReferenceContent(
|
|
98
|
+
reference_id=f"{self.prefix()}:{identifier}",
|
|
99
|
+
title=title,
|
|
100
|
+
content=content,
|
|
101
|
+
content_type=content_type,
|
|
102
|
+
metadata={"entrez_db": self.ENTREZ_DB, "entrez_uid": uid},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def _accession_to_uid(self, accession: str) -> Optional[str]:
|
|
106
|
+
"""Convert a GEO accession (GSE/GDS) to its Entrez UID."""
|
|
107
|
+
handle = None
|
|
108
|
+
try:
|
|
109
|
+
handle = Entrez.esearch(db=self.ENTREZ_DB, term=f"{accession}[Accession]")
|
|
110
|
+
result = Entrez.read(handle)
|
|
111
|
+
if result.get("IdList"):
|
|
112
|
+
return result["IdList"][0]
|
|
113
|
+
except Exception as exc:
|
|
114
|
+
logger.warning(f"esearch failed for {accession}: {exc}")
|
|
115
|
+
finally:
|
|
116
|
+
if handle is not None:
|
|
117
|
+
handle.close()
|
|
118
|
+
return None
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Testing
|
|
122
|
+
|
|
123
|
+
After the fix, validation should catch title mismatches like:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
# In kb/disorders/Asthma.yaml
|
|
127
|
+
datasets:
|
|
128
|
+
- accession: geo:GSE67472
|
|
129
|
+
title: xxxAirway epithelial gene expression in asthma versus healthy controls # Wrong!
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Expected validation error:
|
|
133
|
+
```
|
|
134
|
+
[ERROR] Title mismatch for geo:GSE67472
|
|
135
|
+
Expected: "Airway epithelial gene expression in asthma versus healthy controls"
|
|
136
|
+
Found: "xxxAirway epithelial gene expression in asthma versus healthy controls"
|
|
137
|
+
```
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Bug Report: Zenodo DOIs and bare HTTPS URLs not handled
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Two related bugs prevent fetching Zenodo DOIs and bare HTTPS URLs:
|
|
6
|
+
|
|
7
|
+
1. **Zenodo DOIs return 404**: DOIs like `DOI:10.5281/zenodo.17993529` fail because the DOI source only queries Crossref, but Zenodo DOIs are registered with DataCite.
|
|
8
|
+
|
|
9
|
+
2. **Bare HTTPS URLs not recognized**: URLs like `https://doi.org/10.5281/zenodo.17993529` fail because the prefix parser extracts `https` as the prefix, which gets uppercased to `HTTPS` and doesn't match any source.
|
|
10
|
+
|
|
11
|
+
## Reproduction
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Both of these fail:
|
|
15
|
+
linkml-reference-validator lookup --no-cache https://doi.org/10.5281/zenodo.17993529
|
|
16
|
+
# Output: No source found for reference type: HTTPS://doi.org/10.5281/zenodo.17993529
|
|
17
|
+
|
|
18
|
+
linkml-reference-validator lookup --no-cache DOI:10.5281/zenodo.17993529
|
|
19
|
+
# Output: Failed to fetch DOI:10.5281/zenodo.17993529 - status 404
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Root Cause Analysis
|
|
23
|
+
|
|
24
|
+
### Bug 1: URL Prefix Handling
|
|
25
|
+
|
|
26
|
+
In `src/linkml_reference_validator/etl/reference_fetcher.py`:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
# Line 141 - regex parses https://example.com as:
|
|
30
|
+
# prefix = "https"
|
|
31
|
+
# identifier = "//example.com"
|
|
32
|
+
match = re.match(r"^([A-Za-z_]+)[:\s]+(.+)$", stripped)
|
|
33
|
+
|
|
34
|
+
# Lines 174-178 - prefix normalization uppercases non-file/url prefixes:
|
|
35
|
+
def _normalize_prefix(self, prefix: str) -> str:
|
|
36
|
+
if prefix.lower() in ("file", "url"):
|
|
37
|
+
return prefix.lower()
|
|
38
|
+
return prefix.upper() # "https" becomes "HTTPS"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No source is registered for `HTTPS:`, so lookup fails.
|
|
42
|
+
|
|
43
|
+
### Bug 2: Crossref-only DOI Resolution
|
|
44
|
+
|
|
45
|
+
In `src/linkml_reference_validator/etl/sources/doi.py`:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
# Line 72 - only queries Crossref API
|
|
49
|
+
url = f"https://api.crossref.org/works/{doi}"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Zenodo DOIs (prefix `10.5281/zenodo.*`) are registered with **DataCite**, not Crossref. Crossref returns 404 for these DOIs.
|
|
53
|
+
|
|
54
|
+
## Solution
|
|
55
|
+
|
|
56
|
+
### Fix 1: Handle bare HTTP/HTTPS URLs
|
|
57
|
+
|
|
58
|
+
In `reference_fetcher.py`, add detection for bare URLs before the prefix regex matching:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
def _parse_reference_id(self, reference_id: str) -> tuple[str, str]:
|
|
62
|
+
stripped = reference_id.strip()
|
|
63
|
+
|
|
64
|
+
# NEW: Handle bare URLs (http:// or https://)
|
|
65
|
+
if stripped.lower().startswith(("http://", "https://")):
|
|
66
|
+
return "url", stripped
|
|
67
|
+
|
|
68
|
+
# Existing prefix:identifier parsing...
|
|
69
|
+
match = re.match(r"^([A-Za-z_]+)[:\s]+(.+)$", stripped)
|
|
70
|
+
# ... rest of method
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Fix 2: Add DataCite fallback for DOIs
|
|
74
|
+
|
|
75
|
+
In `doi.py`, try Crossref first, then fall back to DataCite if 404:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
def fetch(
|
|
79
|
+
self, identifier: str, config: ReferenceValidationConfig
|
|
80
|
+
) -> Optional[ReferenceContent]:
|
|
81
|
+
doi = identifier.strip()
|
|
82
|
+
time.sleep(config.rate_limit_delay)
|
|
83
|
+
|
|
84
|
+
# Try Crossref first
|
|
85
|
+
result = self._fetch_from_crossref(doi, config)
|
|
86
|
+
if result:
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
# Fall back to DataCite (handles Zenodo, Figshare, etc.)
|
|
90
|
+
return self._fetch_from_datacite(doi, config)
|
|
91
|
+
|
|
92
|
+
def _fetch_from_crossref(self, doi: str, config: ReferenceValidationConfig) -> Optional[ReferenceContent]:
|
|
93
|
+
"""Fetch from Crossref API."""
|
|
94
|
+
url = f"https://api.crossref.org/works/{doi}"
|
|
95
|
+
headers = {
|
|
96
|
+
"User-Agent": f"linkml-reference-validator/1.0 (mailto:{config.email})",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
response = requests.get(url, headers=headers, timeout=30)
|
|
100
|
+
if response.status_code != 200:
|
|
101
|
+
logger.debug(f"Crossref returned {response.status_code} for DOI:{doi}, will try DataCite")
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
data = response.json()
|
|
105
|
+
if data.get("status") != "ok":
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
message = data.get("message", {})
|
|
109
|
+
return self._parse_crossref_response(doi, message)
|
|
110
|
+
|
|
111
|
+
def _fetch_from_datacite(self, doi: str, config: ReferenceValidationConfig) -> Optional[ReferenceContent]:
|
|
112
|
+
"""Fetch from DataCite API (handles Zenodo, Figshare, Dryad, etc.)."""
|
|
113
|
+
url = f"https://api.datacite.org/dois/{doi}"
|
|
114
|
+
headers = {
|
|
115
|
+
"User-Agent": f"linkml-reference-validator/1.0 (mailto:{config.email})",
|
|
116
|
+
"Accept": "application/json",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
response = requests.get(url, headers=headers, timeout=30)
|
|
120
|
+
if response.status_code != 200:
|
|
121
|
+
logger.warning(f"Failed to fetch DOI:{doi} from both Crossref and DataCite")
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
data = response.json()
|
|
125
|
+
attributes = data.get("data", {}).get("attributes", {})
|
|
126
|
+
|
|
127
|
+
# Extract title
|
|
128
|
+
titles = attributes.get("titles", [])
|
|
129
|
+
title = titles[0].get("title", "") if titles else ""
|
|
130
|
+
|
|
131
|
+
# Extract authors
|
|
132
|
+
authors = self._parse_datacite_creators(attributes.get("creators", []))
|
|
133
|
+
|
|
134
|
+
# Extract year
|
|
135
|
+
year = str(attributes.get("publicationYear", ""))
|
|
136
|
+
|
|
137
|
+
# Extract abstract from descriptions
|
|
138
|
+
abstract = ""
|
|
139
|
+
for desc in attributes.get("descriptions", []):
|
|
140
|
+
if desc.get("descriptionType") == "Abstract":
|
|
141
|
+
abstract = desc.get("description", "")
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
# Publisher as journal equivalent
|
|
145
|
+
publisher = attributes.get("publisher", "")
|
|
146
|
+
|
|
147
|
+
return ReferenceContent(
|
|
148
|
+
reference_id=f"DOI:{doi}",
|
|
149
|
+
title=title,
|
|
150
|
+
content=abstract if abstract else None,
|
|
151
|
+
content_type="abstract_only" if abstract else "unavailable",
|
|
152
|
+
authors=authors,
|
|
153
|
+
journal=publisher,
|
|
154
|
+
year=year,
|
|
155
|
+
doi=doi,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def _parse_datacite_creators(self, creators: list) -> list[str]:
|
|
159
|
+
"""Parse creator list from DataCite response."""
|
|
160
|
+
result = []
|
|
161
|
+
for creator in creators:
|
|
162
|
+
# DataCite may have full name or given/family
|
|
163
|
+
name = creator.get("name")
|
|
164
|
+
if not name:
|
|
165
|
+
given = creator.get("givenName", "")
|
|
166
|
+
family = creator.get("familyName", "")
|
|
167
|
+
if given and family:
|
|
168
|
+
name = f"{given} {family}"
|
|
169
|
+
elif family:
|
|
170
|
+
name = family
|
|
171
|
+
elif given:
|
|
172
|
+
name = given
|
|
173
|
+
if name:
|
|
174
|
+
result.append(name)
|
|
175
|
+
return result
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## DataCite API Response Structure
|
|
179
|
+
|
|
180
|
+
The DataCite API at `https://api.datacite.org/dois/{doi}` returns:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"data": {
|
|
185
|
+
"attributes": {
|
|
186
|
+
"doi": "10.5281/zenodo.17993529",
|
|
187
|
+
"titles": [{"title": "Gene Ontology Curators AI Workshop (Part 1)"}],
|
|
188
|
+
"creators": [
|
|
189
|
+
{
|
|
190
|
+
"name": "Mungall, Christopher",
|
|
191
|
+
"givenName": "Christopher",
|
|
192
|
+
"familyName": "Mungall",
|
|
193
|
+
"affiliation": [{"name": "Lawrence Berkeley National Laboratory"}]
|
|
194
|
+
}
|
|
195
|
+
],
|
|
196
|
+
"publicationYear": 2025,
|
|
197
|
+
"publisher": "Zenodo",
|
|
198
|
+
"descriptions": [
|
|
199
|
+
{
|
|
200
|
+
"description": "Workshop aims to equip curators with...",
|
|
201
|
+
"descriptionType": "Abstract"
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Test Cases
|
|
210
|
+
|
|
211
|
+
Add tests for both fixes:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
@pytest.mark.integration
|
|
215
|
+
def test_zenodo_doi():
|
|
216
|
+
"""Test that Zenodo DOIs are fetched via DataCite."""
|
|
217
|
+
config = ReferenceValidationConfig()
|
|
218
|
+
fetcher = ReferenceFetcher(config)
|
|
219
|
+
ref = fetcher.fetch("DOI:10.5281/zenodo.17993529", force_refresh=True)
|
|
220
|
+
assert ref is not None
|
|
221
|
+
assert "Gene Ontology" in ref.title
|
|
222
|
+
assert ref.year == "2025"
|
|
223
|
+
|
|
224
|
+
@pytest.mark.integration
|
|
225
|
+
def test_bare_https_url():
|
|
226
|
+
"""Test that bare HTTPS URLs are handled."""
|
|
227
|
+
config = ReferenceValidationConfig()
|
|
228
|
+
fetcher = ReferenceFetcher(config)
|
|
229
|
+
ref = fetcher.fetch("https://example.com", force_refresh=True)
|
|
230
|
+
assert ref is not None
|
|
231
|
+
assert ref.reference_id == "url:https://example.com"
|
|
232
|
+
|
|
233
|
+
def test_parse_bare_url():
|
|
234
|
+
"""Test URL parsing for bare HTTP/HTTPS URLs."""
|
|
235
|
+
config = ReferenceValidationConfig()
|
|
236
|
+
fetcher = ReferenceFetcher(config)
|
|
237
|
+
|
|
238
|
+
assert fetcher._parse_reference_id("https://example.com") == ("url", "https://example.com")
|
|
239
|
+
assert fetcher._parse_reference_id("http://example.com/path") == ("url", "http://example.com/path")
|
|
240
|
+
assert fetcher._parse_reference_id("url:https://example.com") == ("url", "https://example.com")
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Files to Modify
|
|
244
|
+
|
|
245
|
+
1. `src/linkml_reference_validator/etl/reference_fetcher.py` - Add bare URL detection
|
|
246
|
+
2. `src/linkml_reference_validator/etl/sources/doi.py` - Add DataCite fallback
|
|
247
|
+
3. `tests/test_reference_fetcher.py` - Add unit tests
|
|
248
|
+
4. `tests/test_doi_source.py` or similar - Add integration tests
|
|
249
|
+
|
|
250
|
+
## Verification
|
|
251
|
+
|
|
252
|
+
After fixes, these should work:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
linkml-reference-validator lookup DOI:10.5281/zenodo.17993529
|
|
256
|
+
linkml-reference-validator lookup https://doi.org/10.5281/zenodo.17993529
|
|
257
|
+
linkml-reference-validator lookup https://zenodo.org/records/17993529
|
|
258
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# CLI Issues
|
|
2
|
+
|
|
3
|
+
## 1) `validate data` summary label is misleading
|
|
4
|
+
|
|
5
|
+
**Current behavior**
|
|
6
|
+
The CLI prints:
|
|
7
|
+
```
|
|
8
|
+
Validation Summary:
|
|
9
|
+
Total checks: <N>
|
|
10
|
+
```
|
|
11
|
+
where `<N>` is the **number of issues** (`len(all_results)`), not the number of checks performed. This makes successful runs look like they performed zero checks (e.g., “Total checks: 0”), which is confusing.
|
|
12
|
+
|
|
13
|
+
**Where**
|
|
14
|
+
`linkml_reference_validator/cli/validate.py` (near the end of `validate data` command).
|
|
15
|
+
|
|
16
|
+
**Repro**
|
|
17
|
+
Run with a file that has valid evidence/snippets and get:
|
|
18
|
+
```
|
|
19
|
+
Validation Summary:
|
|
20
|
+
Total checks: 0
|
|
21
|
+
All validations passed!
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Suggested fix**
|
|
25
|
+
Either:
|
|
26
|
+
- Rename the label to **“Total issues”**, or
|
|
27
|
+
- Add two counters:
|
|
28
|
+
- **Checks performed** (number of excerpt/reference pairs found)
|
|
29
|
+
- **Issues found** (`len(all_results)`)
|
|
30
|
+
|
|
31
|
+
This would make the summary accurate and less surprising.
|