descriptron-core 2.1.2__tar.gz → 2.1.3__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 (138) hide show
  1. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/PKG-INFO +1 -1
  2. descriptron_core-2.1.3/pyproject.toml +82 -0
  3. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_llm_backend.py +5 -0
  4. descriptron_core-2.1.3/src/descriptron_core/tools/biorag_provenance_v1.py +230 -0
  5. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/run_full_pipeline_v2.py +116 -1
  6. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/.gitignore +0 -0
  7. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/LICENSE +0 -0
  8. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/NOTICE +0 -0
  9. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/NOTICE.bak_pre_212_20260929_1100 +0 -0
  10. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/README.md +0 -0
  11. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/pyproject.toml.bak_pre_203_20260924_145320 +0 -0
  12. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/pyproject.toml.bak_pre_204_20260924_165335 +0 -0
  13. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/pyproject.toml.bak_pre_210_20260925_103754 +0 -0
  14. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/pyproject.toml.bak_pre_211_20260925_114912 +0 -0
  15. /descriptron_core-2.1.2/pyproject.toml → /descriptron_core-2.1.3/pyproject.toml.bak_pre_213_20260929_1414 +0 -0
  16. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/pyproject.toml.bak_pre_keys_20260924_165335 +0 -0
  17. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/__init__.py +0 -0
  18. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/cli.py +0 -0
  19. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/cli.py.bak_pre_keys_20260924_165335 +0 -0
  20. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/biorag_system_prompts_v1.txt +0 -0
  21. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/biorag_system_prompts_v2.txt +0 -0
  22. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/diaphorina_user_prompts.txt +0 -0
  23. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/ontologies/aism.obo +0 -0
  24. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/ontologies/pato.obo +0 -0
  25. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/SDD.xsd +0 -0
  26. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/SDD_EnumLib.xsd +0 -0
  27. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/SDD_TypeLib.xsd +0 -0
  28. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/UBIF_BasePattern.xsd +0 -0
  29. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/UBIF_CoreExtensions.xsd +0 -0
  30. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/UBIF_CoreOntology.xsd +0 -0
  31. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/UBIF_EnumLib.xsd +0 -0
  32. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/UBIF_LinneanCore.xsd +0 -0
  33. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/UBIF_TypeLib.xsd +0 -0
  34. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/sdd_1.1/w3c-schemata/xml.xsd +0 -0
  35. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/taxpub/sample_treatment_deposit_example.xml +0 -0
  36. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/taxpub/sample_zookeys_26056_tp.xml +0 -0
  37. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/schemas/taxpub/tax-treatment-NS0-v1_flat.dtd +0 -0
  38. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/taxon_profiles/TEMPLATE_taxon_profile.yaml +0 -0
  39. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/data/biorag_prompts/taxon_profiles/diaphorina_taxon_profile.yaml +0 -0
  40. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/FHS_and_CLAHE_V21.py +0 -0
  41. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_add_discrete_characters_v1.py +0 -0
  42. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_annotation_reorient_v1.py +0 -0
  43. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_annotation_screen_v1.py +0 -0
  44. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_autapomorphy_v1.py +0 -0
  45. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_calibrate_v1.py +0 -0
  46. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_calibration_figures_v1.py +0 -0
  47. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_character_gate_v1.py +0 -0
  48. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_character_reliability_v1.py +0 -0
  49. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_character_robustness_v1.py +0 -0
  50. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_collaborator_forms_v1.py +0 -0
  51. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_computed_sets_v1.py +0 -0
  52. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_confabulation_checker_v2.py +0 -0
  53. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_congruence_compare_v1.py +0 -0
  54. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_description_refiner_v1.py +0 -0
  55. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_descriptive_scoring_v1.py +0 -0
  56. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_feature_policy.py +0 -0
  57. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_frame_orientation_calibrate_v1.py +0 -0
  58. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_frame_orientation_v1.py +0 -0
  59. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_gate_compare_v1.py +0 -0
  60. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_graph_identify_v1.py +0 -0
  61. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_graph_support_figure_v1.py +0 -0
  62. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_homology_frame_v1.py +0 -0
  63. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_instrument_compare_v3.py +0 -0
  64. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_key_builder_v1.py +0 -0
  65. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_key_feature_filter_v2.py +0 -0
  66. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_key_fuzzy_figures_v1.py +0 -0
  67. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_key_fuzzy_v1.py +0 -0
  68. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_key_holdout_v1.py +0 -0
  69. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_key_jackknife_support_v1.py +0 -0
  70. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_label_conflict_check_v1.py +0 -0
  71. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_make_taxon_profile_v1.py +0 -0
  72. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_methods_summary_figure_v1.py +0 -0
  73. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_novelty_score_v1.py +0 -0
  74. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_ontology_annotator_v2.py +0 -0
  75. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_outgroup_batch_check_v1.py +0 -0
  76. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_outline_shape_v1.py +0 -0
  77. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/biorag_system_prompts_v1.txt +0 -0
  78. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/biorag_system_prompts_v2.txt +0 -0
  79. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/diaphorina_user_prompts.txt +0 -0
  80. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/ontologies/aism.obo +0 -0
  81. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/ontologies/pato.obo +0 -0
  82. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/SDD.xsd +0 -0
  83. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/SDD_EnumLib.xsd +0 -0
  84. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/SDD_TypeLib.xsd +0 -0
  85. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/UBIF_BasePattern.xsd +0 -0
  86. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/UBIF_CoreExtensions.xsd +0 -0
  87. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/UBIF_CoreOntology.xsd +0 -0
  88. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/UBIF_EnumLib.xsd +0 -0
  89. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/UBIF_LinneanCore.xsd +0 -0
  90. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/UBIF_TypeLib.xsd +0 -0
  91. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/sdd_1.1/w3c-schemata/xml.xsd +0 -0
  92. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/taxpub/sample_treatment_deposit_example.xml +0 -0
  93. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/taxpub/sample_zookeys_26056_tp.xml +0 -0
  94. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/schemas/taxpub/tax-treatment-NS0-v1_flat.dtd +0 -0
  95. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/taxon_profiles/TEMPLATE_taxon_profile.yaml +0 -0
  96. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_prompts/taxon_profiles/diaphorina_taxon_profile.yaml +0 -0
  97. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_rebuild_downstream_v1.sh +0 -0
  98. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_reliability_figures_v1.py +0 -0
  99. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_rename_species_v1.py +0 -0
  100. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_rerun_descriptive_tables_v1.sh +0 -0
  101. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_retest_compare_v1.py +0 -0
  102. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_specimen_id.py +0 -0
  103. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_subjective_checks_v1.py +0 -0
  104. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_type_material_v1.py +0 -0
  105. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_vlm_characters_v1.py +0 -0
  106. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_vlm_heatmap_figures_v1.py +0 -0
  107. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_vlm_reliability_figure_v1.py +0 -0
  108. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biorag_vlm_roi_figures_v1.py +0 -0
  109. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biosyslit_rag_retrieval.py +0 -0
  110. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/biosyslit_rag_retrieval_v2.py +0 -0
  111. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/build_monograph_exports_v1.py +0 -0
  112. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/build_species_treatment_docx_v2.py +0 -0
  113. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/coco_combiner_V13.py +0 -0
  114. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/coco_converter_v24.py +0 -0
  115. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/color_extraction_with_names-v8.py +0 -0
  116. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/color_phenomics_homology_v2_1.py +0 -0
  117. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/compile_specimen_data.py +0 -0
  118. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/descriptron_centerline.py +0 -0
  119. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/descriptron_coco_tools.py +0 -0
  120. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/descriptron_convert.py +0 -0
  121. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/descriptron_credentials.py +0 -0
  122. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/descriptron_joints.py +0 -0
  123. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/descriptron_metadata.py +0 -0
  124. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/descriptron_rosetta.py +0 -0
  125. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/descriptron_shape_stats.py +0 -0
  126. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/landmark_gpa_V1.py +0 -0
  127. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/landmark_gpa_V2.py +0 -0
  128. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/make_reproducibility_bundle.py +0 -0
  129. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/semi_landmark_and_kpts_procrustesV34_GPA.py +0 -0
  130. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/semi_landmark_and_kpts_procrustesV42_GPA.py +0 -0
  131. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/texture_phenomics_from_coco.py +0 -0
  132. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/texture_phenomics_homology.py +0 -0
  133. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/validate_centerline_lengths.py +0 -0
  134. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/validate_pipeline_gm_vs_geomorph.py +0 -0
  135. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/validate_semilandmarks_vs_geomorph.py +0 -0
  136. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/validate_shape_stats_vs_geomorph.py +0 -0
  137. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/validate_shape_stats_vs_rrpp.py +0 -0
  138. {descriptron_core-2.1.2 → descriptron_core-2.1.3}/src/descriptron_core/tools/validate_texture_glcm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: descriptron-core
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Evidence-tiered species descriptions, keys and audits from phenomic matrices
5
5
  Project-URL: Homepage, https://github.com/alexrvandam/Descriptron
