orfdata 0.1.0__tar.gz → 0.3.0__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.
Files changed (52) hide show
  1. orfdata-0.3.0/MANIFEST.in +3 -0
  2. {orfdata-0.1.0 → orfdata-0.3.0}/PKG-INFO +12 -4
  3. {orfdata-0.1.0 → orfdata-0.3.0}/README.md +6 -3
  4. orfdata-0.3.0/deploy.sh +18 -0
  5. orfdata-0.3.0/docs/design.md +84 -0
  6. orfdata-0.3.0/docs/measurements.md +134 -0
  7. orfdata-0.3.0/docs/reference.md +145 -0
  8. orfdata-0.3.0/docs/species.md +58 -0
  9. orfdata-0.3.0/lint.sh +3 -0
  10. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/__init__.py +15 -0
  11. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/catalog.py +17 -5
  12. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/cli.py +16 -2
  13. orfdata-0.3.0/orfdata/data/datasets.json +1 -0
  14. orfdata-0.3.0/orfdata/dataset.py +281 -0
  15. orfdata-0.3.0/orfdata/dataset_cli.py +80 -0
  16. orfdata-0.3.0/orfdata/datasets.py +172 -0
  17. orfdata-0.3.0/orfdata/measurements.py +364 -0
  18. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/model.py +37 -3
  19. orfdata-0.3.0/orfdata/reference.py +233 -0
  20. orfdata-0.3.0/orfdata/version.py +1 -0
  21. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata.egg-info/PKG-INFO +12 -4
  22. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata.egg-info/SOURCES.txt +22 -1
  23. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata.egg-info/requires.txt +7 -0
  24. {orfdata-0.1.0 → orfdata-0.3.0}/pyproject.toml +4 -2
  25. orfdata-0.3.0/test.sh +3 -0
  26. orfdata-0.3.0/tests/data/references/README.md +24 -0
  27. orfdata-0.3.0/tests/data/references/canis_lupus_familiaris.json +148 -0
  28. orfdata-0.3.0/tests/data/references/homo_sapiens.json +148 -0
  29. orfdata-0.3.0/tests/data/references/mus_musculus.json +148 -0
  30. orfdata-0.3.0/tests/data/references/saccharomyces_cerevisiae.json +72 -0
  31. orfdata-0.3.0/tests/test_measurements.py +356 -0
  32. orfdata-0.3.0/tests/test_reference_context.py +230 -0
  33. orfdata-0.1.0/orfdata/version.py +0 -1
  34. {orfdata-0.1.0 → orfdata-0.3.0}/LICENSE +0 -0
  35. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/build.py +0 -0
  36. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/data/orf-evidence/AUTHOR-LICENSE.txt +0 -0
  37. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/data/orf-evidence/NOTICE.txt +0 -0
  38. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/data/orf-evidence/catalog.json.gz +0 -0
  39. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/data/orf-evidence/manifest.json +0 -0
  40. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/data/uorf-evidence/AUTHOR-LICENSE.txt +0 -0
  41. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/data/uorf-evidence/NOTICE.txt +0 -0
  42. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/data/uorf-evidence/catalog.json.gz +0 -0
  43. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/data/uorf-evidence/manifest.json +0 -0
  44. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/initiation_context.py +0 -0
  45. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata/sequence.py +0 -0
  46. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata.egg-info/dependency_links.txt +0 -0
  47. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata.egg-info/entry_points.txt +0 -0
  48. {orfdata-0.1.0 → orfdata-0.3.0}/orfdata.egg-info/top_level.txt +0 -0
  49. {orfdata-0.1.0 → orfdata-0.3.0}/setup.cfg +0 -0
  50. {orfdata-0.1.0 → orfdata-0.3.0}/tests/test_initiation_context.py +0 -0
  51. {orfdata-0.1.0 → orfdata-0.3.0}/tests/test_reference_orf.py +0 -0
  52. {orfdata-0.1.0 → orfdata-0.3.0}/tests/test_uorf_catalog.py +0 -0
@@ -0,0 +1,3 @@
1
+ include lint.sh test.sh deploy.sh
2
+ recursive-include docs *.md
3
+ recursive-include tests *.py *.json *.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orfdata
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Reference open reading frames and experimental translation evidence on annotated transcripts
5
5
  Author: OpenVax
6
6
  License-Expression: Apache-2.0
@@ -11,6 +11,10 @@ Provides-Extra: pyensembl
11
11
  Requires-Dist: pyensembl>=2.27.2; extra == "pyensembl"
12
12
  Provides-Extra: build-data
13
13
  Requires-Dist: openpyxl>=3.1; extra == "build-data"
14
+ Provides-Extra: data
15
+ Requires-Dist: datacache<2,>=1.25.1; extra == "data"
16
+ Provides-Extra: columnar
17
+ Requires-Dist: pyarrow>=14; extra == "columnar"
14
18
  Provides-Extra: dev
