55This script queries OpenSearch using pds-registry-client and generates CSV reports
66for missing and staged bundles and collections per node.
77
8- For missing products, three CSVs are produced per product type:
9- - overall (all versions)
10- - latest (highest version per LID)
11- - superseded (all older versions per LID)
8+ For missing products, one CSV is produced per product type with a ``superseded``
9+ column (``true``/``false``) indicating whether a given LIDVID is the latest version
10+ for its LID or an older, superseded version.
1211
1312Environment variables required:
1413 REQUEST_SIGNER_AWS_ACCOUNT
@@ -151,20 +150,20 @@ def write_rows_to_csv(rows: list[tuple], output_file: Path) -> int:
151150 return len (rows )
152151
153152
154- def split_by_version (rows : list [tuple ]) -> tuple [ list [tuple ], list [ tuple ] ]:
155- """Split rows into latest and superseded versions grouped by LID.
153+ def annotate_version_status (rows : list [tuple ]) -> list [tuple ]:
154+ """Annotate rows with a `` superseded`` column based on version comparison per LID.
156155
157156 Expects lidvid as the second element (index 1) of each row, in the form
158157 ``urn:nasa:pds:<lid>::<major>.<minor>``. Version comparison is numeric so
159158 that e.g. ``3.9 < 3.13``.
160159
161- Returns:
162- (latest_rows, superseded_rows) where latest_rows contains only the
163- highest-versioned row per LID and superseded_rows contains all others.
160+ Returns a new list of rows with an additional ``superseded`` column appended
161+ (string ``"true"`` or ``"false"``). The highest-versioned row per LID gets
162+ ``"false"``; all others get ``"true"`` .
164163 """
165- by_lid : dict [str , list [tuple [tuple [int , ...], tuple ]]] = defaultdict (list )
164+ by_lid : dict [str , list [tuple [tuple [int , ...], int ]]] = defaultdict (list )
166165
167- for row in rows :
166+ for idx , row in enumerate ( rows ) :
168167 lidvid = row [1 ]
169168 if "::" in lidvid :
170169 lid , ver_str = lidvid .rsplit ("::" , 1 )
@@ -176,38 +175,48 @@ def split_by_version(rows: list[tuple]) -> tuple[list[tuple], list[tuple]]:
176175 except ValueError :
177176 ver_key = (0 ,)
178177
179- by_lid [lid ].append ((ver_key , row ))
178+ by_lid [lid ].append ((ver_key , idx ))
180179
181- latest_rows : list [tuple ] = []
182- superseded_rows : list [tuple ] = []
180+ superseded_indices : set [int ] = set ()
181+ for version_entries in by_lid .values ():
182+ sorted_entries = sorted (version_entries , key = lambda x : x [0 ], reverse = True )
183+ for _ , idx in sorted_entries [1 :]:
184+ superseded_indices .add (idx )
183185
184- for version_rows in by_lid .values ():
185- sorted_rows = sorted (version_rows , key = lambda x : x [0 ], reverse = True )
186- latest_rows .append (sorted_rows [0 ][1 ])
187- superseded_rows .extend (r for _ , r in sorted_rows [1 :])
186+ return [row + ("true" if idx in superseded_indices else "false" ,) for idx , row in enumerate (rows )]
188187
189- return latest_rows , superseded_rows
190188
189+ def _count_by_node (csv_path : Path , superseded : bool | None = None ) -> dict [str , int ]:
190+ """Return a node→count mapping from a CSV file (node is the first column).
191191
192- def _count_by_node (csv_path : Path ) -> dict [str , int ]:
193- """Return a node→count mapping from a CSV file (node is the first column)."""
192+ If ``superseded`` is ``None``, all rows are counted. Pass ``True`` or
193+ ``False`` to count only the rows whose last column matches ``"true"`` or
194+ ``"false"`` respectively (used for missing-product CSVs which carry a
195+ superseded annotation in their last column).
196+ """
194197 counts : dict [str , int ] = defaultdict (int )
195198 if csv_path .exists ():
196199 with open (csv_path , "r" ) as f :
197200 for row in csv .reader (f ):
198- if row :
199- counts [row [0 ]] += 1
201+ if not row :
202+ continue
203+ if superseded is not None :
204+ if len (row ) < 4 :
205+ continue
206+ if (row [- 1 ].lower () == "true" ) != superseded :
207+ continue
208+ counts [row [0 ]] += 1
200209 return counts
201210
202211
203212def generate_metrics_from_csvs (csv_files : dict [str , Path ]) -> str :
204213 """Generate metrics summary markdown from CSV files."""
205214 mb = _count_by_node (csv_files ["missing_bundles" ])
206- mb_latest = _count_by_node (csv_files ["missing_bundles_latest" ] )
207- mb_superseded = _count_by_node (csv_files ["missing_bundles_superseded" ] )
215+ mb_latest = _count_by_node (csv_files ["missing_bundles" ], superseded = False )
216+ mb_superseded = _count_by_node (csv_files ["missing_bundles" ], superseded = True )
208217 mc = _count_by_node (csv_files ["missing_collections" ])
209- mc_latest = _count_by_node (csv_files ["missing_collections_latest" ] )
210- mc_superseded = _count_by_node (csv_files ["missing_collections_superseded" ] )
218+ mc_latest = _count_by_node (csv_files ["missing_collections" ], superseded = False )
219+ mc_superseded = _count_by_node (csv_files ["missing_collections" ], superseded = True )
211220 sb = _count_by_node (csv_files ["staged_bundles" ])
212221 sc = _count_by_node (csv_files ["staged_collections" ])
213222
@@ -295,14 +304,20 @@ def append_history_row(history_file: Path, csv_files: dict[str, Path]) -> None:
295304 def total (path : Path ) -> int :
296305 return sum (_count_by_node (path ).values ())
297306
307+ def latest (path : Path ) -> int :
308+ return sum (_count_by_node (path , superseded = False ).values ())
309+
310+ def superseded_total (path : Path ) -> int :
311+ return sum (_count_by_node (path , superseded = True ).values ())
312+
298313 row = "," .join ([
299314 datetime .now (timezone .utc ).strftime ("%Y-%m-%d" ),
300315 str (total (csv_files ["missing_bundles" ])),
301- str (total (csv_files ["missing_bundles_latest " ])),
302- str (total (csv_files ["missing_bundles_superseded " ])),
316+ str (latest (csv_files ["missing_bundles " ])),
317+ str (superseded_total (csv_files ["missing_bundles " ])),
303318 str (total (csv_files ["missing_collections" ])),
304- str (total (csv_files ["missing_collections_latest " ])),
305- str (total (csv_files ["missing_collections_superseded " ])),
319+ str (latest (csv_files ["missing_collections " ])),
320+ str (superseded_total (csv_files ["missing_collections " ])),
306321 str (total (csv_files ["staged_bundles" ])),
307322 str (total (csv_files ["staged_collections" ])),
308323 ])
@@ -522,30 +537,13 @@ def main() -> int:
522537 data = run_query (query_file , endpoint )
523538 rows = extract_rows (data , include_harvest_date )
524539
525- # Always write the overall CSV
526- count = write_rows_to_csv (rows , output_file )
527- print_info (f" overall → { output_file .name } ({ count } records)" )
528- output_files .append (output_file )
529-
530540 if split_versions :
531- # Derive sibling paths: missing_bundles_in_registry.csv
532- # → missing_bundles_latest_in_registry.csv
533- # → missing_bundles_superseded_in_registry.csv
534- stem = output_file .stem # e.g. "missing_bundles_in_registry"
535- suffix = output_file .suffix
536- base = stem .replace ("_in_registry" , "" ) # "missing_bundles"
537- latest_file = output_dir / f"{ base } _latest_in_registry{ suffix } "
538- superseded_file = output_dir / f"{ base } _superseded_in_registry{ suffix } "
539-
540- latest_rows , superseded_rows = split_by_version (rows )
541-
542- latest_count = write_rows_to_csv (latest_rows , latest_file )
543- print_info (f" latest → { latest_file .name } ({ latest_count } records)" )
544- output_files .append (latest_file )
541+ # Annotate each row with a superseded column before writing
542+ rows = annotate_version_status (rows )
545543
546- superseded_count = write_rows_to_csv (superseded_rows , superseded_file )
547- print_info (f" superseded → { superseded_file .name } ({ superseded_count } records)" )
548- output_files .append (superseded_file )
544+ count = write_rows_to_csv (rows , output_file )
545+ print_info (f" → { output_file .name } ({ count } records)" )
546+ output_files .append (output_file )
549547
550548 except subprocess .CalledProcessError as e :
551549 print_error (f"Failed to generate { description } report: { e .stderr } " )
@@ -560,11 +558,7 @@ def main() -> int:
560558 print_info ("Updating metrics in README..." )
561559 csv_files = {
562560 "missing_bundles" : output_dir / "missing_bundles_in_registry.csv" ,
563- "missing_bundles_latest" : output_dir / "missing_bundles_latest_in_registry.csv" ,
564- "missing_bundles_superseded" : output_dir / "missing_bundles_superseded_in_registry.csv" ,
565561 "missing_collections" : output_dir / "missing_collections_in_registry.csv" ,
566- "missing_collections_latest" : output_dir / "missing_collections_latest_in_registry.csv" ,
567- "missing_collections_superseded" : output_dir / "missing_collections_superseded_in_registry.csv" ,
568562 "staged_bundles" : output_dir / "staged_bundles_in_registry.csv" ,
569563 "staged_collections" : output_dir / "staged_collections_in_registry.csv" ,
570564 }
0 commit comments