6
6
  Project-URL: Repository, https://github.com/alexrvandam/Descriptron
@@ -0,0 +1,82 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "descriptron-core"
7
+ version = "2.1.3"
8
+ description = "Evidence-tiered species descriptions, keys and audits from phenomic matrices"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [{ name = "Alex R. Van Dam" }]
13
+ keywords = ["taxonomy", "morphometrics", "phenomics", "species description", "COCO"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
17
+ "Topic :: Scientific/Engineering :: Image Processing",
18
+ "Intended Audience :: Science/Research",
19
+ "License :: OSI Approved :: Apache Software License",
20
+ "Operating System :: OS Independent",
21
+ ]
22
+
23
+ # Derived from the imports of the 62 programs in this package, not from memory:
24
+ # `python -m descriptron_core.cli --list` names them. No torch, no CUDA, no
25
+ # compiler — this installs in about a minute on Linux, macOS and Windows, and
26
+ # with the deposited COCO files it reproduces every analysis in the methods paper.
27
+ dependencies = [
28
+ "numpy>=1.23",
29
+ "pandas>=1.5",
30
+ "scipy>=1.9",
31
+ "matplotlib>=3.6",
32
+ "scikit-learn>=1.2",
33
+ "scikit-image>=0.20",
34
+ "opencv-python-headless>=4.7", # headless: the analysis half never opens a window
35
+ "pillow>=9",
36
+ "pycocotools>=2.0.6",
37
+ "seaborn>=0.12",
38
+ "statsmodels>=0.14",
39
+ "scikit-posthocs>=0.7",
40
+ "umap-learn>=0.5",
41
+ "networkx>=3",
42
+ "pyefd>=1.6",
43
+ "biopython>=1.81",
44
+ "PyYAML>=6",
45
+ "lxml>=4.9",
46
+ "openpyxl>=3.1",
47
+ "python-docx>=1.0", # treatment and monograph .docx export
48
+ ]
49
+
50
+ [project.optional-dependencies]
51
+ # Only needed to *write* treatments. The audit, key, matrix, novelty scoring and
52
+ # delimitation are all deterministic and need no model at all.
53
+ llm = ["anthropic>=0.40"]
54
+ # literature retrieval (the RAG in BioRAG): BioSysLit treatments from Zenodo and your
55
+ # own PDFs. Keyword retrieval needs only the first two; sentence-embedding retrieval
56
+ # (`index --embeddings`) also needs sentence-transformers + faiss-cpu, which pull in torch.
57
+ rag = ["requests>=2.28", "pdfplumber>=0.10"]
58
+ rag-embeddings = ["requests>=2.28", "pdfplumber>=0.10", "sentence-transformers>=2.2", "faiss-cpu>=1.7"]
59
+ dev = ["pytest>=7", "build>=1", "twine>=5"]
60
+
61
+ [project.scripts]
62
+ descriptron = "descriptron_core.cli:main"
63
+ biorag-pipeline = "descriptron_core.cli:run_pipeline"
64
+ biorag-key = "descriptron_core.cli:run_key_builder"
65
+ biorag-audit = "descriptron_core.cli:run_confabulation_checker"
66
+ biorag-novelty = "descriptron_core.cli:run_novelty_score"
67
+ biorag-calibrate = "descriptron_core.cli:run_calibrate"
68
+ descriptron-keys = "descriptron_core.cli:run_keys"
69
+
70
+ [project.urls]
71
+ Homepage = "https://github.com/alexrvandam/Descriptron"
72
+ Repository = "https://github.com/alexrvandam/Descriptron"
73
+ Issues = "https://github.com/alexrvandam/Descriptron/issues"
74
+
75
+ [tool.hatch.build.targets.wheel]
76
+ packages = ["src/descriptron_core"]
77
+
78
+ # tools/ and data/ sit inside the package directory, so `packages` already ships
79
+ # them; a force-include here would add every file a second time and the build
80
+ # fails with "a second file is being added to the wheel archive at the same path".
81
+ [tool.hatch.build]
82
+ artifacts = ["src/descriptron_core/tools/**", "src/descriptron_core/data/**"]
@@ -82,6 +82,11 @@ class LLMResponse:
82
82
  def _log_call(log_path: Optional[str], record: Dict):
