Riley Claude commited on
Commit
65bb1b0
·
1 Parent(s): e9c393a

fix: Add debug logging and follow-up question support

Browse files

Debugging 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>

Files changed (2) hide show
  1. debug_data.py +102 -0
  2. src/analyzer/chat/demo_app.py +61 -5
debug_data.py ADDED
@@ -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)
src/analyzer/chat/demo_app.py CHANGED
@@ -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 to appropriate handler using pattern matching.
68
- No LLM overhead - pure pattern matching for speed.
 
 
 
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
- # Step 1: Fast routing (no LLM)
262
- route_type, grants, debug_note = smart_route(query)
 
 
 
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')."