Claude Claude commited on
Commit
7e03ed8
Β·
unverified Β·
1 Parent(s): b3bdd22

Fix Population Analysis bugs: question persistence and position detection

Browse files

- Fix example questions clearing when clicking Run button
- Store question in persistent session state (current_question)
- Update state on every text area change to prevent clearing

- Improve position detection accuracy in analyzer
- Weight first/last sentences more heavily (positions typically stated there)
- Add strong phrase matching ("i support", "i oppose", "i don't support")
- Handle negations properly (don't count "don't support" as "support")
- Use scoring system: strong phrases weight=3, regular keywords weight=1
- Reduces mislabeling of sample support/oppose responses

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

pages/2_πŸ“Š_Population_Analysis.py CHANGED
@@ -149,27 +149,28 @@ st.sidebar.info(f"""
149
  **Population:** {population_size} variants
150
  """)
151
 
 
 
 
 
152
  # Main content
153
  col1, col2 = st.columns([2, 1])
154
 
155
  with col1:
156
  st.markdown("### πŸ’­ Question")
157
 
158
- # Use example question if set
159
- if "example_question" in st.session_state:
160
- default_question = st.session_state.example_question
161
- del st.session_state.example_question
162
- else:
163
- default_question = ""
164
-
165
  question = st.text_area(
166
  "Ask your question to the population:",
167
- value=default_question,
168
  placeholder="e.g., Should we build the 500-unit luxury condo tower downtown?",
169
  height=100,
170
- label_visibility="collapsed"
 
171
  )
172
 
 
 
 
173
  run_analysis = st.button(
174
  "πŸš€ Query Population",
175
  type="primary",
@@ -179,13 +180,13 @@ with col1:
179
  with col2:
180
  st.markdown("### πŸ“ Quick Examples")
181
  if st.button("Luxury condos?", use_container_width=True):
182
- st.session_state.example_question = "Should we build a 500-unit luxury condo tower downtown?"
183
  st.rerun()
184
  if st.button("Bike lanes?", use_container_width=True):
185
- st.session_state.example_question = "Should we add protected bike lanes on Main Street?"
186
  st.rerun()
187
  if st.button("Food trucks?", use_container_width=True):
188
- st.session_state.example_question = "Should we allow food trucks in the downtown plaza?"
189
  st.rerun()
190
 
191
  st.markdown("---")
 
149
  **Population:** {population_size} variants
150
  """)
151
 
152
+ # Initialize question in session state if not exists
153
+ if "current_question" not in st.session_state:
154
+ st.session_state.current_question = ""
155
+
156
  # Main content
157
  col1, col2 = st.columns([2, 1])
158
 
159
  with col1:
160
  st.markdown("### πŸ’­ Question")
161
 
 
 
 
 
 
 
 
162
  question = st.text_area(
163
  "Ask your question to the population:",
164
+ value=st.session_state.current_question,
165
  placeholder="e.g., Should we build the 500-unit luxury condo tower downtown?",
166
  height=100,
167
+ label_visibility="collapsed",
168
+ key="question_input"
169
  )
170
 
171
+ # Update session state when question changes
172
+ st.session_state.current_question = question
173
+
174
  run_analysis = st.button(
175
  "πŸš€ Query Population",
176
  type="primary",
 
180
  with col2:
181
  st.markdown("### πŸ“ Quick Examples")
182
  if st.button("Luxury condos?", use_container_width=True):
183
+ st.session_state.current_question = "Should we build a 500-unit luxury condo tower downtown?"
184
  st.rerun()
185
  if st.button("Bike lanes?", use_container_width=True):
186
+ st.session_state.current_question = "Should we add protected bike lanes on Main Street?"
187
  st.rerun()
188
  if st.button("Food trucks?", use_container_width=True):
189
+ st.session_state.current_question = "Should we allow food trucks in the downtown plaza?"
190
  st.rerun()
191
 
192
  st.markdown("---")
src/population/analyzer.py CHANGED
@@ -213,11 +213,43 @@ class ResponseAnalyzer:
213
  )
214
 
215
  def _detect_position(self, text: str) -> Tuple[Position, float]:
216
- """Detect position from text with confidence score"""
217
- # Count keyword matches
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  support_count = sum(
219
  1 for keyword in self.SUPPORT_KEYWORDS
220
- if keyword in text
221
  )
222
  oppose_count = sum(
223
  1 for keyword in self.OPPOSE_KEYWORDS
@@ -228,26 +260,30 @@ class ResponseAnalyzer:
228
  if keyword in text
229
  )
230
 
231
- total_matches = support_count + oppose_count + neutral_count
 
 
 
 
232
 
233
- if total_matches == 0:
234
  return Position.UNCLEAR, 0.0
235
 
236
  # Determine dominant position
237
- if support_count > oppose_count and support_count > neutral_count:
238
- confidence = support_count / max(total_matches, 1)
239
- if support_count >= 3:
240
  return Position.STRONGLY_SUPPORT, min(confidence, 1.0)
241
  return Position.SUPPORT, min(confidence, 1.0)
242
 
243
- elif oppose_count > support_count and oppose_count > neutral_count:
244
- confidence = oppose_count / max(total_matches, 1)
245
- if oppose_count >= 3:
246
  return Position.STRONGLY_OPPOSE, min(confidence, 1.0)
247
  return Position.OPPOSE, min(confidence, 1.0)
248
 
249
  elif neutral_count > 0:
250
- confidence = neutral_count / max(total_matches, 1)
251
  return Position.NEUTRAL, min(confidence, 1.0)
252
 
253
  return Position.UNCLEAR, 0.3
 
213
  )
214
 
215
  def _detect_position(self, text: str) -> Tuple[Position, float]:
216
+ """Detect position from text with confidence score - improved accuracy"""
217
+
218
+ # Strong indicators - look at first and last sentences (where positions are usually stated)
219
+ sentences = text.split('.')
220
+ first_sentence = sentences[0].lower() if sentences else ""
221
+ last_sentence = sentences[-1].lower() if len(sentences) > 1 else ""
222
+
223
+ # Check for clear positive statements in key positions
224
+ strong_support_phrases = [
225
+ "i support", "i agree", "i approve", "i favor", "i endorse",
226
+ "strongly support", "strongly agree", "in favor of",
227
+ "this is a good", "this is beneficial", "i'm excited"
228
+ ]
229
+ strong_oppose_phrases = [
230
+ "i oppose", "i disagree", "i reject", "i'm against",
231
+ "strongly oppose", "strongly disagree", "i cannot support",
232
+ "i don't support", "i can't support", "this is a bad",
233
+ "i'm concerned", "i'm worried", "i must oppose"
234
+ ]
235
+
236
+ # Check first and last sentences for strong indicators (weighted heavily)
237
+ first_last_text = first_sentence + " " + last_sentence
238
+ support_score = 0
239
+ oppose_score = 0
240
+
241
+ for phrase in strong_support_phrases:
242
+ if phrase in first_last_text:
243
+ support_score += 3 # Strong weight for clear statements
244
+
245
+ for phrase in strong_oppose_phrases:
246
+ if phrase in first_last_text:
247
+ oppose_score += 3 # Strong weight for clear statements
248
+
249
+ # Count keyword matches in full text (lower weight)
250
  support_count = sum(
251
  1 for keyword in self.SUPPORT_KEYWORDS
252
+ if keyword in text and not any(neg in text for neg in ["don't " + keyword, "can't " + keyword, "won't " + keyword])
253
  )
254
  oppose_count = sum(
255
  1 for keyword in self.OPPOSE_KEYWORDS
 
260
  if keyword in text
261
  )
262
 
263
+ # Combine scores
264
+ support_score += support_count
265
+ oppose_score += oppose_count
266
+
267
+ total_score = support_score + oppose_score + neutral_count
268
 
269
+ if total_score == 0:
270
  return Position.UNCLEAR, 0.0
271
 
272
  # Determine dominant position
273
+ if support_score > oppose_score and support_score > neutral_count:
274
+ confidence = support_score / max(total_score, 1)
275
+ if support_score >= 5:
276
  return Position.STRONGLY_SUPPORT, min(confidence, 1.0)
277
  return Position.SUPPORT, min(confidence, 1.0)
278
 
279
+ elif oppose_score > support_score and oppose_score > neutral_count:
280
+ confidence = oppose_score / max(total_score, 1)
281
+ if oppose_score >= 5:
282
  return Position.STRONGLY_OPPOSE, min(confidence, 1.0)
283
  return Position.OPPOSE, min(confidence, 1.0)
284
 
285
  elif neutral_count > 0:
286
+ confidence = neutral_count / max(total_score, 1)
287
  return Position.NEUTRAL, min(confidence, 1.0)
288
 
289
  return Position.UNCLEAR, 0.3