orfdata 0.16.0__tar.gz → 0.17.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 (176) hide show
  1. {orfdata-0.16.0/orfdata.egg-info → orfdata-0.17.0}/PKG-INFO +1 -1
  2. {orfdata-0.16.0 → orfdata-0.17.0}/docs/reference.md +37 -3
  3. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/__init__.py +2 -0
  4. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/catalog.py +52 -9
  5. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/cli.py +3 -0
  6. orfdata-0.17.0/orfdata/evidence_summary.py +108 -0
  7. orfdata-0.17.0/orfdata/version.py +1 -0
  8. {orfdata-0.16.0 → orfdata-0.17.0/orfdata.egg-info}/PKG-INFO +1 -1
  9. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata.egg-info/SOURCES.txt +3 -0
  10. orfdata-0.17.0/tests/data/aggregate_evidence.json +877 -0
  11. orfdata-0.17.0/tests/test_evidence_summary.py +178 -0
  12. orfdata-0.16.0/orfdata/version.py +0 -1
  13. {orfdata-0.16.0 → orfdata-0.17.0}/LICENSE +0 -0
  14. {orfdata-0.16.0 → orfdata-0.17.0}/MANIFEST.in +0 -0
  15. {orfdata-0.16.0 → orfdata-0.17.0}/README.md +0 -0
  16. {orfdata-0.16.0 → orfdata-0.17.0}/deploy.sh +0 -0
  17. {orfdata-0.16.0 → orfdata-0.17.0}/docs/copy-yeast.md +0 -0
  18. {orfdata-0.16.0 → orfdata-0.17.0}/docs/coverage.md +0 -0
  19. {orfdata-0.16.0 → orfdata-0.17.0}/docs/design.md +0 -0
  20. {orfdata-0.16.0 → orfdata-0.17.0}/docs/human-tissue.md +0 -0
  21. {orfdata-0.16.0 → orfdata-0.17.0}/docs/ingolia-yeast.md +0 -0
  22. {orfdata-0.16.0 → orfdata-0.17.0}/docs/measurements.md +0 -0
  23. {orfdata-0.16.0 → orfdata-0.17.0}/docs/pairing.md +0 -0
  24. {orfdata-0.16.0 → orfdata-0.17.0}/docs/pfizer-protein.md +0 -0
  25. {orfdata-0.16.0 → orfdata-0.17.0}/docs/pfizer-rna.md +0 -0
  26. {orfdata-0.16.0 → orfdata-0.17.0}/docs/search-references.md +0 -0
  27. {orfdata-0.16.0 → orfdata-0.17.0}/docs/source-references.md +0 -0
  28. {orfdata-0.16.0 → orfdata-0.17.0}/docs/species.md +0 -0
  29. {orfdata-0.16.0 → orfdata-0.17.0}/docs/storage.md +0 -0
  30. {orfdata-0.16.0 → orfdata-0.17.0}/lint.sh +0 -0
  31. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/build.py +0 -0
  32. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/datasets.json +0 -0
  33. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/orf-evidence/AUTHOR-LICENSE.txt +0 -0
  34. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/orf-evidence/NOTICE.txt +0 -0
  35. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/orf-evidence/catalog.json.gz +0 -0
  36. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/orf-evidence/manifest.json +0 -0
  37. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/uorf-evidence/AUTHOR-LICENSE.txt +0 -0
  38. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/uorf-evidence/NOTICE.txt +0 -0
  39. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/uorf-evidence/catalog.json.gz +0 -0
  40. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/data/uorf-evidence/manifest.json +0 -0
  41. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/dataset.py +0 -0
  42. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/dataset_cli.py +0 -0
  43. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/datasets.py +0 -0
  44. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/initiation_context.py +0 -0
  45. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/measurements.py +0 -0
  46. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/model.py +0 -0
  47. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/pairing.py +0 -0
  48. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/reference.py +0 -0
  49. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/reference_mapping.py +0 -0
  50. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/search_reference.py +0 -0
  51. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/sequence.py +0 -0
  52. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/sources/__init__.py +0 -0
  53. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/sources/copy.py +0 -0
  54. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/sources/ingolia.py +0 -0
  55. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/sources/pfizer.py +0 -0
  56. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/sources/pfizer_rna.py +0 -0
  57. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata/sources/wang.py +0 -0
  58. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata.egg-info/dependency_links.txt +0 -0
  59. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata.egg-info/entry_points.txt +0 -0
  60. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata.egg-info/requires.txt +0 -0
  61. {orfdata-0.16.0 → orfdata-0.17.0}/orfdata.egg-info/top_level.txt +0 -0
  62. {orfdata-0.16.0 → orfdata-0.17.0}/pyproject.toml +0 -0
  63. {orfdata-0.16.0 → orfdata-0.17.0}/setup.cfg +0 -0
  64. {orfdata-0.16.0 → orfdata-0.17.0}/test.sh +0 -0
  65. {orfdata-0.16.0 → orfdata-0.17.0}/tests/conftest.py +0 -0
  66. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/GSM1905474.soft +0 -0
  67. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/GSM1905474_KL9_fpkm.txt.gz +0 -0
  68. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/GSM1905476.soft +0 -0
  69. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/GSM1905476_KL10_fpkm.txt.gz +0 -0
  70. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/GSM1905478.soft +0 -0
  71. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/GSM1905478_KL11_fpkm.txt.gz +0 -0
  72. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/GSM1905479.soft +0 -0
  73. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/GSM1905479_KL12_fpkm.txt.gz +0 -0
  74. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/NOTICE.md +0 -0
  75. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/expected.json +0 -0
  76. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/geo-series.soft +0 -0
  77. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/manifest.json +0 -0
  78. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/methods.pdf +0 -0
  79. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/paper.xml +0 -0
  80. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/pmc-metadata.json +0 -0
  81. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/qconcat.fasta +0 -0
  82. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/copy/workbook.xlsx +0 -0
  83. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346111.metadata.txt +0 -0
  84. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346111.quant.txt.gz +0 -0
  85. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346114.metadata.txt +0 -0
  86. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346114.quant.txt.gz +0 -0
  87. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346115.metadata.txt +0 -0
  88. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346115.quant.txt.gz +0 -0
  89. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346116.metadata.txt +0 -0
  90. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346116.quant.txt.gz +0 -0
  91. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346117.metadata.txt +0 -0
  92. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346117.quant.txt.gz +0 -0
  93. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346118.metadata.txt +0 -0
  94. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346118.quant.txt.gz +0 -0
  95. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346120.metadata.txt +0 -0
  96. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346120.quant.txt.gz +0 -0
  97. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346122.metadata.txt +0 -0
  98. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/GSM346122.quant.txt.gz +0 -0
  99. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/NOTICE.md +0 -0
  100. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/expected.json +0 -0
  101. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/manifest.json +0 -0
  102. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/ingolia/readMe.txt.gz +0 -0
  103. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/NOTICE.md +0 -0
  104. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/dog/expected.json +0 -0
  105. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/dog/manifest.json +0 -0
  106. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/dog/protein.tsv +0 -0
  107. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/dog/search.fasta +0 -0
  108. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/human/expected.json +0 -0
  109. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/human/manifest.json +0 -0
  110. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/human/protein.tsv +0 -0
  111. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/human/search.fasta +0 -0
  112. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/mouse/expected.json +0 -0
  113. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/mouse/manifest.json +0 -0
  114. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/mouse/protein.tsv +0 -0
  115. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer/mouse/search.fasta +0 -0
  116. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/GSE325522_family.soft.gz +0 -0
  117. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/GSE325523_family.soft.gz +0 -0
  118. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/GSE325525_family.soft.gz +0 -0
  119. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/NOTICE.md +0 -0
  120. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/counts.csv.gz +0 -0
  121. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/dog-annotation.gtf.gz +0 -0
  122. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/dog-expected.json +0 -0
  123. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/dog-manifest.json +0 -0
  124. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/mouse-annotation.gtf.gz +0 -0
  125. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/mouse-expected.json +0 -0
  126. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/mouse-manifest.json +0 -0
  127. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/pfizer_rna/samples.xlsx +0 -0
  128. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/references/README.md +0 -0
  129. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/references/canis_lupus_familiaris.json +0 -0
  130. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/references/context-keys-v0.4.json +0 -0
  131. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/references/homo_sapiens.json +0 -0
  132. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/references/mus_musculus.json +0 -0
  133. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/references/saccharomyces_cerevisiae.json +0 -0
  134. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/regions/NOTICE.md +0 -0
  135. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/regions/ingolia.json +0 -0
  136. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/search_reference/NOTICE.md +0 -0
  137. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/search_reference/dog.fasta +0 -0
  138. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/search_reference/ensembl83-expected.json +0 -0
  139. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/search_reference/ensembl83-human.fasta.gz +0 -0
  140. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/search_reference/expected.json +0 -0
  141. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/storage/README.md +0 -0
  142. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/storage/generate.py +0 -0
  143. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/storage/v1.json +0 -0
  144. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/storage/v1.sqlite +0 -0
  145. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/storage/v2.json +0 -0
  146. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/storage/v2.sqlite +0 -0
  147. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/storage/v3.json +0 -0
  148. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/storage/v3.sqlite +0 -0
  149. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/wang/NOTICE.md +0 -0
  150. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/wang/annotation.gtf.gz +0 -0
  151. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/wang/copies.zip +0 -0
  152. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/wang/expected.json +0 -0
  153. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/wang/manifest.json +0 -0
  154. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/wang/protein.zip +0 -0
  155. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/wang/rna.zip +0 -0
  156. {orfdata-0.16.0 → orfdata-0.17.0}/tests/data/wang/search.fasta.gz +0 -0
  157. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_copy.py +0 -0
  158. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_cross_reference_pairing.py +0 -0
  159. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_ensembl_search_reference.py +0 -0
  160. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_ingolia.py +0 -0
  161. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_initiation_context.py +0 -0
  162. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_local_build_queries.py +0 -0
  163. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_measurements.py +0 -0
  164. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_paired_selectors.py +0 -0
  165. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_pairing.py +0 -0
  166. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_pfizer.py +0 -0
  167. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_pfizer_rna.py +0 -0
  168. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_reference_context.py +0 -0
  169. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_reference_mapping.py +0 -0
  170. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_reference_orf.py +0 -0
  171. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_region_targets.py +0 -0
  172. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_search_reference.py +0 -0
  173. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_selectors.py +0 -0
  174. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_storage.py +0 -0
  175. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_uorf_catalog.py +0 -0
  176. {orfdata-0.16.0 → orfdata-0.17.0}/tests/test_wang.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orfdata