15
19
  Requires-Dist: pytest; extra == "dev"
16
20
  Requires-Dist: ruff; extra == "dev"
@@ -18,6 +22,7 @@ Requires-Dist: build; extra == "dev"
18
22
  Requires-Dist: twine; extra == "dev"
19
23
  Requires-Dist: openpyxl>=3.1; extra == "dev"
20
24
  Requires-Dist: pyensembl>=2.27.2; extra == "dev"
25
+ Requires-Dist: datacache<2,>=1.25.1; extra == "dev"
21
26
  Dynamic: license-file
22
27
 
23
28
  # orfdata
@@ -90,9 +95,12 @@ RNA products. Reference evidence stays separate from evidence for a variant or
90
95
  fusion. See the [API and integration guide](docs/reference.md).
91
96
 
92
97
  The catalogue API supports other species, with explicit species and assembly
93
- checks. **Bundled evidence is currently human-only**; mouse bindings use synthetic
94
- test data. [Adding another species](docs/reference.md#other-species).
98
+ checks. **Bundled experimental evidence is currently human-only**. Offline tests
99
+ bind real human, mouse, dog and yeast reference slices; those fixtures are not
100
+ new experimental ORF catalogues. [Species and reference identity](docs/species.md).
95
101
 
96
102
  Install with `pip install orfdata[pyensembl]`. See the
103
+ [measurement API](docs/measurements.md) for native units, explicit samples and
104
+ assays, and DataCache-managed downloads, and the
97
105
  [quantitative, multispecies data roadmap](https://github.com/openvax/orfdata/issues/3)
98
- for planned RNA abundance, ribosome occupancy and protein measurements.
106
+ for upcoming RNA abundance, ribosome occupancy and protein data releases.
@@ -68,9 +68,12 @@ RNA products. Reference evidence stays separate from evidence for a variant or
68
68
  fusion. See the [API and integration guide](docs/reference.md).
69
69
 
70
70
  The catalogue API supports other species, with explicit species and assembly
71
- checks. **Bundled evidence is currently human-only**; mouse bindings use synthetic
72
- test data. [Adding another species](docs/reference.md#other-species).
71
+ checks. **Bundled experimental evidence is currently human-only**. Offline tests
72
+ bind real human, mouse, dog and yeast reference slices; those fixtures are not
73
+ new experimental ORF catalogues. [Species and reference identity](docs/species.md).
73
74
 
74
75
  Install with `pip install orfdata[pyensembl]`. See the
76
+ [measurement API](docs/measurements.md) for native units, explicit samples and
77
+ assays, and DataCache-managed downloads, and the
75
78
  [quantitative, multispecies data roadmap](https://github.com/openvax/orfdata/issues/3)
76
- for planned RNA abundance, ribosome occupancy and protein measurements.
79
+ for upcoming RNA abundance, ribosome occupancy and protein data releases.
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # Publish only a reviewed version from clean, synchronized main.
3
+ set -euo pipefail
4
+ PYTHON="${PYTHON:-python}"
5
+ [[ "$(git branch --show-current)" == main ]] || { echo 'Release from main only' >&2; exit 1; }
6
+ [[ -z "$(git status --porcelain)" ]] || { echo 'Release requires a clean tree' >&2; exit 1; }
7
+ git fetch origin main
8
+ [[ "$(git rev-parse HEAD)" == "$(git rev-parse origin/main)" ]] || { echo 'Synchronize main first' >&2; exit 1; }
9
+ version="$($PYTHON -c 'from orfdata.version import __version__; print(__version__)')"
10
+ if git rev-parse "v$version" >/dev/null 2>&1; then echo 'Version tag already exists' >&2; exit 1; fi
11
+ ./lint.sh
12
+ ./test.sh
13
+ "$PYTHON" -m build
14
+ "$PYTHON" -m twine check "dist/orfdata-$version.tar.gz" "dist/orfdata-$version-py3-none-any.whl"
15
+ if [[ "${1:-}" == --dry-run ]]; then exit 0; fi
16
+ "$PYTHON" -m twine upload "dist/orfdata-$version.tar.gz" "dist/orfdata-$version-py3-none-any.whl"
17
+ git tag "v$version"
18
+ git push origin "v$version"
@@ -0,0 +1,84 @@
1
+ # General ORF reference and evidence layer
2
+
3
+ Status: architectural correction to the unmerged oncoref/Varcode/Isovar PRs.
4
+ Working distribution name: orfdata. No release is authorized by this document.
5
+
6
+ ## Ownership
7
+
8
+ PyEnsembl remains responsible for reference genomes, annotations, transcripts,
9
+ exons and canonical proteins. A small separate library adds source-defined ORFs
10
+ on those transcripts and their experimental evidence. oncoref remains a consumer
11
+ for cancer-specific analysis. Neither Varcode nor Isovar owns reference datasets.
12
+ PepData's immune-epitope/property scope is not a fit.
13
+
14
+ The new library has a dependency-light data layer and a PyEnsembl-compatible
15
+ binding API. It must not import Varcode, Isovar or oncoref. No new PyEnsembl core
16
+ API is needed to bind a Transcript; avoid reverse dependencies or monkey-patching.
17
+
18
+ ## Public objects
19
+
20
+ - ORFModel: immutable source ORF identity, assembly, species (NCBI taxon ID and
21
+ scientific name), translation table, parent transcript, spliced coding blocks,
22
+ protein sequence, ORF class. ORF class is distinct from transcript biotype.
23
+ - ORFEvidence: ribosome observations and peptide-level MS observations with their
24
+ source, accession, mapping ambiguity, assay and quality. Native evidence does
25
+ not become evidence for a modified product; orthology is not direct evidence.
26
+ - ORFRecord: model plus evidence, retaining legacy flat accessors for Isovar.
27
+ - ReferenceORF: validated binding of a record to a PyEnsembl Transcript, retaining
28
+ the original transcript (including noncoding biotype), cDNA coding interval,
29
+ coding sequence and actual resolved annotation/version. Multiple ORFs per RNA
30
+ are normal. Annotation lookup never relabels the transcript protein-coding.
31
+ - ORFCatalog: explicitly one species/assembly per catalogue. Query by transcript,
32
+ gene, locus, class or evidence. for_transcript(transcript) validates and returns
33
+ ReferenceORFs. Load custom versioned catalogues offline. The initial bundled
34
+ human data is identified as human-only; no implied mouse evidence.
35
+ - Varcode ORFAnnotation: reference ORF plus effect and realized products. Exports
36
+ separately identify reference model/evidence and modifications, preserving
37
+ phase/splice alternatives. A private transcript-engine adapter permits reuse
38
+ of existing variant logic without exposing a fake transcript as the base ORF.
39
+ - Isovar: attach those same annotations to RNA products with candidate-specific
40
+ RNA support, maintaining conditional translation and unresolved RNA polarity.
41
+
42
+ ## Generality and compatibility
43
+
44
+ Support uORF, overlapping uORF, internal/out-of-frame, downstream and noncoding-
45
+ transcript ORFs with the same model and evidence schema. Preserve source classes
46
+ verbatim rather than equating them with Ensembl transcript biotypes. Complete
47
+ stopped ORFs are the initial binding contract; partial models are rejected
48
+ explicitly rather than given invented boundaries.
49
+
50
+ A generic v1 schema requires species, assembly, translation table and source
51
+ provenance. Legacy Isovar human data may be read only through its explicit known
52
+ human/GRCh38 provenance. Keep legacy Isovar uorf_* imports/call shapes working.
53
+ New generic APIs use orf terminology; defaults remain explicit about dataset scope.
54
+
55
+ Multi-species path: one versioned catalogue per organism/assembly, importer input
56
+ using the same model/evidence schema, optional organism-specific source adapters
57
+ (e.g. OpenProt). Test a mouse noncoding transcript with real PyEnsembl objects,
58
+ plus human coding transcripts, different frames, minus strand, splice junctions,
59
+ assembly/species mismatches and unknown translation tables. Synthetic examples
60
+ are identified as test fixtures, never scientific observations. OpenProt already
61
+ publishes multiple species with Ensembl/RefSeq mappings and evidence filters.
62
+
63
+ ## Review corrections
64
+
65
+ Remove the oncoref runtime dependency from both consumers and withdraw its ORF
66
+ PR when the replacement is reviewable. Keep unrelated lint fixes independently.
67
+ Review evidence retention for duplicate reference codons (retain all copies),
68
+ explicit translation tables and noncoding parents; preserve all Sid alternatives.
69
+ Document unsupported de novo initiation discovery and partial ORFs honestly.
70
+
71
+ ## Verification
72
+
73
+ New library: existing evidence/context/builder tests, generic data round-trip,
74
+ PyEnsembl binding and multi-species isolation, independently installed wheel.
75
+ Varcode/Isovar: focused tests then required full lint/test scripts. Regenerate
76
+ Sid outputs from the original packaged reads. Existing human evidence counts and
77
+ native peptide attribution must remain unchanged. Do not claim release until CI,
78
+ merge and PyPI publication actually complete; publication remains unapproved.
79
+
80
+ Primary sources:
81
+ - https://www.nature.com/articles/s41587-022-01369-0
82
+ - https://www.nature.com/articles/s41586-026-10459-x
83
+ - https://archive.openprot.org/p/help
84
+ - https://github.com/openvax/pyensembl
@@ -0,0 +1,134 @@
1
+ # RNA, ribosome and protein measurements
2
+
3
+ ORFdata adds evidence to PyEnsembl's reference transcripts and proteins. Its
4
+ quantitative layer stores measurements of genes, transcripts, reference ORFs,
5
+ proteins, protein groups and peptides. A measurement stays at the resolution
6
+ the experiment supports. Parent-gene RNA abundance is useful context for an ORF;
7
+ it is not a separate measurement of each ORF in that gene.
8
+
9
+ Install `orfdata[data]` to manage datasets. The initial 0.3 registry is empty;
10
+ the [data roadmap](https://github.com/openvax/orfdata/issues/3) tracks source
11
+ imports. The bundled human ORF evidence catalogue remains available offline.
12
+ Its pooled PSM counts describe spectra, not sample-level protein abundance.
13
+
14
+ ## Discover, download and query
15
+
16
+ ```python
17
+ from orfdata import DatasetRegistry, ScopedTarget
18
+
19
+ registry = DatasetRegistry() # Metadata only; no download.
20
+ releases = registry.list(species="dog", assay="digest_ms")
21
+ ```
22
+
23
+ For a registered release, use `registry.info(name, version)` for its immutable
24
+ manifest, `registry.download(name, version)` to fetch it, and
25
+ `registry.open(name, version)` to read it offline. `inspect` reports missing,
26
+ available or invalid files without downloading or repairing them.
27
+ `download_metadata` returns the artifact's access terms and file hashes/sizes.
28
+ An invalid cache
29
+ requires an explicit `download(..., force=True)`; old reader paths survive.
30
+ Python download options such as timeout, retries and progress go to DataCache.
31
+
32
+ ```python
33
+ # dataset = registry.open(name, version)
34
+ # transcript is a PyEnsembl Transcript in the dataset's exact reference context.
35
+ target = ScopedTarget.from_pyensembl(
36
+ transcript, kind="transcript", namespace="ensembl:110"
37
+ )
38
+ for measurement in dataset.measurements(target=target):
39
+ print(measurement.value, measurement.unit, measurement.status)
40
+ ```
41
+
42
+ Use `ScopedTarget.from_orf(bound_reference_orf, namespace=source_release)` for a
43
+ validated reference ORF. Namespaces and identifiers must match the source's
44
+ mapping table. The default PyEnsembl target uses its unversioned `id`; request
45
+ `versioned=True` when the source uses versioned identifiers. There is no implicit
46
+ mapping across annotation releases, gene isoforms, ORFs or species. A variant or
47
+ fusion product does not inherit a native reference protein measurement.
48
+
49
+ The CLI uses the same operations:
50
+
51
+ ```sh
52
+ orfdata datasets list --species yeast
53
+ orfdata datasets info DATASET --version RELEASE
54
+ orfdata datasets download DATASET --version RELEASE
55
+ orfdata datasets inspect DATASET --version RELEASE
56
+ orfdata datasets query DATASET --version RELEASE --quantity protein_abundance
57
+ ```
58
+
59
+ Queries stream JSON lines. `--reference ref:SHA256` selects an exact context;
60
+ target queries also require `--target`, `--kind` and `--namespace`. Unqualified
61
+ sample/assay ID queries across multiple references are rejected. Existing
62
+ catalogue commands such as `orfdata --gene TPST1` retain their behavior.
63
+
64
+ ## What each value means
65
+
66
+ `Measurement` retains the dataset/version, reference-scoped target, sample and
67
+ assay IDs, value, native unit, linear/log scale and log base, uncertainty and its
68
+ definition, detection limit, status, origin and provenance. Measured zero,
69
+ below detection, not measured, not mapped and missing are distinct. Censored or
70
+ missing values use `None`; native sentinels can remain in provenance.
71
+
72
+ RNA counts/TPM/FPKM, ribosome occupancy, source-defined translation efficiency,
73
+ LFQ/iBAQ/TMT values, peptide intensity, PSM counts, copies per cell and turnover
74
+ remain distinct. No conversion is automatic. Derived/predicted values name a
75
+ method and retain source/input provenance. An estimate from calibration remains
76
+ an estimate; an intensity/TPM ratio does not acquire molecule-ratio units.
77
+
78
+ `Sample` records biological units, pools and source labels. `Assay` separately
79
+ records technical replicates, runs, fractions, TMT plex/channel/bridge,
80
+ normalization and calibration. A sample's strain/breed belongs in its attributes;
81
+ it need not be the strain used to assemble its reference genome. `TargetMapping`
82
+ retains all candidate assignments or an unmapped reason. A protein-group
83
+ representative does not become a uniquely measured protein. Explicit sample
84
+ pairing and orthology comparisons follow in
85
+ [issue #6](https://github.com/openvax/orfdata/issues/6).
86
+
87
+ ## Importing a source
88
+
89
+ Define a `DatasetManifest` with exact reference contexts, raw source URLs,
90
+ SHA-256 hashes and byte sizes, citations, file-specific access/redistribution
91
+ terms, searched/measurable coverage, processing software/version/parameters and
92
+ row accounting. Unavailable metadata stay unknown. Article licensing does not
93
+ substitute for the data files' terms.
94
+
95
+ An importer calls `QuantitativeDataset.create(path, manifest, samples=...,
96
+ assays=..., measurements=..., mappings=...)`. These inputs may be generators;
97
+ measurements are written incrementally. Every observation names a source asset
98
+ or earlier input measurements. Derived inputs precede outputs in the same
99
+ reference context. Duplicate IDs, broken foreign keys, wrong dataset releases
100
+ and cross-reference links fail the import. Failed builds do not replace data.
101
+
102
+ Use `build_dataset(manifest, builder, ...)` for a managed local import.
103
+ `builder(source_paths, output_path)` reads the verified inputs and creates that
104
+ SQLite dataset. DataCache publishes it atomically with input/output checksums
105
+ and a processing receipt. `source_paths={asset_id: local_file}` supports manually
106
+ acquired sources. Importing locally does not grant redistribution rights.
107
+
108
+ A `DatasetRegistry(definitions, cache_root=...)` registers releases as:
109
+
110
+ ```python
111
+ definitions = {
112
+ name: {
113
+ "default_version": version,
114
+ "versions": {version: {
115
+ "manifest": manifest.to_dict(),
116
+ "distribution": {"access_terms": terms, "redistributable": False},
117
+ "assets": {"dataset.sqlite": {
118
+ "url": url, "sha256": sha256, "size": size, "decompress": False,
119
+ }},
120
+ }},
121
+ }
122
+ }
123
+ ```
124
+
125
+ CLI users can supply this JSON with `orfdata datasets --registry FILE ...`.
126
+ `ORFDATA_CACHE`, then `OPENVAX_DATA_CACHE`, then the platform's OpenVax cache
127
+ select the root. ORFdata owns its `orfdata/` child. Release cache identities
128
+ include manifest/reference and content hashes. SQLite partitions every table
129
+ by reference key, with composite foreign keys; human, mouse, dog, yeast and
130
+ arbitrary other taxa use the same schema. The core API needs neither pandas nor
131
+ PyArrow. Optional `orfdata[columnar]` enables `dataset.export_parquet(path)`;
132
+ query columns accompany full measurement JSON, preserving exact values and
133
+ provenance even when a floating-point analysis column cannot represent an
134
+ integer exactly.
@@ -0,0 +1,145 @@
1
+ # Using orfdata
2
+
3
+ See the [README](../README.md) for the purpose, ORF types and basic example.
4
+
5
+ ## Objects and queries
6
+
7
+ `load_catalog()` loads the bundled reference data offline. Catalogue access uses
8
+ only the Python standard library; the optional `pyensembl` extra supplies
9
+ PyEnsembl. Binding uses a transcript you have already loaded.
10
+
11
+ `catalog.for_transcript(transcript)` returns a `ReferenceORF` for each matching
12
+ ORF. Each has:
13
+
14
+ - `model`: source identity, ORF type, coordinates, species and protein sequence.
15
+ - `evidence`: ribosome observations and peptide-level MS observations.
16
+ - `transcript`: the original PyEnsembl transcript, including its original biotype.
17
+ - `coding_sequence` and `protein_sequence`: checked against that transcript.
18
+
19
+ Binding checks the assembly, available species information, transcript identity,
20
+ full spliced coding path, protein sequence and terminal stop. A matching gene
21
+ name alone is insufficient. Incompatible annotations raise an error.
22
+
23
+ To inspect source records without loading a transcript:
24
+
25
+ ```python
26
+ from orfdata import load_catalog
27
+
28
+ catalog = load_catalog()
29
+ records = catalog.query(gene="TPST1", evidence="ms")
30
+ ```
31
+
32
+ ## Evidence
33
+
34
+ Use `orf.evidence.has_evidence("ribosome")`, `"shotgun"` (protease-digest MS),
35
+ `"mhc"` (including human HLA), or `"ms"` (any supporting MS observation).
36
+ `"hla"` selects human HLA observations specifically. Source observations whose
37
+ MS assay type is unspecified remain labelled `"ms_unclassified"`.
38
+
39
+ ```python
40
+ orf.evidence.has_evidence("ms", require_unique=True, require_reviewed=True)
41
+ ```
42
+
43
+ This requires a supporting spectrum review and a single protein mapping
44
+ **within that source's search scope**. It does not imply uniqueness across every
45
+ possible protein. Inspect `orf.evidence.peptides` for peptide sequences, source
46
+ accessions, matching intervals, mapping counts and review quality. Missing counts
47
+ remain unknown; low or mixed spectrum reviews do not count as supporting MS.
48
+
49
+ The distinction between translation, peptide detection and functional protein
50
+ evidence follows the [TransCODE study](https://www.nature.com/articles/s41586-026-10459-x).
51
+ Source evidence belongs to the exact reference ORF. It is not automatically
52
+ evidence for a different isoform, a mutant protein, a fusion or another species.
53
+
54
+ ## Variants and RNA products
55
+
56
+ Varcode keeps the reference object alongside its modification:
57
+
58
+ ```python
59
+ from pyensembl import EnsemblRelease
60
+ from orfdata import load_catalog
61
+ from varcode import Variant, annotate_orf_variants
62
+
63
+ genome = EnsemblRelease(95, species="human") # already downloaded and indexed
64
+ transcript = genome.transcript_by_id("ENST00000304842")
65
+ orf, = load_catalog().for_transcript(transcript)
66
+ variant = Variant("7", 66205486, "C", "G", genome=genome)
67
+ annotation = annotate_orf_variants([variant], orf)
68
+ assert annotation.reference is orf
69
+ print(annotation.effect) # P2A in the TPST1 upstream ORF
70
+ result = annotation.to_dict() # reference, evidence and altered products
71
+ ```
72
+
73
+ Isovar's `annotate_sv_orf_evidence` attaches the same reference annotations to
74
+ reconstructed RNA products and preserves each candidate's RNA support. For
75
+ example, the native TPST1 peptide KIILSKISV is absent from the Sid TPST1–CRCP
76
+ alternatives, so its MS evidence does not support those altered products.
77
+
78
+ The reference integration is available in Varcode 10.12.0; the Isovar 1.50.0
79
+ integration is in review. The stricter reference-context contract is coordinated
80
+ with Varcode 10.13.0 and Isovar 1.51.0.
81
+
82
+ ## Data and limits
83
+
84
+ The bundled human GRCh38 catalogue, dataset `2026-10-11.1`, draws on the
85
+ [GENCODE Phase I reference](https://www.nature.com/articles/s41587-022-01369-0),
86
+ [TransCODE author exports](https://www.nature.com/articles/s41586-026-10459-x),
87
+ and the [community microprotein spectrum benchmark](https://www.nature.com/articles/s41467-025-68002-x).
88
+ All 7,264 ORF paths and proteins were checked against Ensembl 101 GTF, coding RNA
89
+ and noncoding RNA FASTAs. The 2,186 models with supporting MS observations use
90
+ the packaged source-specific filters; this is not a count of proven functional
91
+ proteins. Packaged manifests retain source accessions, checksums, licenses,
92
+ processing rules and dataset version.
93
+
94
+ The current binder accepts complete ORFs with a terminal stop, NCBI translation
95
+ table 1, and ATG/CTG/TTG initiation codons. All bundled models start at ATG.
96
+ Other genetic codes, initiation conventions and incomplete ORFs need additional
97
+ support. Transcript-boundary contexts can be partial; initiation efficiency and
98
+ effects on the main coding region are not established by these data.
99
+
100
+ Circular-RNA translation, stop-codon readthrough and translation from new
101
+ transcripts or fusions are not covered by this reference catalogue. Those need
102
+ additional source data and, where necessary, model support. ORF discovery is
103
+ outside the current API.
104
+
105
+ `load_uorf_catalog()` preserves Isovar's upstream-only snapshot: 3,771 models,
106
+ dataset `2026-10-08.2`. New code should use the broader `load_catalog()` API.
107
+
108
+ ## Other species
109
+
110
+ [ReferenceContext and ScopedTarget](species.md) keep taxa, strains, assemblies
111
+ and annotation versions separate. Custom genomes without species/taxonomy need
112
+ an explicit context when binding; source-pinned contexts require an exact match.
113
+
114
+ `load_catalog(path)` reads a custom versioned JSON catalogue. Each catalogue is
115
+ scoped to one species and assembly; each model records its NCBI taxon, scientific
116
+ name, assembly, translation table and source. Human evidence is never transferred
117
+ to a mouse ortholog. Mouse bindings are tested with synthetic ORFs on PyEnsembl
118
+ transcripts; there is no bundled mouse evidence.
119
+
120
+ To build a catalogue, create `ORFModel` objects from source coordinates and
121
+ sequences, attach source observations with `ORFRecord.from_model(model, evidence)`,
122
+ and construct `ORFCatalog(records, metadata)`. Its `to_dict()` produces the
123
+ `orfdata.catalog.v1` JSON representation. Genomic coding blocks are 1-based,
124
+ inclusive, sorted by genomic position, and include the terminal stop codon.
125
+
126
+ [OpenProt](https://archive.openprot.org/p/help) is a possible source for further species,
127
+ with organism/assembly-specific models, transcript mappings and experimental
128
+ evidence. A future importer must pin releases, preserve ambiguity and licensing,
129
+ and validate coordinate conventions. No OpenProt importer is included yet.
130
+
131
+ ## Rebuild and verify
132
+
133
+ From a development checkout with the PyEnsembl dependency installed:
134
+
135
+ ```sh
136
+ python -m pip install -e '.[dev]'
137
+ python -m orfdata.build --source-dir /path/to/sources --prepare-sources \
138
+ --all-orfs --output orfdata/data/orf-evidence
139
+ ./lint.sh
140
+ ./test.sh
141
+ ```
142
+
143
+ Source preparation downloads pinned public inputs and needs Rscript to extract
144
+ the author export. Ordinary queries need neither R nor spreadsheet libraries.
145
+ See the [design notes](design.md) for the model and library boundaries.
@@ -0,0 +1,58 @@
1
+ # Species and references
2
+
3
+ ORFdata uses the same objects for human, mouse, dog, yeast and custom species.
4
+ Every ORF states its NCBI species taxon, assembly and genetic code. A
5
+ `ReferenceContext` also pins the annotation provider/version and can retain a
6
+ versioned assembly accession, strain taxon/label and sequence-set SHA-256.
7
+
8
+ ```python
9
+ from orfdata import ReferenceContext, ScopedTarget
10
+
11
+ yeast = ReferenceContext(
12
+ taxon_id=4932, species="saccharomyces_cerevisiae",
13
+ genome_build="R64-1-1", assembly_accession="GCA_000146045.2",
14
+ annotation_name="ensembl", annotation_version="115",
15
+ strain="S288C", strain_taxon_id=559292,
16
+ )
17
+ target = ScopedTarget(yeast, "protein", "YFL039C", "ensembl:115")
18
+ print(target.key)
19
+ ```
20
+
21
+ Target keys include the reference, source namespace, kind and source identifier.
22
+ Identical local IDs in different species, reference versions or datasets remain
23
+ separate. Known aliases such as `canis_familiaris` and
24
+ `canis_lupus_familiaris` resolve to dog taxon 9615 through a versioned mapping;
25
+ the original name stays in exports. Assembly names are never automatically
26
+ equated, and strain identity is never discarded. Unknown species require their
27
+ own explicit taxonomy context; they do not require a schema change.
28
+
29
+ `ReferenceContext.from_genome(genome)` resolves available PyEnsembl metadata
30
+ offline. A custom Genome without species/taxonomy needs an explicit context:
31
+
32
+ ```python
33
+ bound = record.bind(transcript, context=yeast)
34
+ # Or: catalog.for_transcript(transcript, context=yeast)
35
+ ```
36
+
37
+ The caller declares metadata absent from a custom genome; any metadata already
38
+ present must agree. The binder still checks transcript version, strand, complete
39
+ spliced path, coding sequence and protein. An ORF model with a
40
+ `reference_context` requires that exact context. Older catalogues without a
41
+ pinned annotation context can bind an identical transcript in another release;
42
+ the actual resolved context and transcript/coding/protein hashes are exported.
43
+ No binding downloads a reference or performs liftover.
44
+
45
+ `context.validate_genome(genome)` checks available metadata against an explicit
46
+ context. Genomic alleles can use `include_annotation=False`: their coordinates
47
+ are independent of a gene annotation release, while taxonomy, strain and
48
+ assembly checks still apply. This does not relax ORF or RNA-product binding.
49
+
50
+ `ScopedTarget` supports genes, transcripts, ORFs, proteins, ambiguous protein
51
+ groups and peptides. A parent's gene or RNA measurement remains at its original
52
+ resolution; this identity layer does not copy it onto every ORF. Orthology and
53
+ equal peptide sequences do not transfer observations between species.
54
+
55
+ The binder currently supports complete stopped ORFs under NCBI table 1, with
56
+ ATG/CTG/TTG initiation. Unsupported codes and initiation codons fail explicitly.
57
+ In particular, yeast nuclear and mitochondrial codes differ; a contig named
58
+ `MT` never selects a code implicitly. See [NCBI's code definitions](https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi).
orfdata-0.3.0/lint.sh ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ set -eu
3
+ "${PYTHON:-python}" -m ruff check orfdata tests
@@ -12,8 +12,12 @@ from .catalog import (
12
12
  load_catalog,
13
13
  load_uorf_catalog,
14
14
  )
15
+ from .dataset import QuantitativeDataset
16
+ from .datasets import DatasetRegistry, build_dataset
15
17
  from .initiation_context import KOZAK_PREFERENCES, InitiationContext, annotate_initiation_context
18
+ from .measurements import Assay, DatasetManifest, Measurement, Sample, SourceAsset, TargetMapping
16
19
  from .model import ORFModel, ReferenceORF
20
+ from .reference import ReferenceContext, ScopedTarget
17
21
  from .version import __version__
18
22
 
19
23
  __all__ = [
@@ -25,6 +29,17 @@ __all__ = [
25
29
  "PeptideEvidence",
26
30
  "ORFModel",
27
31
  "ReferenceORF",
32
+ "ReferenceContext",
33
+ "ScopedTarget",
34
+ "QuantitativeDataset",
35
+ "DatasetRegistry",
36
+ "build_dataset",
37
+ "DatasetManifest",
38
+ "SourceAsset",
39
+ "Sample",
40
+ "Assay",
41
+ "Measurement",
42
+ "TargetMapping",
28
43
  "load_catalog",
29
44
  "UORFCatalog",
30
45
  "UORFRecord",
@@ -15,6 +15,7 @@ from typing import Optional
15
15
 
16
16
  from .initiation_context import KOZAK_PREFERENCES, InitiationContext
17
17
  from .model import ORFModel, ReferenceORF, _species
18
+ from .reference import ReferenceContext
18
19
  from .sequence import CODONS, reverse_complement_dna
19
20
 
20
21
  EVIDENCE_METHODS = ("ribosome", "shotgun", "mhc", "hla", "ms_unclassified", "ms")
@@ -114,6 +115,7 @@ class ORFRecord:
114
115
  species: Optional[str] = None
115
116
  translation_table: int = 1
116
117
  source: str = ""
118
+ reference_context: Optional[ReferenceContext] = None
117
119
 
118
120
  @property
119
121
  def model(self):
@@ -133,6 +135,7 @@ class ORFRecord:
133
135
  species=self.species,
134
136
  translation_table=self.translation_table,
135
137
  source=self.source,
138
+ reference_context=self.reference_context,
136
139
  )
137
140
 
138
141
  @property
@@ -144,6 +147,8 @@ class ORFRecord:
144
147
  """Construct a record from the generic model and independent evidence."""
145
148
  evidence = ORFEvidence() if evidence is None else evidence
146
149
  fields = model.to_dict()
150
+ if model.reference_context is not None:
151
+ fields["reference_context"] = model.reference_context
147
152
  fields["biotype"] = fields.pop("orf_type")
148
153
  return cls(
149
154
  **fields,
@@ -152,13 +157,15 @@ class ORFRecord:
152
157
  initiation_context=initiation_context,
153
158
  )
154
159
 
155
- def bind(self, transcript):
160
+ def bind(self, transcript, *, context=None):
156
161
  """Validate and bind this ORF to a PyEnsembl transcript without relabelling it."""
157
- return ReferenceORF(self, transcript)
162
+ return ReferenceORF(self, transcript, context=context)
158
163
 
159
164
  def to_dict(self):
160
165
  """Return a serializable copy of this record."""
161
166
  result = asdict(self)
167
+ if self.reference_context is None:
168
+ result.pop("reference_context")
162
169
  if self.initiation_context is not None:
163
170
  result["initiation_context"] = self.initiation_context.to_dict()
164
171
  return result
@@ -348,7 +355,7 @@ class ORFCatalog:
348
355
  if r.genome_build != self.genome_build or r.strand not in ("+", "-"):
349
356
  raise ValueError(f"Invalid assembly or strand for {r.orf_id}")
350
357
  r.model # Validate identity and geometry, including non-upstream ORFs.
351
- if (r.taxon_id, r.species) != (self.taxon_id, self.species):
358
+ if (r.taxon_id, _species(r.species)) != (self.taxon_id, _species(self.species)):
352
359
  raise ValueError("Catalogue and ORF species disagree")
353
360
  if (
354
361
  not r.coding_blocks
@@ -425,7 +432,7 @@ class ORFCatalog:
425
432
  """Return an exact source ORF ID; unknown IDs raise KeyError."""
426
433
  return self._by_id[orf_id]
427
434
 
428
- def for_transcript(self, transcript, **filters):
435
+ def for_transcript(self, transcript, *, context=None, **filters):
429
436
  """Return validated ORFs on this transcript, with optional evidence filters.
430
437
 
431
438
  A source model that disagrees with the selected annotation raises rather
@@ -436,8 +443,11 @@ class ORFCatalog:
436
443
  species = getattr(transcript.genome, "species", None)
437
444
  if species is not None and _species(species) != _species(self.species):
438
445
  raise ValueError("Catalogue transcript species mismatch")
446
+ resolved = ReferenceContext.from_genome(transcript.genome, context=context)
447
+ if resolved.taxon_id != self.taxon_id:
448
+ raise ValueError("Catalogue transcript taxon mismatch")
439
449
  return tuple(
440
- record.bind(transcript)
450
+ record.bind(transcript, context=resolved)
441
451
  for record in self.query(**filters)
442
452
  if record.transcript_id.split(".")[0] == str(transcript.id).split(".")[0]
443
453
  )
@@ -576,6 +586,8 @@ def _load_catalog(path=None, *, expected_sha256=None, resource="orf-evidence"):
576
586
  )
577
587
  row["coding_blocks"] = tuple(tuple(x) for x in row["coding_blocks"])
578
588
  row["ribosome_studies"] = tuple(row["ribosome_studies"])
589
+ if row.get("reference_context") is not None:
590
+ row["reference_context"] = ReferenceContext(**row["reference_context"])
579
591
  if row.get("initiation_context") is not None:
580
592
  context = dict(row["initiation_context"])
581
593
  assessment = context.pop("assessment", None)