83
83
  if not log_path:
84
84
  return
85
+ try: # when the call was made, for matching a call to a result
86
+ from datetime import datetime, timezone
87
+ record = {"time_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"), **record}
88
+ except Exception:
89
+ pass
85
90
  with _LOG_LOCK:
86
91
  Path(log_path).parent.mkdir(parents=True, exist_ok=True)
87
92
  with open(log_path, "a") as f:
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ biorag_provenance_v1.py — stamp every output with what produced it
4
+ ==================================================================
5
+
6
+ A number in a manuscript is only checkable if a reader can get from it back to
7
+ the code and the inputs that made it. `manuscript_numbers_v18.tsv` already maps
8
+ each number to the report file that supplied it; this closes the remaining link,
9
+ from the report back to the script, the commit and the exact inputs.
10
+
11
+ Three lines in a script:
12
+
13
+ from biorag_provenance_v1 import stamp, write_json
14
+ ...
15
+ write_json(out_dir / "report.json", payload) # adds "_provenance"
16
+
17
+ or, for anything that is not JSON (a CSV, a figure, a directory of tables):
18
+
19
+ stamp(out_dir, inputs=[args.matrix, args.coco]) # writes provenance.json
20
+
21
+ What is recorded: the script and its SHA-256, the git commit and whether the tree
22
+ was dirty, the full command line, the interpreter and platform, the run time in
23
+ UTC, and a SHA-256 for every named input file. Hashing the inputs is what makes
24
+ the record worth anything — "produced by version X from these exact files" is a
25
+ claim a sceptic can test, "produced by version X" is not.
26
+
27
+ Deliberately: no network, no imports beyond the standard library, and every
28
+ failure is swallowed. A provenance stamp must never be the reason an analysis
29
+ dies.
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import hashlib
34
+ import json
35
+ import os
36
+ import platform
37
+ import subprocess
38
+ import sys
39
+ from datetime import datetime, timezone
40
+ from pathlib import Path
41
+
42
+ __all__ = ["provenance", "stamp", "write_json", "sha256"]
43
+
44
+ _MAX_HASH_BYTES = 2_000_000_000 # 2 GB: beyond this, record the size only
45
+
46
+
47
+ def sha256(path) -> str | None:
48
+ """SHA-256 of a file, or None if it cannot be read. Never raises."""
49
+ try:
50
+ p = Path(path)
51
+ if not p.is_file():
52
+ return None
53
+ if p.stat().st_size > _MAX_HASH_BYTES:
54
+ return f"(not hashed: {p.stat().st_size} bytes)"
55
+ h = hashlib.sha256()
56
+ with open(p, "rb") as fh:
57
+ for chunk in iter(lambda: fh.read(1 << 20), b""):
58
+ h.update(chunk)
59
+ return h.hexdigest()
60
+ except Exception:
61
+ return None
62
+
63
+
64
+ def _git(*args, cwd=None):
65
+ try:
66
+ out = subprocess.run(["git", *args], cwd=cwd, capture_output=True,
67
+ text=True, timeout=10)
68
+ return out.stdout.strip() if out.returncode == 0 else None
69
+ except Exception:
70
+ return None
71
+
72
+
73
+ def provenance(inputs=None, script=None, extra=None) -> dict:
74
+ """The record itself. Safe to call from anywhere; never raises."""
75
+ try:
76
+ script_path = Path(script or sys.argv[0]).resolve()
77
+ except Exception:
78
+ script_path = Path(sys.argv[0] if sys.argv else "unknown")
79
+ repo = script_path.parent if script_path.exists() else None
80
+
81
+ # Only report a commit if THIS FILE is tracked by that repository. The
82
+ # Descriptron scripts currently sit untracked inside a clone of
83
+ # facebookresearch/segment-anything-2, so a naive `git rev-parse HEAD` returns
84
+ # SAM2's commit — a hash that says nothing about the code that produced the
85
+ # output, and is worse than no hash because it looks authoritative.
86
+ commit = dirty = tracked = None
87
+ if repo is not None:
88
+ tracked = bool(_git("ls-files", "--error-unmatch", str(script_path), cwd=repo))
89
+ if tracked:
90
+ commit = _git("rev-parse", "HEAD", cwd=repo)
91
+ dirty = bool(_git("status", "--porcelain", "--", str(script_path), cwd=repo))
92
+
93
+ rec = {
94
+ "script": script_path.name,
95
+ "script_path": str(script_path),
96
+ "script_sha256": sha256(script_path),
97
+ "git_commit": commit,
98
+ "git_dirty": dirty,
99
+ # a run from an untracked tree cannot be pinned to a version; say so in
100
+ # the record rather than leaving a reader to infer it
101
+ "git_tracked": tracked,
102
+ "git_note": (None if commit else
103
+ "this script is NOT tracked by any git repository, so the output "
104
+ "cannot be tied to a version of the code"),
105
+ "command": " ".join(sys.argv),
106
+ "argv": list(sys.argv),
107
+ "cwd": os.getcwd(),
108
+ "python": sys.version.split()[0],
109
+ "platform": platform.platform(),
110
+ "hostname": platform.node(),
111
+ "run_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"),
112
+ "inputs": {},
113
+ }
114
+ for item in (inputs or []):
115
+ if item is None:
116
+ continue
117
+ try:
118
+ rec["inputs"][str(item)] = sha256(item)
119
+ except Exception:
120
+ rec["inputs"][str(item)] = None
121
+ if extra:
122
+ rec["extra"] = extra
123
+ return rec
124
+
125
+
126
+ def stamp(out_dir, inputs=None, script=None, extra=None, name="provenance.json"):
127
+ """Write a provenance record beside an output that is not itself JSON."""
128
+ try:
129
+ d = Path(out_dir)
130
+ d.mkdir(parents=True, exist_ok=True)
131
+ rec = provenance(inputs=inputs, script=script, extra=extra)
132
+ (d / name).write_text(json.dumps(rec, indent=2))
133
+ return d / name
134
+ except Exception:
135
+ return None
136
+
137
+
138
+ def write_json(path, payload, inputs=None, script=None, extra=None, indent=2):
139
+ """json.dump with a `_provenance` key added. Drop-in for an existing dump."""
140
+ try:
141
+ rec = provenance(inputs=inputs, script=script, extra=extra)
142
+ if isinstance(payload, dict):
143
+ payload = {**payload, "_provenance": rec}
144
+ else:
145
+ payload = {"data": payload, "_provenance": rec}
146
+ except Exception:
147
+ pass
148
+ p = Path(path)
149
+ p.parent.mkdir(parents=True, exist_ok=True)
150
+ p.write_text(json.dumps(payload, indent=indent, default=str))
151
+ return p
152
+
153
+
154
+ def verify(record_path) -> dict:
155
+ """
156
+ Re-check a stamped record: does the script still hash the same, do the inputs
157
+ still hash the same? This is what a sceptical reader runs.
158
+ """
159
+ rec = json.loads(Path(record_path).read_text())
160
+ rec = rec.get("_provenance", rec)
161
+ out = {"script": None, "inputs": {}, "ok": True}
162
+ now = sha256(rec.get("script_path"))
163
+ out["script"] = ("unchanged" if now and now == rec.get("script_sha256")
164
+ else "CHANGED or missing")
165
+ if out["script"] != "unchanged":
166
+ out["ok"] = False
167
+ for path, want in (rec.get("inputs") or {}).items():
168
+ got = sha256(path)
169
+ same = (got is not None and got == want)
170
+ out["inputs"][path] = "unchanged" if same else "CHANGED or missing"
171
+ if not same:
172
+ out["ok"] = False
173
+ # records written by run_full_pipeline_v2 also list what the step wrote
174
+ if isinstance(rec.get("outputs"), dict):
175
+ out["outputs"] = {}
176
+ for path, want in rec["outputs"].items():
177
+ if path.startswith("("):
178
+ continue
179
+ got = sha256(path)
180
+ same = (got is not None and got == want)
181
+ out["outputs"][path] = "unchanged" if same else "CHANGED or missing"
182
+ if not same:
183
+ out["ok"] = False
184
+ return out
185
+
186
+
187
+ def which(result_file, search_dir) -> list:
188
+ """Which pipeline step wrote this file? Hash it and look for the hash among the outputs of
189
+ every provenance record under search_dir. A match means this exact content was written by
190
+ that step; no match means the file was changed afterwards or made outside the pipeline."""
191
+ want = sha256(result_file)
192
+ hits = []
193
+ if want is None:
194
+ return hits
195
+ for rp in sorted(Path(search_dir).rglob("*provenance*.json")):
196
+ try:
197
+ rec = json.loads(rp.read_text())
198
+ except Exception:
199
+ continue
200
+ rec = rec.get("_provenance", rec)
201
+ outs = rec.get("outputs") or {}
202
+ if isinstance(outs, dict) and want in outs.values():
203
+ hits.append({"record": str(rp),
204
+ "step": (rec.get("extra") or {}).get("pipeline_step"),
205
+ "script": rec.get("script"), "script_sha256": rec.get("script_sha256"),
206
+ "git_commit": rec.get("git_commit"), "run_utc": rec.get("run_utc"),
207
+ "command": rec.get("command")})
208
+ return hits
209
+
210
+
211
+ if __name__ == "__main__":
212
+ import argparse
213
+ ap = argparse.ArgumentParser(description=__doc__,
214
+ formatter_class=argparse.RawDescriptionHelpFormatter)
215
+ ap.add_argument("--verify", help="a provenance.json (or a report with _provenance) to re-check")
216
+ ap.add_argument("--show", action="store_true", help="print a record for this invocation")
217
+ ap.add_argument("--which", help="a result file: find the provenance record of the step that wrote it")
218
+ ap.add_argument("--search", default=".", help="folder to search for provenance records (with --which)")
219
+ a = ap.parse_args()
220
+ if a.verify:
221
+ result = verify(a.verify)
222
+ print(json.dumps(result, indent=2))
223
+ sys.exit(0 if result["ok"] else 1)
224
+ if a.which:
225
+ hits = which(a.which, a.search)
226
+ print(json.dumps(hits, indent=2) if hits else
227
+ "no provenance record lists this exact file content (changed since, or not made by the pipeline)")
228
+ sys.exit(0 if hits else 1)
229
+ if a.show:
230
+ print(json.dumps(provenance(), indent=2))
@@ -164,6 +164,118 @@ def _has_keypoints(coco_json_path: str) -> bool:
164
164
  return False
