Skip to content

feat(documents): enhance contract comparison with clause frequency analysis#750

Merged
larryro merged 2 commits into
mainfrom
feat/contract-comparison-enhancements
Mar 11, 2026
Merged

feat(documents): enhance contract comparison with clause frequency analysis#750
larryro merged 2 commits into
mainfrom
feat/contract-comparison-enhancements

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Mar 11, 2026

Summary

  • Refactor contract comparison workflow to use direct deterministic document diffing instead of RAG indexing, improving reliability and performance
  • Add clause frequency extraction step that tracks most-negotiated clause families across version transitions with a summary table in the report
  • Improve report structure with fully localized output (headings, labels, change type values), executive overview section, and methodology note
  • Add markdown heading normalization in the crawler to ensure reliable paragraph parsing
  • Add document comparison endpoint in the RAG service for file-level diffing
  • Expose fileId in agent document list tool for downstream workflow consumption

Test plan

  • Verify contract comparison workflow runs end-to-end with 2+ document versions
  • Confirm clause frequency table appears in generated DOCX report with correct aggregated counts
  • Test localized output by passing a language parameter (e.g., de, fr)
  • Validate markdown heading normalization with edge cases (fenced code blocks, consecutive headings)
  • Run new test suites: test_markdown_normalize.py and test_compare_files.py
  • Confirm fileId is returned in document list agent tool responses

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Document comparison now supports uploading files directly by URL, expanding comparison capabilities beyond stored documents.
    • Contract comparison workflow enhanced with clause frequency analysis to identify most negotiated clauses and evolution analysis.
    • Added file extraction field to document tools for improved clarity on which IDs to use for different operations.
  • Bug Fixes

    • Improved markdown rendering with proper heading normalization and spacing.
    • Enhanced error handling and timeout management in document comparison operations.
  • Documentation

    • Clarified approval card placement messaging—cards now consistently described as appearing below your message in chat.
    • Expanded guidance on document ID vs. file ID usage in document retrieval and extraction tools.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

larryro added 2 commits March 11, 2026 14:08
Add fileId to document list results so agents can use the correct
identifier for file extraction tools (pdf, docx, excel, etc.) vs
document_retrieve. Documents without a fileId are now filtered out.
…eterministic diff

Refactor contract comparison workflow to use direct document comparison instead of RAG indexing, add clause frequency extraction step for tracking most-negotiated clauses, and improve report structure with executive overview, frequency table, and full localization support. Also adds markdown heading normalization in the crawler and document comparison endpoint in the RAG service.
@larryro larryro force-pushed the feat/contract-comparison-enhancements branch from 1ff4579 to d735691 Compare March 11, 2026 06:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

This PR implements a file-based document comparison workflow across multiple services. It adds a new /documents/compare-files endpoint to the RAG service to compare files via direct upload, introduces helper functions for file URL resolution and comparison, and updates the contract-comparison workflow configuration to extract clause frequencies and accumulate results. Platform tools and workflows are updated to reference approval card placement ("below your message"), and document listing is enhanced to include file IDs alongside document IDs. Supporting changes include Markdown heading normalization in the crawler service and test coverage for new comparison functionality.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: enhancing contract comparison with clause frequency analysis, which aligns with the primary objective of adding clause frequency extraction and improved report structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/contract-comparison-enhancements

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/platform/convex/documents/list_documents_for_agent.ts (1)

176-185: ⚠️ Potential issue | 🔴 Critical

Fix TypeScript type error for fileId.

The pipeline fails because TypeScript doesn't narrow the fileId type through the filtering at line 118. Although documents without fileId are filtered out, the matches array still has type Doc<'documents'>[] where fileId is Id<"_storage"> | undefined.

🐛 Proposed fix using non-null assertion
   const documents: AgentDocumentItem[] = page.map((doc) => ({
     id: doc._id,
-    fileId: doc.fileId,
+    fileId: doc.fileId!,
     title: getDocumentTitle(doc),
     extension: doc.extension ?? null,
     folderPath: doc.folderId ? (folderPathMap.get(doc.folderId) ?? null) : null,
     teamId: doc.teamId ?? null,
     createdAt: doc._creationTime,
     sizeBytes: extractSize(doc.metadata),
   }));

