Dama12 commited on
Commit
e01ba40
·
1 Parent(s): 09087f2

Mise à jour: routes d'analyse et moteur k2

Browse files
app/api/routes/analysis.py CHANGED
@@ -234,6 +234,124 @@ def _normalize_k2_result_for_frontend(result_dict: dict) -> dict:
234
  Ensures all required fields are present with proper defaults.
235
  """
236
  from app.core.logging import logger
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
  logger.info(f"NORMALIZE: Input result keys: {list(result_dict.keys())}")
239
  logger.info(f"NORMALIZE: Status is: {result_dict.get('status')}")
@@ -277,6 +395,19 @@ def _normalize_k2_result_for_frontend(result_dict: dict) -> dict:
277
 
278
  comp_analysis.setdefault('research_gaps', result_dict.get('research_gaps', []))
279
  comp_analysis.setdefault('confidence_score', result_dict.get('confidence_overall', 0.8))
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
  # Ensure other required fields exist
282
  result_dict.setdefault('research_gaps', [])
@@ -296,6 +427,11 @@ def _normalize_k2_result_for_frontend(result_dict: dict) -> dict:
296
  result_dict['comparative_analysis'].setdefault('confidence_score', result_dict['confidence_overall'])
297
  if result_dict['comparative_analysis'].get('confidence_score') == 0:
298
  result_dict['comparative_analysis']['confidence_score'] = result_dict['confidence_overall']
 
 
 
 
 
299
 
300
  # Ensure arrays are properly formatted
301
  for field in ['research_gaps', 'counter_hypotheses', 'strategic_recommendations', 'reasoning_trace']:
 
234
  Ensures all required fields are present with proper defaults.
235
  """
236
  from app.core.logging import logger
237
+
238
+ def _first_present(source: dict, keys: list, default=None):
239
+ for key in keys:
240
+ value = source.get(key)
241
+ if value not in (None, "", [], {}):
242
+ return value
243
+ return default
244
+
245
+ def _as_percent(value, default=80):
246
+ try:
247
+ score = float(value)
248
+ return int(round(score * 100)) if score <= 1 else int(round(score))
249
+ except (TypeError, ValueError):
250
+ return default
251
+
252
+ def _normalize_contradiction(item):
253
+ if not isinstance(item, dict):
254
+ return {"description": str(item), "resolution": "Investigation required", "confidence": 0.8}
255
+
256
+ topic = _first_present(item, ["topic", "variable", "title"], "Scientific conflict")
257
+ conflict = _first_present(item, ["conflict", "description", "explanation", "impact"])
258
+ statement_a = _first_present(item, ["statement_a", "finding_a"])
259
+ statement_b = _first_present(item, ["statement_b", "finding_b"])
260
+
261
+ if not conflict and (statement_a or statement_b):
262
+ conflict = " vs. ".join([p for p in [statement_a, statement_b] if p])
263
+
264
+ description = conflict or topic or "Scientific contradiction detected"
265
+ resolution = _first_present(item, ["resolution_path", "resolution", "suggested_resolution"], "Investigation required")
266
+ confidence = _first_present(item, ["confidence", "confidence_score"], 0.8)
267
+
268
+ normalized = dict(item)
269
+ normalized.setdefault("topic", topic)
270
+ normalized.setdefault("title", topic)
271
+ normalized.setdefault("description", description)
272
+ normalized.setdefault("conflict", description)
273
+ normalized.setdefault("resolution", resolution)
274
+ normalized.setdefault("resolution_path", resolution)
275
+ normalized.setdefault("confidence", confidence)
276
+ normalized.setdefault("impact_score", _as_percent(confidence))
277
+ return normalized
278
+
279
+ def _normalize_gap(item):
280
+ if not isinstance(item, dict):
281
+ return {"description": str(item), "gap_description": str(item), "importance_score": 0.8}
282
+
283
+ description = _first_present(
284
+ item,
285
+ ["gap_description", "description", "opportunity", "title"],
286
+ "Research opportunity identified"
287
+ )
288
+ suggested = _first_present(
289
+ item,
290
+ ["suggested_investigation", "suggested_direction", "recommendation", "next_step"],
291
+ "Design a targeted follow-up investigation."
292
+ )
293
+ score = _first_present(item, ["importance_score", "impact_score", "score"], 0.8)
294
+
295
+ normalized = dict(item)
296
+ normalized.setdefault("gap_description", description)
297
+ normalized.setdefault("description", description)
298
+ normalized.setdefault("title", description)
299
+ normalized.setdefault("suggested_investigation", suggested)
300
+ normalized.setdefault("suggested_direction", suggested)
301
+ normalized.setdefault("importance_score", score)
302
+ normalized.setdefault("impact_score", _as_percent(score))
303
+ normalized.setdefault("related_variables", [])
304
+ normalized.setdefault("source_documents", [])
305
+ return normalized
306
+
307
+ def _normalize_divergence(item):
308
+ if not isinstance(item, dict):
309
+ return {"description": str(item)}
310
+
311
+ topic = _first_present(item, ["topic", "variable", "title"], "Scientific divergence")
312
+ description = _first_present(
313
+ item,
314
+ ["description", "impact", "conflict", "finding_a", "finding_b"],
315
+ topic
316
+ )
317
+
318
+ normalized = dict(item)
319
+ normalized.setdefault("topic", topic)
320
+ normalized.setdefault("title", topic)
321
+ normalized.setdefault("description", description)
322
+ return normalized
323
+
324
+ def _is_placeholder_summary(value):
325
+ if not isinstance(value, str):
326
+ return True
327
+ return value.strip().lower() in {
328
+ "",
329
+ "analysis completed.",
330
+ "analysis completed",
331
+ "analysis completed successfully",
332
+ }
333
+
334
+ def _build_summary_from_result(data):
335
+ comp = data.get("comparative_analysis") or {}
336
+ parts = []
337
+
338
+ findings = comp.get("common_findings") or []
339
+ if findings:
340
+ parts.append("Key findings: " + " ".join(str(item) for item in findings[:3]))
341
+
342
+ contradictions = comp.get("contradictions") or []
343
+ if contradictions:
344
+ descriptions = [
345
+ item.get("description") if isinstance(item, dict) else str(item)
346
+ for item in contradictions[:2]
347
+ ]
348
+ parts.append("Main conflicts: " + " ".join(d for d in descriptions if d))
349
+
350
+ recommendations = data.get("strategic_recommendations") or []
351
+ if recommendations:
352
+ parts.append("Recommended strategy: " + " ".join(str(item) for item in recommendations[:2]))
353
+
354
+ return "\n\n".join(parts) if parts else None
355
 
