{"record":{"id":"03e1b4cb9014c6f2","repo":"headroomlabs-ai/headroom","slug":"spreadsheet-not-found-p","errorCode":null,"errorMessage":"Spreadsheet not found: {p}","messagePattern":"Spreadsheet not found: (.+?)","errorType":"exception","errorClass":"FileNotFoundError","httpStatus":null,"severity":"error","filePath":"headroom/transforms/spreadsheet_ingest.py","lineNumber":89,"sourceCode":"\n\ndef load_spreadsheet(path: str | Path) -> dict[str, str]:\n    \"\"\"Load a spreadsheet file into ``{sheet_name: csv_text}``.\n\n    Args:\n        path: Path to a ``.xlsx`` or ``.xls`` file.\n\n    Returns:\n        Mapping of sheet name to CSV-rendered text (empty sheets omitted).\n\n    Raises:\n        FileNotFoundError: If the path does not exist.\n        ValueError: If the file extension is unsupported.\n        ImportError: If the required parser dependency is not installed.\n    \"\"\"\n    p = Path(path)\n    if not p.exists():\n        raise FileNotFoundError(f\"Spreadsheet not found: {p}\")\n\n    suffix = p.suffix.lower()\n    if suffix == \".xlsx\":\n        return _load_xlsx(p)\n    if suffix == \".xls\":\n        return _load_xls(p)  # pragma: no cover - legacy .xls path, see _load_xls\n    raise ValueError(f\"Unsupported spreadsheet format '{suffix}'. Supported: .xlsx, .xls\")\n","sourceCodeStart":71,"sourceCodeEnd":97,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/transforms/spreadsheet_ingest.py#L71-L97","documentation":"The spreadsheet ingest entry point checks `Path(path).exists()` before dispatching on suffix and raises FileNotFoundError with the exact offending path when the file is missing. This is a straightforward precondition failure: the caller referenced a spreadsheet that does not exist at that location (or is not readable at that path, e.g. inside a container).","triggerScenarios":"Calling `ingest_spreadsheet(Path(\"data/report.xlsx\"))` when the file was never created, was moved/renamed, or when the process's working directory differs from where the caller assumes the relative path resolves.","commonSituations":"Relative paths in scripts run from a different cwd (cron, CI, Docker WORKDIR); user-uploaded file already cleaned up by the time a background worker processes it; typos or stale paths in config; file present locally but absent in the container image.","solutions":["Check the path exists before calling (see validation code) and log/resolve it early with `Path.resolve()`","Use absolute paths built from a configured base directory instead of cwd-relative strings","If the file may legitimately disappear (async/queued ingestion), treat FileNotFoundError as a retryable condition in the caller"],"exampleFix":"# before\npath = Path(\"uploads/q3.xlsx\")\nsheets = ingest_spreadsheet(path)\n\n# after\npath = (UPLOAD_DIR / \"q3.xlsx\").resolve()\nif not path.exists():\n    raise FileNotFoundError(f\"upload vanished: {path}\")\nsheets = ingest_spreadsheet(path)","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef existing_spreadsheet(path: str | Path) -> Path:\n    p = Path(path).resolve()\n    if not p.is_file():\n        raise FileNotFoundError(f\"no spreadsheet at {p}\")\n    return p\n\nsheets = ingest_spreadsheet(existing_spreadsheet(user_path))","typeGuard":"def is_existing_file(path: str | Path) -> bool:\n    return Path(path).is_file()","tryCatchPattern":"try:\n    sheets = ingest_spreadsheet(path)\nexcept FileNotFoundError:\n    log.warning(\"spreadsheet vanished before ingest: %s\", path)\n    mark_job_retryable(job_id)  # queued-upload race\n","preventionTips":["Resolve paths against an explicit base directory; never rely on cwd","Use absolute paths in configs and job queues","For async pipelines, treat missing files as a retryable state with a bounded attempt count"],"tags":["filesystem","validation","precondition","spreadsheet"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}