docs/LEGACY_DATABASES.md documents all three source Access databases (every table, column, type, and known data-quality quirk) generated from a live read of the real files, so no Windows/Access driver is needed to understand their structure going forward. New migration/ tooling: catalog_schema.py connects to the real files and walks every table (including excluded scratch tables); render_catalog_md.py renders that into the doc's appendix. Raw output checked in at migration/catalog.json so the doc can be regenerated without touching Access again.
99 lines
3.4 KiB
Python
99 lines
3.4 KiB
Python
"""
|
|
Renders catalog.json (from catalog_schema.py) into a Markdown appendix:
|
|
one section per source file, one subsection per table, with a column
|
|
table. Excluded (scratch/template/report) tables are marked and collapsed
|
|
to a one-liner rather than a full column dump, since they carry no data
|
|
worth documenting column-by-column.
|
|
|
|
Usage:
|
|
python render_catalog_md.py catalog.json --output appendix.md
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
SOURCE_TITLES = {
|
|
"utilities": "UTILITIES.accdb",
|
|
"seguros": "SEGUROS 16_be.mdb",
|
|
"scothia": "SCOTHIA.mdb",
|
|
}
|
|
|
|
|
|
def render_table(name: str, entry: dict) -> str:
|
|
lines = [f"#### `{name}`"]
|
|
|
|
if entry.get("excluded"):
|
|
lines.append("")
|
|
lines.append(f"**Excluded from migration** — {entry.get('row_count', '?')} rows. Not detailed here; see the exclusion rationale table above.")
|
|
lines.append("")
|
|
return "\n".join(lines)
|
|
|
|
if "error" in entry:
|
|
lines.append("")
|
|
lines.append(f"**Could not read this table:** `{entry['error']}`")
|
|
lines.append("")
|
|
return "\n".join(lines)
|
|
|
|
lines.append("")
|
|
lines.append(f"Rows: {entry['row_count']} | Columns: {entry['column_count']}")
|
|
lines.append("")
|
|
lines.append("| Column | Type | Nullable |")
|
|
lines.append("|---|---|---|")
|
|
for col in entry["columns"]:
|
|
type_label = col["access_type"] or f"_(inferred: {col['pandas_dtype']})_"
|
|
nullable = "yes" if col["nullable"] else ("no" if col["nullable"] is False else "—")
|
|
col_name = col["name"].replace("|", "\\|")
|
|
lines.append(f"| `{col_name}` | {type_label} | {nullable} |")
|
|
lines.append("")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def render_source(source_name: str, source_data: dict) -> str:
|
|
title = SOURCE_TITLES.get(source_name, source_name)
|
|
lines = [f"### {title}", ""]
|
|
|
|
tables = source_data["tables"]
|
|
included = sorted(t for t, e in tables.items() if not e.get("excluded"))
|
|
excluded = sorted(t for t, e in tables.items() if e.get("excluded"))
|
|
|
|
lines.append(f"{len(tables)} tables total — {len(included)} included in migration, {len(excluded)} excluded (scratch/template/report tables).")
|
|
lines.append("")
|
|
|
|
for table_name in included:
|
|
lines.append(render_table(table_name, tables[table_name]))
|
|
|
|
if excluded:
|
|
lines.append("#### Excluded tables")
|
|
lines.append("")
|
|
lines.append("| Table | Rows |")
|
|
lines.append("|---|---|")
|
|
for table_name in excluded:
|
|
entry = tables[table_name]
|
|
lines.append(f"| `{table_name}` | {entry.get('row_count', '?')} |")
|
|
lines.append("")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("catalog", type=Path)
|
|
parser.add_argument("--output", type=Path, default=Path("appendix.md"))
|
|
args = parser.parse_args()
|
|
|
|
catalog = json.loads(args.catalog.read_text(encoding="utf-8"))
|
|
|
|
sections = ["## Appendix: full table catalog", "", "Generated by `migration/catalog_schema.py` + `migration/render_catalog_md.py`. Regenerate after any change to the source files.", ""]
|
|
for source_name in ["utilities", "seguros", "scothia"]:
|
|
sections.append(render_source(source_name, catalog[source_name]))
|
|
|
|
args.output.write_text("\n".join(sections), encoding="utf-8")
|
|
print(f"Wrote {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|