3
- Version: 0.16.0
3
+ Version: 0.17.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
@@ -12,7 +12,7 @@ PyEnsembl. Binding uses a transcript you have already loaded.
12
12
  ORF. Each has:
13
13
 
14
14
  - `model`: source identity, ORF type, coordinates, species and protein sequence.
15
- - `evidence`: ribosome observations and peptide-level MS observations.
15
+ - `evidence`: ribosome observations, peptide-level MS and native aggregate support.
16
16
  - `transcript`: the original PyEnsembl transcript, including its original biotype.
17
17
  - `coding_sequence` and `protein_sequence`: checked against that transcript.
18
18
 
@@ -46,10 +46,44 @@ possible protein. Inspect `orf.evidence.peptides` for peptide sequences, source
46
46
  accessions, matching intervals, mapping counts and review quality. Missing counts
47
47
  remain unknown; low or mixed spectrum reviews do not count as supporting MS.
48
48
 
49
+ Some sources provide only aggregate support counts. `EvidenceSummary` keeps the
50
+ native metric, definition, value, source/release, source checksum and explicitly
51
+ scoped entity. For example, [OpenProt's native metrics](https://api.openprot.org/api/2.0/SC/downloads/yeast_s288c-openprot-2_2-altprots+isoforms-min_2_pep-R64-1-1.106.tsv.readme.html)
52
+ include an MS score that sums study-specific unique-peptide
53
+ counts; it is neither global peptide uniqueness nor protein abundance. Its
54
+ translation score counts studies with significant translation detection.
55
+ `None` means unknown and remains distinct from a reported zero.
56
+
57
+ Positive summaries in `orf.evidence.summaries` can satisfy general evidence
58
+ queries. To require actual peptide records or named ribosome studies, use:
59
+
60
+ ```python
61
+ orf.evidence.has_evidence("ms", require_resolved=True)
62
+ catalog.query(evidence="ribosome", require_resolved=True)
63
+ ```
64
+
65
+ The CLI uses `--evidence ms --require-resolved`. Uniqueness and review filters
66
+ always require resolved peptide observations. Summaries never create peptide
67
+ sequences, residue coverage, retained variant peptides or mutant-translation
68
+ evidence. Their protein checksum and species/reference checks prevent attaching
69
+ them to a different protein or incompatible source context.
70
+
71
+ A sequence-level summary may have several transcript assignments. Those records
72
+ share the summary's `key`; `catalog.evidence_summaries` returns each native metric
73
+ once and rejects conflicting facts for the same identity. Native accession
74
+ aliases must be resolved explicitly by the source adapter. Counts from different
75
+ metrics and sources are not summed, and a later peptide-resolved snapshot is not
76
+ an additional independent experiment merely because it has more detail.
77
+
78
+ This API includes source-backed OpenProt human, mouse and yeast test slices.
79
+ It does not yet publish those species' OpenProt ORF catalogues; coordinate and
80
+ sequence qualification remains tracked in [#18](https://github.com/openvax/orfdata/issues/18).
81
+
49
82
  The distinction between translation, peptide detection and functional protein
50
83
  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.
84
+ Source evidence stays at its declared ORF or protein-sequence scope. It is not
85
+ automatically evidence for another isoform, a mutant protein, a fusion or
86
+ another species.
53
87
 
54
88
  ## Variants and RNA products
55
89
 
@@ -14,6 +14,7 @@ from .catalog import (
14
14
  )
15
15
  from .dataset import QuantitativeDataset
16
16
  from .datasets import DatasetRegistry, build_dataset
17
+ from .evidence_summary import EvidenceSummary
17
18
  from .initiation_context import KOZAK_PREFERENCES, InitiationContext, annotate_initiation_context
18
19
  from .measurements import (
19
20
  Assay,
@@ -44,6 +45,7 @@ __all__ = [
44
45
  "ORFCatalog",
45
46
  "ORFRecord",
46
47
  "ORFEvidence",
48
+ "EvidenceSummary",
47
49
  "PeptideEvidence",
48
50
  "ORFModel",
49
51
  "ReferenceORF",
@@ -13,6 +13,7 @@ from importlib.resources import files
13
13
  from pathlib import Path
14
14
  from typing import Optional
15
15
 
16
+ from .evidence_summary import EvidenceSummary, unique_summaries
16
17
  from .initiation_context import KOZAK_PREFERENCES, InitiationContext
17
18
  from .model import ORFModel, ReferenceORF, _species
18
19
  from .reference import ReferenceContext
@@ -73,25 +74,41 @@ class ORFEvidence:
73
74
 
74
75
  ribosome_studies: tuple = ()
75
76
  peptides: tuple = ()
77
+ summaries: tuple = ()
78
+
79
+ def __post_init__(self):
80
+ object.__setattr__(self, "summaries", unique_summaries(self.summaries))
76
81
 
77
82
  def to_dict(self):
78
- return asdict(self)
83
+ result = asdict(self)
84
+ if not self.summaries:
85
+ result.pop("summaries")
86
+ return result
79
87
 
80
- def has_evidence(self, method, *, require_unique=False, require_reviewed=False):
81
- """Select reference observations by assay, without clinical promotion."""
88
+ def has_evidence(self, method, *, require_unique=False, require_reviewed=False, require_resolved=False):
89
+ """Select reported support, or require named studies/resolved peptides.
90
+
91
+ Aggregate counts never satisfy uniqueness or spectrum-review filters.
92
+ No selection establishes evidence for a modified protein product.
93
+ """
82
94
  if method not in EVIDENCE_METHODS:
83
95
  raise ValueError(f"Unknown evidence method: {method}")
84
96
  if method == "ribosome":
85
97
  if require_unique or require_reviewed:
86
98
  raise ValueError("MS filters do not apply to ribosome evidence")
87
- return bool(self.ribosome_studies)
88
- return any(
99
+ return bool(self.ribosome_studies) or (not require_resolved and any(
100
+ s.method == "ribosome" and s.is_supporting for s in self.summaries))
101
+ resolved = any(
89
102
  (method == "ms" or p.method == method or (method == "mhc" and p.method == "hla"))
90
103
  and p.is_supporting
91
104
  and (not require_unique or p.unique_in_source)
92
105
  and (not require_reviewed or p.quality == "reviewed_support")
93
106
  for p in self.peptides
94
107
  )
108
+ return resolved or (not (require_resolved or require_unique or require_reviewed) and any(
109
+ (method == "ms" and s.method != "ribosome"
110
+ or s.method == method or method == "mhc" and s.method == "hla")
111
+ and s.is_supporting for s in self.summaries))
95
112
 
96
113
 
97
114
  @dataclass(frozen=True)
@@ -116,6 +133,14 @@ class ORFRecord:
116
133
  translation_table: int = 1
117
134
  source: str = ""
118
135
  reference_context: Optional[ReferenceContext] = None
136
+ summaries: tuple = ()
137
+
138
+ def __post_init__(self):
139
+ object.__setattr__(self, "summaries", unique_summaries(self.summaries))
140
+ if self.summaries:
141
+ model = self.model
142
+ for summary in self.summaries:
143
+ summary.validate_orf(model)
119
144
 
120
145
  @property
121
146
  def model(self):
@@ -140,7 +165,7 @@ class ORFRecord:
140
165
 
141
166
  @property
142
167
  def evidence(self):
143
- return ORFEvidence(self.ribosome_studies, self.peptides)
168
+ return ORFEvidence(self.ribosome_studies, self.peptides, self.summaries)
144
169
 
145
170
  @classmethod
146
171
  def from_model(cls, model, evidence=None, *, initiation_context=None):
@@ -154,6 +179,7 @@ class ORFRecord:
154
179
  **fields,
155
180
  ribosome_studies=evidence.ribosome_studies,
156
181
  peptides=evidence.peptides,
182
+ summaries=evidence.summaries,
157
183
  initiation_context=initiation_context,
158
184
  )
159
185
 
@@ -164,6 +190,8 @@ class ORFRecord:
164
190
  def to_dict(self):
165
191
  """Return a serializable copy of this record."""
166
192
  result = asdict(self)
193
+ if not self.summaries:
194
+ result.pop("summaries")
167
195
  if self.reference_context is None:
168
196
  result.pop("reference_context")
169
197
  if self.initiation_context is not None:
@@ -284,9 +312,10 @@ class ORFRecord:
284
312
  translation_change="not_measured",
285
313
  )
286
314
 
287
- def has_evidence(self, method, *, require_unique=False, require_reviewed=False):
315
+ def has_evidence(self, method, *, require_unique=False, require_reviewed=False, require_resolved=False):
288
316
  return self.evidence.has_evidence(
289
- method, require_unique=require_unique, require_reviewed=require_reviewed
317
+ method, require_unique=require_unique, require_reviewed=require_reviewed,
318
+ require_resolved=require_resolved,
290
319
  )
291
320
 
292
321
  def _coding_offset(self, position):
@@ -423,6 +452,13 @@ class ORFCatalog:
423
452
  ):
424
453
  raise ValueError(f"Peptide sequence does not match {r.orf_id}")
425
454
 
455
+ self._evidence_summaries = unique_summaries(s for r in self.records for s in r.summaries)
456
+
457
+ @property
458
+ def evidence_summaries(self):
459
+ """Distinct source counts, without multiplying transcript assignments."""
460
+ return self._evidence_summaries
461
+
426
462
  @property
427
463
  def metadata(self):
428
464
  """Return a copy of dataset version, sources, scope and provenance."""
@@ -472,6 +508,7 @@ class ORFCatalog:
472
508
  evidence=None,
473
509
  require_unique=False,
474
510
  require_reviewed=False,
511
+ require_resolved=False,
475
512
  biotype=None,
476
513
  include_initiation_context=False,
477
514
  ):
@@ -483,6 +520,8 @@ class ORFCatalog:
483
520
  are unversioned identifiers. Empty results do not prove no translation.
484
521
  ``require_unique`` means unique within the evidence source's mapping
485
522
  scope, not unique in every normal human proteome.
523
+ ``require_resolved`` excludes aggregate-only support: MS needs a peptide
524
+ observation and ribosome evidence needs a named study.
486
525
  ``include_initiation_context`` also intersects placed spliced start
487
526
  flanks, for example an upstream Kozak SNV. The default remains coding
488
527
  blocks only. It does not include introns between context bases.
@@ -504,6 +543,8 @@ class ORFCatalog:
504
543
  raise ValueError("require_unique applies to MS evidence only")
505
544
  if require_reviewed and evidence not in EVIDENCE_METHODS[1:]:
506
545
  raise ValueError("require_reviewed applies to MS evidence only")
546
+ if require_resolved and evidence is None:
547
+ raise ValueError("require_resolved requires an evidence method")
507
548
 
508
549
  return tuple(
509
550
  r
@@ -530,7 +571,8 @@ class ORFCatalog:
530
571
  and (
531
572
  evidence is None
532
573
  or r.has_evidence(
533
- evidence, require_unique=require_unique, require_reviewed=require_reviewed
574
+ evidence, require_unique=require_unique, require_reviewed=require_reviewed,
575
+ require_resolved=require_resolved,
534
576
  )
535
577
  )
536
578
  )
@@ -604,6 +646,7 @@ def _load_catalog(path=None, *, expected_sha256=None, resource="orf-evidence"):
604
646
  p["spectrum_ids"] = tuple(p.get("spectrum_ids", ()))
605
647
  peptides.append(PeptideEvidence(**p))
606
648
  row["peptides"] = tuple(peptides)
649
+ row["summaries"] = tuple(EvidenceSummary.from_dict(s) for s in row.get("summaries", ()))
607
650
  records.append(ORFRecord(**row))
608
651
  return ORFCatalog(records, metadata)
609
652
 
@@ -24,6 +24,8 @@ def run(argv=None, prog=None, *, catalog_loader=load_catalog):
24
24
  parser.add_argument("--transcript", help="Exact source transcript ID (unversioned)")
25
25
  parser.add_argument("--orf-id", help="Exact catalogue ORF identifier")
26
26
  parser.add_argument("--evidence", choices=EVIDENCE_METHODS)
27
+ parser.add_argument("--require-resolved", action="store_true",
28
+ help="Require resolved peptides or named ribosome studies; exclude aggregate-only support")
27
29
  parser.add_argument(
28
30
  "--require-unique", action="store_true", help="Require one protein mapping in the MS source"
29
31
  )
@@ -69,6 +71,7 @@ def run(argv=None, prog=None, *, catalog_loader=load_catalog):
69
71
  "gene": args.gene,
70
72
  "transcript_id": args.transcript,
71
73
  "evidence": args.evidence,
74
+ "require_resolved": args.require_resolved,
72
75
  "require_unique": args.require_unique,
73
76
  "require_reviewed": args.require_reviewed,
74
77
  "biotype": args.biotype,
@@ -0,0 +1,108 @@
1
+ """Source-reported support counts, separate from resolved observations."""
2
+
3
+ import hashlib
4
+ import re
5
+ from dataclasses import asdict, dataclass, replace
6
+ from typing import Optional
7
+ from urllib.parse import urlparse
8
+
9
+ from .reference import ReferenceContext, ScopedTarget
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class EvidenceSummary:
14
+ """One native count of experimental support for an exact source entity.
15
+
16
+ ``value`` is a nonnegative integer or unknown (None), never a confidence
17
+ score or abundance. ``definition`` preserves the source's counting rules;
18
+ a sum of unique peptides per study is not a globally unique peptide count.
19
+ A protein-sequence summary can support several transcript assignments,
20
+ which retain the same ``key`` instead of multiplying independent evidence.
21
+ """
22
+
23
+ source: str
24
+ release: str
25
+ target: ScopedTarget
26
+ protein_sha256: str
27
+ method: str
28
+ metric: str
29
+ value: Optional[int]
30
+ definition: str
31
+ scope: str
32
+ source_url: str
33
+ source_sha256: str
34
+
35
+ def __post_init__(self):
36
+ if not isinstance(self.target, ScopedTarget):
37
+ raise TypeError("Evidence summaries require a ScopedTarget")
38
+ if self.method not in ("ribosome", "shotgun", "mhc", "hla", "ms_unclassified", "ms"):
39
+ raise ValueError("Unknown summary evidence method")
40
+ if self.method == "hla" and self.target.reference.taxon_id != 9606:
41
+ raise ValueError("Use mhc for non-human immunopeptidomics evidence")
42
+ if self.scope not in ("protein_sequence", "orf") or self.target.kind != {
43
+ "protein_sequence": "protein", "orf": "orf"
44
+ }.get(self.scope):
45
+ raise ValueError("Evidence summary scope and target kind disagree")
46
+ if any(not isinstance(x, str) or not x.strip() for x in (self.source, self.release, self.metric, self.definition)):
47
+ raise ValueError("Source, release, native metric and counting definition are required")
48
+ for value in (self.protein_sha256, self.source_sha256):
49
+ if not isinstance(value, str) or not re.fullmatch(r"[0-9a-f]{64}", value):
50
+ raise ValueError("Evidence summaries require lowercase SHA-256 pins")
51
+ if not isinstance(self.source_url, str) or urlparse(self.source_url).scheme not in ("https", "http") or not urlparse(self.source_url).netloc:
52
+ raise ValueError("Evidence summaries require a source URL")
53
+ if self.value is not None and (type(self.value) is not int or self.value < 0):
54
+ raise ValueError("A support count must be a nonnegative integer or None")
55
+
56
+ @property
57
+ def key(self):
58
+ """Shared source metric identity, independent of transcript assignment."""
59
+ return (self.source, self.release, self.target.key, self.scope, self.method, self.metric)
60
+
61
+ @property
62
+ def is_supporting(self):
63
+ """Positive reported support; zero and unknown both fail selection."""
64
+ return self.value is not None and self.value > 0
65
+
66
+ def validate_orf(self, model):
67
+ """Reject attachment to another species, protein sequence or native ORF."""
68
+ if self.target.reference.taxon_id != model.taxon_id:
69
+ raise ValueError("Summary and ORF species disagree")
70
+ reference = self.target.reference
71
+ if reference.genome_build is not None and reference.genome_build != model.genome_build:
72
+ raise ValueError("Summary and ORF assemblies disagree")
73
+ for field in ("strain", "strain_taxon_id"):
74
+ source = getattr(reference, field)
75
+ destination = getattr(model.reference_context, field, None)
76
+ if source != destination:
77
+ raise ValueError("Summary and ORF strain contexts disagree")
78
+ if self.protein_sha256 != hashlib.sha256(model.protein_sequence.encode("ascii")).hexdigest():
79
+ raise ValueError("Summary and ORF protein sequences disagree")
80
+ if self.scope == "orf" and (
81
+ self.target.identifier != model.orf_id
82
+ or reference.genome_build != model.genome_build
83
+ or (model.reference_context is not None and reference.key != model.reference_context.key)
84
+ ):
85
+ raise ValueError("Summary and native ORF identities disagree")
86
+
87
+ def to_dict(self):
88
+ return asdict(self)
89
+
90
+ @classmethod
91
+ def from_dict(cls, record):
92
+ record = dict(record)
93
+ target = dict(record["target"])
94
+ target["reference"] = ReferenceContext(**target["reference"])
95
+ record["target"] = ScopedTarget(**target)
96
+ return cls(**record)
97
+
98
+
99
+ def unique_summaries(summaries):
100
+ """Preserve first occurrence; conflicting facts for one identity are errors."""
101
+ result = {}
102
+ for summary in summaries:
103
+ if not isinstance(summary, EvidenceSummary):
104
+ raise TypeError("Expected EvidenceSummary")
105
+ previous = result.setdefault(summary.key, summary)
106
+ if previous != replace(summary, target=previous.target):
107
+ raise ValueError("Conflicting evidence summaries for one source metric")
108
+ return tuple(result.values())
@@ -0,0 +1 @@
1
+ __version__ = "0.17.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orfdata
3
- Version: 0.16.0
3
+ Version: 0.17.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
@@ -26,6 +26,7 @@ orfdata/cli.py
26
26
  orfdata/dataset.py
27
27
  orfdata/dataset_cli.py
28
28
  orfdata/datasets.py
29
+ orfdata/evidence_summary.py
29
30
  orfdata/initiation_context.py
30
31
  orfdata/measurements.py
31
32
  orfdata/model.py
@@ -60,6 +61,7 @@ tests/conftest.py
60
61
  tests/test_copy.py
61
62
  tests/test_cross_reference_pairing.py
62
63
  tests/test_ensembl_search_reference.py
64
+ tests/test_evidence_summary.py
63
65
  tests/test_ingolia.py
64
66
  tests/test_initiation_context.py
65
67
  tests/test_local_build_queries.py
@@ -77,6 +79,7 @@ tests/test_selectors.py
77
79
  tests/test_storage.py
78
80
  tests/test_uorf_catalog.py
79
81
  tests/test_wang.py
82
+ tests/data/aggregate_evidence.json
80
83
  tests/data/copy/GSM1905474.soft
81
84
  tests/data/copy/GSM1905474_KL9_fpkm.txt.gz
82
85
  tests/data/copy/GSM1905476.soft