Alternatively, cast to string explicitly since Convex IDs serialize as strings:

-    fileId: doc.fileId,
+    fileId: doc.fileId as string,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/documents/list_documents_for_agent.ts` around lines
176 - 185, The TypeScript error comes from doc.fileId being typed as
Id<"_storage"> | undefined when mapping to AgentDocumentItem; update the mapping
in the documents creation to guarantee a non-null string for fileId (e.g. use a
non-null assertion or cast): replace fileId: doc.fileId with fileId: doc.fileId!
(or fileId: String(doc.fileId) / fileId: doc.fileId as string) inside the
page.map(...) so AgentDocumentItem.fileId is a string and the type error is
resolved; keep the rest of the mapping (id, title via getDocumentTitle,
extension, folderPath via folderPathMap, teamId, createdAt, sizeBytes via
extractSize) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@services/platform/convex/agent_tools/documents/helpers/fetch_document_comparison.ts`:
- Around line 211-239: Create the AbortController and timeout before starting
the signed-URL downloads and pass its signal into the two fetch calls (the
fetch(baseFileUrl) and fetch(comparisonFileUrl)) so those downloads are bounded
by FETCH_TIMEOUT_MS; also pass the same signal into the final POST fetch to
/api/v1/documents/compare-files and ensure you clearTimeout(timeoutId) after
success (and handle abort errors) so the timeout is properly cleaned up.

In
`@services/platform/convex/workflow_engine/action_defs/document/document_action.ts`:
- Around line 46-55: The resolveFileName function currently returns a hardcoded
'Unknown' when metadata is missing and ignores any supplied baseFileName; change
resolveFileName(ctx, fileId) to accept an optional fallbackName (e.g.,
resolveFileName(ctx, fileId, fallbackName?: string)) and return
metadata?.fileName ?? fallbackName ?? '' instead of 'Unknown' so a provided
baseFileName is honoured and we avoid injecting "Unknown" that breaks extension
validation; make the same change to the other similar implementation mentioned
(the block at the other location corresponding to lines 205-211).

In `@services/rag/app/routers/documents.py`:
- Around line 511-516: The upload handling currently maps ValueError to a 422
but lets text decode errors fall through to a 500; catch UnicodeDecodeError
raised by extract_text() and raise
HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e))
from e, either by adding an explicit except UnicodeDecodeError as e: block
before the generic except Exception or by combining UnicodeDecodeError with the
existing ValueError branch; reference extract_text(), HTTPException and
status.HTTP_422_UNPROCESSABLE_ENTITY when locating where to add the new
exception handling.

In `@services/rag/app/services/rag_service.py`:
- Around line 475-508: In compare_files, ensure the service is initialized
before using the vision client: call the service's initialize() method (or the
module-level initializer used elsewhere) at the start of compare_files so
_vision_client is set when extract_text is invoked; update compare_files to
invoke initialize() (or await initialize() if async) before calling extract_text
for base and comparison files to avoid uninitialized OCR behavior in cold
workers.

---

Outside diff comments:
In `@services/platform/convex/documents/list_documents_for_agent.ts`:
- Around line 176-185: The TypeScript error comes from doc.fileId being typed as
Id<"_storage"> | undefined when mapping to AgentDocumentItem; update the mapping
in the documents creation to guarantee a non-null string for fileId (e.g. use a
non-null assertion or cast): replace fileId: doc.fileId with fileId: doc.fileId!
(or fileId: String(doc.fileId) / fileId: doc.fileId as string) inside the
page.map(...) so AgentDocumentItem.fileId is a string and the type error is
resolved; keep the rest of the mapping (id, title via getDocumentTitle,
extension, folderPath via folderPathMap, teamId, createdAt, sizeBytes via
extractSize) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a3e8c08d-033f-46b8-8b21-6c4fd6f7e52b

📥 Commits

Reviewing files that changed from the base of the PR and between a60aa65 and 1ff4579.

📒 Files selected for processing (22)
  • examples/workflows/contract-comparison/config.json
  • services/crawler/app/services/base_converter.py
  • services/crawler/tests/test_markdown_normalize.py
  • services/platform/app/features/chat/components/workflow-update-approval-card.tsx
  • services/platform/convex/agent_tools/documents/document_list_tool.ts
  • services/platform/convex/agent_tools/documents/document_retrieve_tool.ts
  • services/platform/convex/agent_tools/documents/helpers/fetch_document_comparison.ts
  • services/platform/convex/agent_tools/integrations/create_bound_integration_tool.ts
  • services/platform/convex/agent_tools/integrations/integration_tool.ts
  • services/platform/convex/agent_tools/workflows/create_bound_workflow_tool.ts
  • services/platform/convex/agent_tools/workflows/create_workflow_tool.ts
  • services/platform/convex/agent_tools/workflows/run_workflow_tool.ts
  • services/platform/convex/agent_tools/workflows/save_workflow_definition_tool.ts
  • services/platform/convex/agent_tools/workflows/update_workflow_step_tool.ts
  • services/platform/convex/agents/integration/agent.ts
  • services/platform/convex/documents/__tests__/list_documents_for_agent.test.ts
  • services/platform/convex/documents/list_documents_for_agent.ts
  • services/platform/convex/workflow_engine/action_defs/document/document_action.ts
  • services/platform/convex/workflow_engine/instructions/core_instructions.ts
  • services/rag/app/routers/documents.py
  • services/rag/app/services/rag_service.py
  • services/rag/tests/test_compare_files.py

Comment on lines +211 to +239
const [baseResponse, compResponse] = await Promise.all([
fetch(baseFileUrl),
fetch(comparisonFileUrl),
]);
if (!baseResponse.ok) {
throw new Error(`Failed to download base file: ${baseResponse.status}`);
}
if (!compResponse.ok) {
throw new Error(
`Failed to download comparison file: ${compResponse.status}`,
);
}

const [baseBlob, compBlob] = await Promise.all([
baseResponse.blob(),
compResponse.blob(),
]);

const url = `${ragServiceUrl}/api/v1/documents/compare-files`;

const formData = new FormData();
formData.append('base_file', baseBlob, baseFileName);
formData.append('comparison_file', compBlob, comparisonFileName);
if (maxChanges != null) {
formData.append('max_changes', String(maxChanges));
}

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bound the storage downloads with the same timeout budget.

Only the final POST is protected by FETCH_TIMEOUT_MS; the signed-URL downloads and blob() reads happen before the AbortController exists. A slow storage response can pin this synchronous path until the outer action times out.

