Spaces:
Sleeping
Sleeping
| import sys | |
| import os | |
| sys.path.append(os.path.join(os.path.dirname(__file__), '..')) | |
| from pocketflow import Node, BatchNode | |
| from utils.questionnaire import load_questionnaire, save_questionnaire | |
| from utils.mbti_scoring import traditional_mbti_score, determine_mbti_type | |
| from utils.report_generator import generate_report | |
| # Conditional LLM import | |
| try: | |
| from utils.call_llm import call_llm | |
| LLM_AVAILABLE = True | |
| except ImportError: | |
| LLM_AVAILABLE = False | |
| def call_llm(prompt): | |
| return "LLM not available - install dependencies" | |
| from datetime import datetime | |
| class LoadQuestionnaireNode(Node): | |
| def prep(self, shared): | |
| return shared.get("config", {}).get("import_file") | |
| def exec(self, import_file): | |
| return load_questionnaire(import_file) | |
| def post(self, shared, prep_res, exec_res): | |
| shared["questionnaire"]["questions"] = exec_res | |
| shared["questionnaire"]["metadata"]["created_at"] = datetime.now().isoformat() | |
| return "default" | |
| class PresentQuestionsNode(Node): | |
| def prep(self, shared): | |
| return shared["questionnaire"]["questions"], shared["config"]["ui_mode"] | |
| def exec(self, inputs): | |
| questions, ui_mode = inputs | |
| responses = {} | |
| if ui_mode == "cli": | |
| print("\n=== MBTI Personality Questionnaire ===") | |
| print("Rate each statement from 1-5:") | |
| print("1=Strongly Disagree, 2=Disagree, 3=Neutral, 4=Agree, 5=Strongly Agree\n") | |
| for q in questions: | |
| while True: | |
| try: | |
| print(f"Q{q['id']}: {q['text']}") | |
| response = int(input("Your rating (1-5): ")) | |
| if 1 <= response <= 5: | |
| responses[q['id']] = response | |
| break | |
| else: | |
| print("Please enter a number between 1 and 5.") | |
| except ValueError: | |
| print("Please enter a valid number.") | |
| print() | |
| else: | |
| # For Gradio mode, we'll handle this in the main interface | |
| # For now, return empty responses to be filled by UI | |
| pass | |
| return responses | |
| def post(self, shared, prep_res, exec_res): | |
| shared["questionnaire"]["responses"] = exec_res | |
| return "default" | |
| class AnalyzeResponsesBatchNode(BatchNode): | |
| def prep(self, shared): | |
| return list(shared["questionnaire"]["responses"].items()) | |
| def exec(self, response_item): | |
| question_id, response = response_item | |
| # Validate and normalize response | |
| if isinstance(response, str): | |
| response_map = { | |
| 'strongly_disagree': 1, 'disagree': 2, 'neutral': 3, | |
| 'agree': 4, 'strongly_agree': 5 | |
| } | |
| normalized = response_map.get(response, 3) | |
| else: | |
| normalized = max(1, min(5, int(response))) | |
| return (question_id, normalized) | |
| def post(self, shared, prep_res, exec_res_list): | |
| # Update responses with normalized values | |
| normalized_responses = dict(exec_res_list) | |
| shared["questionnaire"]["responses"] = normalized_responses | |
| return "default" | |
| class TraditionalScoringNode(Node): | |
| def prep(self, shared): | |
| return shared["questionnaire"]["responses"] | |
| def exec(self, responses): | |
| return traditional_mbti_score(responses) | |
| def post(self, shared, prep_res, exec_res): | |
| shared["analysis"]["traditional_scores"] = exec_res | |
| return "default" | |
| class LLMAnalysisNode(Node): | |
| def __init__(self, max_retries=3): | |
| super().__init__(max_retries=max_retries) | |
| if not LLM_AVAILABLE: | |
| print("Warning: LLM not available, using fallback analysis") | |
| def prep(self, shared): | |
| responses = shared["questionnaire"]["responses"] | |
| questions = shared["questionnaire"]["questions"] | |
| mbti_type = shared["results"]["mbti_type"] | |
| traditional_scores = shared["analysis"]["traditional_scores"] | |
| # Ensure questions are loaded | |
| if not questions: | |
| questions = load_questionnaire() | |
| shared["questionnaire"]["questions"] = questions | |
| # Format responses for LLM with dimension info | |
| formatted_responses = [] | |
| for q in questions: | |
| response_val = responses.get(q['id'], 3) | |
| response_text = {1: "Strongly Disagree", 2: "Disagree", 3: "Neutral", | |
| 4: "Agree", 5: "Strongly Agree"}[response_val] | |
| dimension = q.get('dimension', 'Unknown') | |
| formatted_responses.append(f"Q{q['id']} ({dimension}): {q['text']} - **{response_text}**") | |
| print(f"DEBUG: Formatted {len(formatted_responses)} questions for LLM") | |
| return "\n".join(formatted_responses), mbti_type, traditional_scores | |
| def exec(self, inputs): | |
| formatted_responses, mbti_type, traditional_scores = inputs | |
| # Format dimension scores for context | |
| dimension_info = [] | |
| pairs = [('E', 'I'), ('S', 'N'), ('T', 'F'), ('J', 'P')] | |
| for dim1, dim2 in pairs: | |
| score1 = traditional_scores.get(f'{dim1}_score', 0.5) | |
| score2 = traditional_scores.get(f'{dim2}_score', 0.5) | |
| stronger = dim1 if score1 > score2 else dim2 | |
| percentage = max(score1, score2) * 100 | |
| dimension_info.append(f"{dim1}/{dim2}: {stronger} ({percentage:.1f}%)") | |
| prompt = f""" | |
| You are analyzing MBTI questionnaire responses for someone determined to be {mbti_type} type. | |
| Here are their EXACT responses to each question: | |
| {formatted_responses} | |
| Traditional scoring results: | |
| {chr(10).join(dimension_info)} | |
| IMPORTANT: You have been provided with the complete set of questions and responses above. Please analyze these SPECIFIC responses. | |
| Provide a detailed analysis that: | |
| 1. **Response Pattern Analysis**: Identify which responses strongly support the {mbti_type} determination and which might seem unexpected. Reference specific questions (e.g., "Q5 shows...", "Your response to Q12 indicates..."). | |
| 2. **Characteristic Alignment**: Explain how their responses align with typical {mbti_type} characteristics, citing specific questions as evidence. | |
| 3. **Out-of-Character Responses**: Point out any responses that seem inconsistent with typical {mbti_type} patterns and provide possible explanations. | |
| 4. **Behavioral Patterns**: Describe key behavioral patterns shown through their responses, referencing the relevant questions. | |
| 5. **Strengths & Growth Areas**: Based on their specific responses, identify strengths they demonstrate and areas for potential growth. | |
| 6. **Communication & Work Style**: Infer their communication and work preferences from their question responses. | |
| Must reference the actual questions provided above throughout your analysis using markdown anchor links like [Q1](#Q1), [Q2](#Q2), etc. This will create clickable links to the specific questions in the report. Do not make assumptions about questions not provided. | |
| """ | |
| return call_llm(prompt) | |
| def post(self, shared, prep_res, exec_res): | |
| shared["analysis"]["llm_analysis"] = exec_res | |
| # Store responses data for report | |
| responses = shared["questionnaire"]["responses"] | |
| questions = shared["questionnaire"]["questions"] | |
| responses_data = [] | |
| for q in questions: | |
| response_val = responses.get(q['id'], 3) | |
| response_text = {1: "Strongly Disagree", 2: "Disagree", 3: "Neutral", | |
| 4: "Agree", 5: "Strongly Agree"}[response_val] | |
| responses_data.append({ | |
| 'id': q['id'], | |
| 'text': q['text'], | |
| 'dimension': q.get('dimension', 'Unknown'), | |
| 'response': response_text, | |
| 'value': response_val | |
| }) | |
| shared["analysis"]["responses_data"] = responses_data | |
| return "default" | |
| class DetermineMBTITypeNode(Node): | |
| def prep(self, shared): | |
| return shared["analysis"]["traditional_scores"], shared["analysis"].get("llm_analysis", "") | |
| def exec(self, inputs): | |
| traditional_scores, llm_analysis = inputs | |
| # Primary determination from traditional scoring | |
| mbti_type = determine_mbti_type(traditional_scores) | |
| # Calculate confidence scores | |
| confidence_scores = {} | |
| pairs = [('E', 'I'), ('S', 'N'), ('T', 'F'), ('J', 'P')] | |
| for dim1, dim2 in pairs: | |
| score1 = traditional_scores.get(f'{dim1}_score', 0.5) | |
| score2 = traditional_scores.get(f'{dim2}_score', 0.5) | |
| confidence = abs(score1 - score2) # Higher difference = higher confidence | |
| confidence_scores[f'{dim1}{dim2}_confidence'] = confidence | |
| return { | |
| "mbti_type": mbti_type, | |
| "confidence_scores": confidence_scores | |
| } | |
| def post(self, shared, prep_res, exec_res): | |
| shared["results"]["mbti_type"] = exec_res["mbti_type"] | |
| shared["analysis"]["confidence_scores"] = exec_res["confidence_scores"] | |
| return "default" | |
| class GenerateReportNode(Node): | |
| def prep(self, shared): | |
| return ( | |
| shared["results"]["mbti_type"], | |
| shared["analysis"], | |
| shared["config"]["output_format"] | |
| ) | |
| def exec(self, inputs): | |
| mbti_type, analysis, output_format = inputs | |
| return generate_report(mbti_type, analysis, output_format) | |
| def post(self, shared, prep_res, exec_res): | |
| shared["exports"]["report_path"] = exec_res | |
| return "default" | |
| class ExportDataNode(Node): | |
| def prep(self, shared): | |
| return shared["questionnaire"], shared["results"] | |
| def exec(self, inputs): | |
| questionnaire, results = inputs | |
| # Create export data | |
| export_data = { | |
| "questionnaire": questionnaire, | |
| "results": results, | |
| "metadata": { | |
| "exported_at": datetime.now().isoformat(), | |
| "version": "1.0" | |
| } | |
| } | |
| # Generate filename | |
| timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') | |
| mbti_type = results.get("mbti_type", "UNKNOWN") | |
| filename = f"mbti_questionnaire_{mbti_type}_{timestamp}.json" | |
| return export_data, filename | |
| def post(self, shared, prep_res, exec_res): | |
| export_data, filename = exec_res | |
| success = save_questionnaire(export_data, filename) | |
| if success: | |
| shared["exports"]["questionnaire_json"] = filename | |
| return "default" |