Keep Capture Metadata Beside Your CSV
Disclosure: Prepared with AI assistance by the CommentTok editorial team, which is affiliated with the exporter described here. All teaching examples are fictional.
A filename is a weak memory of how a dataset was collected. After a few downloads, you can have several plausible versions of the same CSV and no reliable record of which video, collection time, or review note belongs to each one.
A sidecar manifest helps by keeping that context in a separate JSON file. The CSV stays unchanged, while a fingerprint connects the manifest to the exact bytes you analyzed. This tutorial uses Python's standard library and a deliberately small four-column comment export.
What the manifest establishes: which bytes the notes refer to. It does not establish that those bytes are authentic, complete, or collected at the time someone entered.
Separate observed facts from supplied context
The export has author, handle, comment, and likes. It lacks source-video URLs, capture times, comment posting times, comment IDs, and parent IDs. That makes provenance useful, but a manifest cannot recover information that was never recorded.
Some manifest fields are computed: filename, byte length, column names, record count, and SHA-256 fingerprint. Others are supplied: the source URL and known capture time. A third time, manifest_created_at, describes when the script ran. Keeping those categories distinct prevents a recent processing date from masquerading as a collection date.
Generate a manifest from the bytes you parse
Save the code below as capture_manifest.py. It reads a small file into memory, checks the expected header and record widths, and prints JSON. No package installation, account, or network connection is required.
"""Print a sidecar manifest; does not modify the input CSV."""
import argparse
import csv
import hashlib
import io
import json
from datetime import datetime, timezone
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument("csv_file", type=Path)
parser.add_argument("--source-url", required=True)
parser.add_argument("--captured-at", help="Actual ISO capture time with timezone, if known")
args = parser.parse_args()
if args.captured_at:
captured = datetime.fromisoformat(args.captured_at.replace("Z", "+00:00"))
if captured.utcoffset() is None:
parser.error("--captured-at needs a timezone offset")
raw = args.csv_file.read_bytes()
reader = csv.reader(io.StringIO(raw.decode("utf-8-sig"), newline=""), strict=True)
header = next(reader, None)
if header != ["author", "handle", "comment", "likes"]:
parser.error("Expected author,handle,comment,likes header")
count = 0
for row in reader:
if len(row) != len(header):
parser.error("A record has the wrong number of fields")
count += 1
manifest = {
"manifest_version": 1,
"file_name": args.csv_file.name,
"sha256": hashlib.sha256(raw).hexdigest(),
"byte_length": len(raw),
"columns": header,
"records": count,
"source_video_url": args.source_url,
"captured_at": args.captured_at,
"manifest_created_at": datetime.now(timezone.utc).isoformat(),
"coverage": "unknown",
}
print(json.dumps(manifest, indent=2))
The same raw value feeds the parser and fingerprint, so the count and hash describe the same in-memory snapshot. The script does not validate every field's meaning; for example, it does not check whether likes is numeric or whether the supplied URL exists.
Python's csv documentation explains why parsing should handle quoted delimiters and newlines. Counting physical lines would overcount a comment that contains a line break. The utf-8-sig decoder also accommodates an initial UTF-8 byte-order mark.
Try it with explicitly fictional data
Create sample.csv with the following invented record. Neither the author label nor the comment represents a real person or captured discussion.
author,handle,comment,likes
"Example A","example_a","How should I store it?","2"
Then run:
python3 capture_manifest.py sample.csv \
--source-url 'https://example.com/fictional-video' \
> sample.manifest.json
The example URL is deliberately fictional. The manifest will show one record, the four expected columns, coverage set to unknown, and captured_at set to null. Its creation time reflects your run. The fingerprint depends on the exact bytes you saved, including line endings.
For a real capture, supply the real URL. Add --captured-at only if you know the original collection time, using an ISO value with a timezone offset. Omitting an unknown capture time is more honest than deriving it from the file modification time.
Verify before attaching downstream results
Before using the manifest with another copy, recompute the SHA-256 digest and compare it with the saved value. Python provides SHA-256 in its standard hashlib module.
Different digests mean the byte sequences differ. An unchanged digest is a useful integrity check, but does not authenticate the original collector or prove the truth of manually entered metadata. Anyone who can replace both files can recompute a matching manifest.
Renaming a file leaves its byte fingerprint unchanged, while changing line endings can change the fingerprint without changing the apparent table. Decide whether your workflow tracks exact files or normalized records before treating hashes as dataset identities.
Carry limitations into the next artifact
If you create a cleaned CSV, keep it separate and generate a new manifest. Record the original fingerprint as a parent reference in your processing notes, alongside the transformation performed. Do not overwrite the original manifest with the cleaned file's fingerprint and lose that relationship.
A local reference combining fingerprint and record number can locate a row in a saved file. It cannot identify the same platform comment across captures. Missing comment IDs still make deduplication uncertain, and missing parent IDs still prevent reliable thread reconstruction.
For large files, replace the in-memory approach with a carefully designed streaming workflow. For small exports, the more immediate improvement is disciplined metadata: preserve the input, record what you know, mark what you do not, and keep the limits attached when you share the analysis.
By the CommentTok editorial team, affiliated with the exporter described here. Prepared with AI assistance. The sample record and URL are fictional. The included script was exercised locally; no complete-data guarantee, verified collection identity, or human editorial review is claimed.
