diff --git a/apps/worker/app/services/common/file_utils.py b/apps/worker/app/services/common/file_utils.py index c266afc97..90eb7a1eb 100644 --- a/apps/worker/app/services/common/file_utils.py +++ b/apps/worker/app/services/common/file_utils.py @@ -4,6 +4,10 @@ import pandas as pd +# Shared cap for cosmetic asset filename stems (images/tables). Keeps OS +# basename limits safe while preserving a short debug-friendly context. +MAX_ASSET_FILE_NAME_CHARS = 80 + def clean_file(path_, mode="remove", cols=None): """ diff --git a/apps/worker/app/services/document_parser/formats/markdown/deferred_summary.py b/apps/worker/app/services/document_parser/formats/markdown/deferred_summary.py index 06ff1ad06..7684fee73 100644 --- a/apps/worker/app/services/document_parser/formats/markdown/deferred_summary.py +++ b/apps/worker/app/services/document_parser/formats/markdown/deferred_summary.py @@ -29,7 +29,7 @@ from shared.services.ai.summary.engine import summarize from shared.services.ai.summary.model import AssetSummary, BodySummary from shared.utils.chunk_refs import build_chunk_ref -from app.services.common.file_utils import path_handle +from app.services.common.file_utils import MAX_ASSET_FILE_NAME_CHARS, path_handle # Each deferred task now carries the engine's typed contract straight through to # the apply step (audit §4.5): assets → AssetSummary, text → BodySummary. The row @@ -280,7 +280,10 @@ def _apply_image_summary_result( image_dir = original_task.image_dir old_img_name = original_task.image_name image_suffix = original_task.image_suffix - safe_title = path_handle(str(img_title), mode="clean_single") + cleaned_title = path_handle(str(img_title), mode="clean_single") + if not isinstance(cleaned_title, str) or not cleaned_title: + return + safe_title = cleaned_title[:MAX_ASSET_FILE_NAME_CHARS] img_num_match = re.match(r"image-(\d+)", str(old_img_name)) img_num = ( img_num_match.group(1) @@ -290,6 +293,8 @@ def _apply_image_summary_result( else "0" ) new_img_name = path_handle(f"image-{img_num}-{safe_title}", mode="clean_single") + if not isinstance(new_img_name, str) or not new_img_name: + return old_path = os.path.join(image_dir, f"{old_img_name}{image_suffix}") new_path = os.path.join(image_dir, f"{new_img_name}{image_suffix}") if old_path == new_path or not os.path.exists(old_path): diff --git a/apps/worker/app/services/document_parser/formats/markdown/image_asset.py b/apps/worker/app/services/document_parser/formats/markdown/image_asset.py index 185ae69f9..1958bdbf0 100644 --- a/apps/worker/app/services/document_parser/formats/markdown/image_asset.py +++ b/apps/worker/app/services/document_parser/formats/markdown/image_asset.py @@ -19,7 +19,7 @@ from loguru import logger from shared.utils.chunk_refs import build_chunk_ref -from app.services.common.file_utils import path_handle +from app.services.common.file_utils import MAX_ASSET_FILE_NAME_CHARS, path_handle @dataclass(frozen=True) @@ -135,7 +135,7 @@ def build_markdown_image_asset( def build_markdown_image_name(*, image_count: int, last_context: str) -> str: image_name_context = path_handle(last_context.strip(), mode="clean_single") if image_name_context: - image_name_context = image_name_context[:60] + image_name_context = image_name_context[:MAX_ASSET_FILE_NAME_CHARS] return f"image-{image_count}-{image_name_context}" return f"image-{image_count}" diff --git a/apps/worker/app/services/document_parser/tables/table_text_parser.py b/apps/worker/app/services/document_parser/tables/table_text_parser.py index 261bbe653..935f04e6c 100644 --- a/apps/worker/app/services/document_parser/tables/table_text_parser.py +++ b/apps/worker/app/services/document_parser/tables/table_text_parser.py @@ -8,7 +8,7 @@ import pandas as pd from bs4 import BeautifulSoup, Tag -_MAX_TABLE_NAME_CHARS = 80 +from app.services.common.file_utils import MAX_ASSET_FILE_NAME_CHARS def sanitize_table_name_from_header(raw_header_text: str) -> str: @@ -31,8 +31,8 @@ def sanitize_table_name_from_header(raw_header_text: str) -> str: meaningful = [field for field in unique if _is_meaningful_token(field)] result = " ".join(meaningful) - if len(result) > _MAX_TABLE_NAME_CHARS: - result = result[:_MAX_TABLE_NAME_CHARS].rstrip() + if len(result) > MAX_ASSET_FILE_NAME_CHARS: + result = result[:MAX_ASSET_FILE_NAME_CHARS].rstrip() return result