refactor: convert f-string logging to lazy %-style format (G004)
Sweep all 1676 G004 violations across 112 files, converting
`logger.<level>(f"…{x}…")` to `logger.<level>("…%s…", x)`.
Why: ruff rule G004 was added to pyproject.toml to enforce lazy
%-style logging — defers formatting until the log level is enabled
and lets structured log tooling match the unformatted template.
Conversion preserves rendered output byte-for-byte:
- `{x}` → `%s` + `x`
- `{x!r}` / `{x!s}` / `{x!a}` → `%r` / `%s` / `%a`
- Format specs (`{x:.2f}`, `{x:>10}`) → `%s` + `format(x, 'spec')`
(printf-style specs aren't 1:1 with Python format specs, so we
delegate to `format()` to keep identical output)
- Literal `%` → `%%`
- Concatenated f-strings (`f"a {x} " "b"`) flattened
- Trailing kwargs (`exc_info=True`) preserved
Verified:
- `uv run ruff check --select G004` → 0 violations
- `uv run ty check -- nextcloud_mcp_server` → passes
- `uv run pytest tests/unit/` → 1010 passed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
a4e6125d28
commit
665cb9b1eb
@@ -120,11 +120,11 @@ async def get_indexed_doc_types(user_id: str) -> set[str]:
|
||||
if point.payload and point.payload.get("doc_type")
|
||||
}
|
||||
|
||||
logger.debug(f"Found indexed document types for user {user_id}: {doc_types}")
|
||||
logger.debug("Found indexed document types for user %s: %s", user_id, doc_types)
|
||||
return doc_types
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to query Qdrant for doc_types: {e}")
|
||||
logger.warning("Failed to query Qdrant for doc_types: %s", e)
|
||||
return set()
|
||||
|
||||
|
||||
|
||||
@@ -100,9 +100,13 @@ class BM25HybridSearchAlgorithm(SearchAlgorithm):
|
||||
score_threshold = kwargs.get("score_threshold", self.score_threshold)
|
||||
|
||||
logger.info(
|
||||
f"BM25 hybrid search: query='{query}', user={user_id}, "
|
||||
f"limit={limit}, score_threshold={score_threshold}, doc_type={doc_type}, "
|
||||
f"fusion={self.fusion_name}"
|
||||
"BM25 hybrid search: query='%s', user=%s, limit=%s, score_threshold=%s, doc_type=%s, fusion=%s",
|
||||
query,
|
||||
user_id,
|
||||
limit,
|
||||
score_threshold,
|
||||
doc_type,
|
||||
self.fusion_name,
|
||||
)
|
||||
|
||||
# Generate dense embedding for semantic search
|
||||
@@ -112,7 +116,7 @@ class BM25HybridSearchAlgorithm(SearchAlgorithm):
|
||||
dense_embedding = await embedding_service.embed(query)
|
||||
# Store for reuse by callers (e.g., viz_routes PCA visualization)
|
||||
self.query_embedding = dense_embedding
|
||||
logger.debug(f"Generated dense embedding (dimension={len(dense_embedding)})")
|
||||
logger.debug("Generated dense embedding (dimension=%s)", len(dense_embedding))
|
||||
|
||||
# Generate sparse embedding for BM25 keyword search
|
||||
with trace_operation("search.get_bm25_service"):
|
||||
@@ -120,8 +124,8 @@ class BM25HybridSearchAlgorithm(SearchAlgorithm):
|
||||
with trace_operation("search.sparse_embedding_bm25"):
|
||||
sparse_embedding = await bm25_service.encode_async(query)
|
||||
logger.debug(
|
||||
f"Generated sparse embedding "
|
||||
f"({len(sparse_embedding['indices'])} non-zero terms)"
|
||||
"Generated sparse embedding (%s non-zero terms)",
|
||||
len(sparse_embedding["indices"]),
|
||||
)
|
||||
|
||||
# Build Qdrant filter
|
||||
@@ -189,15 +193,16 @@ class BM25HybridSearchAlgorithm(SearchAlgorithm):
|
||||
raise
|
||||
|
||||
logger.info(
|
||||
f"Qdrant {self.fusion_name.upper()} fusion returned {len(search_response.points)} results "
|
||||
f"(before deduplication)"
|
||||
"Qdrant %s fusion returned %s results (before deduplication)",
|
||||
self.fusion_name.upper(),
|
||||
len(search_response.points),
|
||||
)
|
||||
|
||||
if search_response.points:
|
||||
# Log top 3 fusion scores to help with threshold tuning
|
||||
top_scores = [p.score for p in search_response.points[:3]]
|
||||
logger.debug(
|
||||
f"Top 3 {self.fusion_name.upper()} fusion scores: {top_scores}"
|
||||
"Top 3 %s fusion scores: %s", self.fusion_name.upper(), top_scores
|
||||
)
|
||||
|
||||
# Deduplicate by (doc_id, doc_type, chunk_start, chunk_end)
|
||||
@@ -233,12 +238,12 @@ class BM25HybridSearchAlgorithm(SearchAlgorithm):
|
||||
if len(results) >= limit:
|
||||
break
|
||||
|
||||
logger.info(f"Returning {len(results)} unverified results after deduplication")
|
||||
logger.info("Returning %s unverified results after deduplication", len(results))
|
||||
if results:
|
||||
result_details = [
|
||||
f"{r.doc_type}_{r.id} (score={r.score:.3f}, title='{r.title}')"
|
||||
for r in results[:5] # Show top 5
|
||||
]
|
||||
logger.debug(f"Top results: {', '.join(result_details)}")
|
||||
logger.debug("Top results: %s", ", ".join(result_details))
|
||||
|
||||
return results
|
||||
|
||||
@@ -67,20 +67,26 @@ async def _get_chunk_from_qdrant(
|
||||
excerpt = point.payload.get("excerpt")
|
||||
if excerpt:
|
||||
logger.debug(
|
||||
f"Retrieved chunk from Qdrant for {doc_type} {doc_id}: "
|
||||
f"{len(excerpt)} chars"
|
||||
"Retrieved chunk from Qdrant for %s %s: %s chars",
|
||||
doc_type,
|
||||
doc_id,
|
||||
len(excerpt),
|
||||
)
|
||||
return str(excerpt)
|
||||
|
||||
logger.debug(
|
||||
f"Chunk not found in Qdrant for {doc_type} {doc_id}, "
|
||||
f"chunk [{chunk_start}:{chunk_end}]. Will fall back to document fetch."
|
||||
"Chunk not found in Qdrant for %s %s, chunk [%s:%s]. Will fall back to document fetch.",
|
||||
doc_type,
|
||||
doc_id,
|
||||
chunk_start,
|
||||
chunk_end,
|
||||
)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error querying Qdrant for chunk: {e}. Falling back to document fetch.",
|
||||
"Error querying Qdrant for chunk: %s. Falling back to document fetch.",
|
||||
e,
|
||||
exc_info=True,
|
||||
)
|
||||
return None
|
||||
@@ -129,8 +135,11 @@ async def _get_chunk_by_index_from_qdrant(
|
||||
excerpt = point.payload.get("excerpt")
|
||||
if excerpt:
|
||||
logger.debug(
|
||||
f"Retrieved adjacent chunk {chunk_index} from Qdrant for "
|
||||
f"{doc_type} {doc_id}: {len(excerpt)} chars"
|
||||
"Retrieved adjacent chunk %s from Qdrant for %s %s: %s chars",
|
||||
chunk_index,
|
||||
doc_type,
|
||||
doc_id,
|
||||
len(excerpt),
|
||||
)
|
||||
return str(excerpt)
|
||||
|
||||
@@ -138,8 +147,11 @@ async def _get_chunk_by_index_from_qdrant(
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"Could not retrieve adjacent chunk {chunk_index} for "
|
||||
f"{doc_type} {doc_id}: {e}"
|
||||
"Could not retrieve adjacent chunk %s for %s %s: %s",
|
||||
chunk_index,
|
||||
doc_type,
|
||||
doc_id,
|
||||
e,
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -181,19 +193,21 @@ async def _get_deck_metadata_from_qdrant(
|
||||
stack_id = point.payload.get("stack_id")
|
||||
if board_id is not None and stack_id is not None:
|
||||
logger.debug(
|
||||
f"Retrieved deck metadata for card {card_id}: "
|
||||
f"board_id={board_id}, stack_id={stack_id}"
|
||||
"Retrieved deck metadata for card %s: board_id=%s, stack_id=%s",
|
||||
card_id,
|
||||
board_id,
|
||||
stack_id,
|
||||
)
|
||||
return {"board_id": int(board_id), "stack_id": int(stack_id)}
|
||||
|
||||
logger.debug(
|
||||
f"Could not find deck metadata in Qdrant for card {card_id} "
|
||||
f"(might be legacy data without board_id/stack_id)"
|
||||
"Could not find deck metadata in Qdrant for card %s (might be legacy data without board_id/stack_id)",
|
||||
card_id,
|
||||
)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Error querying Qdrant for deck metadata: {e}")
|
||||
logger.debug("Error querying Qdrant for deck metadata: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
@@ -389,8 +403,9 @@ async def get_chunk_with_context(
|
||||
|
||||
if chunk_text:
|
||||
logger.info(
|
||||
f"Retrieved chunk from Qdrant cache for {doc_type} {doc_id} "
|
||||
f"(avoids document re-fetch/re-parse)"
|
||||
"Retrieved chunk from Qdrant cache for %s %s (avoids document re-fetch/re-parse)",
|
||||
doc_type,
|
||||
doc_id,
|
||||
)
|
||||
|
||||
# Fetch adjacent chunks for context expansion
|
||||
@@ -495,24 +510,30 @@ async def get_chunk_with_context(
|
||||
return None
|
||||
|
||||
logger.info(
|
||||
f"Falling back to document fetch for {doc_type} {doc_id} "
|
||||
f"(Qdrant cache miss, possibly legacy data)"
|
||||
"Falling back to document fetch for %s %s (Qdrant cache miss, possibly legacy data)",
|
||||
doc_type,
|
||||
doc_id,
|
||||
)
|
||||
|
||||
# Fetch full document text (notes, deck cards, news items, etc.)
|
||||
full_text = await _fetch_document_text(nc_client, doc_id, doc_type, user_id)
|
||||
if full_text is None:
|
||||
logger.warning(
|
||||
f"Could not fetch document text for {doc_type} {doc_id}, "
|
||||
"skipping context expansion"
|
||||
"Could not fetch document text for %s %s, skipping context expansion",
|
||||
doc_type,
|
||||
doc_id,
|
||||
)
|
||||
return None
|
||||
|
||||
# Validate offsets
|
||||
if chunk_start < 0 or chunk_end > len(full_text) or chunk_start >= chunk_end:
|
||||
logger.warning(
|
||||
f"Invalid chunk offsets for {doc_type} {doc_id}: "
|
||||
f"start={chunk_start}, end={chunk_end}, doc_len={len(full_text)}"
|
||||
"Invalid chunk offsets for %s %s: start=%s, end=%s, doc_len=%s",
|
||||
doc_type,
|
||||
doc_id,
|
||||
chunk_start,
|
||||
chunk_end,
|
||||
len(full_text),
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -650,13 +671,18 @@ async def _fetch_document_text(
|
||||
board_id=board_id, stack_id=stack_id, card_id=int(doc_id)
|
||||
)
|
||||
logger.debug(
|
||||
f"Retrieved deck card {doc_id} using metadata "
|
||||
f"(board_id={board_id}, stack_id={stack_id})"
|
||||
"Retrieved deck card %s using metadata (board_id=%s, stack_id=%s)",
|
||||
doc_id,
|
||||
board_id,
|
||||
stack_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to fetch card with metadata (board_id={board_id}, "
|
||||
f"stack_id={stack_id}, card_id={doc_id}): {e}, falling back to iteration"
|
||||
"Failed to fetch card with metadata (board_id=%s, stack_id=%s, card_id=%s): %s, falling back to iteration",
|
||||
board_id,
|
||||
stack_id,
|
||||
doc_id,
|
||||
e,
|
||||
)
|
||||
|
||||
# Fallback: Iterate through all boards/stacks (for legacy data or if fast path failed)
|
||||
@@ -671,7 +697,9 @@ async def _fetch_document_text(
|
||||
# Skip deleted boards (soft delete: deletedAt > 0)
|
||||
if board.deletedAt > 0:
|
||||
logger.debug(
|
||||
f"Skipping deleted board {board.id} while searching for card {doc_id}"
|
||||
"Skipping deleted board %s while searching for card %s",
|
||||
board.id,
|
||||
doc_id,
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -686,13 +714,15 @@ async def _fetch_document_text(
|
||||
card = c
|
||||
card_found = True
|
||||
logger.debug(
|
||||
f"Found deck card {doc_id} in board {board.id}, "
|
||||
f"stack {stack.id} (fallback iteration)"
|
||||
"Found deck card %s in board %s, stack %s (fallback iteration)",
|
||||
doc_id,
|
||||
board.id,
|
||||
stack.id,
|
||||
)
|
||||
break
|
||||
|
||||
if not card_found:
|
||||
logger.warning(f"Deck card {doc_id} not found in any board/stack")
|
||||
logger.warning("Deck card %s not found in any board/stack", doc_id)
|
||||
return None
|
||||
|
||||
# Type narrowing: card is set if we reach here
|
||||
@@ -705,10 +735,12 @@ async def _fetch_document_text(
|
||||
content_parts.append(card.description)
|
||||
return "\n\n".join(content_parts)
|
||||
else:
|
||||
logger.warning(f"Unsupported doc_type for context expansion: {doc_type}")
|
||||
logger.warning("Unsupported doc_type for context expansion: %s", doc_type)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching document {doc_type} {doc_id}: {e}", exc_info=True)
|
||||
logger.error(
|
||||
"Error fetching document %s %s: %s", doc_type, doc_id, e, exc_info=True
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class PDFHighlighter:
|
||||
try:
|
||||
shutil.rmtree(temp_dir)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to clean up temp directory {temp_dir}: {e}")
|
||||
logger.warning("Failed to clean up temp directory %s: %s", temp_dir, e)
|
||||
|
||||
return full_text, page_boundaries
|
||||
|
||||
@@ -202,7 +202,7 @@ class PDFHighlighter:
|
||||
try:
|
||||
page_words = page.get_text("words")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to extract words from page: {e}")
|
||||
logger.error("Failed to extract words from page: %s", e)
|
||||
return 0
|
||||
|
||||
if not page_words:
|
||||
@@ -225,8 +225,12 @@ class PDFHighlighter:
|
||||
)
|
||||
]
|
||||
logger.debug(
|
||||
f"Filtered to {len(page_words)} words in region "
|
||||
f"({rx0:.0f}, {ry0:.0f}, {rx1:.0f}, {ry1:.0f})"
|
||||
"Filtered to %s words in region (%s, %s, %s, %s)",
|
||||
len(page_words),
|
||||
format(rx0, ".0f"),
|
||||
format(ry0, ".0f"),
|
||||
format(rx1, ".0f"),
|
||||
format(ry1, ".0f"),
|
||||
)
|
||||
|
||||
if not page_words:
|
||||
@@ -286,17 +290,19 @@ class PDFHighlighter:
|
||||
if len(current_matches) >= len(chunk_words) * 0.5:
|
||||
matches = current_matches
|
||||
logger.debug(
|
||||
f"Found match at position {start_pos}: "
|
||||
f"{len(matches)}/{len(chunk_words)} words"
|
||||
"Found match at position %s: %s/%s words",
|
||||
start_pos,
|
||||
len(matches),
|
||||
len(chunk_words),
|
||||
)
|
||||
break # Take FIRST match, not best/longest
|
||||
|
||||
if not matches:
|
||||
logger.debug(f"No word matches found (chunk has {len(chunk_words)} words)")
|
||||
logger.debug("No word matches found (chunk has %s words)", len(chunk_words))
|
||||
return 0
|
||||
|
||||
logger.debug(
|
||||
f"Matched {len(matches)} words out of {len(chunk_words)} chunk words"
|
||||
"Matched %s words out of %s chunk words", len(matches), len(chunk_words)
|
||||
)
|
||||
|
||||
# Build rectangles from matched words
|
||||
@@ -324,8 +330,9 @@ class PDFHighlighter:
|
||||
# A chunk should be mostly contiguous text
|
||||
if large_gaps > len(matches) * 0.3: # More than 30% have gaps
|
||||
logger.debug(
|
||||
f"Rejecting scattered matches: {large_gaps} large gaps "
|
||||
f"out of {len(matches)} matches"
|
||||
"Rejecting scattered matches: %s large gaps out of %s matches",
|
||||
large_gaps,
|
||||
len(matches),
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -512,12 +519,12 @@ class PDFHighlighter:
|
||||
rects = page.search_for(phrase.strip())
|
||||
if rects:
|
||||
anchor_rect = rects[0] # Use first match
|
||||
logger.debug(f"Found chunk anchor using phrase: '{phrase[:30]}...'")
|
||||
logger.debug("Found chunk anchor using phrase: '%s...'", phrase[:30])
|
||||
break
|
||||
|
||||
if not anchor_rect:
|
||||
page_num = page.number + 1 if page.number is not None else "unknown"
|
||||
logger.warning(f"Could not find chunk text on page {page_num}")
|
||||
logger.warning("Could not find chunk text on page %s", page_num)
|
||||
return 0
|
||||
|
||||
# Calculate chunk height based on character count
|
||||
@@ -558,8 +565,10 @@ class PDFHighlighter:
|
||||
fill_shape.commit()
|
||||
|
||||
logger.debug(
|
||||
f"Added bounding box at y={chunk_rect.y0:.0f}-{chunk_rect.y1:.0f} "
|
||||
f"(estimated {estimated_lines:.1f} lines)"
|
||||
"Added bounding box at y=%s-%s (estimated %s lines)",
|
||||
format(chunk_rect.y0, ".0f"),
|
||||
format(chunk_rect.y1, ".0f"),
|
||||
format(estimated_lines, ".1f"),
|
||||
)
|
||||
|
||||
return 1
|
||||
@@ -626,7 +635,9 @@ class PDFHighlighter:
|
||||
# Log if page differs from stored metadata
|
||||
if stored_page_number and stored_page_number != page_num:
|
||||
logger.info(
|
||||
f"Chunk primarily on page {page_num}, metadata says {stored_page_number}"
|
||||
"Chunk primarily on page %s, metadata says %s",
|
||||
page_num,
|
||||
stored_page_number,
|
||||
)
|
||||
|
||||
# Extract page text
|
||||
@@ -644,8 +655,12 @@ class PDFHighlighter:
|
||||
page_text_length = page_end - page_start
|
||||
|
||||
logger.debug(
|
||||
f"Extracted {len(chunk_text)} chars on page {page_num} "
|
||||
f"(offsets {page_relative_start}-{page_relative_end} of {page_text_length})"
|
||||
"Extracted %s chars on page %s (offsets %s-%s of %s)",
|
||||
len(chunk_text),
|
||||
page_num,
|
||||
page_relative_start,
|
||||
page_relative_end,
|
||||
page_text_length,
|
||||
)
|
||||
|
||||
# Get page and add highlights
|
||||
@@ -672,13 +687,15 @@ class PDFHighlighter:
|
||||
doc.close()
|
||||
|
||||
logger.info(
|
||||
f"Generated {len(png_bytes):,} byte image with {highlight_count} highlights"
|
||||
"Generated %s byte image with %s highlights",
|
||||
format(len(png_bytes), ","),
|
||||
highlight_count,
|
||||
)
|
||||
|
||||
return (png_bytes, page_num, highlight_count)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error highlighting chunk: {e}", exc_info=True)
|
||||
logger.error("Error highlighting chunk: %s", e, exc_info=True)
|
||||
return None
|
||||
|
||||
finally:
|
||||
@@ -688,7 +705,9 @@ class PDFHighlighter:
|
||||
shutil.rmtree(temp_pdf_path.parent)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to delete temp directory {temp_pdf_path.parent}: {e}"
|
||||
"Failed to delete temp directory %s: %s",
|
||||
temp_pdf_path.parent,
|
||||
e,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -791,11 +810,11 @@ class PDFHighlighter:
|
||||
)
|
||||
results[chunk_index] = ([normalized], page_num)
|
||||
|
||||
logger.info(f"Computed bboxes for {len(results)}/{len(chunks)} chunks")
|
||||
logger.info("Computed bboxes for %s/%s chunks", len(results), len(chunks))
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error computing chunk bboxes: {e}", exc_info=True)
|
||||
logger.error("Error computing chunk bboxes: %s", e, exc_info=True)
|
||||
return results
|
||||
|
||||
finally:
|
||||
@@ -805,7 +824,7 @@ class PDFHighlighter:
|
||||
try:
|
||||
shutil.rmtree(temp_pdf_path.parent)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to clean up temp dir: {e}")
|
||||
logger.warning("Failed to clean up temp dir: %s", e)
|
||||
|
||||
@staticmethod
|
||||
def highlight_chunks_batch(
|
||||
@@ -853,8 +872,9 @@ class PDFHighlighter:
|
||||
doc = pymupdf.open(temp_pdf_path)
|
||||
|
||||
logger.debug(
|
||||
f"Batch highlighting: {len(chunks)} chunks, "
|
||||
f"{len(page_boundaries)} pages"
|
||||
"Batch highlighting: %s chunks, %s pages",
|
||||
len(chunks),
|
||||
len(page_boundaries),
|
||||
)
|
||||
|
||||
# Group chunks by their target page for efficient rendering
|
||||
@@ -873,7 +893,7 @@ class PDFHighlighter:
|
||||
)
|
||||
|
||||
if not chunk_page_info:
|
||||
logger.warning(f"Chunk {chunk_index}: not found on any page")
|
||||
logger.warning("Chunk %s: not found on any page", chunk_index)
|
||||
continue
|
||||
|
||||
page_num = chunk_page_info["page_num"]
|
||||
@@ -881,8 +901,10 @@ class PDFHighlighter:
|
||||
# Log if page differs from stored metadata
|
||||
if stored_page_num and stored_page_num != page_num:
|
||||
logger.debug(
|
||||
f"Chunk {chunk_index}: found on page {page_num}, "
|
||||
f"metadata says {stored_page_num}"
|
||||
"Chunk %s: found on page %s, metadata says %s",
|
||||
chunk_index,
|
||||
page_num,
|
||||
stored_page_num,
|
||||
)
|
||||
|
||||
# Extract page-relative portion of chunk text
|
||||
@@ -905,7 +927,7 @@ class PDFHighlighter:
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"Chunks distributed across {len(chunks_by_page)} unique pages"
|
||||
"Chunks distributed across %s unique pages", len(chunks_by_page)
|
||||
)
|
||||
|
||||
# OPTIMIZATION: Render each page ONCE, then draw highlights using PIL
|
||||
@@ -929,7 +951,9 @@ class PDFHighlighter:
|
||||
page_rect = page.rect
|
||||
|
||||
logger.debug(
|
||||
f"Page {page_num}: rendered once, processing {len(page_chunks)} chunks"
|
||||
"Page %s: rendered once, processing %s chunks",
|
||||
page_num,
|
||||
len(page_chunks),
|
||||
)
|
||||
|
||||
for (
|
||||
@@ -949,7 +973,7 @@ class PDFHighlighter:
|
||||
)
|
||||
|
||||
if bbox is None:
|
||||
logger.warning(f"Chunk {chunk_index}: could not find bbox")
|
||||
logger.warning("Chunk %s: could not find bbox", chunk_index)
|
||||
continue
|
||||
|
||||
# Copy base image for this chunk
|
||||
@@ -985,24 +1009,27 @@ class PDFHighlighter:
|
||||
results[chunk_index] = (png_bytes, page_num, 1)
|
||||
|
||||
logger.debug(
|
||||
f"Chunk {chunk_index}: {len(png_bytes):,} bytes, "
|
||||
f"page {page_num}, bbox {pil_bbox}"
|
||||
"Chunk %s: %s bytes, page %s, bbox %s",
|
||||
chunk_index,
|
||||
format(len(png_bytes), ","),
|
||||
page_num,
|
||||
pil_bbox,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Chunk {chunk_index}: error - {e}")
|
||||
logger.error("Chunk %s: error - %s", chunk_index, e)
|
||||
continue
|
||||
|
||||
doc.close()
|
||||
|
||||
logger.info(
|
||||
f"Batch highlighted {len(results)}/{len(chunks)} chunks successfully"
|
||||
"Batch highlighted %s/%s chunks successfully", len(results), len(chunks)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in batch highlighting: {e}", exc_info=True)
|
||||
logger.error("Error in batch highlighting: %s", e, exc_info=True)
|
||||
return results
|
||||
|
||||
finally:
|
||||
@@ -1011,4 +1038,4 @@ class PDFHighlighter:
|
||||
try:
|
||||
shutil.rmtree(temp_pdf_path.parent)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to clean up temp dir: {e}")
|
||||
logger.warning("Failed to clean up temp dir: %s", e)
|
||||
|
||||
@@ -77,8 +77,12 @@ class SemanticSearchAlgorithm(SearchAlgorithm):
|
||||
score_threshold = kwargs.get("score_threshold", self.score_threshold)
|
||||
|
||||
logger.info(
|
||||
f"Semantic search: query='{query}', user={user_id}, "
|
||||
f"limit={limit}, score_threshold={score_threshold}, doc_type={doc_type}"
|
||||
"Semantic search: query='%s', user=%s, limit=%s, score_threshold=%s, doc_type=%s",
|
||||
query,
|
||||
user_id,
|
||||
limit,
|
||||
score_threshold,
|
||||
doc_type,
|
||||
)
|
||||
|
||||
# Generate embedding for query
|
||||
@@ -87,7 +91,7 @@ class SemanticSearchAlgorithm(SearchAlgorithm):
|
||||
# Store for reuse by callers (e.g., viz_routes PCA visualization)
|
||||
self.query_embedding = query_embedding
|
||||
logger.debug(
|
||||
f"Generated embedding for query (dimension={len(query_embedding)})"
|
||||
"Generated embedding for query (dimension=%s)", len(query_embedding)
|
||||
)
|
||||
|
||||
# Build Qdrant filter
|
||||
@@ -127,14 +131,14 @@ class SemanticSearchAlgorithm(SearchAlgorithm):
|
||||
raise
|
||||
|
||||
logger.info(
|
||||
f"Qdrant returned {len(search_response.points)} results "
|
||||
f"(before deduplication)"
|
||||
"Qdrant returned %s results (before deduplication)",
|
||||
len(search_response.points),
|
||||
)
|
||||
|
||||
if search_response.points:
|
||||
# Log top 3 scores to help with threshold tuning
|
||||
top_scores = [p.score for p in search_response.points[:3]]
|
||||
logger.debug(f"Top 3 similarity scores: {top_scores}")
|
||||
logger.debug("Top 3 similarity scores: %s", top_scores)
|
||||
|
||||
# Deduplicate by (doc_id, doc_type, chunk_start, chunk_end)
|
||||
# This allows multiple chunks from same doc, but removes duplicate chunks
|
||||
@@ -155,12 +159,12 @@ class SemanticSearchAlgorithm(SearchAlgorithm):
|
||||
if len(results) >= limit:
|
||||
break
|
||||
|
||||
logger.info(f"Returning {len(results)} unverified results after deduplication")
|
||||
logger.info("Returning %s unverified results after deduplication", len(results))
|
||||
if results:
|
||||
result_details = [
|
||||
f"{r.doc_type}_{r.id} (score={r.score:.3f}, title='{r.title}')"
|
||||
for r in results[:5] # Show top 5
|
||||
]
|
||||
logger.debug(f"Top results: {', '.join(result_details)}")
|
||||
logger.debug("Top results: %s", ", ".join(result_details))
|
||||
|
||||
return results
|
||||
|
||||
Reference in New Issue
Block a user