MADTOOL / hypotheses_service.py
quanliang's picture
Claude Fable 5.1
Auto-resolve source papers for hypothesis follow-up; renumber hypotheses
662a0bb
Raw History Blame Contribute Delete
12.4 kB
from io import BytesIO
from zipfile import ZipFile
from typing import Dict, Callable, Any, List
from hypotheses_generation import generate_hypotheses_from_system_charts
class HypothesesService:
"""
Service class for Scientific Hypotheses Generation.
Handles the orchestration of parsing system charts, generating hypotheses,
and packaging results using the new synergistic approach.
"""
def __init__(self):
pass
async def generate_hypotheses(
self,
system_charts_content_list: List[str],
llm,
material_goal_1: str = "Property #1 for xxx material",
material_goal_2: str = "Property #2 for xxx material",
iterations_count: int = 1,
progress_callback: Callable[[Dict[str, Any]], None] = None
) -> Dict[str, Any]:
"""
Main hypotheses generation pipeline using synergistic approach.
Args:
system_charts_content_list: List of content strings from multiple system chart files
llm: Language model for hypothesis generation
material_goal_1: First material design goal to guide hypothesis generation
material_goal_2: Second material design goal to guide hypothesis generation
iterations_count: Number of hypothesis iterations to generate
progress_callback: Function to report progress updates
Returns:
Dict containing generation results and metadata
"""
try:
# Initial progress update
if progress_callback:
progress_callback({
"status": "starting",
"progress": 0,
"message": "Preparing for synergistic hypothesis generation..."
})
# Handle both single content and list of contents for backward compatibility
if isinstance(system_charts_content_list, str):
system_charts_content_list = [system_charts_content_list]
# Create chart data structure for multiple files
chart_data = []
for i, content in enumerate(system_charts_content_list, 1):
if content and content.strip():
chart_data.append({
'content': content,
'citation': f'System chart file {i}',
'title': f'System Chart File {i}',
'raw_content': content # Pass the raw content
})
if not chart_data:
return {
"success": False,
"message": "No valid system chart content provided for hypothesis generation.",
"zip_data": None,
"filename": None
}
print(f"📊 Prepared {len(chart_data)} system chart files for synergistic hypothesis generation")
# Update progress after parsing
if progress_callback:
progress_callback({
"status": "parsing_complete",
"progress": 10,
"total_charts": len(chart_data),
"message": f"System charts prepared from {len(chart_data)} files. Starting synergistic hypothesis generation..."
})
# Create progress callback function for the generation process
def generation_progress_callback(progress_data):
if progress_callback:
# Adjust progress to fit within our overall range (10% to 95%)
adjusted_progress = 10 + int((progress_data.get("progress", 0) / 100) * 85)
progress_data["progress"] = adjusted_progress
progress_callback(progress_data)
# Generate hypotheses using the new synergistic approach
print(f"🧠 Starting synergistic hypothesis generation with goals: '{material_goal_1}' and '{material_goal_2}' ({iterations_count} iteration{'s' if iterations_count > 1 else ''})...")
hypotheses_content, hypotheses = await generate_hypotheses_from_system_charts(
chart_data,
llm,
material_goal_1,
material_goal_2,
iterations_count,
generation_progress_callback
)
# Note: Cross-chart analysis is now integrated into the main generation process
cross_analysis_content = "" # No longer separate
# Create ZIP file in memory
if progress_callback:
progress_callback({
"status": "packaging",
"progress": 95,
"message": "Creating results package..."
})
zip_data = self._create_zip_file(
hypotheses_content,
cross_analysis_content
)
# Prepare result
return {
"success": True,
"message": f"Generated {iterations_count} iteration{'s' if iterations_count > 1 else ''} of synergistic hypotheses from {len(chart_data)} system chart files using cross-paper analysis approach!",
"zip_data": zip_data,
"filename": "hypotheses.zip",
"hypotheses": hypotheses,
"charts_processed": len(chart_data),
"cross_analysis_included": True, # Now always included in main process
"method": "synergistic_analysis",
"iterations_count": iterations_count
}
except Exception as e:
return {
"success": False,
"message": f"Error during synergistic hypothesis generation: {str(e)}",
"zip_data": None,
"filename": None
}
def _create_zip_file(
self,
hypotheses_content: str,
cross_analysis_content: str = ""
) -> bytes:
"""Create ZIP file containing hypothesis generation results."""
zip_buffer = BytesIO()
with ZipFile(zip_buffer, "w") as zipf:
# Add main hypotheses file
zipf.writestr("hypotheses.txt", hypotheses_content)
print(f"📦 Created synergistic hypotheses ZIP package with {len(zipf.namelist())} files")
# Get ZIP data and close buffer
zip_data = zip_buffer.getvalue()
zip_buffer.close()
return zip_data
@staticmethod
def validate_multiple_system_charts_content(content_list: List[str]) -> Dict[str, Any]:
"""
Validate that the provided content list is suitable for synergistic hypothesis generation.
Args:
content_list: List of content strings to validate
Returns:
Dict with validation results
"""
try:
if not content_list:
return {
"valid": False,
"message": "No content provided for hypothesis generation.",
"chart_count": 0
}
valid_files = 0
total_data_rows = 0
file_details = []
for i, content in enumerate(content_list, 1):
if not content or not content.strip():
file_details.append(f"File {i}: Empty content")
continue
# Check for new format with 100 '=' delimiters
lines = content.strip().split('\n')
has_new_format = any(line.strip() == '=' * 100 for line in lines)
has_paper_keywords = any(line.strip().startswith('PAPER:') for line in lines)
has_chart_keywords = any(line.strip().startswith('SYSTEM_CHART:') for line in lines)
if has_new_format and has_paper_keywords and has_chart_keywords:
# Count entries in new format
paper_count = len([line for line in lines if line.strip().startswith('PAPER:')])
chart_count = len([line for line in lines if line.strip().startswith('SYSTEM_CHART:')])
if paper_count > 0 and chart_count > 0 and paper_count == chart_count:
valid_files += 1
total_data_rows += paper_count
file_details.append(f"File {i}: Valid format with {paper_count} entries")
else:
file_details.append(f"File {i}: Format issues - {paper_count} papers, {chart_count} charts")
else:
# Fallback to table-based validation
table_lines = [line for line in lines if '|' in line and not line.strip().startswith('|---')]
if table_lines:
data_rows = len([line for line in table_lines if line.strip() and (
('|' in line) and not any(
header_word in line.lower() for header_word in ['processing', 'structure', 'property', 'mechanism']
)
)])
if data_rows >= 2:
valid_files += 1
total_data_rows += data_rows
file_details.append(f"File {i}: Valid table format with {data_rows} rows")
else:
file_details.append(f"File {i}: Insufficient table data ({data_rows} rows)")
else:
file_details.append(f"File {i}: No valid format found")
if valid_files == 0:
return {
"valid": False,
"message": f"No valid system chart files found. Details: {'; '.join(file_details)}",
"chart_count": 0,
"valid_charts": 0,
"file_details": file_details
}
if total_data_rows < 2:
return {
"valid": False,
"message": f"Insufficient data across all files. Need multiple entries for synergistic analysis. Found {total_data_rows} total entries.",
"chart_count": len(content_list),
"valid_charts": valid_files,
"file_details": file_details
}
return {
"valid": True,
"message": f"Validated {valid_files}/{len(content_list)} system chart files for synergistic hypothesis generation. Found {total_data_rows} total entries.",
"chart_count": len(content_list),
"valid_charts": valid_files,
"data_entries": total_data_rows,
"file_details": file_details
}
except Exception as e:
return {
"valid": False,
"message": f"Error validating content: {str(e)}",
"chart_count": 0,
"file_details": []
}
@staticmethod
def validate_system_charts_content(content) -> Dict[str, Any]:
"""
Validate that the provided content is suitable for synergistic hypothesis generation.
Handles both single content string and list of content strings.
Args:
content: Content to validate (str or List[str])
Returns:
Dict with validation results
"""
# Handle both single content and list of contents
if isinstance(content, str):
return HypothesesService.validate_multiple_system_charts_content([content])
elif isinstance(content, list):
return HypothesesService.validate_multiple_system_charts_content(content)
else:
return {
"valid": False,
"message": "Invalid content type. Expected string or list of strings.",
"chart_count": 0
}