Created
November 3, 2025 19:16
-
-
Save guinslym/ab9b88f210d85bbf4580d9139c069c80 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import lh3.api | |
| from datetime import datetime | |
| import sys | |
| #need to install openpyxl | |
| try: | |
| from openpyxl import Workbook | |
| from openpyxl.styles import Font, PatternFill, Alignment, Border, Side | |
| from openpyxl.utils import get_column_letter | |
| except ImportError: | |
| print("Error: openpyxl is required. Installing it now...") | |
| import subprocess | |
| subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl", "--break-system-packages"]) | |
| from openpyxl import Workbook | |
| from openpyxl.styles import Font, PatternFill, Alignment, Border, Side | |
| from openpyxl.utils import get_column_letter | |
| def format_header(ws, row, columns): | |
| """Apply formatting to header row.""" | |
| header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") | |
| header_font = Font(bold=True, color="FFFFFF", size=11) | |
| border = Border( | |
| left=Side(style='thin'), | |
| right=Side(style='thin'), | |
| top=Side(style='thin'), | |
| bottom=Side(style='thin') | |
| ) | |
| for col_num, column_title in enumerate(columns, 1): | |
| cell = ws.cell(row=row, column=col_num) | |
| cell.value = column_title | |
| cell.fill = header_fill | |
| cell.font = header_font | |
| cell.alignment = Alignment(horizontal='center', vertical='center') | |
| cell.border = border | |
| def auto_adjust_columns(ws): | |
| """Auto-adjust column widths based on content.""" | |
| for column in ws.columns: | |
| max_length = 0 | |
| column_letter = get_column_letter(column[0].column) | |
| for cell in column: | |
| try: | |
| if cell.value: | |
| max_length = max(max_length, len(str(cell.value))) | |
| except: | |
| pass | |
| adjusted_width = min(max_length + 2, 100) # Max width of 100 | |
| ws.column_dimensions[column_letter].width = adjusted_width | |
| def apply_data_formatting(ws, start_row): | |
| """Apply formatting to data rows.""" | |
| border = Border( | |
| left=Side(style='thin'), | |
| right=Side(style='thin'), | |
| top=Side(style='thin'), | |
| bottom=Side(style='thin') | |
| ) | |
| for row in ws.iter_rows(min_row=start_row, max_row=ws.max_row): | |
| for cell in row: | |
| cell.border = border | |
| cell.alignment = Alignment(vertical='top', wrap_text=True) | |
| def create_faq_summary_sheet(wb, faqs_data): | |
| """Create summary sheet with FAQ overview.""" | |
| ws = wb.active | |
| ws.title = "FAQ Summary" | |
| # Header | |
| ws['A1'] = "LibraryH3lp FAQ Report" | |
| ws['A1'].font = Font(bold=True, size=14) | |
| ws['A2'] = f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" | |
| ws['A2'].font = Font(italic=True, size=10) | |
| # Column headers | |
| headers = ['FAQ ID', 'FAQ Name', 'Public', 'Queue', 'Theme', | |
| 'Total Questions', 'Total Views', 'Total Likes', | |
| 'Total Dislikes', 'Total Searches'] | |
| format_header(ws, 4, headers) | |
| # Data rows | |
| row_num = 5 | |
| for faq in faqs_data: | |
| stats = faq.get('stats', {}) | |
| ws.cell(row=row_num, column=1, value=faq.get('id')) | |
| ws.cell(row=row_num, column=2, value=faq.get('name')) | |
| ws.cell(row=row_num, column=3, value='Yes' if faq.get('public') else 'No') | |
| ws.cell(row=row_num, column=4, value=faq.get('queue') or 'N/A') | |
| ws.cell(row=row_num, column=5, value=faq.get('theme') or 'N/A') | |
| ws.cell(row=row_num, column=6, value=stats.get('num_questions', 0)) | |
| ws.cell(row=row_num, column=7, value=stats.get('num_views', 0)) | |
| ws.cell(row=row_num, column=8, value=stats.get('num_likes', 0)) | |
| ws.cell(row=row_num, column=9, value=stats.get('num_dislikes', 0)) | |
| ws.cell(row=row_num, column=10, value=stats.get('num_searches', 0)) | |
| row_num += 1 | |
| # Apply formatting | |
| apply_data_formatting(ws, 5) | |
| auto_adjust_columns(ws) | |
| # Add totals row | |
| total_row = row_num + 1 | |
| ws.cell(row=total_row, column=1, value="TOTALS").font = Font(bold=True) | |
| ws.cell(row=total_row, column=6, value=f"=SUM(F5:F{row_num-1})").font = Font(bold=True) | |
| ws.cell(row=total_row, column=7, value=f"=SUM(G5:G{row_num-1})").font = Font(bold=True) | |
| ws.cell(row=total_row, column=8, value=f"=SUM(H5:H{row_num-1})").font = Font(bold=True) | |
| ws.cell(row=total_row, column=9, value=f"=SUM(I5:I{row_num-1})").font = Font(bold=True) | |
| ws.cell(row=total_row, column=10, value=f"=SUM(J5:J{row_num-1})").font = Font(bold=True) | |
| def create_all_questions_sheet(wb, faqs_data): | |
| """Create sheet with all questions from all FAQs.""" | |
| ws = wb.create_sheet(title="All Questions") | |
| # Column headers | |
| headers = ['FAQ ID', 'FAQ Name', 'Question ID', 'Question Text', | |
| 'Published', 'Views', 'Likes', 'Dislikes', | |
| 'Like Ratio (%)', 'Last Updated', 'Topics'] | |
| format_header(ws, 1, headers) | |
| # Data rows | |
| row_num = 2 | |
| for faq in faqs_data: | |
| faq_id = faq.get('id') | |
| faq_name = faq.get('name') | |
| questions = faq.get('questions', []) | |
| if not questions: | |
| # Add FAQ row even if no questions | |
| ws.cell(row=row_num, column=1, value=faq_id) | |
| ws.cell(row=row_num, column=2, value=faq_name) | |
| ws.cell(row=row_num, column=3, value='') | |
| ws.cell(row=row_num, column=4, value='(No questions)') | |
| row_num += 1 | |
| else: | |
| for question in questions: | |
| ws.cell(row=row_num, column=1, value=faq_id) | |
| ws.cell(row=row_num, column=2, value=faq_name) | |
| ws.cell(row=row_num, column=3, value=question.get('id')) | |
| ws.cell(row=row_num, column=4, value=question.get('question', '')) | |
| ws.cell(row=row_num, column=5, value='Yes' if question.get('published') else 'No') | |
| ws.cell(row=row_num, column=6, value=question.get('views', 0)) | |
| ws.cell(row=row_num, column=7, value=question.get('likes', 0)) | |
| ws.cell(row=row_num, column=8, value=question.get('dislikes', 0)) | |
| # Calculate like ratio | |
| likes = question.get('likes', 0) | |
| dislikes = question.get('dislikes', 0) | |
| if likes + dislikes > 0: | |
| like_ratio = (likes / (likes + dislikes)) * 100 | |
| ws.cell(row=row_num, column=9, value=round(like_ratio, 1)) | |
| else: | |
| ws.cell(row=row_num, column=9, value='N/A') | |
| ws.cell(row=row_num, column=10, value=question.get('updatedAt', '')) | |
| # Topics | |
| topics = question.get('topics', []) | |
| ws.cell(row=row_num, column=11, value=', '.join(topics) if topics else '') | |
| row_num += 1 | |
| # Apply formatting | |
| apply_data_formatting(ws, 2) | |
| auto_adjust_columns(ws) | |
| def create_individual_faq_sheets(wb, faqs_data): | |
| """Create individual sheets for each FAQ.""" | |
| for faq in faqs_data: | |
| faq_name = faq.get('name', 'Unknown') | |
| faq_id = faq.get('id') | |
| # Clean sheet name (Excel has restrictions) | |
| sheet_name = f"{faq_id}_{faq_name}"[:31] # Max 31 chars | |
| # Remove invalid characters | |
| for char in ['\\', '/', '*', '?', ':', '[', ']']: | |
| sheet_name = sheet_name.replace(char, '_') | |
| ws = wb.create_sheet(title=sheet_name) | |
| # FAQ Info | |
| ws['A1'] = "FAQ Information" | |
| ws['A1'].font = Font(bold=True, size=12) | |
| ws['A2'] = "FAQ ID:" | |
| ws['B2'] = faq_id | |
| ws['A3'] = "FAQ Name:" | |
| ws['B3'] = faq_name | |
| ws['A4'] = "Public:" | |
| ws['B4'] = 'Yes' if faq.get('public') else 'No' | |
| ws['A5'] = "Queue:" | |
| ws['B5'] = faq.get('queue') or 'N/A' | |
| ws['A6'] = "Theme:" | |
| ws['B6'] = faq.get('theme') or 'N/A' | |
| # Statistics | |
| stats = faq.get('stats', {}) | |
| ws['A8'] = "Statistics" | |
| ws['A8'].font = Font(bold=True, size=12) | |
| ws['A9'] = "Total Questions:" | |
| ws['B9'] = stats.get('num_questions', 0) | |
| ws['A10'] = "Total Views:" | |
| ws['B10'] = stats.get('num_views', 0) | |
| ws['A11'] = "Total Likes:" | |
| ws['B11'] = stats.get('num_likes', 0) | |
| ws['A12'] = "Total Dislikes:" | |
| ws['B12'] = stats.get('num_dislikes', 0) | |
| ws['A13'] = "Total Searches:" | |
| ws['B13'] = stats.get('num_searches', 0) | |
| # Questions | |
| questions = faq.get('questions', []) | |
| if questions: | |
| ws['A15'] = "Questions" | |
| ws['A15'].font = Font(bold=True, size=12) | |
| headers = ['ID', 'Question', 'Published', 'Views', 'Likes', 'Dislikes', 'Updated', 'Topics'] | |
| format_header(ws, 16, headers) | |
| row_num = 17 | |
| for question in questions: | |
| ws.cell(row=row_num, column=1, value=question.get('id')) | |
| ws.cell(row=row_num, column=2, value=question.get('question', '')) | |
| ws.cell(row=row_num, column=3, value='Yes' if question.get('published') else 'No') | |
| ws.cell(row=row_num, column=4, value=question.get('views', 0)) | |
| ws.cell(row=row_num, column=5, value=question.get('likes', 0)) | |
| ws.cell(row=row_num, column=6, value=question.get('dislikes', 0)) | |
| ws.cell(row=row_num, column=7, value=question.get('updatedAt', '')) | |
| topics = question.get('topics', []) | |
| ws.cell(row=row_num, column=8, value=', '.join(topics) if topics else '') | |
| row_num += 1 | |
| apply_data_formatting(ws, 17) | |
| else: | |
| ws['A15'] = "No questions for this FAQ" | |
| auto_adjust_columns(ws) | |
| def main(): | |
| """Main function to generate Excel report.""" | |
| print("Connecting to LibraryH3lp...") | |
| try: | |
| client = lh3.api.Client() | |
| except Exception as e: | |
| print(f"Error: Could not connect to LibraryH3lp: {e}") | |
| return | |
| print("Retrieving FAQ data...") | |
| try: | |
| all_faqs = client.all('faqs').get_list() | |
| if not all_faqs: | |
| print("No FAQs found.") | |
| return | |
| print(f"Found {len(all_faqs)} FAQs. Processing...") | |
| faqs_data = [] | |
| for idx, faq_summary in enumerate(all_faqs, 1): | |
| faq_id = faq_summary.get('id') | |
| faq_name = faq_summary.get('name', 'Unknown') | |
| print(f" Processing FAQ {idx}/{len(all_faqs)}: {faq_name} (ID: {faq_id})") | |
| try: | |
| faq = client.one('faqs', faq_id) | |
| faq_data = faq.get() | |
| if not faq_data or isinstance(faq_data, str): | |
| continue | |
| # Get questions | |
| questions = [] | |
| try: | |
| questions_list = faq.all('questions').get_list() | |
| if questions_list and isinstance(questions_list, list): | |
| questions = questions_list | |
| except Exception: | |
| pass | |
| faq_info = { | |
| 'id': faq_data.get('id'), | |
| 'name': faq_data.get('name'), | |
| 'public': faq_data.get('public'), | |
| 'queue': faq_data.get('queue'), | |
| 'theme': faq_data.get('theme'), | |
| 'stats': faq_data.get('stats_overview', {}), | |
| 'questions': questions | |
| } | |
| faqs_data.append(faq_info) | |
| except Exception as e: | |
| print(f" Warning: Error processing FAQ {faq_id}: {e}") | |
| continue | |
| print(f"\nCreating Excel workbook...") | |
| # Create workbook | |
| wb = Workbook() | |
| # Create sheets | |
| print(" Creating FAQ Summary sheet...") | |
| create_faq_summary_sheet(wb, faqs_data) | |
| print(" Creating All Questions sheet...") | |
| create_all_questions_sheet(wb, faqs_data) | |
| print(" Creating individual FAQ sheets...") | |
| create_individual_faq_sheets(wb, faqs_data) | |
| # Save file | |
| filename = f"libraryh3lp_faqs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" | |
| wb.save(filename) | |
| print(f"\n✓ Excel file created successfully: {filename}") | |
| print(f" Total FAQs: {len(faqs_data)}") | |
| total_questions = sum(len(faq['questions']) for faq in faqs_data) | |
| print(f" Total Questions: {total_questions}") | |
| print(f"\nThe file contains:") | |
| print(f" • FAQ Summary sheet - Overview of all FAQs") | |
| print(f" • All Questions sheet - All questions from all FAQs") | |
| print(f" • Individual sheets for each FAQ with details") | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment