Spaces:
Sleeping
fix: Add debug logging and follow-up question support
Browse filesDebugging improvements:
1. Added debug_grant() function to log complete grant data
2. Debug logging for first grant in search results
3. Debug logging for specific grant details
Follow-up support:
1. smart_route() now accepts previous_grants parameter
2. Detects short follow-up questions (≤8 words with keywords like "sure", "more", "details")
3. Reuses previous grants for context
4. generate_response() tracks last grants on function object
Diagnostic tools:
- debug_data.py script to verify data loading
- Checks snapshots, search index, and diagnoses issues
- Helps identify if problem is in data, index, or context building
Expected improvements:
- Follow-ups like "you sure?" now reference previous grants
- Console logs show what data is actually loaded
- Can quickly diagnose missing funding data
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- debug_data.py +102 -0
- src/analyzer/chat/demo_app.py +61 -5
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Debug script to verify grant data is loading correctly.
|
| 3 |
+
Run with: python debug_data.py
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from src.analyzer.data_loader import load_current_grants
|
| 8 |
+
from src.analyzer.search.service import get_grant_by_id
|
| 9 |
+
|
| 10 |
+
print("=" * 60)
|
| 11 |
+
print("GRANT DATA DEBUG")
|
| 12 |
+
print("=" * 60)
|
| 13 |
+
|
| 14 |
+
# Load all grants
|
| 15 |
+
print("\n1. Loading grants from snapshots...")
|
| 16 |
+
grants = load_current_grants(Path('data/snapshots'))
|
| 17 |
+
print(f" ✓ Loaded {len(grants)} grants")
|
| 18 |
+
|
| 19 |
+
# Check grant 2313 specifically
|
| 20 |
+
print("\n2. Checking competition-2313 (Battery Feasibility)...")
|
| 21 |
+
grant_2313 = next((g for g in grants if '2313' in g.id), None)
|
| 22 |
+
|
| 23 |
+
if grant_2313:
|
| 24 |
+
print(f" ✓ Found grant: {grant_2313.title}")
|
| 25 |
+
print(f" Status: {grant_2313.status}")
|
| 26 |
+
print(f" Has funding: {grant_2313.funding is not None}")
|
| 27 |
+
|
| 28 |
+
if grant_2313.funding:
|
| 29 |
+
print(f" Funding min: £{grant_2313.funding.min:,.0f}" if grant_2313.funding.min else " Funding min: None")
|
| 30 |
+
print(f" Funding max: £{grant_2313.funding.max:,.0f}" if grant_2313.funding.max else " Funding max: None")
|
| 31 |
+
print(f" Total pot: £{grant_2313.funding.total_pot:,.0f}" if grant_2313.funding.total_pot else " Total pot: None")
|
| 32 |
+
else:
|
| 33 |
+
print(" ❌ NO FUNDING DATA")
|
| 34 |
+
|
| 35 |
+
print(f" Has summary: {grant_2313.summary is not None and len(grant_2313.summary) > 0}")
|
| 36 |
+
print(f" Close date: {grant_2313.close_date}")
|
| 37 |
+
print(f" URL: {grant_2313.url}")
|
| 38 |
+
else:
|
| 39 |
+
print(" ❌ Grant 2313 not found in loaded grants!")
|
| 40 |
+
|
| 41 |
+
# Check grant 2314
|
| 42 |
+
print("\n3. Checking competition-2314 (Battery Concept)...")
|
| 43 |
+
grant_2314 = next((g for g in grants if '2314' in g.id), None)
|
| 44 |
+
|
| 45 |
+
if grant_2314:
|
| 46 |
+
print(f" ✓ Found grant: {grant_2314.title}")
|
| 47 |
+
print(f" Status: {grant_2314.status}")
|
| 48 |
+
|
| 49 |
+
if grant_2314.funding:
|
| 50 |
+
print(f" Funding min: £{grant_2314.funding.min:,.0f}" if grant_2314.funding.min else " Funding min: None")
|
| 51 |
+
print(f" Funding max: £{grant_2314.funding.max:,.0f}" if grant_2314.funding.max else " Funding max: None")
|
| 52 |
+
else:
|
| 53 |
+
print(" ❌ NO FUNDING DATA")
|
| 54 |
+
else:
|
| 55 |
+
print(" ❌ Grant 2314 not found!")
|
| 56 |
+
|
| 57 |
+
# Check search index
|
| 58 |
+
print("\n4. Checking search index (get_grant_by_id)...")
|
| 59 |
+
try:
|
| 60 |
+
indexed_grant = get_grant_by_id("competition-2313")
|
| 61 |
+
if indexed_grant:
|
| 62 |
+
print(f" ✓ Found via search index: {indexed_grant.title}")
|
| 63 |
+
print(f" Status via index: {indexed_grant.status}")
|
| 64 |
+
print(f" Has funding via index: {indexed_grant.funding is not None}")
|
| 65 |
+
|
| 66 |
+
if indexed_grant.funding:
|
| 67 |
+
print(f" Funding via index: £{indexed_grant.funding.min:,.0f} - £{indexed_grant.funding.max:,.0f}")
|
| 68 |
+
else:
|
| 69 |
+
print(" ❌ NO FUNDING DATA VIA INDEX")
|
| 70 |
+
else:
|
| 71 |
+
print(" ❌ Not found via search index")
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f" ❌ Error accessing index: {e}")
|
| 74 |
+
|
| 75 |
+
# Summary
|
| 76 |
+
print("\n" + "=" * 60)
|
| 77 |
+
print("DIAGNOSIS:")
|
| 78 |
+
print("=" * 60)
|
| 79 |
+
|
| 80 |
+
has_data = grant_2313 and grant_2313.funding and grant_2313.funding.min
|
| 81 |
+
has_indexed_data = False
|
| 82 |
+
|
| 83 |
+
try:
|
| 84 |
+
indexed_grant = get_grant_by_id("competition-2313")
|
| 85 |
+
has_indexed_data = indexed_grant and indexed_grant.funding and indexed_grant.funding.min
|
| 86 |
+
except:
|
| 87 |
+
pass
|
| 88 |
+
|
| 89 |
+
if has_data and has_indexed_data:
|
| 90 |
+
print("✓ Data is present and index is working")
|
| 91 |
+
print(" Issue is likely in context building or LLM prompt")
|
| 92 |
+
elif has_data and not has_indexed_data:
|
| 93 |
+
print("✓ Data exists in snapshots")
|
| 94 |
+
print("❌ Search index is not preserving the data")
|
| 95 |
+
print(" ACTION: Rebuild search index")
|
| 96 |
+
elif not has_data:
|
| 97 |
+
print("❌ Data missing from snapshot files")
|
| 98 |
+
print(" ACTION: Re-run scraper to get funding data")
|
| 99 |
+
else:
|
| 100 |
+
print("? Unclear - check logs above")
|
| 101 |
+
|
| 102 |
+
print("=" * 60)
|
|
@@ -28,6 +28,36 @@ from pathlib import Path
|
|
| 28 |
logging.basicConfig(level=logging.INFO)
|
| 29 |
logger = logging.getLogger(__name__)
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# Global state
|
| 32 |
llm_client = LLMClient()
|
| 33 |
ALL_GRANTS = None
|
|
@@ -62,10 +92,13 @@ def cached_search(query: str, limit: int = 10) -> tuple:
|
|
| 62 |
return tuple()
|
| 63 |
|
| 64 |
|
| 65 |
-
def smart_route(query: str) -> Tuple[str, List[Grant], str]:
|
| 66 |
"""
|
| 67 |
-
Route query
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
Returns: (route_type, relevant_grants, debug_note)
|
| 71 |
|
|
@@ -80,11 +113,26 @@ def smart_route(query: str) -> Tuple[str, List[Grant], str]:
|
|
| 80 |
# Extract any competition IDs mentioned
|
| 81 |
comp_ids = re.findall(r'(?:competition-)?(\d+)', q)
|
| 82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
# Route 1: Specific grant detail
|
| 84 |
if comp_ids and any(word in q for word in ['about', 'detail', 'tell me', 'explain', 'describe']):
|
| 85 |
grant_id = f"competition-{comp_ids[0]}"
|
| 86 |
grant = get_grant_by_id(grant_id)
|
| 87 |
if grant:
|
|
|
|
| 88 |
return ('detail', [grant], f"Detail view for {grant_id}")
|
| 89 |
else:
|
| 90 |
return ('search', [], f"Grant {grant_id} not found, falling back to search")
|
|
@@ -116,6 +164,8 @@ def smart_route(query: str) -> Tuple[str, List[Grant], str]:
|
|
| 116 |
for grant_id, score in cached_results:
|
| 117 |
g = get_grant_by_id(grant_id)
|
| 118 |
if g:
|
|
|
|
|
|
|
| 119 |
grants.append(g)
|
| 120 |
|
| 121 |
return ('search', grants, f"Found {len(grants)} grants via semantic search")
|
|
@@ -258,10 +308,16 @@ def generate_response(query: str, history: List[Tuple[str, str]]) -> str:
|
|
| 258 |
start_time = time.time()
|
| 259 |
|
| 260 |
try:
|
| 261 |
-
#
|
| 262 |
-
|
|
|
|
|
|
|
|
|
|
| 263 |
logger.info(f"Route: {route_type} | Grants: {len(grants)} | Note: {debug_note}")
|
| 264 |
|
|
|
|
|
|
|
|
|
|
| 265 |
# Step 2: Handle no results
|
| 266 |
if not grants:
|
| 267 |
return "I couldn't find any grants matching your query. Try different keywords, or ask about a specific competition ID (e.g., 'tell me about competition-2313')."
|
|
|
|
| 28 |
logging.basicConfig(level=logging.INFO)
|
| 29 |
logger = logging.getLogger(__name__)
|
| 30 |
|
| 31 |
+
|
| 32 |
+
def debug_grant(grant: Grant) -> str:
|
| 33 |
+
"""Debug helper: Show what data is actually in a grant object"""
|
| 34 |
+
debug_lines = [
|
| 35 |
+
f"Grant Debug Info:",
|
| 36 |
+
f" ID: {grant.id}",
|
| 37 |
+
f" Title: {grant.title}",
|
| 38 |
+
f" Status: {grant.status}",
|
| 39 |
+
f" Has funding object: {grant.funding is not None}",
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
if grant.funding:
|
| 43 |
+
debug_lines.extend([
|
| 44 |
+
f" Funding.min: {grant.funding.min}",
|
| 45 |
+
f" Funding.max: {grant.funding.max}",
|
| 46 |
+
f" Funding.total_pot: {grant.funding.total_pot}",
|
| 47 |
+
])
|
| 48 |
+
else:
|
| 49 |
+
debug_lines.append(" No funding data")
|
| 50 |
+
|
| 51 |
+
debug_lines.extend([
|
| 52 |
+
f" Has summary: {grant.summary is not None and len(grant.summary) > 0}",
|
| 53 |
+
f" Has eligibility: {grant.eligibility is not None and len(grant.eligibility) > 0}",
|
| 54 |
+
f" Close date: {grant.close_date}",
|
| 55 |
+
f" URL: {grant.url}",
|
| 56 |
+
])
|
| 57 |
+
|
| 58 |
+
return "\n".join(debug_lines)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
# Global state
|
| 62 |
llm_client = LLMClient()
|
| 63 |
ALL_GRANTS = None
|
|
|
|
| 92 |
return tuple()
|
| 93 |
|
| 94 |
|
| 95 |
+
def smart_route(query: str, previous_grants: Optional[List[Grant]] = None) -> Tuple[str, List[Grant], str]:
|
| 96 |
"""
|
| 97 |
+
Route query with support for follow-up questions.
|
| 98 |
+
|
| 99 |
+
Args:
|
| 100 |
+
query: User query
|
| 101 |
+
previous_grants: Grants from previous query (for follow-ups)
|
| 102 |
|
| 103 |
Returns: (route_type, relevant_grants, debug_note)
|
| 104 |
|
|
|
|
| 113 |
# Extract any competition IDs mentioned
|
| 114 |
comp_ids = re.findall(r'(?:competition-)?(\d+)', q)
|
| 115 |
|
| 116 |
+
# NEW: Route 0 - Vague follow-up questions
|
| 117 |
+
# If query is very short and we have previous context, reuse it
|
| 118 |
+
if previous_grants and len(q.split()) <= 8: # Short query
|
| 119 |
+
follow_up_keywords = [
|
| 120 |
+
'sure', 'certain', 'really', 'positive',
|
| 121 |
+
'more', 'extra', 'additional', 'else', 'other',
|
| 122 |
+
'details', 'info', 'information',
|
| 123 |
+
'about them', 'about those', 'about that',
|
| 124 |
+
'tell me more', 'what about', 'how about'
|
| 125 |
+
]
|
| 126 |
+
if any(keyword in q for keyword in follow_up_keywords):
|
| 127 |
+
logger.info(f"Follow-up detected, reusing {len(previous_grants)} previous grants")
|
| 128 |
+
return ('detail', previous_grants, f"Follow-up about previous {len(previous_grants)} grants")
|
| 129 |
+
|
| 130 |
# Route 1: Specific grant detail
|
| 131 |
if comp_ids and any(word in q for word in ['about', 'detail', 'tell me', 'explain', 'describe']):
|
| 132 |
grant_id = f"competition-{comp_ids[0]}"
|
| 133 |
grant = get_grant_by_id(grant_id)
|
| 134 |
if grant:
|
| 135 |
+
logger.info(f"\n{debug_grant(grant)}") # Debug logging
|
| 136 |
return ('detail', [grant], f"Detail view for {grant_id}")
|
| 137 |
else:
|
| 138 |
return ('search', [], f"Grant {grant_id} not found, falling back to search")
|
|
|
|
| 164 |
for grant_id, score in cached_results:
|
| 165 |
g = get_grant_by_id(grant_id)
|
| 166 |
if g:
|
| 167 |
+
if len(grants) == 0: # Only debug first grant to avoid spam
|
| 168 |
+
logger.info(f"\n{debug_grant(g)}")
|
| 169 |
grants.append(g)
|
| 170 |
|
| 171 |
return ('search', grants, f"Found {len(grants)} grants via semantic search")
|
|
|
|
| 308 |
start_time = time.time()
|
| 309 |
|
| 310 |
try:
|
| 311 |
+
# Extract previous grants from conversation state if available
|
| 312 |
+
previous_grants = getattr(generate_response, '_last_grants', None)
|
| 313 |
+
|
| 314 |
+
# Step 1: Fast routing with follow-up support
|
| 315 |
+
route_type, grants, debug_note = smart_route(query, previous_grants)
|
| 316 |
logger.info(f"Route: {route_type} | Grants: {len(grants)} | Note: {debug_note}")
|
| 317 |
|
| 318 |
+
# Store grants for next follow-up
|
| 319 |
+
generate_response._last_grants = grants
|
| 320 |
+
|
| 321 |
# Step 2: Handle no results
|
| 322 |
if not grants:
|
| 323 |
return "I couldn't find any grants matching your query. Try different keywords, or ask about a specific competition ID (e.g., 'tell me about competition-2313')."
|