⏱️ Suggested fix
+async function downloadBlob(
+  fileUrl: string,
+  label: 'base' | 'comparison',
+  signal: AbortSignal,
+): Promise<Blob> {
+  const response = await fetch(fileUrl, { signal });
+  if (!response.ok) {
+    throw new Error(`Failed to download ${label} file: ${response.status}`);
+  }
+  return await response.blob();
+}
+
 export async function fetchDocumentComparisonByUrls(
   ragServiceUrl: string,
   baseFileUrl: string,
   baseFileName: string,
   comparisonFileUrl: string,
   comparisonFileName: string,
   maxChanges?: number,
 ): Promise<DocumentComparisonResult> {
-  const [baseResponse, compResponse] = await Promise.all([
-    fetch(baseFileUrl),
-    fetch(comparisonFileUrl),
-  ]);
-  if (!baseResponse.ok) {
-    throw new Error(`Failed to download base file: ${baseResponse.status}`);
-  }
-  if (!compResponse.ok) {
-    throw new Error(
-      `Failed to download comparison file: ${compResponse.status}`,
-    );
-  }
-
-  const [baseBlob, compBlob] = await Promise.all([
-    baseResponse.blob(),
-    compResponse.blob(),
-  ]);
-
-  const url = `${ragServiceUrl}/api/v1/documents/compare-files`;
-
   const controller = new AbortController();
   const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
 
   try {
+    const [baseBlob, compBlob] = await Promise.all([
+      downloadBlob(baseFileUrl, 'base', controller.signal),
+      downloadBlob(comparisonFileUrl, 'comparison', controller.signal),
+    ]);
+
+    const url = `${ragServiceUrl}/api/v1/documents/compare-files`;
+
     const response = await fetch(url, {
       method: 'POST',
       body: formData,
       signal: controller.signal,
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/convex/agent_tools/documents/helpers/fetch_document_comparison.ts`
around lines 211 - 239, Create the AbortController and timeout before starting
the signed-URL downloads and pass its signal into the two fetch calls (the
fetch(baseFileUrl) and fetch(comparisonFileUrl)) so those downloads are bounded
by FETCH_TIMEOUT_MS; also pass the same signal into the final POST fetch to
/api/v1/documents/compare-files and ensure you clearTimeout(timeoutId) after
success (and handle abort errors) so the timeout is properly cleaned up.

Comment on lines +46 to +55
async function resolveFileName(
ctx: ActionCtx,
fileId: string,
): Promise<string> {
const metadata = await ctx.runQuery(
internal.file_metadata.internal_queries.getByStorageId,
{ storageId: toId<'_storage'>(fileId) },
);
return metadata?.fileName ?? 'Unknown';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Resolve comparison filenames independently and stop defaulting to "Unknown".

Right now we ignore a supplied baseFileName unless comparisonFileName is also present, and a missing metadata row returns "Unknown". The compare-files endpoint validates file extensions, so that combination turns otherwise valid compares into a hard 400.

🔧 Suggested fix
 async function resolveFileName(
   ctx: ActionCtx,
   fileId: string,
 ): Promise<string> {
   const metadata = await ctx.runQuery(
     internal.file_metadata.internal_queries.getByStorageId,
     { storageId: toId<'_storage'>(fileId) },
   );
-  return metadata?.fileName ?? 'Unknown';
+  if (!metadata?.fileName) {
+    throw new Error(
+      `File name not available for ${fileId}. Pass baseFileName/comparisonFileName explicitly.`,
+    );
+  }
+  return metadata.fileName;
 }
@@
-        const [baseFileName, compFileName] =
-          params.baseFileName && params.comparisonFileName
-            ? [params.baseFileName, params.comparisonFileName]
-            : await Promise.all([
-                resolveFileName(ctx, params.baseFileId),
-                resolveFileName(ctx, params.comparisonFileId),
-              ]);
+        const [baseFileName, compFileName] = await Promise.all([
+          params.baseFileName
+            ? Promise.resolve(params.baseFileName)
+            : resolveFileName(ctx, params.baseFileId),
+          params.comparisonFileName
+            ? Promise.resolve(params.comparisonFileName)
+            : resolveFileName(ctx, params.comparisonFileId),
+        ]);

Also applies to: 205-211

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/convex/workflow_engine/action_defs/document/document_action.ts`
around lines 46 - 55, The resolveFileName function currently returns a hardcoded
'Unknown' when metadata is missing and ignores any supplied baseFileName; change
resolveFileName(ctx, fileId) to accept an optional fallbackName (e.g.,
resolveFileName(ctx, fileId, fallbackName?: string)) and return
metadata?.fileName ?? fallbackName ?? '' instead of 'Unknown' so a provided
baseFileName is honoured and we avoid injecting "Unknown" that breaks extension
validation; make the same change to the other similar implementation mentioned
(the block at the other location corresponding to lines 205-211).

Comment on lines +511 to +516
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=str(e),
) from e
except Exception as e:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Treat text decode failures as 422s.

extract_text() can raise UnicodeDecodeError for supported text formats, so malformed .txt/.csv uploads currently fall through to the generic 500 path. Map them with the existing ValueError branch instead.

💡 Suggested handling
-    except ValueError as e:
+    except (ValueError, UnicodeDecodeError) as e:
         raise HTTPException(
             status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
             detail=str(e),
         ) from e
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=str(e),
) from e
except Exception as e:
except (ValueError, UnicodeDecodeError) as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=str(e),
) from e
except Exception as e:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/rag/app/routers/documents.py` around lines 511 - 516, The upload
handling currently maps ValueError to a 422 but lets text decode errors fall
through to a 500; catch UnicodeDecodeError raised by extract_text() and raise
HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e))
from e, either by adding an explicit except UnicodeDecodeError as e: block
before the generic except Exception or by combining UnicodeDecodeError with the
existing ValueError branch; reference extract_text(), HTTPException and
status.HTTP_422_UNPROCESSABLE_ENTITY when locating where to add the new
exception handling.

Comment on lines +475 to +508
async def compare_files(
self,
base_bytes: bytes,
base_filename: str,
comparison_bytes: bytes,
comparison_filename: str,
*,
max_changes: int = 500,
) -> dict[str, Any]:
"""Compare two uploaded files using deterministic paragraph-level diffing.

Extracts text directly from file bytes — no database storage or embedding.
"""
from tale_knowledge.extraction import extract_text

from .diff_service import compute_diff

base_text, _ = await extract_text(
base_bytes,
base_filename,
vision_client=self._vision_client,
)
if not base_text or not base_text.strip():
raise ValueError(f"No text could be extracted from base file: {base_filename}")

comp_text, _ = await extract_text(
comparison_bytes,
comparison_filename,
vision_client=self._vision_client,
)
if not comp_text or not comp_text.strip():
raise ValueError(f"No text could be extracted from comparison file: {comparison_filename}")

diff_result = compute_diff(base_text, comp_text, max_changes=max_changes)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Call initialize() before extracting the uploaded files.

compare_files() is the only public entrypoint here that skips the standard initialization guard. On a cold worker, _vision_client stays unset even when configured, so OCR-backed comparisons can silently degrade or fail until some other endpoint initializes the singleton.

🛠️ Minimal fix
     async def compare_files(
         self,
         base_bytes: bytes,
         base_filename: str,
         comparison_bytes: bytes,
         comparison_filename: str,
         *,
         max_changes: int = 500,
     ) -> dict[str, Any]:
         """Compare two uploaded files using deterministic paragraph-level diffing.
 
         Extracts text directly from file bytes — no database storage or embedding.
         """
+        if not self.initialized:
+            await self.initialize()
+
         from tale_knowledge.extraction import extract_text
 
         from .diff_service import compute_diff
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def compare_files(
self,
base_bytes: bytes,
base_filename: str,
comparison_bytes: bytes,
comparison_filename: str,
*,
max_changes: int = 500,
) -> dict[str, Any]:
"""Compare two uploaded files using deterministic paragraph-level diffing.
Extracts text directly from file bytesno database storage or embedding.
"""
from tale_knowledge.extraction import extract_text
from .diff_service import compute_diff
base_text, _ = await extract_text(
base_bytes,
base_filename,
vision_client=self._vision_client,
)
if not base_text or not base_text.strip():
raise ValueError(f"No text could be extracted from base file: {base_filename}")
comp_text, _ = await extract_text(
comparison_bytes,
comparison_filename,
vision_client=self._vision_client,
)
if not comp_text or not comp_text.strip():
raise ValueError(f"No text could be extracted from comparison file: {comparison_filename}")
diff_result = compute_diff(base_text, comp_text, max_changes=max_changes)
async def compare_files(
self,
base_bytes: bytes,
base_filename: str,
comparison_bytes: bytes,
comparison_filename: str,
*,
max_changes: int = 500,
) -> dict[str, Any]:
"""Compare two uploaded files using deterministic paragraph-level diffing.
Extracts text directly from file bytesno database storage or embedding.
"""
if not self.initialized:
await self.initialize()
from tale_knowledge.extraction import extract_text
from .diff_service import compute_diff
base_text, _ = await extract_text(
base_bytes,
base_filename,
vision_client=self._vision_client,
)
if not base_text or not base_text.strip():
raise ValueError(f"No text could be extracted from base file: {base_filename}")
comp_text, _ = await extract_text(
comparison_bytes,
comparison_filename,
vision_client=self._vision_client,
)
if not comp_text or not comp_text.strip():
raise ValueError(f"No text could be extracted from comparison file: {comparison_filename}")
diff_result = compute_diff(base_text, comp_text, max_changes=max_changes)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/rag/app/services/rag_service.py` around lines 475 - 508, In
compare_files, ensure the service is initialized before using the vision client:
call the service's initialize() method (or the module-level initializer used
elsewhere) at the start of compare_files so _vision_client is set when
extract_text is invoked; update compare_files to invoke initialize() (or await
initialize() if async) before calling extract_text for base and comparison files
to avoid uninitialized OCR behavior in cold workers.

@larryro larryro merged commit a2e85cd into main Mar 11, 2026
16 checks passed
@larryro larryro deleted the feat/contract-comparison-enhancements branch March 11, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant