vault-cli: commit one-off migration scripts before extraction to standalone repo
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sort /data/_unsorted (old pre-RJFI JD tree) into /data/R,J,F,I storage tree.
|
||||
|
||||
Dry-run by default; pass --execute to actually move files.
|
||||
"""
|
||||
import os, shutil, sys, csv
|
||||
|
||||
UNSORTED = "/data/_unsorted"
|
||||
DATA = "/data"
|
||||
|
||||
# old_prefix -> new_prefix (folder-level rename/move rules)
|
||||
# Order matters: longer/more specific prefixes first.
|
||||
MAPPING = [
|
||||
# --- WhatsApp exports: wholesale move, old->new folder rename ---
|
||||
("1 Life admin/11 Me & other living things/11.54 Whatsapp exports",
|
||||
"R Ryan/R.1 Life admin/R.11 Identity & vital records/R.11.54 Whatsapp exports"),
|
||||
|
||||
# --- 12 Home, house, and transport -> split F.11 (home) / F.12 (vehicles) ---
|
||||
("1 Life admin/12 Home, house, and transport/12.12 Home insurance & claims",
|
||||
"F Family/F.1 Home, household & finances/F.11 Home & property/F.11.12 Home insurance & claims"),
|
||||
("1 Life admin/12 Home, house, and transport/12.21 Electricity, gas, & water",
|
||||
"F Family/F.1 Home, household & finances/F.11 Home & property/F.11.21 Electricity, gas, & water"),
|
||||
("1 Life admin/12 Home, house, and transport/12.23 All other utilities & services",
|
||||
"F Family/F.1 Home, household & finances/F.11 Home & property/F.11.23 All other utilities & services"),
|
||||
("1 Life admin/12 Home, house, and transport/12.24 Renovations & improvements",
|
||||
"F Family/F.1 Home, household & finances/F.11 Home & property/F.11.24 Renovations & improvements"),
|
||||
("1 Life admin/12 Home, house, and transport/12.25 House repairs, maintenance, & upkeep",
|
||||
"F Family/F.1 Home, household & finances/F.11 Home & property/F.11.24 Renovations & improvements"),
|
||||
("1 Life admin/12 Home, house, and transport/12.61 Move out of 6 Dorothy",
|
||||
"F Family/F.1 Home, household & finances/F.11 Home & property/F.11.61 Move out of 6 Dorothy"),
|
||||
("1 Life admin/12 Home, house, and transport/12.30 Vehicles",
|
||||
"F Family/F.1 Home, household & finances/F.12 Vehicles/F.12.30 Vehicles"),
|
||||
("1 Life admin/12 Home, house, and transport/12.32 Motor vehicle auto insurance & claims",
|
||||
"F Family/F.1 Home, household & finances/F.12 Vehicles/F.12.32 Motor vehicle auto insurance & claims"),
|
||||
("1 Life admin/12 Home, house, and transport/12.33 Car repairs, maintenance, & upkeep",
|
||||
"F Family/F.1 Home, household & finances/F.12 Vehicles/F.12.33 Car repairs, maintenance, & upkeep"),
|
||||
|
||||
# --- 13 Money -> split R.13 (solo) / F.13 (joint); tax + financial insurance are joint per design doc ---
|
||||
("1 Life admin/13 Money earned, spent, saved, & owed/13.54 Tax returns & accounting",
|
||||
"F Family/F.1 Home, household & finances/F.13 Household finances/F.13.05 Tax returns & accounting"),
|
||||
("1 Life admin/13 Money earned, spent, saved, & owed/13.12 Financial insurance & claims",
|
||||
"F Family/F.1 Home, household & finances/F.13 Household finances/F.13.12 Financial insurance & claims"),
|
||||
("1 Life admin/13 Money earned, spent, saved, & owed/13.44 Cryptocurrency",
|
||||
"R Ryan/R.1 Life admin/R.13 Personal finance/R.13.44 Cryptocurrency"),
|
||||
|
||||
# --- 11 Me & other living things -> mostly R (Ryan personal) ---
|
||||
("1 Life admin/11 Me & other living things/11.11 Birth certificate & proof of name",
|
||||
"R Ryan/R.1 Life admin/R.11 Identity & vital records/R.11.11 Birth certificate & proof of name"),
|
||||
("1 Life admin/11 Me & other living things/11.14 Licenses",
|
||||
"R Ryan/R.1 Life admin/R.11 Identity & vital records/R.11.14 Licenses"),
|
||||
("1 Life admin/11 Me & other living things/11.16 Legal documents & certificates",
|
||||
"R Ryan/R.1 Life admin/R.11 Identity & vital records/R.11.16 Legal documents & certificates"),
|
||||
("1 Life admin/11 Me & other living things/11.28 Reproductive health",
|
||||
"R Ryan/R.1 Life admin/R.12 Health & body/R.12.28 Reproductive health"),
|
||||
("1 Life admin/11 Me & other living things/11.83 My library",
|
||||
"R Ryan/R.1 Life admin/R.11 Identity & vital records/R.11.83 Ryan's library"),
|
||||
|
||||
# --- 15 Travel -> R.15 Travel (solo trips; matches exactly) ---
|
||||
("1 Life admin/15 Travel, events, & entertainment/15.41 All short trips",
|
||||
"R Ryan/R.1 Life admin/R.15 Travel/R.15.41 All short trips"),
|
||||
|
||||
# --- 2 Career -> R.2 Career, ~1:1 ---
|
||||
("2 Career/21 Perfect Day", "R Ryan/R.2 Career/R.21 Perfect Day"),
|
||||
("2 Career/22 Sejour", "R Ryan/R.2 Career/R.22 Sejour"),
|
||||
("2 Career/23 Consulting", "R Ryan/R.2 Career/R.23 Consulting"),
|
||||
("2 Career/24 Stanford", "R Ryan/R.2 Career/R.24 Stanford"),
|
||||
("2 Career/29 Old applications & archives", "R Ryan/R.2 Career/R.29 Old applications & archives"),
|
||||
|
||||
# --- 3 Hobbies -> R.3 Hobbies, ~1:1 ---
|
||||
("3 Hobbies & interests/31 Language learning", "R Ryan/R.3 Hobbies & interests/R.31 Language learning"),
|
||||
("3 Hobbies & interests/36 Projects", "R Ryan/R.3 Hobbies & interests/R.36 Projects"),
|
||||
|
||||
# --- 4 Flying -> R.4 Flying, ~1:1 ---
|
||||
("4 Flying/41 Certification", "R Ryan/R.4 Flying/R.41 Certification, legal, & insurance"),
|
||||
("4 Flying/43 Resources", "R Ryan/R.4 Flying/R.43 Resources"),
|
||||
]
|
||||
|
||||
# Items needing human judgment - do NOT auto-move, just report.
|
||||
FLAG_FOR_REVIEW = [
|
||||
"1 Life admin/10 Management of area 10-19", # SMS backups - unclear whose/where
|
||||
"1 Life admin/11 Me & other living things/11.43 My family", # Mom's book proposal
|
||||
"1 Life admin/11 Me & other living things/11.09 Archive for category 11", # AI deck for Jen
|
||||
"1 Life admin/11 Me & other living things/11.71 My career sales pitch",
|
||||
"1 Life admin/13 Money earned, spent, saved, & owed/13.41 Bank accounts", # solo or joint?
|
||||
"1 Life admin/13 Money earned, spent, saved, & owed/13.01 Inbox for category 13",
|
||||
"1 Life admin/12 Home, house, and transport/12.01 Inbox for category 12",
|
||||
"1 Life admin/14 My digital life/14.14 My data storage & backups",
|
||||
"3 Hobbies & interests/30 Management for Hobbies",
|
||||
]
|
||||
|
||||
|
||||
def find_files(root):
|
||||
for dirpath, _, filenames in os.walk(root):
|
||||
for fn in filenames:
|
||||
yield os.path.join(dirpath, fn)
|
||||
|
||||
|
||||
def plan_moves():
|
||||
moves = [] # (src, dst)
|
||||
unmatched = []
|
||||
for src in find_files(UNSORTED):
|
||||
rel = os.path.relpath(src, UNSORTED)
|
||||
matched = False
|
||||
for old_prefix, new_prefix in MAPPING:
|
||||
if rel == old_prefix or rel.startswith(old_prefix + os.sep):
|
||||
suffix = rel[len(old_prefix):]
|
||||
dst = os.path.join(DATA, new_prefix.replace("/", os.sep) + suffix)
|
||||
moves.append((src, dst))
|
||||
matched = True
|
||||
break
|
||||
if not matched:
|
||||
is_flagged = any(rel.startswith(f) for f in FLAG_FOR_REVIEW)
|
||||
unmatched.append((rel, is_flagged))
|
||||
return moves, unmatched
|
||||
|
||||
|
||||
def main():
|
||||
execute = "--execute" in sys.argv
|
||||
moves, unmatched = plan_moves()
|
||||
|
||||
print(f"=== PLANNED MOVES: {len(moves)} files ===")
|
||||
by_target = {}
|
||||
for src, dst in moves:
|
||||
key = os.path.dirname(dst)
|
||||
by_target[key] = by_target.get(key, 0) + 1
|
||||
for k, v in sorted(by_target.items()):
|
||||
print(f" {v:5d} -> {k}")
|
||||
|
||||
print(f"\n=== FLAGGED FOR REVIEW (not moved): {sum(1 for _, f in unmatched if f)} files ===")
|
||||
flagged_dirs = {}
|
||||
for rel, is_flagged in unmatched:
|
||||
if is_flagged:
|
||||
d = os.path.dirname(rel)
|
||||
flagged_dirs[d] = flagged_dirs.get(d, 0) + 1
|
||||
for k, v in sorted(flagged_dirs.items()):
|
||||
print(f" {v:5d} :: {k}")
|
||||
|
||||
print(f"\n=== TRULY UNMATCHED (no mapping rule, not flagged): {sum(1 for _, f in unmatched if not f)} files ===")
|
||||
other_dirs = {}
|
||||
for rel, is_flagged in unmatched:
|
||||
if not is_flagged:
|
||||
d = os.path.dirname(rel)
|
||||
other_dirs[d] = other_dirs.get(d, 0) + 1
|
||||
for k, v in sorted(other_dirs.items()):
|
||||
print(f" {v:5d} :: {k}")
|
||||
|
||||
if execute:
|
||||
print("\n=== EXECUTING MOVES ===")
|
||||
moved = 0
|
||||
for src, dst in moves:
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
if os.path.exists(dst):
|
||||
print(f" SKIP (dst exists): {dst}")
|
||||
continue
|
||||
shutil.move(src, dst)
|
||||
moved += 1
|
||||
print(f"Moved {moved} files.")
|
||||
else:
|
||||
print("\n(dry run only -- pass --execute to actually move files)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user