feat: add raw artifact audit trail
This commit is contained in:
@@ -7,6 +7,7 @@ from typing import Optional
|
||||
from .db import DEFAULT_DB_PATH, connect, import_template_file, init_db, load_sample_data
|
||||
from .indicators import compute_area_scores
|
||||
from .import_templates import ImportTemplateError, validate_tabular_file
|
||||
from .raw_artifacts import fingerprint_file
|
||||
from .reporting import format_scores_table, write_monthly_report
|
||||
|
||||
|
||||
@@ -69,6 +70,17 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
help="CSV, XLSX, or XLSM file to import.",
|
||||
)
|
||||
|
||||
fingerprint_parser = subparsers.add_parser(
|
||||
"fingerprint-file",
|
||||
help="Compute raw artifact metadata for an import file.",
|
||||
)
|
||||
fingerprint_parser.add_argument(
|
||||
"--file",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="File to hash and describe.",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
@@ -132,5 +144,10 @@ def main(argv: Optional[list[str]] = None) -> int:
|
||||
)
|
||||
return 0
|
||||
|
||||
if args.command == "fingerprint-file":
|
||||
fingerprint = fingerprint_file(args.file)
|
||||
print(fingerprint.to_json())
|
||||
return 0
|
||||
|
||||
parser.error(f"Unknown command: {args.command}")
|
||||
return 2
|
||||
|
||||
40
src/shanghai_housing/raw_artifacts.py
Normal file
40
src/shanghai_housing/raw_artifacts.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import mimetypes
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RawArtifactFingerprint:
|
||||
original_filename: str
|
||||
raw_uri: str
|
||||
sha256: str
|
||||
size_bytes: int
|
||||
mime_type: str | None
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(asdict(self), ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def fingerprint_file(file_path: Path) -> RawArtifactFingerprint:
|
||||
path = file_path.resolve()
|
||||
digest = hashlib.sha256()
|
||||
size_bytes = 0
|
||||
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
size_bytes += len(chunk)
|
||||
|
||||
sha256 = digest.hexdigest()
|
||||
mime_type, _ = mimetypes.guess_type(path.name)
|
||||
return RawArtifactFingerprint(
|
||||
original_filename=path.name,
|
||||
raw_uri=f"raw://sha256/{sha256}/{path.name}",
|
||||
sha256=sha256,
|
||||
size_bytes=size_bytes,
|
||||
mime_type=mime_type,
|
||||
)
|
||||
Reference in New Issue
Block a user