← Files LegalQuants LitigationARCHIVED FILE

skills/timenarratives/scripts/build_packet.py

12.1 KB · Oct 5, 2026 · 18:33 UTC

↓ Download file

"""Compile an explicit, bounded TimeNarratives source packet."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from conversation_units import parse_conversation_for_packet
from email_units import EmailParseError, parse_email
from packet_core import (
    LIMITS,
    container_record,
    finalize_packet,
    root_record,
    unreadable_container_record,
    validate_request,
)
from packet_core import PacketBuildError as PacketBuildError
from packet_docx import parse_docx_for_packet
from packet_filters import (
    apply_selected_source_filter,
    apply_selected_source_type_filter,
    propagate_leaf_uncertainty,
)
from packet_io import compile_to_directory, run_cli
from source_paths import (
    SourcePathError,
    SourceReadError,
    media_type,
    read_bounded_file,
    source_type_for_name,
)
from text_units import TextDecodeError, make_text_units


class _Compiler:
    def __init__(self, request: dict, source_root: Path) -> None:
        self.request = request
        self.source_root = source_root
        self.roots: list[dict] = []
        self.containers: list[dict] = []
        self.mime_leaves: list[dict] = []
        self.parts: list[dict] = []
        self.units: list[dict] = []
        self.errors: list[str] = []
        self.limitations: list[str] = []
        self.seen_hashes: dict[str, str] = {}
        self.selected_bytes = 0
        self.derived_count = 0

    def compile(self) -> dict:
        for index, selection in enumerate(self.request["selections"], start=1):
            root_id = f"S{index:04d}"
            if selection["kind"] == "user_note":
                self._user_note(root_id, index, selection["text"])
            else:
                self._selected_file(root_id, index, selection)
        propagate_leaf_uncertainty(self.roots, self.containers, self.mime_leaves)
        return finalize_packet(
            self.request,
            self.roots,
            self.containers,
            self.mime_leaves,
            self.parts,
            self.units,
            self.errors,
            self.limitations,
        )

    def _selected_file(self, root_id: str, index: int, selection: dict) -> None:
        relative = selection["path"]
        root = root_record(root_id, index, selection["kind"], relative)
        self.roots.append(root)
        source_type = (
            "conversation"
            if selection["kind"] == "conversation"
            else source_type_for_name(relative)
        )
        try:
            selected = read_bounded_file(
                self.source_root, relative, max_bytes=LIMITS["selectedContainerBytes"]
            )
        except (SourcePathError, SourceReadError) as exc:
            root.update(disposition="unreadable", reason=type(exc).__name__)
            self.errors.append("selected_source_unreadable")
            self.containers.append(
                unreadable_container_record(
                    root_id, root_id, relative, source_type, type(exc).__name__
                )
            )
            return
        self.selected_bytes += selected.byte_length
        if self.selected_bytes > LIMITS["selectedPacketBytes"]:
            root.update(
                disposition="unreadable", reason="selected_packet_bytes_exceeded"
            )
            self.errors.append("selected_packet_bytes_exceeded")
            self.containers.append(
                unreadable_container_record(
                    root_id,
                    root_id,
                    selected.display_name,
                    source_type,
                    root["reason"],
                )
            )
            return
        container = container_record(
            root_id,
            root_id,
            None,
            f"selection:{index - 1}",
            source_type,
            media_type(source_type, selected.display_name),
            selected.display_name,
            selected.data,
        )
        self.containers.append(container)
        duplicate_key = selected.sha256
        if source_type == "conversation":
            duplicate_key += ":conversation:" + selection["conversationId"]
        if duplicate_key in self.seen_hashes:
            root.update(disposition="exactDuplicate", reason="raw_sha256_match")
            container.update(disposition="exactDuplicate", reason="raw_sha256_match")
            return
        self.seen_hashes[duplicate_key] = root_id
        if source_type == "conversation":
            parsed = parse_conversation_for_packet(
                selected.data, selection, container, root, self.request["filters"]
            )
            self.parts.extend(parsed["parts"])
            self.units.extend(parsed["units"])
            self.errors.extend(parsed["errors"])
            self.limitations.extend(parsed["limitations"])
            return
        self._parse_container(container, root, selected.data)

    def _user_note(self, root_id: str, index: int, value: str) -> None:
        root = root_record(root_id, index, "user_note", None)
        self.roots.append(root)
        data = value.encode("utf-8")
        container = container_record(
            root_id,
            root_id,
            None,
            f"selection:{index - 1}",
            "user_note",
            "text/plain",
            "user-note",
            data,
        )
        self.containers.append(container)
        self.selected_bytes += len(data)
        reason = None
        if len(data) > LIMITS["selectedContainerBytes"]:
            reason = "selected_container_bytes_exceeded"
        elif self.selected_bytes > LIMITS["selectedPacketBytes"]:
            reason = "selected_packet_bytes_exceeded"
        if reason:
            root.update(disposition="unreadable", reason=reason)
            container.update(disposition="unreadable", reason=reason)
            self.errors.append(reason)
            return
        if not apply_selected_source_filter(container, root, self.request["filters"]):
            return
        root["disposition"] = "requiresConfirmation"
        container.update(
            disposition="requiresConfirmation",
            reason=container["reason"] or "user_attestation",
        )
        units = make_text_units(
            root_id,
            data,
            kind="user_note",
            role="attested",
            eligibility="requires_confirmation",
            source_class="user_attested",
            source_author=self.request["actor"]["name"],
        )
        for unit in units:
            unit["assertedByActorId"] = self.request["actor"]["id"]
        self.units.extend(units)

    def _parse_container(self, container: dict, root: dict, data: bytes) -> None:
        source_type = container["sourceType"]
        if source_type == "unsupported":
            root.update(disposition="unreadable", reason="unsupported_format")
            container.update(disposition="unreadable", reason="unsupported_format")
            self.errors.append("unsupported_format")
            return
        if not apply_selected_source_type_filter(
            container, root, self.request["filters"]
        ):
            return
        if source_type == "text":
            self._parse_text(container, root, data)
        elif source_type == "email":
            self._parse_eml(container, root, data)
        elif source_type == "docx":
            self._parse_docx(container, root, data)

    def _parse_text(self, container: dict, root: dict, data: bytes) -> None:
        if not apply_selected_source_filter(container, root, self.request["filters"]):
            return
        try:
            units = make_text_units(
                container["containerId"],
                data,
                kind="text_block",
                role="current",
                eligibility=(
                    "requires_confirmation"
                    if container["filterDisposition"] == "filter_indeterminate"
                    else "eligible"
                ),
            )
        except TextDecodeError:
            container.update(
                disposition="unreadable", reason="strict_utf8_decode_failed"
            )
            root.update(disposition="unreadable", reason="strict_utf8_decode_failed")
            self.errors.append("strict_utf8_decode_failed")
            return
        for unit in units:
            unit["assertedByActorId"] = None
        self.units.extend(units)

    def _parse_eml(self, container: dict, root: dict, data: bytes) -> None:
        try:
            parsed = parse_email(container["containerId"], data)
        except EmailParseError:
            container.update(disposition="unreadable", reason="malformed_eml")
            root.update(disposition="unreadable", reason="malformed_eml")
            self.errors.append("malformed_eml")
            return
        container.update(
            sourceTime=parsed["sourceTime"],
            sourceTimeKind="eml_date" if parsed["sourceTime"] else None,
            sourceAuthor=parsed["sourceAuthor"],
        )
        if not apply_selected_source_filter(container, root, self.request["filters"]):
            return
        if container["filterDisposition"] == "filter_indeterminate":
            for unit in parsed["units"]:
                if unit["eligibility"] == "eligible":
                    unit["eligibility"] = "requires_confirmation"
        if any(
            unit["eligibility"] == "requires_confirmation" for unit in parsed["units"]
        ):
            if container["disposition"] == "ready":
                container.update(
                    disposition="requiresConfirmation", reason="email_content_ambiguity"
                )
            if root["disposition"] == "ready":
                root.update(
                    disposition="requiresConfirmation", reason="email_content_ambiguity"
                )
        for unit in parsed["units"]:
            unit["assertedByActorId"] = None
        self.mime_leaves.extend(parsed["leaves"])
        self.units.extend(parsed["units"])
        self.limitations.extend(
            x for x in parsed["limitations"] if x not in self.limitations
        )
        for child in parsed["children"]:
            self._derived_container(root, child)

    def _derived_container(self, root: dict, child: dict) -> None:
        self.derived_count += 1
        if self.derived_count > LIMITS["derivedContainers"]:
            root.update(
                disposition="requiresConfirmation", reason="derived_container_cap"
            )
            self.errors.append("derived_container_cap_exceeded")
            return
        data = child.pop("rawBytes")
        container = container_record(
            child["containerId"],
            root["rootId"],
            child["parentContainerId"],
            child["originLocator"],
            child["sourceType"],
            child["mediaType"],
            child["displayName"],
            data,
        )
        self.containers.append(container)
        prior = self.seen_hashes.get(container["rawSha256"])
        if prior:
            container.update(disposition="exactDuplicate", reason="raw_sha256_match")
            for leaf in self.mime_leaves:
                if leaf["childContainerId"] == container["containerId"]:
                    leaf.update(disposition="exactDuplicate", reason="raw_sha256_match")
            return
        self.seen_hashes[container["rawSha256"]] = container["containerId"]
        self._parse_container(container, root, data)

    def _parse_docx(self, container: dict, root: dict, data: bytes) -> None:
        parsed = parse_docx_for_packet(data, container, root, self.request["filters"])
        self.parts.extend(parsed["parts"])
        self.units.extend(parsed["units"])
        self.errors.extend(parsed["errors"])
        self.limitations.extend(
            x for x in parsed["limitations"] if x not in self.limitations
        )


def compile_packet(request: Any, source_root: Path) -> dict:
    return _Compiler(validate_request(request), Path(source_root)).compile()


def compile_packet_to_directory(request: Any, source_root: Path, run_dir: Path) -> dict:
    return compile_to_directory(compile_packet, request, source_root, run_dir)


def main(argv: list[str] | None = None) -> int:
    return run_cli(compile_packet, argv, description=__doc__)


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: 77307db1b047b545b0a03292ffce5e85bab234d8d65461b2a003328e2609c091