fairscape-cli 1.2.1__tar.gz → 1.2.2__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 (101) hide show
  1. {fairscape_cli-1.2.1/src/fairscape_cli.egg-info → fairscape_cli-1.2.2}/PKG-INFO +1 -1
  2. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/pyproject.toml +1 -1
  3. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/build_commands.py +3 -2
  4. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/GenomicData.py +3 -3
  5. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/datasheet_generator.py +38 -0
  6. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/summary_generator.py +37 -3
  7. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/base.html +70 -70
  8. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/subcrates.html +2 -0
  9. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/summary.html +1 -1
  10. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/entailments/find_outputs.py +3 -2
  11. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/entailments/inverse.py +3 -2
  12. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/bagit.py +2 -1
  13. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/rocrate.py +15 -14
  14. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/tabular.py +2 -2
  15. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/provenance_tracker.py +3 -2
  16. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/utils/build_utils.py +30 -5
  17. fairscape_cli-1.2.2/src/fairscape_cli/utils/serialization.py +31 -0
  18. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2/src/fairscape_cli.egg-info}/PKG-INFO +1 -1
  19. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/SOURCES.txt +2 -1
  20. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/LICENSE +0 -0
  21. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/MANIFEST.in +0 -0
  22. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/README.md +0 -0
  23. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/setup.cfg +0 -0
  24. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/__init__.py +0 -0
  25. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/__main__.py +0 -0
  26. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/__init__.py +0 -0
  27. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/augment_commands.py +0 -0
  28. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/import_commands.py +0 -0
  29. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/publish_commands.py +0 -0
  30. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/rocrate_commands.py +0 -0
  31. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/schema_commands.py +0 -0
  32. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/track.py +0 -0
  33. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/config.py +0 -0
  34. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/PhysioNetImporter.py +0 -0
  35. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/__init__.py +0 -0
  36. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/bioproject_fetcher.py +0 -0
  37. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/cell_line_api.py +0 -0
  38. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/__init__.py +0 -0
  39. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/connectors/__init__.py +0 -0
  40. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/connectors/dataverse_connector.py +0 -0
  41. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/connectors/figshare_connector.py +0 -0
  42. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/research_data.py +0 -0
  43. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/__init__.py +0 -0
  44. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/evidence_graph/__init__.py +0 -0
  45. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/evidence_graph/graph_builder.py +0 -0
  46. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/evidence_graph/html_builder.py +0 -0
  47. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/generate_datasheet.py +0 -0
  48. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/__init__.py +0 -0
  49. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/section_generators.py +0 -0
  50. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/utilities/__init__.py +0 -0
  51. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/.DS_Store +0 -0
  52. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/preview.html +0 -0
  53. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/distribution.html +0 -0
  54. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/overview.html +0 -0
  55. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/use_cases.html +0 -0
  56. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/entailments/__init__.py +0 -0
  57. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/entailments/evi.xml +0 -0
  58. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/jupyter/__init__.py +0 -0
  59. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/jupyter/magic.py +0 -0
  60. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/MLModel.py +0 -0
  61. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/__init__.py +0 -0
  62. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/base.py +0 -0
  63. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/biochem_entity.py +0 -0
  64. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/computation.py +0 -0
  65. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/dataset.py +0 -0
  66. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/experiment.py +0 -0
  67. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/guid_utils.py +0 -0
  68. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/instrument.py +0 -0
  69. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/pep.py +0 -0
  70. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/sample.py +0 -0
  71. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/__init__.py +0 -0
  72. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/__init__.py +0 -0
  73. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/cm4ai_schemas.py +0 -0
  74. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_apms_embedding.json +0 -0
  75. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_apmsloader_ppi_edgelist.json +0 -0
  76. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_apmsloader_ppi_gene_node_attributes.json +0 -0
  77. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_coembedding.json +0 -0
  78. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_hierarchy_cx.json +0 -0
  79. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_hierarchy_ppi_cutoff.json +0 -0
  80. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_image_embedding_emd.json +0 -0
  81. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_image_embedding_labels_prob.json +0 -0
  82. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_imageloader_gene_node_attributes.json +0 -0
  83. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_imageloader_samplescopy.json +0 -0
  84. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_imageloader_uniquecopy.json +0 -0
  85. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/default.py +0 -0
  86. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/utils.py +0 -0
  87. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/software.py +0 -0
  88. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/utils.py +0 -0
  89. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/publish/__init__.py +0 -0
  90. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/publish/publish_tools.py +0 -0
  91. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/__init__.py +0 -0
  92. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/config.py +0 -0
  93. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/io_capture.py +0 -0
  94. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/metadata_generator.py +0 -0
  95. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/utils.py +0 -0
  96. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/utils/huggingface_utils.py +0 -0
  97. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/utils/merkle.py +0 -0
  98. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/dependency_links.txt +0 -0
  99. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/entry_points.txt +0 -0
  100. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/requires.txt +0 -0
  101. {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fairscape-cli
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: A utility for packaging objects and validating metadata for FAIRSCAPE
5
5
  Author-email: Max Levinson <mal8ch@virginia.edu>, Justin Niestroy <jniestroy@gmail.com>, Sadnan Al Manir <sadnanalmanir@gmail.com>, Tim Clark <twc8q@virginia.edu>
6
6
  License: Copyright 2023 THE RECTOR AND VISITORS OF THE UNIVERSITY OF VIRGINIA
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fairscape-cli"
7
- version = "1.2.1"
7
+ version = "1.2.2"
8
8
  description = "A utility for packaging objects and validating metadata for FAIRSCAPE"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -29,6 +29,7 @@ from fairscape_cli.models import (
29
29
  )
30
30
 
31
31
  from fairscape_models.rocrate import ROCrateV1_2, ROCrateMetadataElem
32
+ from fairscape_cli.utils.serialization import prune_none
32
33
  from fairscape_models.conversion.converter import ROCToTargetConverter
33
34
  from fairscape_models.conversion.mapping.croissant import MAPPING_CONFIGURATION as CROISSANT_MAPPING
34
35
 
@@ -535,7 +536,7 @@ def generate_evidence_graph(
535
536
 
536
537
  # Write the updated metadata back to the file
537
538
  with open(metadata_file, 'w') as f:
538
- json.dump(metadata, f, indent=2)
539
+ json.dump(prune_none(metadata), f, indent=2)
539
540
 
540
541
  click.echo(f"Added hasEvidenceGraph reference to {ark_id} in RO-Crate metadata")
541
542
  except Exception as e:
@@ -774,4 +775,4 @@ def validate_merkle_command(ctx, rocrate_path: pathlib.Path, release: bool):
774
775
  elif s != c:
775
776
  click.echo(f" CHANGED: {url}")
776
777
 
777
- ctx.exit(1)
778
+ ctx.exit(1)
@@ -103,6 +103,7 @@ from fairscape_cli.data_fetcher.cell_line_api import get_cell_line_entity
103
103
  from fairscape_cli.data_fetcher.bioproject_fetcher import fetch_bioproject_data
104
104
 
105
105
  from fairscape_cli.models.rocrate import GenerateROCrate, AppendCrate
106
+ from fairscape_cli.utils.serialization import prune_none
106
107
  from fairscape_cli.models.dataset import GenerateDataset
107
108
  from fairscape_cli.models.experiment import GenerateExperiment
108
109
  from fairscape_cli.models.instrument import GenerateInstrument
@@ -342,7 +343,7 @@ class GenomicData(BaseModel):
342
343
  root_dataset_node["hasPart"].append({"@id": entity_id})
343
344
 
344
345
  f.seek(0)
345
- json.dump(crate_json, f, indent=2)
346
+ json.dump(prune_none(crate_json), f, indent=2)
346
347
  f.truncate()
347
348
 
348
349
 
@@ -360,7 +361,7 @@ class GenomicData(BaseModel):
360
361
 
361
362
  if updated:
362
363
  f.seek(0)
363
- json.dump(crate_json_final, f, indent=2)
364
+ json.dump(prune_none(crate_json_final), f, indent=2)
364
365
  f.truncate()
365
366
 
366
367
 
@@ -500,4 +501,3 @@ class GenomicData(BaseModel):
500
501
  experiments=Experiments(items=internal_experiments),
501
502
  outputs=Outputs(items=outputs_list)
502
503
  )
503
-
@@ -159,6 +159,8 @@ class DatasheetGenerator:
159
159
  distribution = distribution_converter.convert()
160
160
 
161
161
  subcrate_items = self._process_all_subcrates()
162
+ if not subcrate_items:
163
+ subcrate_items = self._build_single_crate_composition()
162
164
  composition = CompositionSection(items=subcrate_items) if subcrate_items else None
163
165
 
164
166
  return FairscapeDatasheet(
@@ -226,6 +228,42 @@ class DatasheetGenerator:
226
228
 
227
229
  return subcrate_items
228
230
 
231
+ def _build_single_crate_composition(self) -> List[SubCrateItem]:
232
+ """When no subcrates exist, treat the main crate itself as a single subcrate."""
233
+ try:
234
+ converter = ROCToTargetConverter(
235
+ source_crate=self.main_crate,
236
+ mapping_configuration=SUBCRATE_MAPPING_CONFIGURATION,
237
+ global_index=self.global_metadata_index
238
+ )
239
+ subcrate_item = converter.convert()
240
+ subcrate_item.published = self.published
241
+ subcrate_item.preview_url = ""
242
+
243
+ if not subcrate_item.size and self.base_dir.exists():
244
+ try:
245
+ dir_size = get_directory_size(str(self.base_dir))
246
+ subcrate_item.size = format_size(dir_size)
247
+ except Exception:
248
+ subcrate_item.size = "Unknown"
249
+
250
+ main_root = self.main_crate.metadataGraph[1].model_dump()
251
+ if not subcrate_item.doi:
252
+ subcrate_item.doi = main_root.get('identifier')
253
+ if not subcrate_item.related_publications:
254
+ pubs = main_root.get('associatedPublication', [])
255
+ if pubs:
256
+ subcrate_item.related_publications = pubs if isinstance(pubs, list) else [pubs]
257
+
258
+ self._enhance_subcrate_item(subcrate_item, self.main_crate)
259
+
260
+ return [subcrate_item]
261
+ except Exception as e:
262
+ print(f"Error building single crate composition: {e}")
263
+ import traceback
264
+ traceback.print_exc()
265
+ return []
266
+
229
267
  def _enhance_subcrate_item(self, subcrate_item: SubCrateItem, subcrate: ROCrateV1_2):
230
268
  """Add statistical summary info to subcrate item if present."""
231
269
  root_dict = subcrate.metadataGraph[1].model_dump() if len(subcrate.metadataGraph) > 1 else {}
@@ -11,6 +11,7 @@ from jinja2 import Environment
11
11
  from fairscape_models.rocrate import ROCrateV1_2
12
12
  from fairscape_models.conversion.mapping.AIReady import score_rocrate
13
13
  from fairscape_models.conversion.models.AIReady import AIReadyScore
14
+ from fairscape_cli.utils.serialization import model_dump_pruned
14
15
 
15
16
 
16
17
  @dataclass
@@ -89,7 +90,7 @@ class SummarySectionGenerator:
89
90
  formats = []
90
91
  formats = [f for f in formats if f and f != "unknown"]
91
92
 
92
- return SummaryData(
93
+ summary = SummaryData(
93
94
  name=root_data.get("name", "Unnamed Dataset"),
94
95
  description=root_data.get("description", ""),
95
96
  total_size_formatted=size_str,
@@ -100,6 +101,36 @@ class SummarySectionGenerator:
100
101
  formats=formats
101
102
  )
102
103
 
104
+ # Fallback: compute from graph if evi:* fields are absent (single crate case)
105
+ if summary.total_entities == 0:
106
+ formats_set = set()
107
+ for item in crate.metadataGraph:
108
+ if item.guid == "ro-crate-metadata.json":
109
+ continue
110
+ item_dict = item.model_dump(by_alias=True)
111
+ item_type = item_dict.get("@type", "")
112
+ type_str = " ".join(item_type) if isinstance(item_type, list) else str(item_type)
113
+
114
+ if "ROCrate" in type_str or "CreativeWork" in type_str:
115
+ continue
116
+
117
+ summary.total_entities += 1
118
+
119
+ if "Dataset" in type_str:
120
+ summary.dataset_count += 1
121
+ fmt = item_dict.get("fileFormat")
122
+ if fmt and fmt != "unknown":
123
+ formats_set.add(fmt)
124
+ elif "Software" in type_str or "SoftwareSourceCode" in type_str:
125
+ summary.software_count += 1
126
+ elif "Computation" in type_str:
127
+ summary.computation_count += 1
128
+
129
+ if not summary.formats and formats_set:
130
+ summary.formats = sorted(formats_set)
131
+
132
+ return summary
133
+
103
134
  @staticmethod
104
135
  def _format_size(size_bytes: int) -> str:
105
136
  """Format bytes to human-readable size."""
@@ -160,7 +191,7 @@ class SummarySectionGenerator:
160
191
 
161
192
  def save_aiready_score(self, raw_score: AIReadyScore, output_path: Path) -> None:
162
193
  """Save the AI-Ready score to a JSON file."""
163
- score_dict = raw_score.model_dump()
194
+ score_dict = model_dump_pruned(raw_score)
164
195
  with open(output_path, 'w') as f:
165
196
  json.dump(score_dict, f, indent=2)
166
197
 
@@ -183,8 +214,10 @@ class SummarySectionGenerator:
183
214
  self.save_aiready_score(raw_score, aiready_json_path)
184
215
 
185
216
  desc = summary.description
217
+ description_truncated = False
186
218
  if len(desc) > 500:
187
- desc = desc[:500].rsplit(" ", 1)[0] + "..."
219
+ desc = desc[:500].rsplit(" ", 1)[0]
220
+ description_truncated = True
188
221
 
189
222
  formats_str = ", ".join(sorted(summary.formats)[:10])
190
223
  if len(summary.formats) > 10:
@@ -192,6 +225,7 @@ class SummarySectionGenerator:
192
225
 
193
226
  context = {
194
227
  'description': desc,
228
+ 'description_truncated': description_truncated,
195
229
  'total_size': summary.total_size_formatted,
196
230
  'total_entities': f"{summary.total_entities:,}" if summary.total_entities else "N/A",
197
231
  'formats': formats_str,
@@ -18,18 +18,18 @@
18
18
  .container {
19
19
  max-width: 1100px;
20
20
  margin: 0 auto;
21
- padding: 20px;
21
+ padding: 12px;
22
22
  background-color: white;
23
23
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
24
24
  /* Added for smooth transition */
25
25
  transition: margin-left 0.3s ease-in-out;
26
26
  }
27
27
  header {
28
- margin-bottom: 20px;
28
+ margin-bottom: 10px;
29
29
  border-bottom: 2px solid #2c3e50;
30
- padding-bottom: 15px;
30
+ padding-bottom: 8px;
31
31
  background-color: #f8f9fa;
32
- padding: 20px;
32
+ padding: 12px;
33
33
  border-radius: 5px;
34
34
  }
35
35
  h1 {
@@ -40,35 +40,35 @@
40
40
  h2 {
41
41
  font-size: 20px;
42
42
  color: #2c3e50;
43
- margin-top: 25px;
44
- margin-bottom: 15px;
43
+ margin-top: 12px;
44
+ margin-bottom: 8px;
45
45
  border-bottom: 1px solid #ddd;
46
- padding-bottom: 8px;
46
+ padding-bottom: 4px;
47
47
  }
48
48
  h3 {
49
49
  font-size: 18px;
50
50
  color: #2c3e50;
51
- margin-top: 20px;
52
- margin-bottom: 10px;
51
+ margin-top: 10px;
52
+ margin-bottom: 6px;
53
53
  }
54
54
  h4 {
55
55
  font-size: 16px;
56
56
  color: #2c3e50;
57
- margin-top: 15px;
58
- margin-bottom: 10px;
57
+ margin-top: 8px;
58
+ margin-bottom: 6px;
59
59
  }
60
60
  .summary-section {
61
61
  background-color: #f8f9fa;
62
- padding: 20px;
62
+ padding: 12px;
63
63
  border-radius: 4px;
64
- margin-bottom: 30px;
64
+ margin-bottom: 15px;
65
65
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
66
66
  }
67
67
  .summary-row {
68
68
  display: flex;
69
- margin-bottom: 12px;
69
+ margin-bottom: 6px;
70
70
  border-bottom: 1px solid #eee;
71
- padding-bottom: 8px;
71
+ padding-bottom: 4px;
72
72
  }
73
73
  .summary-label {
74
74
  width: 230px;
@@ -79,24 +79,24 @@
79
79
  flex: 1;
80
80
  }
81
81
  .section-header {
82
- margin: 25px 0 15px 0;
83
- padding-bottom: 8px;
82
+ margin: 12px 0 8px 0;
83
+ padding-bottom: 4px;
84
84
  border-bottom: 1px solid #ddd;
85
85
  color: #2c3e50;
86
86
  }
87
87
  .composition-summary {
88
88
  display: flex;
89
89
  flex-wrap: wrap;
90
- gap: 20px;
91
- margin-bottom: 20px;
90
+ gap: 10px;
91
+ margin-bottom: 10px;
92
92
  }
93
93
  .composition-item {
94
94
  background-color: #f8f9fa;
95
95
  border-radius: 4px;
96
- padding: 15px;
96
+ padding: 10px;
97
97
  text-align: center;
98
98
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
99
- width: calc(20% - 20px);
99
+ width: calc(20% - 10px);
100
100
  min-width: 120px;
101
101
  }
102
102
  .composition-count {
@@ -110,20 +110,20 @@
110
110
  color: #555;
111
111
  }
112
112
  .subcrates-container {
113
- margin-top: 30px;
113
+ margin-top: 15px;
114
114
  }
115
115
  .subcrate-summary {
116
116
  background-color: #f8f9fa;
117
117
  border-radius: 4px;
118
- padding: 20px;
119
- margin-bottom: 20px;
118
+ padding: 12px;
119
+ margin-bottom: 10px;
120
120
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
121
121
  }
122
122
  .subcrate-metadata {
123
- margin-bottom: 15px;
123
+ margin-bottom: 8px;
124
124
  }
125
125
  .metadata-item {
126
- margin-bottom: 6px;
126
+ margin-bottom: 3px;
127
127
  }
128
128
  .metadata-label {
129
129
  font-weight: bold;
@@ -133,24 +133,24 @@
133
133
  .section-summary {
134
134
  background-color: white;
135
135
  border-radius: 4px;
136
- padding: 15px;
137
- margin-top: 15px;
138
- margin-bottom: 15px;
136
+ padding: 10px;
137
+ margin-top: 8px;
138
+ margin-bottom: 8px;
139
139
  border: 1px solid #eaeaea;
140
140
  }
141
141
  .summary-grid {
142
142
  display: grid;
143
143
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
144
- gap: 15px;
144
+ gap: 10px;
145
145
  }
146
146
  .summary-item {
147
- margin-bottom: 15px;
147
+ margin-bottom: 8px;
148
148
  }
149
149
  details {
150
- margin-top: 15px;
150
+ margin-top: 8px;
151
151
  border: 1px solid #eaeaea;
152
152
  border-radius: 4px;
153
- padding: 10px;
153
+ padding: 8px;
154
154
  }
155
155
  summary {
156
156
  font-weight: bold;
@@ -175,7 +175,7 @@
175
175
  color: #2c3e50;
176
176
  }
177
177
  .view-full-link {
178
- margin-top: 15px;
178
+ margin-top: 8px;
179
179
  text-align: right;
180
180
  }
181
181
  .view-full-link a {
@@ -194,15 +194,15 @@
194
194
  .use-cases-section {
195
195
  background-color: #f8f9fa;
196
196
  border-radius: 4px;
197
- padding: 20px;
198
- margin-bottom: 30px;
197
+ padding: 12px;
198
+ margin-bottom: 15px;
199
199
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
200
200
  }
201
201
  .distribution-item,
202
202
  .use-cases-item {
203
- margin-bottom: 12px;
203
+ margin-bottom: 6px;
204
204
  border-bottom: 1px solid #eee;
205
- padding-bottom: 8px;
205
+ padding-bottom: 4px;
206
206
  }
207
207
  .distribution-label,
208
208
  .use-cases-label {
@@ -226,16 +226,16 @@
226
226
  }
227
227
  .subcrate-title {
228
228
  border-bottom: 2px solid #2c3e50;
229
- padding-bottom: 10px;
230
- margin-bottom: 15px;
229
+ padding-bottom: 6px;
230
+ margin-bottom: 8px;
231
231
  color: #2c3e50;
232
232
  }
233
233
  .composition-grid {
234
234
  display: grid;
235
235
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
236
- gap: 15px;
237
- margin-top: 15px;
238
- margin-bottom: 15px;
236
+ gap: 8px;
237
+ margin-top: 8px;
238
+ margin-bottom: 8px;
239
239
  }
240
240
  .composition-card {
241
241
  border: 1px solid #eaeaea;
@@ -259,12 +259,12 @@
259
259
  color: #2c3e50;
260
260
  }
261
261
  .card-content {
262
- padding: 15px;
262
+ padding: 8px 10px;
263
263
  }
264
264
  .card-stats {
265
265
  display: flex;
266
266
  flex-direction: column;
267
- gap: 10px;
267
+ gap: 6px;
268
268
  }
269
269
  .stat-item {
270
270
  display: flex;
@@ -292,9 +292,9 @@
292
292
  .compact-grid {
293
293
  display: grid;
294
294
  grid-template-columns: repeat(2, 1fr);
295
- gap: 10px;
296
- margin-top: 10px;
297
- margin-bottom: 10px;
295
+ gap: 6px;
296
+ margin-top: 6px;
297
+ margin-bottom: 6px;
298
298
  }
299
299
 
300
300
  .summary-card {
@@ -312,7 +312,7 @@
312
312
 
313
313
  .indent {
314
314
  margin-left: 12px;
315
- margin-bottom: 8px;
315
+ margin-bottom: 4px;
316
316
  }
317
317
 
318
318
  .small {
@@ -330,13 +330,13 @@
330
330
  }
331
331
 
332
332
  .subcrate-composition h4 {
333
- margin-top: 12px;
334
- margin-bottom: 8px;
333
+ margin-top: 8px;
334
+ margin-bottom: 4px;
335
335
  }
336
336
 
337
337
  footer {
338
- margin-top: 30px;
339
- padding: 20px;
338
+ margin-top: 15px;
339
+ padding: 12px;
340
340
  background-color: #f8f9fa;
341
341
  border-top: 1px solid #ddd;
342
342
  border-radius: 5px;
@@ -354,15 +354,15 @@
354
354
  text-decoration: underline;
355
355
  }
356
356
  .regulatory-section {
357
- margin-top: 20px;
358
- margin-bottom: 20px;
357
+ margin-top: 10px;
358
+ margin-bottom: 10px;
359
359
  padding: 0;
360
360
  }
361
361
 
362
362
  .regulatory-section h3 {
363
363
  font-size: 18px;
364
364
  color: #2c3e50;
365
- margin-bottom: 15px;
365
+ margin-bottom: 8px;
366
366
  border-bottom: none;
367
367
  padding-bottom: 0;
368
368
  }
@@ -370,12 +370,12 @@
370
370
  .regulatory-grid {
371
371
  display: grid;
372
372
  grid-template-columns: repeat(2, 1fr);
373
- gap: 15px;
373
+ gap: 8px;
374
374
  }
375
375
 
376
376
  .regulatory-item {
377
377
  background-color: #f8f9fa;
378
- padding: 12px 15px;
378
+ padding: 8px 10px;
379
379
  border-radius: 4px;
380
380
  border: none;
381
381
  }
@@ -388,9 +388,9 @@
388
388
 
389
389
  .irb-detail {
390
390
  background-color: #f8f9fa;
391
- padding: 12px 15px;
391
+ padding: 8px 10px;
392
392
  border-radius: 4px;
393
- margin-top: 15px;
393
+ margin-top: 8px;
394
394
  }
395
395
 
396
396
  .irb-org {
@@ -425,9 +425,9 @@
425
425
 
426
426
  .regulatory-exemptions {
427
427
  background-color: #f8f9fa;
428
- padding: 12px 15px;
428
+ padding: 8px 10px;
429
429
  border-radius: 4px;
430
- margin-top: 15px;
430
+ margin-top: 8px;
431
431
  }
432
432
 
433
433
  .regulatory-exemptions p {
@@ -504,9 +504,9 @@
504
504
  /* START: Executive Summary and AI-Readiness Score styles */
505
505
  .executive-summary-section {
506
506
  background-color: #e9ecef;
507
- padding: 20px;
507
+ padding: 12px;
508
508
  border-radius: 5px;
509
- margin-bottom: 20px;
509
+ margin-bottom: 10px;
510
510
  border: 1px solid #ced4da;
511
511
  }
512
512
 
@@ -521,18 +521,18 @@
521
521
  .summary-description {
522
522
  font-size: 13px;
523
523
  color: #495057;
524
- margin-bottom: 15px;
524
+ margin-bottom: 8px;
525
525
  }
526
526
 
527
527
  .exec-summary-grid {
528
528
  display: grid;
529
529
  grid-template-columns: 1fr 1fr;
530
- gap: 15px;
530
+ gap: 10px;
531
531
  }
532
532
 
533
533
  .exec-summary-item {
534
534
  background-color: #f8f9fa;
535
- padding: 12px 15px;
535
+ padding: 8px 10px;
536
536
  border-radius: 4px;
537
537
  border: 1px solid #e9ecef;
538
538
  }
@@ -541,15 +541,15 @@
541
541
  font-weight: bold;
542
542
  color: #2c3e50;
543
543
  display: block;
544
- margin-bottom: 10px;
545
- padding-bottom: 6px;
544
+ margin-bottom: 6px;
545
+ padding-bottom: 4px;
546
546
  border-bottom: 1px solid #dee2e6;
547
547
  font-size: 13px;
548
548
  }
549
549
 
550
550
  .exec-summary-item .stat-item {
551
551
  display: block;
552
- margin-bottom: 6px;
552
+ margin-bottom: 4px;
553
553
  font-size: 12px;
554
554
  }
555
555
 
@@ -297,9 +297,11 @@
297
297
  </div>
298
298
  </div>
299
299
  </div>
300
+ {% if subcrate.preview_url %}
300
301
  <div class="view-full-link">
301
302
  <a href="{{ subcrate.preview_url }}">View Full Dataset Details</a>
302
303
  </div>
304
+ {% endif %}
303
305
  </div>
304
306
  {% endfor %} {% else %}
305
307
  <p>No subcrates found.</p>
@@ -1,7 +1,7 @@
1
1
  <div id="datasheet-summary" class="executive-summary-section">
2
2
  <h2>Datasheet Summary</h2>
3
3
  {% if description %}
4
- <p class="summary-description">{{ description }}</p>
4
+ <p class="summary-description">{{ description }}{% if description_truncated %}... <a href="#description" style="color: #2c3e50; font-size: 12px;">[read full description]</a>{% endif %}</p>
5
5
  {% endif %}
6
6
 
7
7
  <div class="exec-summary-grid">
@@ -1,6 +1,7 @@
1
1
  import pathlib
2
2
  import json
3
3
  from typing import List, Dict, Tuple, Set, Any
4
+ from fairscape_cli.utils.serialization import prune_none
4
5
 
5
6
  def extract_datasets_from_graph(graph: List[Dict]) -> List[Tuple[str, bool]]:
6
7
  """
@@ -169,7 +170,7 @@ def add_inputs_outputs_to_rocrate(rocrate_path: pathlib.Path) -> Tuple[bool, str
169
170
  metadata["@graph"] = graph
170
171
 
171
172
  with open(metadata_path, 'w') as f:
172
- json.dump(metadata, f, indent=2)
173
+ json.dump(prune_none(metadata), f, indent=2)
173
174
 
174
175
  input_count = len(inputs)
175
176
  output_count = len(outputs)
@@ -179,4 +180,4 @@ def add_inputs_outputs_to_rocrate(rocrate_path: pathlib.Path) -> Tuple[bool, str
179
180
  except json.JSONDecodeError as e:
180
181
  return False, f"Error parsing JSON: {e}"
181
182
  except Exception as e:
182
- return False, f"Unexpected error: {e}"
183
+ return False, f"Unexpected error: {e}"
@@ -1,5 +1,6 @@
1
1
  import pathlib
2
2
  import json
3
+ from fairscape_cli.utils.serialization import prune_none
3
4
  from typing import List, Tuple, Dict, Any
4
5
  from rdflib import Graph, URIRef
5
6
  from rdflib.namespace import OWL
@@ -192,7 +193,7 @@ def augment_rocrate_with_inverses(
192
193
  if modified_count > 0:
193
194
  try:
194
195
  with open(metadata_file_path, 'w') as f:
195
- json.dump(json_data, f, indent=2, ensure_ascii=False)
196
+ json.dump(prune_none(json_data), f, indent=2, ensure_ascii=False)
196
197
  print(f"RO-Crate '{metadata_file_path}' augmented with inverse properties. {modified_count} modifications made.")
197
198
  except Exception as e:
198
199
  print(f"Error saving augmented RO-Crate JSON to {metadata_file_path}: {e}")
@@ -200,4 +201,4 @@ def augment_rocrate_with_inverses(
200
201
  else:
201
202
  print(f"No inverse properties needed to be added or RO-Crate '{metadata_file_path}' is already consistent.")
202
203
 
203
- return True
204
+ return True
@@ -10,6 +10,7 @@ from pydantic import (
10
10
  from typing import (
11
11
  Optional
12
12
  )
13
+ from fairscape_cli.utils.serialization import model_dump_pruned
13
14
 
14
15
 
15
16
  class BagIt(BaseModel):
@@ -94,7 +95,7 @@ class BagIt(BaseModel):
94
95
  bagit_info_path = self.bagit_path / 'bag-info.txt'
95
96
 
96
97
  with bagit_info_path.open(mode="w") as bag_info_file:
97
- for key, value in self.model_dump(by_alias=True).items():
98
+ for key, value in model_dump_pruned(self, by_alias=True).items():
98
99
  if key != 'bagit_path' and key != 'rocrate_path':
99
100
  bag_info_file.write('%s: %s\n' % (key, value))
100
101