356
  logger.info(f"NORMALIZE: Input result keys: {list(result_dict.keys())}")
357
  logger.info(f"NORMALIZE: Status is: {result_dict.get('status')}")
 
395
 
396
  comp_analysis.setdefault('research_gaps', result_dict.get('research_gaps', []))
397
  comp_analysis.setdefault('confidence_score', result_dict.get('confidence_overall', 0.8))
398
+
399
+ comp_analysis['contradictions'] = [
400
+ _normalize_contradiction(item) for item in comp_analysis.get('contradictions', [])
401
+ ]
402
+ comp_analysis['divergences'] = [
403
+ _normalize_divergence(item) for item in comp_analysis.get('divergences', [])
404
+ ]
405
+ comp_analysis['research_gaps'] = [
406
+ _normalize_gap(item) for item in comp_analysis.get('research_gaps', [])
407
+ ]
408
+ result_dict['research_gaps'] = [
409
+ _normalize_gap(item) for item in (result_dict.get('research_gaps') or comp_analysis.get('research_gaps', []))
410
+ ]
411
 
412
  # Ensure other required fields exist
413
  result_dict.setdefault('research_gaps', [])
 
427
  result_dict['comparative_analysis'].setdefault('confidence_score', result_dict['confidence_overall'])
428
  if result_dict['comparative_analysis'].get('confidence_score') == 0:
429
  result_dict['comparative_analysis']['confidence_score'] = result_dict['confidence_overall']
430
+
431
+ if _is_placeholder_summary(result_dict.get('reasoning_summary')):
432
+ rebuilt_summary = _build_summary_from_result(result_dict)
433
+ if rebuilt_summary:
434
+ result_dict['reasoning_summary'] = rebuilt_summary
435
 
436
  # Ensure arrays are properly formatted
437
  for field in ['research_gaps', 'counter_hypotheses', 'strategic_recommendations', 'reasoning_trace']:
app/services/k2_think_engine.py CHANGED
@@ -207,12 +207,12 @@ class K2ThinkEngine:
207
  {{
208
  "comparative_analysis": {{
209
  "document_ids": ["doc_id1", "doc_id2"],
210
- "divergences": [{{"topic": "description"}}],
211
- "contradictions": [{{"topic": "...", "conflict": "...", "resolution_path": "...", "confidence": 0.8}}],
212
  "common_findings": ["finding1"],
213
  "confidence_score": 0.9
214
  }},
215
- "research_gaps": [{{"gap_description": "...", "importance_score": 0.8, "related_variables": [], "suggested_investigation": "...", "source_documents": ["doc_id1"]}}],
216
  "counter_hypotheses": [{{"hypothesis": "...", "rationale": "...", "potential_bias": "...", "validation_experiment": "...", "confidence_against": 0.8}}],
217
  "proposed_protocol": {{
218
  "title": "...", "objective": "...", "steps": [{{"step_number": 1, "description": "...", "materials": [], "critical_parameters": []}}],
@@ -227,6 +227,7 @@ class K2ThinkEngine:
227
  - NO preamble, NO explanations before or after JSON.
228
  - KEEP REASONING CONCISE: Focus on direct analysis to stay within processing time limits.
229
  - Use valid citations e.g. (Author, Year).
 
230
  """
231
 
232
  # 3. Appel au modèle via ChatOpenAI (manual JSON parsing)
 
207
  {{
208
  "comparative_analysis": {{
209
  "document_ids": ["doc_id1", "doc_id2"],
210
+ "divergences": [{{"topic": "...", "description": "...", "impact": "..."}}],
211
+ "contradictions": [{{"topic": "...", "description": "...", "conflict": "...", "resolution": "...", "resolution_path": "...", "confidence": 0.8, "impact_score": 80}}],
212
  "common_findings": ["finding1"],
213
  "confidence_score": 0.9
214
  }},
215
+ "research_gaps": [{{"title": "...", "description": "...", "gap_description": "...", "importance_score": 0.8, "impact_score": 80, "related_variables": [], "suggested_investigation": "...", "suggested_direction": "...", "source_documents": ["doc_id1"]}}],
216
  "counter_hypotheses": [{{"hypothesis": "...", "rationale": "...", "potential_bias": "...", "validation_experiment": "...", "confidence_against": 0.8}}],
217
  "proposed_protocol": {{
218
  "title": "...", "objective": "...", "steps": [{{"step_number": 1, "description": "...", "materials": [], "critical_parameters": []}}],
 
227
  - NO preamble, NO explanations before or after JSON.
228
  - KEEP REASONING CONCISE: Focus on direct analysis to stay within processing time limits.
229
  - Use valid citations e.g. (Author, Year).
230
+ - Avoid placeholder text. Every contradiction and research gap must include a display-ready description.
231
  """
232
 
233
  # 3. Appel au modèle via ChatOpenAI (manual JSON parsing)