"""Record applications without quotas; preserve outcome events and interview history.""" from __future__ import annotations import argparse import hashlib import application_records as records FIT_CLASSES = ("core", "adjacent", "stretch", "no-go") CHANNELS = ("strong", "moderate", "weak") def summary(data: dict, all_applications: bool = False) -> None: cohort_id = data["cohort_metadata"]["cohort_id"] applications = [row for row in data["applications"] if all_applications or row.get("cohort_id") == cohort_id] print(f"Application record: {'all' if all_applications else cohort_id} ({len(applications)} applications; no quota)") for field, values in (("fit_class", FIT_CLASSES), ("channel", CHANNELS)): for value in values: print(f" {field} {value}: {sum(row.get(field) == value for row in applications)}") unknown = sum(row.get(field) is None for row in applications) if unknown: print(f" {field} unrecorded: {unknown}") outcomes = sorted({row["outcome"] for row in applications}) print(" outcomes: " + ", ".join(f"{value}={sum(row['outcome'] == value for row in applications)}" for value in outcomes)) print(f" interview reached (including later withdrawal/rejection): {sum(records.interview_reached(row) for row in applications)}") def add_application(data: dict, args: argparse.Namespace) -> None: records.valid_date(args.date) identity = args.id or "app-" + hashlib.sha256(f"{args.company.casefold()}|{args.job_id or args.role.casefold()}|{args.date}".encode()).hexdigest()[:12] candidate = { "id": identity, "company": args.company, "role": args.role, "label": f"{args.company} — {args.role}", "application_date": args.date, "fit_class": args.fit_class, "evidence_fit": args.evidence_fit, "hard_gate": args.hard_gate, "channel": args.channel, "outcome": args.outcome, "cohort_id": data["cohort_metadata"]["cohort_id"], "job_id": args.job_id, "url": args.url, "fit_override_reason": args.fit_override_reason, "rubric_version": args.rubric_version, "next_action": args.next_action, "current_summary": args.outcome, "events": [{"type": args.outcome, "date": args.date}], } for existing in data["applications"]: same_job = args.job_id and existing.get("job_id") == args.job_id and existing["company"].casefold() == args.company.casefold() same_url = args.url and existing.get("url") == args.url same_application = existing["company"].casefold() == args.company.casefold() and existing["role"].casefold() == args.role.casefold() and existing.get("application_date") == args.date if existing["id"] == identity or same_job or same_url or same_application: if all(existing.get(key) == value for key, value in candidate.items() if key not in {"events", "id", "current_summary", "next_action", "outcome"}): return raise ValueError(f"Application already exists ({existing['id']}); use update instead") data["applications"].append(candidate) records.validate(data) def update_application(data: dict, args: argparse.Namespace) -> None: records.valid_date(args.date) matches = [row for row in data["applications"] if row["id"] == args.id] if not matches: raise ValueError(f"Unknown application ID: {args.id}") row = matches[0] if row.get("application_date") and args.date < row["application_date"]: raise ValueError("event date precedes application date") outcome_dates = [event["date"] for event in row.get("events", []) if (event.get("outcome") or event.get("type") in records.OUTCOMES) and event.get("date")] outcome_dates.extend(row[key] for key in ("withdrawn", "rejection_date") if row.get(key)) if args.outcome and outcome_dates and args.date < max(outcome_dates): raise ValueError("older outcome cannot replace current state; record it as a historical note") event = {"type": args.event or args.outcome or "note", "date": args.date} if args.note: event["note"] = args.note if args.outcome: event["outcome"] = args.outcome if event not in row.setdefault("events", []): row["events"].append(event) if args.outcome: row["outcome"] = args.outcome row["current_summary"] = args.note or args.outcome row["next_action"] = args.next_action or ("Await response." if args.outcome in {"applied", "interviewing", "open"} else "Review recorded outcome before further action.") elif args.next_action: row["next_action"] = args.next_action def main() -> None: parser = argparse.ArgumentParser(description=__doc__) commands = parser.add_subparsers(dest="command", required=True) view = commands.add_parser("summary") view.add_argument("--all", action="store_true") add = commands.add_parser("add") for field in ("company", "role", "date"): add.add_argument("--" + field, required=True) add.add_argument("--id") add.add_argument("--job-id") add.add_argument("--url") add.add_argument("--fit-class", choices=FIT_CLASSES, required=True) add.add_argument("--evidence-fit", type=float, required=True) add.add_argument("--channel", choices=CHANNELS, required=True) add.add_argument("--hard-gate", choices=("pass", "fail", "unknown"), required=True) add.add_argument("--outcome", choices=sorted(records.OUTCOMES), default="applied") add.add_argument("--fit-override-reason") add.add_argument("--rubric-version", default="critique-framework-2026-09-11") add.add_argument("--next-action", default="Await response.") update = commands.add_parser("update") update.add_argument("--id", required=True) update.add_argument("--date", required=True, help="Date of this event") update.add_argument("--outcome", choices=sorted(records.OUTCOMES)) update.add_argument("--event", choices=("interview_invited", "interview_held", "assessment_passed", "contact_attempted", "contact_conversation", "referral_received", "note")) update.add_argument("--note") update.add_argument("--next-action") args = parser.parse_args() try: if args.command == "summary": summary(records.load(), args.all) return with records.transaction() as data: (add_application if args.command == "add" else update_application)(data, args) summary(data) except (ValueError, OSError) as error: parser.exit(2, f"ERROR: {error}\n") if __name__ == "__main__": main()