Spaces:
Sleeping
Sleeping
| """ | |
| Smart Chat Interface for Data Adjustment | |
| Uses liveboard context to intelligently understand requests | |
| and bundle confirmations into smart prompts. | |
| """ | |
| from dotenv import load_dotenv | |
| import os | |
| from smart_data_adjuster import SmartDataAdjuster | |
| load_dotenv() | |
| def chat_loop(): | |
| """Main smart chat loop""" | |
| print(""" | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β β | |
| β Smart Data Adjustment Chat β | |
| β β | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| This chat understands your liveboard and visualizations! | |
| Commands: | |
| - Type your adjustment request naturally | |
| - Reference visualizations by number (e.g., "viz 2") | |
| - "done" or "exit" to quit | |
| - "help" for examples | |
| Examples: | |
| - "make 1080p Webcam 40 billion" | |
| - "increase tablet revenue to 100B" | |
| - "in viz 2, set laptops to 50B" | |
| - "set profit margin to 30% for electronics" | |
| """) | |
| # Setup - can be overridden by environment or passed as arguments | |
| database = os.getenv('SNOWFLAKE_DATABASE') | |
| schema = os.getenv('DEMO_SCHEMA', "20251116_140933_AMAZO_SAL") | |
| liveboard_guid = os.getenv('DEMO_LIVEBOARD_GUID', "9a30c9e4-efba-424a-8359-b16eb3a43ec3") | |
| print(f"π Initializing...") | |
| adjuster = SmartDataAdjuster(database, schema, liveboard_guid) | |
| adjuster.connect() | |
| # Load liveboard context | |
| if not adjuster.load_liveboard_context(): | |
| print("β Failed to load liveboard context") | |
| return | |
| print("\n" + "="*80) | |
| print("Ready! I understand your liveboard context.") | |
| print("="*80 + "\n") | |
| # Show numbered visualizations | |
| print("π Available Visualizations:") | |
| print("-" * 80) | |
| for idx, viz in enumerate(adjuster.visualizations, start=1): | |
| print(f" [{idx}] {viz['name']}") | |
| cols = ', '.join(viz['columns'][:5]) # Show first 5 columns | |
| if len(viz['columns']) > 5: | |
| cols += f"... (+{len(viz['columns'])-5} more)" | |
| print(f" Columns: {cols}") | |
| print("-" * 80) | |
| print("π‘ TIP: You can reference visualizations by number (e.g., 'viz 2') or naturally!") | |
| print("="*80 + "\n") | |
| while True: | |
| # Get user input | |
| user_input = input("\n㪠You: ").strip() | |
| if not user_input: | |
| continue | |
| # Check for exit | |
| if user_input.lower() in ['done', 'exit', 'quit', 'bye']: | |
| print("\nπ Goodbye!") | |
| break | |
| # Check for help | |
| if user_input.lower() == 'help': | |
| print(""" | |
| π Help - Smart Data Adjustment | |
| I understand your liveboard context, so you can be natural: | |
| Examples: | |
| β "make 1080p webcam 40 billion" | |
| β I'll find the viz with products and TOTAL_REVENUE | |
| β "increase tablet to 100B" | |
| β I'll match to the right product visualization | |
| β "in viz 2, set laptops to 50B" | |
| β Use viz numbers to be specific! | |
| β "set profit margin to 30% for electronics" | |
| β I'll find the viz with profit margin and categories | |
| I'll show you what I understood and ask for one yes/no confirmation! | |
| """) | |
| continue | |
| try: | |
| # Check if user specified a viz number | |
| viz_number = None | |
| import re | |
| viz_match = re.search(r'\bviz\s+(\d+)\b', user_input, re.IGNORECASE) | |
| if viz_match: | |
| viz_number = int(viz_match.group(1)) | |
| if viz_number < 1 or viz_number > len(adjuster.visualizations): | |
| print(f"β Invalid viz number. Please use 1-{len(adjuster.visualizations)}") | |
| continue | |
| # Remove viz reference from request | |
| user_input = re.sub(r'\bviz\s+\d+\b', '', user_input, flags=re.IGNORECASE).strip() | |
| user_input = re.sub(r'^,?\s*', '', user_input) # Clean up leading comma/space | |
| # Match request to visualization | |
| print(f"\nπ€ Analyzing request...") | |
| match = adjuster.match_request_to_viz(user_input) | |
| if not match: | |
| print("β I couldn't understand that request.") | |
| print("π‘ Try being more specific or type 'help' for examples") | |
| continue | |
| # If user specified viz number, override the match | |
| if viz_number: | |
| match['viz'] = adjuster.visualizations[viz_number - 1] | |
| print(f" β Using specified viz: [{match['viz']['name']}]") | |
| else: | |
| print(f" β Matched to: [{match['viz']['name']}]") | |
| print(f" β Entity: {match['entity_value']}") | |
| print(f" β Confidence: {match['confidence'].upper()}") | |
| # If low confidence, ask for confirmation | |
| if match['confidence'] == 'low': | |
| print(f"\nβ οΈ I'm not very confident about this match.") | |
| confirm_match = input(" Is this correct? [yes/no]: ").strip().lower() | |
| if confirm_match not in ['yes', 'y']: | |
| print("π‘ Try rephrasing your request") | |
| continue | |
| # Get current value | |
| print(f"\nπ Querying current data...") | |
| current_value = adjuster.get_current_value( | |
| match['entity_value'], | |
| match['metric_column'] | |
| ) | |
| if current_value == 0: | |
| print(f"β οΈ No data found for '{match['entity_value']}'") | |
| print("π‘ Check the spelling or try a different entity") | |
| continue | |
| # Calculate target value if percentage | |
| target_value = match.get('target_value') | |
| if match.get('is_percentage'): | |
| percentage = match.get('percentage', 0) | |
| target_value = current_value * (1 + percentage / 100) | |
| print(f" π‘ {percentage:+.1f}% change = ${current_value:,.0f} β ${target_value:,.0f}") | |
| # Generate strategy | |
| strategy = adjuster.generate_strategy( | |
| match['entity_value'], | |
| match['metric_column'], | |
| current_value, | |
| target_value | |
| ) | |
| # Present smart bundled confirmation | |
| # Update match with calculated target if percentage | |
| if match.get('is_percentage'): | |
| match['target_value'] = target_value | |
| confirmation = adjuster.present_smart_confirmation(match, current_value, strategy) | |
| print(confirmation) | |
| # Get user decision | |
| response = input("Run SQL? [yes/no]: ").strip().lower() | |
| if response not in ['yes', 'y']: | |
| print("\nβ Cancelled - no changes made") | |
| print("π‘ You can try a different adjustment or rephrase") | |
| continue | |
| # Execute | |
| print(f"\nβοΈ Executing SQL...") | |
| result = adjuster.execute_sql(strategy['sql']) | |
| if result['success']: | |
| print(f"\nβ SUCCESS! Updated {result['rows_affected']} rows") | |
| print(f"π Refresh your ThoughtSpot liveboard to see changes") | |
| print(f" URL: https://se-thoughtspot-cloud.thoughtspot.cloud/#/pinboard/{liveboard_guid}") | |
| else: | |
| print(f"\nβ FAILED: {result['error']}") | |
| # Check for common errors | |
| if 'out of representable range' in result['error'].lower(): | |
| print("\nπ‘ The number is too large for the database column.") | |
| print(" Try a smaller target value (e.g., 40B instead of 50B)") | |
| except KeyboardInterrupt: | |
| print("\n\nβ οΈ Interrupted") | |
| break | |
| except Exception as e: | |
| print(f"\nβ Error: {e}") | |
| import traceback | |
| print(traceback.format_exc()) | |
| # Cleanup | |
| adjuster.close() | |
| print("\nβ Connection closed") | |
| if __name__ == "__main__": | |
| try: | |
| chat_loop() | |
| except KeyboardInterrupt: | |
| print("\n\nπ Goodbye!") | |