165
165
 
166
166
 
167
+ _OUT_FLAGS = ("--output_dir", "--output-dir", "--out_dir", "--out-dir", "--output_base", "--outdir")
168
+ _MAX_OUTPUT_FILES = 2000 # hash at most this many files written by one step
169
+
170
+
171
+ def _dir_manifest(d: Path) -> Dict:
172
+ """A cheap fingerprint of an input folder (e.g. thousands of images): names, sizes and
173
+ modification times of its files, hashed together. Contents are not read."""
174
+ import hashlib
175
+ h, n = hashlib.sha256(), 0
176
+ for p in sorted(d.rglob("*")):
177
+ if p.is_file():
178
+ st = p.stat()
179
+ h.update(f"{p.relative_to(d)}\t{st.st_size}\t{int(st.st_mtime)}\n".encode())
180
+ n += 1
181
+ return {"files": n, "manifest_sha256": h.hexdigest()}
182
+
183
+
184
+ def _prov_import():
185
+ """biorag_provenance_v1 sits beside this script (working tree, pip package, Docker)."""
186
+ if str(SCRIPT_DIR) not in sys.path:
187
+ sys.path.insert(0, str(SCRIPT_DIR))
188
+ import biorag_provenance_v1
189
+ return biorag_provenance_v1
190
+
191
+
192
+ def _step_pre_state(cmd: List[str]) -> Dict:
193
+ """Before a step runs: its script and every file or folder named on its command line."""
194
+ try:
195
+ sha256 = _prov_import().sha256
196
+ script = next((c for c in cmd if str(c).endswith(".py")), None)
197
+ files, folders = {}, {}
198
+ skip_next = False
199
+ for c in cmd[1:]:
200
+ if skip_next: # the step's output folder is not an input
201
+ skip_next = False
202
+ continue
203
+ if str(c) in _OUT_FLAGS:
204
+ skip_next = True
205
+ continue
206
+ p = Path(str(c))
207
+ if str(c).startswith("-") or str(c) == script or len(str(c)) > 4096:
208
+ continue
209
+ if p.is_file():
210
+ files[str(p)] = sha256(p)
211
+ elif p.is_dir():
212
+ folders[str(p)] = _dir_manifest(p)
213
+ return {"script": script, "files": files, "folders": folders}
214
+ except Exception as e: # never the reason a step fails
215
+ return {"error": f"{type(e).__name__}: {e}"}
216
+
217
+
218
+ def _stamp_step(cmd: List[str], step_name: str, log_dir: Path, pre: Dict,
219
+ t0: float, elapsed: float, returncode: int) -> None:
220
+ """Write <log_dir>/<step>.provenance.json (and provenance_<step>.json in the step's output
221
+ folder): the step script and its SHA-256, git commit if tracked, the command, a SHA-256 of
222
+ every input file named on the command line (taken BEFORE the step ran; files the step then
223
+ overwrote are listed under both inputs and outputs), a manifest of every input folder, and a
224
+ SHA-256 of every file the step wrote. Check later with
225
+ `descriptron biorag_provenance_v1 --verify <file>` (or `python biorag_provenance_v1.py ...` from the source tree). Never raises."""
226
+ try:
227
+ _pv = _prov_import()
228
+ provenance, sha256 = _pv.provenance, _pv.sha256
229
+ extra = {"pipeline_step": step_name, "exit_code": returncode,
230
+ "seconds": round(elapsed, 1), "python_bin": cmd[0] if cmd else None,
231
+ "input_folders": pre.get("folders", {}),
232
+ "pipeline_script": Path(__file__).name,
233
+ "pipeline_script_sha256": sha256(Path(__file__).resolve())}
234
+ try:
235
+ from importlib.metadata import version
236
+ extra["descriptron_packages"] = {}
237
+ for dist in ("descriptron", "descriptron-core", "descriptron-vision"):
238
+ try:
239
+ extra["descriptron_packages"][dist] = version(dist)
240
+ except Exception:
241
+ pass
242
+ except Exception:
243
+ pass
244
+ rec = provenance(inputs=list(pre.get("files", {})), script=pre.get("script"), extra=extra)
245
+ rec["command"] = " ".join(str(c) for c in cmd) # the step's command, not the pipeline's
246
+ rec["argv"] = [str(c) for c in cmd]
247
+ rec["inputs"] = pre.get("files", {}) # hashes as they were before the run
248
+ if pre.get("error"):
249
+ rec["pre_state_error"] = pre["error"]
250
+ out_dir = None
251
+ for i, c in enumerate(cmd[:-1]):
252
+ if str(c) in _OUT_FLAGS:
253
+ out_dir = Path(str(cmd[i + 1]))
254
+ break
255
+ outputs = {}
256
+ if out_dir is not None and out_dir.is_dir():
257
+ for p in sorted(out_dir.rglob("*")):
258
+ if len(outputs) >= _MAX_OUTPUT_FILES:
259
+ outputs["(truncated)"] = f"more than {_MAX_OUTPUT_FILES} files written"
260
+ break
261
+ # the pipeline's own bookkeeping (its running log, the step logs and every
262
+ # provenance record) keeps changing after the step and is not the step's output
263
+ if (p.is_file() and p.stat().st_mtime >= t0 - 1
264
+ and not p.name.startswith("provenance_")
265
+ and not p.name.endswith(".provenance.json")
266
+ and p.name != "pipeline_log.txt"
267
+ and log_dir.resolve() not in p.resolve().parents):
268
+ outputs[str(p)] = sha256(p)
269
+ rec["outputs"] = outputs
270
+ rec["output_dir"] = str(out_dir) if out_dir else None
271
+ text = json.dumps(rec, indent=2, default=str)
272
+ (log_dir / f"{step_name}.provenance.json").write_text(text)
273
+ if out_dir is not None and out_dir.is_dir():
274
+ (out_dir / f"provenance_{step_name}.json").write_text(text)
275
+ except Exception as e:
276
+ logger.warning(f" (provenance record not written: {type(e).__name__}: {e})")
277
+
278
+
167
279
  def _run_step(cmd: List[str], step_name: str, log_dir: Path,
168
280
  dry_run: bool = False) -> bool:
169
281
  cmd_str = " \\\n ".join(cmd)
@@ -173,6 +285,7 @@ def _run_step(cmd: List[str], step_name: str, log_dir: Path,
173
285
  logger.info(f" [DRY RUN] Skipping execution")
174
286
  return True
175
287
 
288
+ pre = _step_pre_state(cmd) # provenance: hash the inputs before the step can change them
176
289
  log_file = log_dir / f"{step_name}.log"
177
290
  with open(log_file, "w") as lf:
178
291
  lf.write(f"# {step_name}\n# {datetime.now().isoformat()}\n")
@@ -185,6 +298,8 @@ def _run_step(cmd: List[str], step_name: str, log_dir: Path,
185
298
  )
186
299
  elapsed = time.time() - t0
187
300
 
301
+ _stamp_step(cmd, step_name, log_dir, pre, t0, elapsed, result.returncode)
302
+
188
303
  if result.returncode != 0:
189
304
  logger.error(f" FAILED (exit code {result.returncode}, {elapsed:.0f}s)")
190
305
  logger.error(f" See log: {log_file}")
@@ -1538,7 +1653,7 @@ def step_char_gate(cfg: Dict, python: str, log_dir: Path) -> bool:
1538
1653
  # only what it was told to. "llm" is available and reads better, but measured on the Diaphorina
1539
1654
  # treatments it also deleted statements about characters that had passed, in 47% of its edits —
1540
1655
  # invisible to both the subsequence check and the numeric audit. See BIORAG_V2_README.md §9d.
1541
- engine = cfg.get("gate_engine", "python")
1656
+ engine = cfg.get("gate_engine") or "python" # argparse stores None when --gate_engine is not given
1542
1657
  if engine == "llm" and cfg.get("llm_backend") == "none":
1543
1658
  logger.info(" no LLM backend configured — editing by rule instead (--engine python)")
1544
1659
  engine = "python"