Work an archive collection well: read a finding aid, scope a request to boxes and folders, handle material with care, record provenance, cite items so they can be re-requested, and log restrictions before planning any use. Use when visiting an archive, preparing a request from a finding aid, or recording and citing archival material for later work.
./install.sh archival-research-basicsname: archival_research_basics description: "Work an archive collection well: read a finding aid, scope a request to boxes and folders, handle material with care, record provenance, cite items so they can be re-requested, and log restrictions before planning any use. Use when visiting an archive, preparing a request from a finding aid, or recording and citing archival material for later work." metadata: { "includeInPrompt": true }
Give archival work a record that another researcher can follow back to the same items. The routine starts with a finding aid, narrows a broad interest to named boxes and folders, tracks what was requested and how it was handled, records provenance for every item used, writes citations that support a fresh request for the same item, and logs restrictions before any use is planned. A citation that lacks a repository, a collection, a box, or a folder does not point anywhere precise, so the work treats those four fields as required from the start.
Scope:
python3 --version returned on the drafting machine, against a fixture built by the script itself with --make-fixture. The fixture run returned entries_total: 7, entries_with_all_fields: 3, entries_missing_fields: 4, citations_rerequestable: 3, citations_not_rerequestable: 4, restriction_conflicts: 1, duplicate_ids: 1, verdict: REVIEW. The script uses only the Python standard library (os and sys), so no package version is quoted and no install step is needed. What was NOT verified: no specific archive's reading room rules, request system, or catalog were tested. Rules vary by repository and this draft says to confirm them on site.ITEM: <item id> where the item id is a short code you assign once and never reuse for a different item.repository: <repository name>collection: <collection title>box: <box number>folder: <folder number or title>restriction: <none, or the restriction that applies>planned_use: <unrestricted, or the limited use planned>python3 archival_check.py LOG_FILE on it.Deliver these items, in this order:
Run: python3 archival_check.py --make-fixture fixture builds the worked fixture in a folder named fixture, holding a research_log.txt file. Then run: python3 archival_check.py fixture/research_log.txt to reproduce the output below. Run it on a real visit with python3 archival_check.py LOG_FILE, where LOG_FILE is the dated research log for that visit in the block form from Workflow step 8.
Expected output for the fixture run:
text
log_file: fixture/research_log.txt
entries_total: 7
entries_with_all_fields: 3
entries_missing_fields: 4
ENTRY entry=1 id=ARC-001 missing=- citation_rerequestable=yes restriction_conflict=no duplicate=no
ENTRY entry=2 id=ARC-002 missing=repository citation_rerequestable=no restriction_conflict=no duplicate=no
ENTRY entry=3 id=ARC-003 missing=collection citation_rerequestable=no restriction_conflict=no duplicate=no
ENTRY entry=4 id=ARC-004 missing=box citation_rerequestable=no restriction_conflict=no duplicate=no
ENTRY entry=5 id=ARC-005 missing=folder citation_rerequestable=no restriction_conflict=no duplicate=no
ENTRY entry=6 id=ARC-006 missing=- citation_rerequestable=yes restriction_conflict=yes duplicate=no
ENTRY entry=7 id=ARC-001 missing=- citation_rerequestable=yes restriction_conflict=no duplicate=yes
citations_rerequestable: 3
citations_not_rerequestable: 4
restriction_conflicts: 1
duplicate_ids: 1
verdict: REVIEW
The fixture is built to show each checker result clearly: one complete entry passes, four entries each miss one required field (repository, collection, box, and folder in turn), one complete entry pairs a closed restriction with an unrestricted planned use, and one entry reuses the first item id. That gives entries_missing_fields: 4, citations_not_rerequestable: 4, restriction_conflicts: 1, duplicate_ids: 1, and a verdict of REVIEW.
archival_check.py (uses only the Python standard library). Save this code as archival_check.py:
python
#!/usr/bin/env python3
"""Check an archival research log for completeness and use conflicts.
Usage:
python3 archival_check.py --make-fixture DIR
python3 archival_check.py LOG_FILE
The research log is a plain text file made of entry blocks
separated by blank lines. Each block starts with an item line,
then field lines in the form key: value.
ITEM: ARC-001
repository: Example Repository
collection: Example Papers
box: 3
folder: 12
restriction: none
planned_use: unrestricted
Required fields for a citation that can be re-requested are
repository, collection, box, and folder. A missing or empty
required field is reported per entry. An entry counts as a
restriction conflict when its restriction value is not none
or empty and its planned_use value is unrestricted. A repeated
item id counts as a duplicate from its second use onward.
Output lists each entry with its missing fields, citation
status, restriction conflict flag, and duplicate flag, then
prints summary counts. Exit code is 0 when the log file can
be read, 1 when it cannot. Standard library only.
"""
import os
import sys
REQUIRED = ["repository", "collection", "box", "folder"]
OPEN_RESTRICTIONS = ["", "none", "no", "open"]
def parse_log(text):
entries = []
blocks = []
current = []
for line in text.splitlines():
if line.strip() == "":
if current:
blocks.append(current)
current = []
else:
current.append(line)
if current:
blocks.append(current)
for block in blocks:
item_id = "-"
fields = {}
for line in block:
stripped = line.strip()
if ":" not in stripped:
continue
key, _, value = stripped.partition(":")
key = key.strip().lower()
value = value.strip()
if key == "item":
item_id = value if value else "-"
else:
fields[key] = value
entries.append((item_id, fields))
return entries
def check(log_path):
if not os.path.isfile(log_path):
print(f"error: log file not found: {log_path}")
return 1
with open(log_path, "r", encoding="utf-8", errors="replace") as fh:
text = fh.read()
entries = parse_log(text)
seen = set()
rows = []
missing_count = 0
rerequestable = 0
conflicts = 0
duplicates = 0
for item_id, fields in entries:
missing = [name for name in REQUIRED if not fields.get(name)]
if missing:
missing_count += 1
citation_ok = not missing
if citation_ok:
rerequestable += 1
restriction = fields.get("restriction", "").strip().lower()
planned = fields.get("planned_use", "").strip().lower()
conflict = restriction not in OPEN_RESTRICTIONS and planned == "unrestricted"
if conflict:
conflicts += 1
duplicate = item_id != "-" and item_id in seen
if duplicate:
duplicates += 1
if item_id != "-":
seen.add(item_id)
rows.append((item_id, missing, citation_ok, conflict, duplicate))
total = len(entries)
with_all = total - missing_count
print(f"log_file: {log_path}")
print(f"entries_total: {total}")
print(f"entries_with_all_fields: {with_all}")
print(f"entries_missing_fields: {missing_count}")
for idx, (item_id, missing, citation_ok, conflict, duplicate) in enumerate(rows, start=1):
missing_label = ",".join(missing) if missing else "-"
print(
f"ENTRY entry={idx} id={item_id} missing={missing_label} "
f"citation_rerequestable={'yes' if citation_ok else 'no'} "
f"restriction_conflict={'yes' if conflict else 'no'} "
f"duplicate={'yes' if duplicate else 'no'}"
)
print(f"citations_rerequestable: {rerequestable}")
print(f"citations_not_rerequestable: {total - rerequestable}")
print(f"restriction_conflicts: {conflicts}")
print(f"duplicate_ids: {duplicates}")
has_issue = missing_count > 0 or conflicts > 0 or duplicates > 0
print(f"verdict: {'REVIEW' if has_issue else 'PASS'}")
return 0
def make_fixture(base):
os.makedirs(base, exist_ok=True)
log_path = os.path.join(base, "research_log.txt")
blocks = [
(
"ITEM: ARC-001\n"
"repository: Example Repository\n"
"collection: Example Papers\n"
"box: 3\n"
"folder: 12\n"
"restriction: none\n"
"planned_use: unrestricted\n"
),
(
"ITEM: ARC-002\n"
"collection: Example Papers\n"
"box: 3\n"
"folder: 14\n"
"restriction: none\n"
"planned_use: unrestricted\n"
),
(
"ITEM: ARC-003\n"
"repository: Example Repository\n"
"box: 4\n"
"folder: 2\n"
"restriction: none\n"
"planned_use: unrestricted\n"
),
(
"ITEM: ARC-004\n"
"repository: Example Repository\n"
"collection: Example Papers\n"
"folder: 7\n"
"restriction: none\n"
"planned_use: unrestricted\n"
),
(
"ITEM: ARC-005\n"
"repository: Example Repository\n"
"collection: Example Papers\n"
"box: 5\n"
"restriction: none\n"
"planned_use: unrestricted\n"
),
(
"ITEM: ARC-006\n"
"repository: Example Repository\n"
"collection: Example Papers\n"
"box: 6\n"
"folder: 1\n"
"restriction: closed\n"
"planned_use: unrestricted\n"
),
(
"ITEM: ARC-001\n"
"repository: Example Repository\n"
"collection: Example Papers\n"
"box: 3\n"
"folder: 12\n"
"restriction: none\n"
"planned_use: unrestricted\n"
),
]
with open(log_path, "w", encoding="utf-8") as fh:
fh.write("\n".join(blocks))
print(f"fixture_log: {log_path}")
return 0
def main():
args = sys.argv[1:]
if len(args) == 2 and args[0] == "--make-fixture":
return make_fixture(args[1])
if len(args) != 1:
print("usage: archival_check.py LOG_FILE")
print(" archival_check.py --make-fixture DIR")
return 2
return check(args[0])
if __name__ == "__main__":
raise SystemExit(main())
publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.