From f6268c60bda07ac8a7fa905dcf5e69027eb7ce7e Mon Sep 17 00:00:00 2001 From: br34th5 Date: Sun, 5 May 2024 17:51:35 +0300 Subject: [PATCH 01/23] added few projects --- .../clickup_extract/clickup_notes_split.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/personal_notes/clickup_extract/clickup_notes_split.py diff --git a/src/personal_notes/clickup_extract/clickup_notes_split.py b/src/personal_notes/clickup_extract/clickup_notes_split.py new file mode 100644 index 0000000..3cc2281 --- /dev/null +++ b/src/personal_notes/clickup_extract/clickup_notes_split.py @@ -0,0 +1,39 @@ +import os +import pandas as pd + +#preparing clickup exported CSV file for cleaning unnecessary columns +# Read the CSV file into a DataFrame +csv_file = 'export_copy.csv' # Replace 'your_file.csv' with the path to your CSV file +df = pd.read_csv(csv_file) + +# Specify the columns you want to delete +columns_to_delete = ['Task ID', 'Attachments', 'Date Created', 'Date Created Text','Due Date', 'Due Date Text','Start Date', 'Start Date Text','Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', 'Time Spent', 'Time Spent Text', + 'Rolled Up Time', 'Rolled Up Time Text'] +# Replace with the names of columns you want to delete + +# Drop the specified columns from the DataFrame +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get all the unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() +# Print or use the unique values as needed +print(unique_values) + +# Write the modified DataFrame back to a CSV file +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) +for value in unique_values: + # Sanitize the string to replace problematic characters + sanitized_value = value.replace('/', '_') # Replace '/' with '_' + output_file = os.path.join(output_dir, f'{sanitized_value}.csv') + # Filter the DataFrame based on the current value + filtered_df = df[df['List Name'] == value] + # Drop additional columns from the filtered DataFrame + additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop + filtered_df = filtered_df.drop(columns=additional_columns_to_drop) + # Write the filtered DataFrame to a CSV file + filtered_df.to_csv(output_file, index=False) From c1ce73940e1301d753a30618084e309e8d308adc Mon Sep 17 00:00:00 2001 From: br34th5 Date: Sun, 5 May 2024 18:06:43 +0300 Subject: [PATCH 02/23] are they added now? --- src/personal_finance/.txt | 0 src/personal_finance/run_script.sh | 9 + src/personal_finance/scheme.txt | 15 + src/personal_finance/v1.7.py | 143 +++++++ src/personal_finance/v1.8.py | 152 +++++++ src/personal_finance/v1.9.py | 230 +++++++++++ src/personal_finance/v2.1.py | 374 ++++++++++++++++++ src/personal_finance/v2.py | 309 +++++++++++++++ src/personal_notes/discord_extract/.py | 0 .../discord_extract/disc_extract_s.py | 0 10 files changed, 1232 insertions(+) create mode 100644 src/personal_finance/.txt create mode 100644 src/personal_finance/run_script.sh create mode 100644 src/personal_finance/scheme.txt create mode 100644 src/personal_finance/v1.7.py create mode 100644 src/personal_finance/v1.8.py create mode 100644 src/personal_finance/v1.9.py create mode 100644 src/personal_finance/v2.1.py create mode 100644 src/personal_finance/v2.py create mode 100644 src/personal_notes/discord_extract/.py create mode 100644 src/personal_notes/discord_extract/disc_extract_s.py diff --git a/src/personal_finance/.txt b/src/personal_finance/.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/personal_finance/run_script.sh b/src/personal_finance/run_script.sh new file mode 100644 index 0000000..33cd4fc --- /dev/null +++ b/src/personal_finance/run_script.sh @@ -0,0 +1,9 @@ +#!/bin/zsh + +# Navigate to the directory where your Python script is located +cd /home/eikov/fin + +# Run your Python script +python scrape.py + +chmod +x run_script.sh diff --git a/src/personal_finance/scheme.txt b/src/personal_finance/scheme.txt new file mode 100644 index 0000000..0bc6f71 --- /dev/null +++ b/src/personal_finance/scheme.txt @@ -0,0 +1,15 @@ +step1: download seb monthly csv israsus +step2: rename them by month and add +(if parsing incomes) or -(if parsing spendings) +step3: enjoy clean data in xlsx , showing Date/Sum/Moketojas/Paskirtis + +Scheme +Step 0 DONE: Download monthly banko israsus +Step 1 DONE: Extract wanted columns: "DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS" .... and clean data to make it more neat, understandable. f +ilter-out own transactions + +TBD: +Step 2: add new column "Category" +Step 3: Excel calculations, analysis, pie chart and projections +Step 4 DONE: Extract incomes and clean data: filter-out own transactions +Extra: Calculate avg of 3 months fixed costs spending +Extra: Compare this realistic report with realistic numbers to my manual "personal finance" balance sheet diff --git a/src/personal_finance/v1.7.py b/src/personal_finance/v1.7.py new file mode 100644 index 0000000..42f7430 --- /dev/null +++ b/src/personal_finance/v1.7.py @@ -0,0 +1,143 @@ +import pandas as pd +import os +from io import StringIO +import json + +# Define the directory containing the CSV files +directory = 'OGCSV/' + +# Define the pattern for file names +file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type + +# Initialize empty dictionaries to store dataframes for each month +spending_dfs = {} +income_dfs = {} + +# Define the filters +filter1 = "KOVALIŪNAS EINARAS" +filter2 = "Einaras Kovaliūnas" # Add your second filter value here + +# Function to extract first 4 words from a string +def extract_first_4_words(text): + if pd.isna(text): # Check if the value is NaN + return "" # Return an empty string if NaN + words = text.split()[:4] + return ' '.join(words) + + +# Iterate over the file numbers from 1 to 12 +for file_num in range(1, 13): + month = str(file_num) + spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + for file_type in ['+', '-']: + filename = file_pattern.format(file_num, file_type) + filepath = os.path.join(directory, filename) + if os.path.exists(filepath): + print(f"Processing file: {filename}") + # Initialize an empty list to store the valid lines of the CSV file + valid_lines = [] + skipped_rows = [] + + # Read the CSV file line by line + with open(filepath, 'r', encoding='utf-8') as file: + for i, line in enumerate(file): + if i < 2: # Skip the first two lines (first row and header row) + continue + try: + # Attempt to parse the line as CSV + pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';' + valid_lines.append(line) # If successful, add the line to the list of valid lines + except pd.errors.ParserError: + print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}") + skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row + + # Read the valid lines into a DataFrame, skipping the header line + bank_data = None + try: + bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';') + except pd.errors.ParserError: + print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.") + if not valid_lines: + print("No valid data read due to errors.") + continue + + # If data was successfully read, extract specific columns and apply filter + if bank_data is not None: + bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns + + # Replace empty values with NaN + bank_data.replace('-', pd.NA, inplace=True) + + # Rename columns + bank_data.columns = ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"] + + # Apply filters + bank_data = bank_data[(bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter1) & + (bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter2)] + + # Filter out rows that are the same as column headers or contain all empty values + bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]), axis=1)] + + # Extract first 4 words from specified columns + bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_4_words) + bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_4_words) + + # Append the DataFrame to the appropriate dictionary based on file type + if file_type == '+': + income_dfs[month] = bank_data + elif file_type == '-': + spending_dfs[month] = bank_data + else: + print(f"No valid data read from file {filename}.") + + # Display the DataFrame + if file_type == '+': + print(f"\nIncome DataFrame for month {month}:") + print(income_dfs[month]) + elif file_type == '-': + print(f"\nSpending DataFrame for month {month}:") + print(spending_dfs[month]) + # Save DataFrames to JSON files with UTF-8 encoding + income_json_filename = f"income_month_{month}.json" + spending_json_filename = f"spending_month_{month}.json" + + # Save DataFrames to Excel files + income_excel_filename = f"income_month_{month}.xlsx" + spending_excel_filename = f"spending_month_{month}.xlsx" + + # Drop rows with NaN values + income_dfs[month].dropna(inplace=True) + spending_dfs[month].dropna(inplace=True) + + # Convert DataFrames to JSON strings + income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str) + spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str) + + # Write JSON strings to files with UTF-8 encoding + with open(income_json_filename, 'w', encoding='utf-8') as income_file: + for line in income_json_str.split('\n'): + if line.strip(): # Check if line is not empty + income_file.write(line.strip() + '\n') + + with open(spending_json_filename, 'w', encoding='utf-8') as spending_file: + for line in spending_json_str.split('\n'): + if line.strip(): # Check if line is not empty + spending_file.write(line.strip() + '\n') + + # Save DataFrames to Excel files + income_dfs[month].to_excel(income_excel_filename, index=False) + spending_dfs[month].to_excel(spending_excel_filename, index=False) + + print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}") + print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}") + + # Display skipped rows + if skipped_rows: + print(f"\nSkipped rows in file {filename}:") + for row_num, row_content in skipped_rows: + print(f"Row {row_num}: {row_content}") + else: + print(f"File {filename} not found. Skipping...") + +print("All files processed.") diff --git a/src/personal_finance/v1.8.py b/src/personal_finance/v1.8.py new file mode 100644 index 0000000..9584e84 --- /dev/null +++ b/src/personal_finance/v1.8.py @@ -0,0 +1,152 @@ +import pandas as pd +import os +from io import StringIO +import json + +# Define the directory containing the CSV files +directory = '/home/eikov/fin/OG-CSV' + +# Define the pattern for file names +file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type + +# Initialize empty dictionaries to store dataframes for each month +spending_dfs = {} +income_dfs = {} + +# Define the filters +filter1 = "KOVALIŪNAS EINARAS" +filter2 = "Einaras Kovaliūnas" # Add your second filter value here + +# Function to extract first 4 words from a string +def extract_first_4_words(text): + if pd.isna(text): # Check if the value is NaN + return "" # Return an empty string if NaN + words = text.split()[:4] + return ' '.join(words) + + +# Iterate over the file numbers from 1 to 12 +for file_num in range(1, 13): + month = str(file_num) + spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + for file_type in ['+', '-']: + filename = file_pattern.format(file_num, file_type) + filepath = os.path.join(directory, filename) + if os.path.exists(filepath): + print(f"Processing file: {filename}") + # Initialize an empty list to store the valid lines of the CSV file + valid_lines = [] + skipped_rows = [] + + # Read the CSV file line by line + with open(filepath, 'r', encoding='utf-8') as file: + for i, line in enumerate(file): + if i < 2: # Skip the first two lines (first row and header row) + continue + try: + # Attempt to parse the line as CSV + pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';' + valid_lines.append(line) # If successful, add the line to the list of valid lines + except pd.errors.ParserError: + print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}") + skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row + + # Read the valid lines into a DataFrame, skipping the header line + bank_data = None + try: + bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';') + except pd.errors.ParserError: + print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.") + if not valid_lines: + print("No valid data read due to errors.") + continue + + # If data was successfully read, extract specific columns and apply filter + if bank_data is not None: + bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns + + # Replace empty values with NaN + bank_data.replace('-', pd.NA, inplace=True) + + # Rename columns + bank_data.columns = ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"] + + # Apply filters + bank_data = bank_data[(bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter1) & + (bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter2)] + + # Filter out rows that are the same as column headers or contain all empty values + bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]), axis=1)] + + # Extract first 4 words from specified columns + bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_4_words) + bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_4_words) + + # Append the DataFrame to the appropriate dictionary based on file type + if file_type == '+': + income_dfs[month] = bank_data + elif file_type == '-': + spending_dfs[month] = bank_data + else: + print(f"No valid data read from file {filename}.") + + # Display the DataFrame + if file_type == '+': + print(f"\nIncome DataFrame for month {month}:") + print(income_dfs[month]) + elif file_type == '-': + print(f"\nSpending DataFrame for month {month}:") + print(spending_dfs[month]) + + # Specify the folder path + output_folder_income = "/home/eikov/fin/income" + output_folder_spending = "/home/eikov/fin/spendings" + + # Create the folder if it doesn't exist + os.makedirs(output_folder_income, exist_ok=True) + os.makedirs(output_folder_spending, exist_ok=True) + + # Save DataFrames to JSON files with UTF-8 encoding + income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json") + spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json") + + # Save DataFrames to Excel files + income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx") + spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx") + + # Drop rows with NaN values + income_dfs[month].dropna(inplace=True) + spending_dfs[month].dropna(inplace=True) + + # Convert DataFrames to JSON strings + income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str) + spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str) + """ + # Write JSON strings to files with UTF-8 encoding + with open(income_json_filename, 'w+', encoding='utf-8') as income_file: + for line in income_json_str.split('\n'): + if line.strip(): # Check if line is not empty + income_file.write(line.strip() + '\n') + + with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file: + for line in spending_json_str.split('\n'): + if line.strip(): # Check if line is not empty + spending_file.write(line.strip() + '\n') + """ + # Save DataFrames to Excel files + income_dfs[month].to_excel(income_excel_filename, index=False) + spending_dfs[month].to_excel(spending_excel_filename, index=False) + + print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}") + print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}") + + # Display skipped rows + if skipped_rows: + print(f"\nSkipped rows in file {filename}:") + for row_num, row_content in skipped_rows: + print(f"Row {row_num}: {row_content}") + else: + print(f"File {filename} not found. Skipping...") + +print("All files processed.") diff --git a/src/personal_finance/v1.9.py b/src/personal_finance/v1.9.py new file mode 100644 index 0000000..6318e8b --- /dev/null +++ b/src/personal_finance/v1.9.py @@ -0,0 +1,230 @@ +import pandas as pd +import os +from io import StringIO +import json +import openpyxl + +# Define the directory containing the CSV files +directory = '/home/eikov/fin/OG-CSV' + +# Define the pattern for file names +file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type + +# Initialize empty dictionaries to store dataframes for each month +spending_dfs = {} +income_dfs = {} + +# Define the filters +filter1 = "KOVALIŪNAS EINARAS" +filter2 = "Einaras Kovaliūnas" # Add your second filter value here + +# Function to extract first 4 words from a string +def extract_first_4_words(text): + if pd.isna(text): # Check if the value is NaN + return "" # Return an empty string if NaN + words = text.split()[:4] + return ' '.join(words) + + +# Iterate over the file numbers from 1 to 12 +for file_num in range(1, 13): + month = str(file_num) + spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + for file_type in ['+', '-']: + filename = file_pattern.format(file_num, file_type) + filepath = os.path.join(directory, filename) + if os.path.exists(filepath): + print(f"Processing file: {filename}") + # Initialize an empty list to store the valid lines of the CSV file + valid_lines = [] + skipped_rows = [] + + # Read the CSV file line by line + with open(filepath, 'r', encoding='utf-8') as file: + for i, line in enumerate(file): + if i < 2: # Skip the first two lines (first row and header row) + continue + try: + # Attempt to parse the line as CSV + pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';' + valid_lines.append(line) # If successful, add the line to the list of valid lines + except pd.errors.ParserError: + print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}") + skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row + + # Read the valid lines into a DataFrame, skipping the header line + bank_data = None + try: + bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';') + except pd.errors.ParserError: + print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.") + if not valid_lines: + print("No valid data read due to errors.") + continue + + # If data was successfully read, extract specific columns and apply filter + if bank_data is not None: + bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns + + # Replace empty values with NaN + bank_data.replace('-', pd.NA, inplace=True) + + # Rename columns + bank_data.columns = ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"] + + # Apply filters + bank_data = bank_data[(bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter1) & + (bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter2)] + + # Filter out rows that are the same as column headers or contain all empty values + bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]), axis=1)] + + # Extract first 4 words from specified columns + bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_4_words) + bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_4_words) + + # Append the DataFrame to the appropriate dictionary based on file type + if file_type == '+': + income_dfs[month] = bank_data + elif file_type == '-': + spending_dfs[month] = bank_data + else: + print(f"No valid data read from file {filename}.") + + # Display the DataFrame + if file_type == '+': + print(f"\nIncome DataFrame for month {month}:") + print(income_dfs[month]) + elif file_type == '-': + print(f"\nSpending DataFrame for month {month}:") + print(spending_dfs[month]) + + # Specify the folder path + output_folder_income = "/home/eikov/fin/income" + output_folder_spending = "/home/eikov/fin/spendings" + + # Create the folder if it doesn't exist + os.makedirs(output_folder_income, exist_ok=True) + os.makedirs(output_folder_spending, exist_ok=True) + + # Save DataFrames to JSON files with UTF-8 encoding + income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json") + spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json") + + # Save DataFrames to Excel files + income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx") + spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx") + + # Drop rows with NaN values + income_dfs[month].dropna(inplace=True) + spending_dfs[month].dropna(inplace=True) + + # Convert DataFrames to JSON strings + income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str) + spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str) + """ + # Write JSON strings to files with UTF-8 encoding + with open(income_json_filename, 'w+', encoding='utf-8') as income_file: + for line in income_json_str.split('\n'): + if line.strip(): # Check if line is not empty + income_file.write(line.strip() + '\n') + + with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file: + for line in spending_json_str.split('\n'): + if line.strip(): # Check if line is not empty + spending_file.write(line.strip() + '\n') + """ + # Save DataFrames to Excel files + income_dfs[month].to_excel(income_excel_filename, index=False) + spending_dfs[month].to_excel(spending_excel_filename, index=False) + + print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}") + print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}") + + # Display skipped rows + if skipped_rows: + print(f"\nSkipped rows in file {filename}:") + for row_num, row_content in skipped_rows: + print(f"Row {row_num}: {row_content}") + else: + print(f"File {filename} not found. Skipping...") + +print("All files processed.") + + + +#Merge spending seperate xlsx files to one +directory = '/home/eikov/fin/spendings' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + +# Save the merged workbook to the output directory +output_filename = os.path.join(output_directory, "merged_spending_data.xlsx") +merged_workbook.save(output_filename) + + +#Merge income seperate xlsx files to one +directory = '/home/eikov/fin/income' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + +# Save the merged workbook to the output directory +output_filename = os.path.join(output_directory, "merged_income_data.xlsx") +merged_workbook.save(output_filename) \ No newline at end of file diff --git a/src/personal_finance/v2.1.py b/src/personal_finance/v2.1.py new file mode 100644 index 0000000..520b93e --- /dev/null +++ b/src/personal_finance/v2.1.py @@ -0,0 +1,374 @@ +import pandas as pd +import os +from io import StringIO +import json +import openpyxl + +# Define the directory containing the CSV files +directory = '/home/eikov/fin/OG-CSV' + +# Define the pattern for file names +file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type + +# Initialize empty dictionaries to store dataframes for each month +spending_dfs = {} +income_dfs = {} + +# Define the filters +filter1 = "KOVALIŪNAS EINARAS" +filter2 = "Einaras Kovaliūnas" # Add your second filter value here + +# Function to extract first 4 words from a string +def extract_first_4_words(text): + if pd.isna(text): # Check if the value is NaN + return "" # Return an empty string if NaN + words = text.split()[:4] + return ' '.join(words) + + +# Iterate over the file numbers from 1 to 12 +for file_num in range(1, 13): + month = str(file_num) + spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + for file_type in ['+', '-']: + filename = file_pattern.format(file_num, file_type) + filepath = os.path.join(directory, filename) + if os.path.exists(filepath): + print(f"Processing file: {filename}") + # Initialize an empty list to store the valid lines of the CSV file + valid_lines = [] + skipped_rows = [] + + # Read the CSV file line by line + with open(filepath, 'r', encoding='utf-8') as file: + for i, line in enumerate(file): + if i < 2: # Skip the first two lines (first row and header row) + continue + try: + # Attempt to parse the line as CSV + pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';' + valid_lines.append(line) # If successful, add the line to the list of valid lines + except pd.errors.ParserError: + print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}") + skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row + + # Read the valid lines into a DataFrame, skipping the header line + bank_data = None + try: + bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';') + except pd.errors.ParserError: + print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.") + if not valid_lines: + print("No valid data read due to errors.") + continue + + # If data was successfully read, extract specific columns and apply filter + if bank_data is not None: + bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns + + # Replace empty values with NaN + bank_data.replace('-', pd.NA, inplace=True) + + # Rename columns + bank_data.columns = ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"] + + # Apply filters + bank_data = bank_data[(bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter1) & + (bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter2)] + + # Filter out rows that are the same as column headers or contain all empty values + bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]), axis=1)] + + # Extract first 4 words from specified columns + bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_4_words) + bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_4_words) + + # Append the DataFrame to the appropriate dictionary based on file type + if file_type == '+': + income_dfs[month] = bank_data + elif file_type == '-': + spending_dfs[month] = bank_data + else: + print(f"No valid data read from file {filename}.") + + # Display the DataFrame + if file_type == '+': + print(f"\nIncome DataFrame for month {month}:") + print(income_dfs[month]) + elif file_type == '-': + print(f"\nSpending DataFrame for month {month}:") + print(spending_dfs[month]) + + # Specify the folder path + output_folder_income = "/home/eikov/fin/income" + output_folder_spending = "/home/eikov/fin/spendings" + + # Create the folder if it doesn't exist + os.makedirs(output_folder_income, exist_ok=True) + os.makedirs(output_folder_spending, exist_ok=True) + + # Save DataFrames to JSON files with UTF-8 encoding + income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json") + spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json") + + # Save DataFrames to Excel files + income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx") + spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx") + + # Drop rows with NaN values + income_dfs[month].dropna(inplace=True) + spending_dfs[month].dropna(inplace=True) + + # Convert DataFrames to JSON strings + income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str) + spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str) + """ + # Write JSON strings to files with UTF-8 encoding + with open(income_json_filename, 'w+', encoding='utf-8') as income_file: + for line in income_json_str.split('\n'): + if line.strip(): # Check if line is not empty + income_file.write(line.strip() + '\n') + + with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file: + for line in spending_json_str.split('\n'): + if line.strip(): # Check if line is not empty + spending_file.write(line.strip() + '\n') + """ + # Save DataFrames to Excel files + income_dfs[month].to_excel(income_excel_filename, index=False) + spending_dfs[month].to_excel(spending_excel_filename, index=False) + + print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}") + print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}") + + # Display skipped rows + if skipped_rows: + print(f"\nSkipped rows in file {filename}:") + for row_num, row_content in skipped_rows: + print(f"Row {row_num}: {row_content}") + else: + print(f"File {filename} not found. Skipping...") + +print("All files processed.") + + + + + + + + +#Merge spending seperate xlsx files to one +directory = '/home/eikov/fin/spendings' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + +# Save the merged workbook to the output directory +output_filename = os.path.join(output_directory, "merged_spending_data.xlsx") +merged_workbook.save(output_filename) + + +#Merge income seperate xlsx files to one +directory = '/home/eikov/fin/income' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + +# Save the merged workbook to the output directory +output_filename = os.path.join(output_directory, "merged_income_data.xlsx") +merged_workbook.save(output_filename) + + + + + + + + + + +# finding unique strings in INCOME and categorise it +# Path to the directory containing the file +output_directory = '/home/eikov/fin/analysis/' +# Specify the filename +filename = 'merged_income_data.xlsx' +# Construct the full path +file_path = f'{output_directory}/{filename}' + +# Initialize an empty set to store unique values +unique_values = set() + +# Read the Excel file +xls = pd.ExcelFile(file_path) + +# Iterate over each sheet in the Excel file +for sheet_name in xls.sheet_names: + # Read the sheet into a DataFrame + df = pd.read_excel(xls, sheet_name) + # Check the number of columns in the DataFrame + if df.shape[1] < 4: # If the number of columns is less than 4 + print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.") + continue # Skip to the next sheet + # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2) + unique_values.update(df.iloc[:, 2].unique()) + +# Create a DataFrame with unique values and save it to 'categories.xlsx' +unique_df = pd.DataFrame({'Unique Values': list(unique_values)}) +unique_df.to_excel(f'{output_directory}/categories_income.xlsx', index=False) + + + + + + + + +# finding unique strings in SPENDINGS and categorise it +# Path to the directory containing the file +output_directory2 = '/home/eikov/fin/analysis/' +# Specify the filename +filename2 = 'merged_spending_data.xlsx' +# Construct the full path +file_path2 = f'{output_directory2}/{filename2}' + +# Initialize an empty set to store unique values +unique_values2 = set() + +# Read the Excel file +xls = pd.ExcelFile(file_path2) + +# Iterate over each sheet in the Excel file +for sheet_name in xls.sheet_names: + # Read the sheet into a DataFrame + df = pd.read_excel(xls, sheet_name) + # Check the number of columns in the DataFrame + if df.shape[1] < 4: # If the number of columns is less than 4 + print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.") + continue # Skip to the next sheet + # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2) + unique_values2.update(df.iloc[:, 2].unique()) + +# Create a DataFrame with unique values and save it to 'categories.xlsx' +unique_df = pd.DataFrame({'Unique Values': list(unique_values2)}) +unique_df.to_excel(f'{output_directory}/categories_spendings.xlsx', index=False) + + + + + + + + +""" +#turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx +taipogi unikaliu moketoju/gaveju sarasus is pajamu ir islaidu. rankiniu budu priskiriu kiekvienam gavejui/moketojui +kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx. +priedo, manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo. +belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile. +""" +#INCOME +def merge_additional_sheets(base_file, additional_files, output_file): + # Create the output directory if it doesn't exist + output_directory_analysis = '/home/eikov/fin/analysis' + os.makedirs(output_directory_analysis, exist_ok=True) + + # Create a Pandas ExcelWriter object + with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer: + # Write the base file to the output file + base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None) + for sheet_name, df in base_df.items(): + df.to_excel(writer, sheet_name=sheet_name, index=False) + + # Write additional files to the output file as new sheets + for file in additional_files: + additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None) + for sheet_name, df in additional_df.items(): + df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) + +# List of additional files to merge as new sheets +additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx'] + +# Merge additional sheets with merged_income_data.xlsx +merge_additional_sheets('merged_income_data.xlsx', additional_files, 'Galutinis_Income.xlsx') + + +#SPENDINGS +def merge_additional_sheets(base_file, additional_files, output_file): + # Create the output directory if it doesn't exist + output_directory_analysis_s = '/home/eikov/fin/analysis' + os.makedirs(output_directory_analysis_s, exist_ok=True) + + # Create a Pandas ExcelWriter object + with pd.ExcelWriter(f'{output_directory_analysis_s}/{output_file}', engine='xlsxwriter') as writer: + # Write the base file to the output file + base_df = pd.read_excel(f'{output_directory_analysis_s}/{base_file}', sheet_name=None) + for sheet_name, df in base_df.items(): + df.to_excel(writer, sheet_name=sheet_name, index=False) + + # Write additional files to the output file as new sheets + for file in additional_files: + additional_df = pd.read_excel(f'{output_directory_analysis_s}/{file}', sheet_name=None) + for sheet_name, df in additional_df.items(): + df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) + +# List of additional files to merge as new sheets +additional_files = ['cat-spendings.xlsx', 'Analysis_spending.xlsx'] + +# Merge additional sheets with merged_income_data.xlsx +merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx') \ No newline at end of file diff --git a/src/personal_finance/v2.py b/src/personal_finance/v2.py new file mode 100644 index 0000000..ac32c08 --- /dev/null +++ b/src/personal_finance/v2.py @@ -0,0 +1,309 @@ +import pandas as pd +import os +from io import StringIO +import json +import openpyxl + +# Define the directory containing the CSV files +directory = '/home/eikov/fin/OG-CSV' + +# Define the pattern for file names +file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type + +# Initialize empty dictionaries to store dataframes for each month +spending_dfs = {} +income_dfs = {} + +# Define the filters +filter1 = "KOVALIŪNAS EINARAS" +filter2 = "Einaras Kovaliūnas" # Add your second filter value here + +# Function to extract first 4 words from a string +def extract_first_4_words(text): + if pd.isna(text): # Check if the value is NaN + return "" # Return an empty string if NaN + words = text.split()[:4] + return ' '.join(words) + + +# Iterate over the file numbers from 1 to 12 +for file_num in range(1, 13): + month = str(file_num) + spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + for file_type in ['+', '-']: + filename = file_pattern.format(file_num, file_type) + filepath = os.path.join(directory, filename) + if os.path.exists(filepath): + print(f"Processing file: {filename}") + # Initialize an empty list to store the valid lines of the CSV file + valid_lines = [] + skipped_rows = [] + + # Read the CSV file line by line + with open(filepath, 'r', encoding='utf-8') as file: + for i, line in enumerate(file): + if i < 2: # Skip the first two lines (first row and header row) + continue + try: + # Attempt to parse the line as CSV + pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';' + valid_lines.append(line) # If successful, add the line to the list of valid lines + except pd.errors.ParserError: + print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}") + skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row + + # Read the valid lines into a DataFrame, skipping the header line + bank_data = None + try: + bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';') + except pd.errors.ParserError: + print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.") + if not valid_lines: + print("No valid data read due to errors.") + continue + + # If data was successfully read, extract specific columns and apply filter + if bank_data is not None: + bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns + + # Replace empty values with NaN + bank_data.replace('-', pd.NA, inplace=True) + + # Rename columns + bank_data.columns = ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"] + + # Apply filters + bank_data = bank_data[(bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter1) & + (bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter2)] + + # Filter out rows that are the same as column headers or contain all empty values + bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]), axis=1)] + + # Extract first 4 words from specified columns + bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_4_words) + bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_4_words) + + # Append the DataFrame to the appropriate dictionary based on file type + if file_type == '+': + income_dfs[month] = bank_data + elif file_type == '-': + spending_dfs[month] = bank_data + else: + print(f"No valid data read from file {filename}.") + + # Display the DataFrame + if file_type == '+': + print(f"\nIncome DataFrame for month {month}:") + print(income_dfs[month]) + elif file_type == '-': + print(f"\nSpending DataFrame for month {month}:") + print(spending_dfs[month]) + + # Specify the folder path + output_folder_income = "/home/eikov/fin/income" + output_folder_spending = "/home/eikov/fin/spendings" + + # Create the folder if it doesn't exist + os.makedirs(output_folder_income, exist_ok=True) + os.makedirs(output_folder_spending, exist_ok=True) + + # Save DataFrames to JSON files with UTF-8 encoding + income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json") + spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json") + + # Save DataFrames to Excel files + income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx") + spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx") + + # Drop rows with NaN values + income_dfs[month].dropna(inplace=True) + spending_dfs[month].dropna(inplace=True) + + # Convert DataFrames to JSON strings + income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str) + spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str) + """ + # Write JSON strings to files with UTF-8 encoding + with open(income_json_filename, 'w+', encoding='utf-8') as income_file: + for line in income_json_str.split('\n'): + if line.strip(): # Check if line is not empty + income_file.write(line.strip() + '\n') + + with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file: + for line in spending_json_str.split('\n'): + if line.strip(): # Check if line is not empty + spending_file.write(line.strip() + '\n') + """ + # Save DataFrames to Excel files + income_dfs[month].to_excel(income_excel_filename, index=False) + spending_dfs[month].to_excel(spending_excel_filename, index=False) + + print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}") + print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}") + + # Display skipped rows + if skipped_rows: + print(f"\nSkipped rows in file {filename}:") + for row_num, row_content in skipped_rows: + print(f"Row {row_num}: {row_content}") + else: + print(f"File {filename} not found. Skipping...") + +print("All files processed.") + + + + + + + + +#Merge spending seperate xlsx files to one +directory = '/home/eikov/fin/spendings' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + +# Save the merged workbook to the output directory +output_filename = os.path.join(output_directory, "merged_spending_data.xlsx") +merged_workbook.save(output_filename) + + +#Merge income seperate xlsx files to one +directory = '/home/eikov/fin/income' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + +# Save the merged workbook to the output directory +output_filename = os.path.join(output_directory, "merged_income_data.xlsx") +merged_workbook.save(output_filename) + + + + + + + + + + +# finding unique strings in INCOME and categorise it +# Path to the directory containing the file +output_directory = '/home/eikov/fin/analysis/' +# Specify the filename +filename = 'merged_income_data.xlsx' +# Construct the full path +file_path = f'{output_directory}/{filename}' + +# Initialize an empty set to store unique values +unique_values = set() + +# Read the Excel file +xls = pd.ExcelFile(file_path) + +# Iterate over each sheet in the Excel file +for sheet_name in xls.sheet_names: + # Read the sheet into a DataFrame + df = pd.read_excel(xls, sheet_name) + # Check the number of columns in the DataFrame + if df.shape[1] < 4: # If the number of columns is less than 4 + print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.") + continue # Skip to the next sheet + # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2) + unique_values.update(df.iloc[:, 2].unique()) + +# Create a DataFrame with unique values and save it to 'categories.xlsx' +unique_df = pd.DataFrame({'Unique Values': list(unique_values)}) +unique_df.to_excel(f'{output_directory}/categories_income.xlsx', index=False) + + + + + + + + +# finding unique strings in SPENDINGS and categorise it +# Path to the directory containing the file +output_directory2 = '/home/eikov/fin/analysis/' +# Specify the filename +filename2 = 'merged_spending_data.xlsx' +# Construct the full path +file_path2 = f'{output_directory2}/{filename2}' + +# Initialize an empty set to store unique values +unique_values2 = set() + +# Read the Excel file +xls = pd.ExcelFile(file_path2) + +# Iterate over each sheet in the Excel file +for sheet_name in xls.sheet_names: + # Read the sheet into a DataFrame + df = pd.read_excel(xls, sheet_name) + # Check the number of columns in the DataFrame + if df.shape[1] < 4: # If the number of columns is less than 4 + print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.") + continue # Skip to the next sheet + # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2) + unique_values2.update(df.iloc[:, 2].unique()) + +# Create a DataFrame with unique values and save it to 'categories.xlsx' +unique_df = pd.DataFrame({'Unique Values': list(unique_values2)}) +unique_df.to_excel(f'{output_directory}/categories_spendings.xlsx', index=False) diff --git a/src/personal_notes/discord_extract/.py b/src/personal_notes/discord_extract/.py new file mode 100644 index 0000000..e69de29 diff --git a/src/personal_notes/discord_extract/disc_extract_s.py b/src/personal_notes/discord_extract/disc_extract_s.py new file mode 100644 index 0000000..e69de29 From c53544ebccc177fbb4f379aa705861849777bd4e Mon Sep 17 00:00:00 2001 From: br34th5 Date: Sun, 5 May 2024 18:10:44 +0300 Subject: [PATCH 03/23] now they are added --- .../__pycache__/json.cpython-311.pyc | Bin 0 -> 1962 bytes .../__pycache__/responses.cpython-311.pyc | Bin 0 -> 445 bytes .../discord_extract/cleaning.py | 45 +++++ .../discord_extract/disc_del_channels.py | 94 ++++++++++ .../discord_extract/disc_extract_c.py | 163 ++++++++++++++++++ .../discord_extract/disc_extract_s.py | 159 +++++++++++++++++ .../discord_extract/json_to_txt.py | 43 +++++ .../discord_extract/json_to_txt2.py | 47 +++++ .../discord_extract/migration.txt | 11 ++ .../discord_extract/responses.py | 2 + src/personal_notes/discord_extract/scheme.txt | 15 ++ 11 files changed, 579 insertions(+) create mode 100644 src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc create mode 100644 src/personal_notes/discord_extract/__pycache__/responses.cpython-311.pyc create mode 100644 src/personal_notes/discord_extract/cleaning.py create mode 100644 src/personal_notes/discord_extract/disc_del_channels.py create mode 100644 src/personal_notes/discord_extract/disc_extract_c.py create mode 100644 src/personal_notes/discord_extract/json_to_txt.py create mode 100644 src/personal_notes/discord_extract/json_to_txt2.py create mode 100644 src/personal_notes/discord_extract/migration.txt create mode 100644 src/personal_notes/discord_extract/responses.py create mode 100644 src/personal_notes/discord_extract/scheme.txt diff --git a/src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc b/src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6d1902a50236a51b16ae44c38e60634c642ebd4 GIT binary patch literal 1962 zcmbVMO-$TI6rSazLE=Y%qUCs@fUPoA=(l z`R2`gZ~RL#DIjPI53Ww%*y(7!^1YT}6}|LtCJF5iX!YlwiMa0rCqN`wb&4aT#4jCcDTiMJCZA#%K~@ z=x|14HuMM4=oW^3F!oy^PUBTRl#b6NcFIx!Bs7}{ZAhH(FNM=yP+<%`{9_9IDzp@m zG)WTY!VPG)HDn1o(iTKOO@%AfwvanMlQXW*=?!f^)0=-qrtP%l6mt3$a{7NF zr^#vL4nSsU2T7ip?+iq%^(Zb7$4iAQ%^DYImWXpfWf)yFQ|&w+nUDvY)CveEbB58= z%RJQHf2o(JZwbDS`r7{Tq3^AvODdJM;yg7?xlGO6tf^}rHdeR=YkJ^%ANJx@E9peh z%B91if{pMXkni%&CqOn(89hM{v)^Frn9b}V{{)|O`+MeT=Y8}m>SH`MIFD0xnV29l zxj~Or6^)vH<@faLJ&Dh-}FNz)5Ar8Yml-YIDm{ zg&J@LSuIX0nyi(mL=>~68>IM1wIo&;qGjDss5uctYYBjh`kWU(SXf?3fX^1y ztemxEd3X`_?#p{Pw}*wkE|1Yo!}q|8R%E!H=`j`Jv3iAS9y_h5)QgAd@%S;g^Jj)U zPd$cAd$F>iFH{~aA25`FsXRxCVwi^i8ohX6Py|y}QT3AHKR=JH$kq&mURN|v4BaeF z>nfp!*LrZXL)oHB76enJ#b6tMC>v=Xj9WAmiyBejjigWfKgeEuWL_r=DjhZYLG&xZ z9D-0Theiv}y&pRV3n7i(AQtmLqg%Syn2b++sI zR~u8$rmE8Ru#>szW^UT|LhIIAN1e?%Y|dqKz#O;EZ4Wq^TW;o-eGk+^>srT}wppr; z*ZI7|=UqPEL=kTAHQ(s$uJUeYe-kl0xDI4(ydh>GLdn6+;cDr}(aq6D>fDC-OsvLg zSAK4FQj(jJ>M3cr$@yC%025#ZUVz&9DVdMFVz0-3 X!`EZKr$4&g!o6u>Z>RV-X$ghy1xT>+x1%zK3E$I03>bkiN&AJFiE% z+a;k_`h@`<37HqRtZA@?mI+-~AiJwG4^T{q{d33ZBf6L0$phhecQY1?9_?Uql8OCE9H&L-v z*k-B$C481;pWWde)~lPJxjOJ$h`lPhV!9OdJ3D+L>^6TN}^mOIT3!VUTG z_ooN!)o;D~t*O_VV@NuoQ@Y0L>g_Ypb 0: + # Make the request + async for msg in channel.history(limit=None): + # Create a dictionary to store message data + message_data = { + 'id': msg.id, + 'category': channel.category.name, + 'channel': channel.name, + 'content': msg.content, + 'timestamp': msg.created_at.strftime('%Y-%m-%d %H:%M') + } + + # Serialize message data into a string + message_str = json.dumps({msg.id: message_data}) + + # Save message data to the file + with open('messages.json', 'a') as file: + file.write(message_str + '\n') # Write message and add newline for readability + + # Decrement the token bucket + RATE_LIMIT_BUCKET -= 1 + else: + # Wait until tokens refill in the bucket + await asyncio.sleep(1) + RATE_LIMIT_BUCKET = RATE_LIMIT_THRESHOLD # Refill the token bucket + await monitor_channel(channel) + + except Forbidden as e: + print(f"Permission error: {e}") + except HTTPException as e: + # Check if the exception is due to rate limiting + if e.status == 429: + retry_after = e.retry_after #testing + print(f"We are being rate limited. We could retry in {retry_after} seconds.") #testing + print(f"We are being rate limited. Exiting for now.") + await client.close() # Exit the program + else: + print(f"HTTP error: {e}") + except Exception as e: + print(f"An error occurred: {e}") + +@client.event +async def on_ready(): + global request_count + print(f'{client.user} has connected to Discord!') + print('Monitoring channels...') + + # Record the start time + start_time = time.time() + + for guild in client.guilds: + print(f'Guild: {guild.name}') + for category in guild.categories: + if category.name in categories_to_monitor: + print(f'Category: {category.name}') + for channel in category.channels: + if isinstance(channel, discord.TextChannel): + await monitor_channel(channel) + + # Calculate and display the elapsed time + elapsed_time = time.time() - start_time + print(f"Total requests made: {request_count}") + print(f"Total time elapsed: {elapsed_time} seconds") + + await client.close() + +# Run the bot +client.run(TOKEN) + + +# After posts are scraped, time to look for duplicates and remove them: +def remove_duplicates(json_file): + # Read the contents of the JSON file into a list + with open(json_file, 'r') as file: + lines = file.readlines() + + # Initialize lists to store message data + ids = [] + category = [] + channels = [] + contents = [] + timestamps = [] + + # Extract message data from each line in the JSON file + for line in lines: + message = json.loads(line) + message_id = next(iter(message)) # Get the message ID + ids.append(message_id) + category.append(message[message_id]['category']) + channels.append(message[message_id]['channel']) + contents.append(message[message_id]['content']) + timestamps.append(message[message_id]['timestamp']) + + # Create a DataFrame from the extracted data + df = pd.DataFrame({'id': ids, 'category': category, 'channel': channels, 'content': contents, 'timestamp': timestamps}) + + # Drop duplicates based on message ID + clean_df = df.drop_duplicates(subset=['id']) + + # Open the new file to save cleaned messages + with open('clean_messages.json', 'w') as file: + # Serialize each message individually and write them to the file + for _, message_row in clean_df.iterrows(): + message_data = { + 'id': message_row['id'], + 'category': message_row['category'], + 'channel': message_row['channel'], + 'content': message_row['content'], + 'timestamp': message_row['timestamp'] + } + json.dump(message_data, file) + file.write('\n') # Add newline for readability + +# Example usage: +remove_duplicates('messages.json') diff --git a/src/personal_notes/discord_extract/disc_extract_s.py b/src/personal_notes/discord_extract/disc_extract_s.py index e69de29..b62de7d 100644 --- a/src/personal_notes/discord_extract/disc_extract_s.py +++ b/src/personal_notes/discord_extract/disc_extract_s.py @@ -0,0 +1,159 @@ +import os +import discord +import json +import asyncio +from dotenv import load_dotenv +from discord import Intents, Client +from discord.errors import Forbidden, HTTPException +import pandas as pd +from collections import deque +import time + +#this bot scrapes whole server + +# Load bot token from .env file +load_dotenv() +TOKEN = os.getenv('DISCORD_TOKEN') + +# Set up intents +intents = Intents.default() +intents.messages = True + +# Define permission flags +PERMISSION_READ_MESSAGES = 1 << 6 # 65536 +PERMISSION_MANAGE_MESSAGES = 1 << 13 # 8192 +PERMISSION_READ_MESSAGE_HISTORY = 1 << 11 # 73728 + +# Calculate permissions integer +permissions = PERMISSION_READ_MESSAGES | PERMISSION_MANAGE_MESSAGES | PERMISSION_READ_MESSAGE_HISTORY + +# Initialize the Discord client with permissions +client = Client(intents=intents, permissions=permissions) + +# Define rate limit settings +RATE_LIMIT_THRESHOLD = 3 # Maximum number of requests per second +RATE_LIMIT_BUCKET = RATE_LIMIT_THRESHOLD # Initial token bucket capacity + +# Initialize request counter +request_count = 0 + +async def monitor_channel(channel): + global RATE_LIMIT_BUCKET + global request_count + try: + # Increment request count for each request + request_count += 1 + + # Check if there are enough tokens in the bucket + if RATE_LIMIT_BUCKET > 0: + # Make the request + async for msg in channel.history(limit=None): + # Create a dictionary to store message data + message_data = { + 'id': msg.id, + 'category': channel.category.name, + 'channel': channel.name, + 'content': msg.content, + 'timestamp': msg.created_at.strftime('%Y-%m-%d %H:%M') + } + + # Serialize message data into a string + message_str = json.dumps({msg.id: message_data}) + + # Save message data to the file + with open('messages.json', 'a') as file: + file.write(message_str + '\n') # Write message and add newline for readability + + # Decrement the token bucket + RATE_LIMIT_BUCKET -= 1 + else: + # Wait until tokens refill in the bucket + await asyncio.sleep(1) + RATE_LIMIT_BUCKET = RATE_LIMIT_THRESHOLD # Refill the token bucket + await monitor_channel(channel) + + except Forbidden as e: + print(f"Permission error: {e}") + except HTTPException as e: + # Check if the exception is due to rate limiting + if e.status == 429: + retry_after = e.retry_after #testing + print(f"We are being rate limited. We could retry in {retry_after} seconds.") #testing + print(f"We are being rate limited. Exiting for now.") + await client.close() # Exit the program + else: + print(f"HTTP error: {e}") + except Exception as e: + print(f"An error occurred: {e}") + +@client.event +async def on_ready(): + global request_count + print(f'{client.user} has connected to Discord!') + print('Monitoring channels...') + + # Record the start time + start_time = time.time() + + for guild in client.guilds: + print(f'Guild: {guild.name}') + for channel in guild.channels: + if isinstance(channel, discord.TextChannel): + await monitor_channel(channel) + + # Calculate and display the elapsed time + elapsed_time = time.time() - start_time + print(f"Total requests made: {request_count}") + print(f"Total time elapsed: {elapsed_time} seconds") + + await client.close() + +# Run the bot +client.run(TOKEN) + + +# After posts are scraped, time to look for duplicates and remove them: +def remove_duplicates(json_file): + # Read the contents of the JSON file into a list + with open(json_file, 'r') as file: + lines = file.readlines() + + # Initialize lists to store message data + ids = [] + category = [] + channels = [] + contents = [] + timestamps = [] + + # Extract message data from each line in the JSON file + for line in lines: + message = json.loads(line) + message_id = next(iter(message)) # Get the message ID + ids.append(message_id) + category.append(message[message_id]['category']) + channels.append(message[message_id]['channel']) + contents.append(message[message_id]['content']) + timestamps.append(message[message_id]['timestamp']) + + # Create a DataFrame from the extracted data + df = pd.DataFrame({'id': ids, 'category': category, 'channel': channels, 'content': contents, 'timestamp': timestamps}) + + # Drop duplicates based on message ID + clean_df = df.drop_duplicates(subset=['id']) + + # Open the new file to save cleaned messages + with open('clean_messages.json', 'w') as file: + # Serialize each message individually and write them to the file + for _, message_row in clean_df.iterrows(): + message_data = { + 'id': message_row['id'], + 'category': message_row['category'], + 'channel': message_row['channel'], + 'content': message_row['content'], + 'timestamp': message_row['timestamp'] + } + json.dump(message_data, file) + file.write('\n') # Add newline for readability + +# Example usage: +remove_duplicates('messages.json') \ No newline at end of file diff --git a/src/personal_notes/discord_extract/json_to_txt.py b/src/personal_notes/discord_extract/json_to_txt.py new file mode 100644 index 0000000..f7633f2 --- /dev/null +++ b/src/personal_notes/discord_extract/json_to_txt.py @@ -0,0 +1,43 @@ +import os +import json +import pandas as pd + +#next: use regex for <#832925562611302400> format. either show these numbers as letters and tags or don't show at all + +# Load the messages from the JSON file +with open('clean_messages.json', 'r', encoding='utf-8') as file: + messages = [json.loads(line) for line in file] + +# Create a DataFrame from the messages +df = pd.DataFrame(messages) + +# Sort the DataFrame by 'channel' and 'timestamp' +df.sort_values(by=['channel', 'timestamp'], inplace=True) + +# Group the messages by the 'channel' column +grouped = df.groupby('channel') + +# Create a folder to store the text files for categories +os.makedirs('categories', exist_ok=True) + +# Iterate over each group (channel) +for channel, group_df in grouped: + # Get the category of the channel + category = group_df['category'].iloc[0] # Assuming 'category' is a column in the DataFrame + + # Create a folder for the category if it doesn't exist + category_folder = os.path.join('categories', category) + os.makedirs(category_folder, exist_ok=True) + + # Create a folder for the channel + channel_folder = os.path.join(category_folder, channel) + os.makedirs(channel_folder, exist_ok=True) + + # Create a text file for the channel's content + text_file_path = os.path.join(channel_folder, 'content.txt') + + # Write the non-empty content strings to the text file + with open(text_file_path, 'w', encoding='utf-8') as text_file: + for content in group_df['content']: + if content and content != ".": + text_file.write(content + '\n') diff --git a/src/personal_notes/discord_extract/json_to_txt2.py b/src/personal_notes/discord_extract/json_to_txt2.py new file mode 100644 index 0000000..154b6b9 --- /dev/null +++ b/src/personal_notes/discord_extract/json_to_txt2.py @@ -0,0 +1,47 @@ +import os +import json +import pandas as pd +import re + +# Load the messages from the JSON file +with open('clean_messages.json', 'r', encoding='utf-8') as file: + messages = [json.loads(line) for line in file] + +# Create a DataFrame from the messages +df = pd.DataFrame(messages) + +# Sort the DataFrame by 'channel' and 'timestamp' +df.sort_values(by=['channel', 'timestamp'], inplace=True) + +# Replace the Discord ID format (<#832925562611302400>) with an empty string or a custom string +def replace_discord_id(content, replacement=''): + return re.sub(r'<#\d+>', replacement, content) +df['content'] = df['content'].apply(replace_discord_id) + +# Group the messages by the 'channel' column +grouped = df.groupby('channel') + +# Create a folder to store the text files for categories +os.makedirs('categories', exist_ok=True) + +# Iterate over each group (channel) +for channel, group_df in grouped: + # Get the category of the channel + category = group_df['category'].iloc[0] # Assuming 'category' is a column in the DataFrame + + # Create a folder for the category if it doesn't exist + category_folder = os.path.join('categories', category) + os.makedirs(category_folder, exist_ok=True) + + # Create a folder for the channel + channel_folder = os.path.join(category_folder, channel) + os.makedirs(channel_folder, exist_ok=True) + + # Create a text file for the channel's content + text_file_path = os.path.join(channel_folder, 'content.txt') + + # Write the non-empty content strings to the text file + with open(text_file_path, 'w', encoding='utf-8') as text_file: + for content in group_df['content']: + if content and content != ".": + text_file.write(content + '\n') diff --git a/src/personal_notes/discord_extract/migration.txt b/src/personal_notes/discord_extract/migration.txt new file mode 100644 index 0000000..b188b9a --- /dev/null +++ b/src/personal_notes/discord_extract/migration.txt @@ -0,0 +1,11 @@ +from clickup to Anytype +1. paziuret kokius 10 columns pasilikt, nes anytype tik tiek columns gali turet +2. split csv by spaces(R) [p.s tikslai, uzduotys ir projektai tures but splitinami i: 1) tikslai ir uzduotys 2) projektai nes virs 1k eiluciu] +2. then scplit also by (P) +import splited spaces to Anytype spaces + +from discord to Logseq +1. Extract and delete posts from discord +2. Create channels as pages in Logseq +3. Convert to EDN/JSON/OPML +4. Import to Logseq \ No newline at end of file diff --git a/src/personal_notes/discord_extract/responses.py b/src/personal_notes/discord_extract/responses.py new file mode 100644 index 0000000..4c2a606 --- /dev/null +++ b/src/personal_notes/discord_extract/responses.py @@ -0,0 +1,2 @@ +def get_response(user_input: str) -> str: + raise NotImplementedError("Code is missing") \ No newline at end of file diff --git a/src/personal_notes/discord_extract/scheme.txt b/src/personal_notes/discord_extract/scheme.txt new file mode 100644 index 0000000..e8ce428 --- /dev/null +++ b/src/personal_notes/discord_extract/scheme.txt @@ -0,0 +1,15 @@ +Discord zjbs, convenient del tags ir siaip galima betka share'int lengvai per discord. +Taciau.. +Kas savaite/menesi reik extractint visus post'us ir juos store'int kazkur Open Source Privacy app'e. +Kodel: nes pradeda laggint discord ir siaip ten not cool. + + +The extraction code scheme: +1. setting up a bot in the server https://www.youtube.com/watch?v=UYJDKSah-Ww +pip install discord +pip install python-dotenv +2. Read posts, copy and store each post content in json. (Dont forget to 1) right click on category and disable private category mode 2 server -> roles -> @everyone -> permissions -> view) +3. Cleaning up: in chosen category delete channels and then create them again. Because it will take years to delete many posts, better to del channels +4. run json_to_txt.py to save data in folders and txt as well +5. pip uninstall discord + From e3307bf17f9d39fd3e2f28e3dfb4f963a140d6d0 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Sun, 5 May 2024 18:31:34 +0300 Subject: [PATCH 04/23] finally --- .../__pycache__/config.cpython-311.pyc | Bin 0 -> 1074 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 915 bytes .../discord_extract/2024-05-extract.txt | 26 ++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 src/LoL-support/Database/__pycache__/config.cpython-311.pyc create mode 100644 src/LoL-support/Database/__pycache__/config.cpython-312.pyc create mode 100644 src/personal_notes/discord_extract/2024-05-extract.txt diff --git a/src/LoL-support/Database/__pycache__/config.cpython-311.pyc b/src/LoL-support/Database/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73d76817177a55cf6b97a1ea7bcda1cfb49931b1 GIT binary patch literal 1074 zcmZWn-Amh06u&p0tr|y}&K6|^w<(3$Yz)R8WH6`0Vv%jWX?4WrwuZGyc9V*mDMS%g z!3WukKI}mlIEMeo9$P6`0vm&|fo}!bi%+{ZwbgDXxhLo2cfNAZXG!V;gkUqIV9erRtz9Meh$RNQ)-DrzvroGWcA#0 z7261(_h_o4E~_?5=!WihS*GpeFtXp|Gp%AYOdy9P=jO0a z=`(IQ;Wh+l>;ZqNT}mz_-IG3#+P!vmw+S8T3YzWoLg2FG0=U^zIy|O1-i^+ojH8>z z`n{5@+p=Lga@H&wnyeeLvx4OH`=vxy&!f1A>C`@lk*fLKE2{miE2cb;F4Zeu$Ky(owe;$In`i^`;P#wMJv%KC(-HS==9ft%FFp`bfFeqsP?97z3ECQ{f!Hkhc}X2 zNiSLNmb~N{X%o!G8O)I{rt1=C{hsD9&3_x*STNTYfNxM#A7SUA@q(!p^XM_AOGsD2 jraMU5fUpkkR65@VC&AtZ=wvqm6g^=hv=#D#C&>O6j>7t) literal 0 HcmV?d00001 diff --git a/src/LoL-support/Database/__pycache__/config.cpython-312.pyc b/src/LoL-support/Database/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27a4159419fffc960d761ca481e336ceac078203 GIT binary patch literal 915 zcmaJ<%}>-o6rbtmE=wW!5q1MH4M9i`P>mN4#6$!Gfe@7&qNZ$Tm*RF>r(MEF*Q^HP z`f3S)-}}vbef_9uZGf4seL+7U z0N=Qin9u@^0S2~!4QyBfE}Vl9*pe-5z&SwxNZ4u7AXpU*`G|W4r0q96TyToFED|7+ zPo(&z!j{NZ;}6@mK**krEfJ~+k+8%Wrz`?HEiMEOsECXupQ11~f(eWCGlu#p@gss<|62LuSI#IF3rA&hVoe^ZUwR%ime+omU8(SnMQONXX zfg#Nt@m2`WSsR>1`Oxw3`lX8Q1UmLYz2KFxtvgr`7m>bxxiVaEO2|+N|0+}>$g*i> z(Fz^}aVC|W5V-+OkFVsB&$Xyh@Q7=LRQ8F3Lqnk{E`cow0^&Hd-rh z-hQ$2a{5?g@!tyQ% Date: Tue, 7 May 2024 22:25:41 +0300 Subject: [PATCH 05/23] finally it shows total sum=(B1:{last_row}). But I have to manually open sheets, click on cell and press enter. I found a way to automate it so that it shows instantly sum results, but it caused more problems for additional_files structure. it stays like this now --- src/personal_finance/v2.1.py | 59 +++-- src/personal_finance/v2.2.py | 497 +++++++++++++++++++++++++++++++++++ 2 files changed, 536 insertions(+), 20 deletions(-) create mode 100644 src/personal_finance/v2.2.py diff --git a/src/personal_finance/v2.1.py b/src/personal_finance/v2.1.py index 520b93e..f922e87 100644 --- a/src/personal_finance/v2.1.py +++ b/src/personal_finance/v2.1.py @@ -4,6 +4,32 @@ import json import openpyxl +""" +This code is designed to process bank statement files and organize the data into separate DataFrames for income and spending transactions. It performs the following tasks: + +1. Reads bank statement files from a specified directory. +2. Extracts relevant information from each file, such as transaction date, amount, payer/payee name, and purpose. +3. Applies data cleaning and preprocessing steps, including removing unnecessary columns and extracting the first two words from payer/payee names and payment purposes. +4. Organizes the data into separate DataFrames for income and spending transactions, grouped by month. +5. Saves the DataFrames as JSON and Excel files in separate directories for income and spending. +6. Merges separate monthly Excel files into single "merged_income_data.xlsx" and "merged_spending_data.xlsx" files. +7. Extracts unique payer/payee names from the merged files and saves them as "categories_income.xlsx" and "categories_spendings.xlsx". +8. Allows manual categorization of payer/payee names by creating "cat-income.xlsx" and "cat-spendings.xlsx" files. +9. Merges the categorized data and analysis sheets into final "Galutinis_Income.xlsx" and "Galutinis_spendings.xlsx" files. + +The code is designed to be modular and extensible, allowing for easy integration of additional data processing or analysis steps as needed. +""" + +""" +#next: in Line 229 +modify Excel file: at the end of the entries in column "SUMA" make a formula to add sum all values of that column in order to calculate total income/spending. +or if it's not possible then simply add sum function to B80 cell. + +#next: in 8: create DF for each found excel spending(-) file(ignore the last row with total sum) and join into one DF. +now iterate each unique value and sum all "SUMA" values. if that's not possible, convert DF to set and then sum key values. +""" + + # Define the directory containing the CSV files directory = '/home/eikov/fin/OG-CSV' @@ -18,14 +44,16 @@ filter1 = "KOVALIŪNAS EINARAS" filter2 = "Einaras Kovaliūnas" # Add your second filter value here -# Function to extract first 4 words from a string -def extract_first_4_words(text): + +# Function to extract first 2 words from a string +def extract_first_2_words(text): if pd.isna(text): # Check if the value is NaN return "" # Return an empty string if NaN - words = text.split()[:4] + words = text.split()[:2] return ' '.join(words) + # Iterate over the file numbers from 1 to 12 for file_num in range(1, 13): month = str(file_num) @@ -81,8 +109,8 @@ def extract_first_4_words(text): bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]), axis=1)] # Extract first 4 words from specified columns - bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_4_words) - bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_4_words) + bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_2_words) + bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_2_words) # Append the DataFrame to the appropriate dictionary based on file type if file_type == '+': @@ -108,7 +136,7 @@ def extract_first_4_words(text): os.makedirs(output_folder_income, exist_ok=True) os.makedirs(output_folder_spending, exist_ok=True) - # Save DataFrames to JSON files with UTF-8 encoding + # Save DataFrames to JSON files income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json") spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json") @@ -123,18 +151,7 @@ def extract_first_4_words(text): # Convert DataFrames to JSON strings income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str) spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str) - """ - # Write JSON strings to files with UTF-8 encoding - with open(income_json_filename, 'w+', encoding='utf-8') as income_file: - for line in income_json_str.split('\n'): - if line.strip(): # Check if line is not empty - income_file.write(line.strip() + '\n') - - with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file: - for line in spending_json_str.split('\n'): - if line.strip(): # Check if line is not empty - spending_file.write(line.strip() + '\n') - """ + # Save DataFrames to Excel files income_dfs[month].to_excel(income_excel_filename, index=False) spending_dfs[month].to_excel(spending_excel_filename, index=False) @@ -211,6 +228,8 @@ def extract_first_4_words(text): merged_workbook = openpyxl.Workbook() # Loop through each file and add it as a new sheet to the workbook +#additionally add at the end of the entries in column "SUMA" make a formula to add sum all values of that column in order to calculate total income/spending. +#or if it's not possible then simply add sum function(=SUM(B1:B79)) to B80 cell in each sheet for i, file_name in enumerate(excel_files): # Load the workbook from file workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) @@ -314,7 +333,6 @@ def extract_first_4_words(text): - """ #turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx taipogi unikaliu moketoju/gaveju sarasus is pajamu ir islaidu. rankiniu budu priskiriu kiekvienam gavejui/moketojui @@ -371,4 +389,5 @@ def merge_additional_sheets(base_file, additional_files, output_file): additional_files = ['cat-spendings.xlsx', 'Analysis_spending.xlsx'] # Merge additional sheets with merged_income_data.xlsx -merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx') \ No newline at end of file +merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx') + diff --git a/src/personal_finance/v2.2.py b/src/personal_finance/v2.2.py new file mode 100644 index 0000000..ac55d17 --- /dev/null +++ b/src/personal_finance/v2.2.py @@ -0,0 +1,497 @@ +import pandas as pd +import os +from io import StringIO +import json +import openpyxl + +""" +This code is designed to process bank statement files and organize the data into separate DataFrames for income and spending transactions. It performs the following tasks: + +1. Reads bank statement files from a specified directory. +2. Extracts relevant information from each file, such as transaction date, amount, payer/payee name, and purpose. +3. Applies data cleaning and preprocessing steps, including removing unnecessary columns and extracting the first two words from payer/payee names and payment purposes. +4. Organizes the data into separate DataFrames for income and spending transactions, grouped by month. +5. Saves the DataFrames as JSON and Excel files in separate directories for income and spending. +6. Merges separate monthly Excel files into single "merged_income_data.xlsx" and "merged_spending_data.xlsx" files. +7. Extracts unique payer/payee names from the merged files and saves them as "categories_income.xlsx" and "categories_spendings.xlsx". +8. Allows manual categorization of payer/payee names by creating "cat-income.xlsx" and "cat-spendings.xlsx" files. +9. Merges the categorized data and analysis sheets into final "Galutinis_Income.xlsx" and "Galutinis_spendings.xlsx" files. + +The code is designed to be modular and extensible, allowing for easy integration of additional data processing or analysis steps as needed. +""" + +""" +#next: in 4. modify Excel file: at the end of the entries in column "SUMA" make a formula to add sum all values of that column in order to calculate total income/spending. +or if it's not possible then simply add sum function to B80 cell. + +#next: in 8: create DF for each found excel spending(-) file(ignore the last row with total sum) and join into one DF. +now iterate each unique value and sum all "SUMA" values. if that's not possible, convert DF to set and then sum key values. +""" + + +# Define the directory containing the CSV files +directory = '/home/eikov/fin/OG-CSV' + +# Define the pattern for file names +file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type + +# Initialize empty dictionaries to store dataframes for each month +spending_dfs = {} +income_dfs = {} + +# Define the filters +filter1 = "KOVALIŪNAS EINARAS" +filter2 = "Einaras Kovaliūnas" # Add your second filter value here + + +# Function to extract first 2 words from a string +def extract_first_2_words(text): + if pd.isna(text): # Check if the value is NaN + return "" # Return an empty string if NaN + words = text.split()[:2] + return ' '.join(words) + + + +# Iterate over the file numbers from 1 to 12 +for file_num in range(1, 13): + month = str(file_num) + spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + for file_type in ['+', '-']: + filename = file_pattern.format(file_num, file_type) + filepath = os.path.join(directory, filename) + if os.path.exists(filepath): + print(f"Processing file: {filename}") + # Initialize an empty list to store the valid lines of the CSV file + valid_lines = [] + skipped_rows = [] + + # Read the CSV file line by line + with open(filepath, 'r', encoding='utf-8') as file: + for i, line in enumerate(file): + if i < 2: # Skip the first two lines (first row and header row) + continue + try: + # Attempt to parse the line as CSV + pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';' + valid_lines.append(line) # If successful, add the line to the list of valid lines + except pd.errors.ParserError: + print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}") + skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row + + # Read the valid lines into a DataFrame, skipping the header line + bank_data = None + try: + bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';') + except pd.errors.ParserError: + print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.") + if not valid_lines: + print("No valid data read due to errors.") + continue + + # If data was successfully read, extract specific columns and apply filter + if bank_data is not None: + bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns + + # Replace empty values with NaN + bank_data.replace('-', pd.NA, inplace=True) + + # Rename columns + bank_data.columns = ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"] + + # Apply filters + bank_data = bank_data[(bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter1) & + (bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter2)] + + # Filter out rows that are the same as column headers or contain all empty values + bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]), axis=1)] + + # Extract first 4 words from specified columns + bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_2_words) + bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_2_words) + + # Replace commas with periods in the "SUMA" column + bank_data["SUMA"] = bank_data["SUMA"].str.replace(',', '.') + + # Convert the values in the "SUMA" column to numeric data type + bank_data["SUMA"] = pd.to_numeric(bank_data["SUMA"], errors='coerce') + # Now the "SUMA" column contains numeric values + + + # Append the DataFrame to the appropriate dictionary based on file type + if file_type == '+': + income_dfs[month] = bank_data + elif file_type == '-': + spending_dfs[month] = bank_data + else: + print(f"No valid data read from file {filename}.") + + # Display the DataFrame + if file_type == '+': + print(f"\nIncome DataFrame for month {month}:") + print(income_dfs[month]) + elif file_type == '-': + print(f"\nSpending DataFrame for month {month}:") + print(spending_dfs[month]) + + # Specify the folder path + output_folder_income = "/home/eikov/fin/income" + output_folder_spending = "/home/eikov/fin/spendings" + + # Create the folder if it doesn't exist + os.makedirs(output_folder_income, exist_ok=True) + os.makedirs(output_folder_spending, exist_ok=True) + + # Save DataFrames to JSON files + income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json") + spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json") + + # Save DataFrames to Excel files + income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx") + spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx") + + # Drop rows with NaN values + income_dfs[month].dropna(inplace=True) + spending_dfs[month].dropna(inplace=True) + + # Convert DataFrames to JSON strings + income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str) + spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str) + + # Save DataFrames to Excel files + income_dfs[month].to_excel(income_excel_filename, index=False) + spending_dfs[month].to_excel(spending_excel_filename, index=False) + + print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}") + print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}") + + # Display skipped rows + if skipped_rows: + print(f"\nSkipped rows in file {filename}:") + for row_num, row_content in skipped_rows: + print(f"Row {row_num}: {row_content}") + else: + print(f"File {filename} not found. Skipping...") + +print("All files processed.") + + + + + + + + +#Merge spending seperate xlsx files to one +directory = '/home/eikov/fin/spendings' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + + # Add total sum formula(B2:B100) at the end of the last row + last_row = merged_sheet.max_row + formula_str = f'=SUM(B2:B100)' + merged_sheet[f'A{last_row + 1}'] = formula_str + + +# Save the merged workbook to the output directory +output_filename = os.path.join(output_directory, "merged_spending_data.xlsx") +merged_workbook.save(output_filename) + + +#Merge income seperate xlsx files to one +directory = '/home/eikov/fin/income' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + + # Add total sum formula(B2:B100) at the end of the last row + last_row = merged_sheet.max_row + formula_str = f'=SUM(B2:B100)' + merged_sheet[f'A{last_row + 1}'] = formula_str + + + # Save the merged workbook to the output directory + output_filename = os.path.join(output_directory, "merged_income_data.xlsx") + merged_workbook.save(output_filename) + + + + + + + + + + + +# finding unique strings in INCOME and categorise it +# Path to the directory containing the file +output_directory = '/home/eikov/fin/analysis/' +# Specify the filename +filename = 'merged_income_data.xlsx' +# Construct the full path +file_path = f'{output_directory}/{filename}' + +# Initialize an empty set to store unique values +unique_values = set() + +# Read the Excel file +xls = pd.ExcelFile(file_path) + +# Iterate over each sheet in the Excel file +for sheet_name in xls.sheet_names: + # Read the sheet into a DataFrame + df = pd.read_excel(xls, sheet_name) + # Check the number of columns in the DataFrame + if df.shape[1] < 4: # If the number of columns is less than 4 + print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.") + continue # Skip to the next sheet + # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2) + unique_values.update(df.iloc[:, 2].unique()) + +# Create a DataFrame with unique values and save it to 'categories.xlsx' +unique_df = pd.DataFrame({'Unique Values': list(unique_values)}) +unique_df.to_excel(f'{output_directory}/categories_income.xlsx', index=False) + + + + + + + + +# finding unique strings in SPENDINGS and categorise it +# Path to the directory containing the file +output_directory2 = '/home/eikov/fin/analysis/' +# Specify the filename +filename2 = 'merged_spending_data.xlsx' +# Construct the full path +file_path2 = f'{output_directory2}/{filename2}' + +# Initialize an empty set to store unique values +unique_values2 = set() + +# Read the Excel file +xls = pd.ExcelFile(file_path2) + +# Iterate over each sheet in the Excel file +for sheet_name in xls.sheet_names: + # Read the sheet into a DataFrame + df = pd.read_excel(xls, sheet_name) + # Check the number of columns in the DataFrame + if df.shape[1] < 4: # If the number of columns is less than 4 + print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.") + continue # Skip to the next sheet + # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2) + unique_values2.update(df.iloc[:, 2].unique()) + +# Create a DataFrame with unique values and save it to 'categories.xlsx' +unique_df = pd.DataFrame({'Unique Values': list(unique_values2)}) +unique_df.to_excel(f'{output_directory}/categories_spendings.xlsx', index=False) + + + + + + + +""" +#turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx +taipogi unikaliu moketoju/gaveju sarasus is pajamu ir islaidu. rankiniu budu priskiriu kiekvienam gavejui/moketojui +kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx. +priedo, manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo. +belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile. +""" +""" + # Save the merged workbook to the output directory + output_filename = os.path.join(output_directory, "merged_income_data.xlsx") + merged_workbook.save(output_filename) +""" +#INCOME +def merge_additional_sheets(base_file, additional_files, output_file): + # Create the output directory if it doesn't exist + output_directory_analysis = '/home/eikov/fin/analysis' + os.makedirs(output_directory_analysis, exist_ok=True) +""" + # Create a Pandas ExcelWriter object + with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer: + # Write the base file to the output file + base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None) + for sheet_name, df in base_df.items(): + df.to_excel(writer, sheet_name=sheet_name, index=False) + + # Write additional files to the output file as new sheets + for file in additional_files: + additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None) + for sheet_name, df in additional_df.items(): + df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) +""" +# List of additional files to merge as new sheets +additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx'] + +# Merge additional sheets with merged_income_data.xlsx +merge_additional_sheets('merged_income_data.xlsx', additional_files, 'Galutinis_Income.xlsx') + + +#SPENDINGS +def merge_additional_sheets(base_file, additional_files, output_file): + # Create the output directory if it doesn't exist + output_directory_analysis_s = '/home/eikov/fin/analysis' + os.makedirs(output_directory_analysis_s, exist_ok=True) + + # Create a Pandas ExcelWriter object + with pd.ExcelWriter(f'{output_directory_analysis_s}/{output_file}', engine='xlsxwriter') as writer: + # Write the base file to the output file + base_df = pd.read_excel(f'{output_directory_analysis_s}/{base_file}', sheet_name=None) + for sheet_name, df in base_df.items(): + df.to_excel(writer, sheet_name=sheet_name, index=False) + + # Write additional files to the output file as new sheets + for file in additional_files: + additional_df = pd.read_excel(f'{output_directory_analysis_s}/{file}', sheet_name=None) + for sheet_name, df in additional_df.items(): + df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) + +# List of additional files to merge as new sheets +additional_files = ['cat-spendings.xlsx', 'Analysis_spending.xlsx'] + +# Merge additional sheets with merged_income_data.xlsx +merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx') + + +""" +#turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx +taipogi unikaliu moketoju/gaveju sarasus is pajamu ir islaidu. rankiniu budu priskiriu kiekvienam gavejui/moketojui +kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx. +priedo, manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo. +belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile. +""" +""" + # Save the merged workbook to the output directory + output_filename = os.path.join(output_directory, "merged_income_data.xlsx") + merged_workbook.save(output_filename) +""" +#INCOME +def merge_additional_sheets(base_file, additional_files, output_file): + # Create the output directory if it doesn't exist + output_directory_analysis = '/home/eikov/fin/analysis' + os.makedirs(output_directory_analysis, exist_ok=True) + + # Create a Pandas ExcelWriter object + with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer: + # Write the base file to the output file + base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None) + for sheet_name, df in base_df.items(): + df.to_excel(writer, sheet_name=sheet_name, index=False) + # Add total sum formula to the end of the last row + last_row = df.shape[0] + 1 + formula_str = f'=SUM(B2:B{last_row})' + writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) + + # Write additional files to the output file as new sheets + for file in additional_files: + additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None) + for sheet_name, df in additional_df.items(): + df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) + # Add total sum formula to the end of the last row + if file not in additional_files: + last_row = df.shape[0] + 1 + formula_str = f'=SUM(B2:B{last_row})' + writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) + +# List of additional files to merge as new sheets +additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx'] + +# Merge additional sheets with merged_income_data.xlsx +merge_additional_sheets('merged_income_data.xlsx', additional_files, 'Galutinis_Income.xlsx') + + +#SPENDINGS +def merge_additional_sheets(base_file, additional_files, output_file): + # Create the output directory if it doesn't exist + output_directory_analysis = '/home/eikov/fin/analysis' + os.makedirs(output_directory_analysis, exist_ok=True) + + # Create a Pandas ExcelWriter object + with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer: + # Write the base file to the output file + base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None) + for sheet_name, df in base_df.items(): + df.to_excel(writer, sheet_name=sheet_name, index=False) + # Add total sum formula to the end of the last row + last_row = df.shape[0] + 1 + formula_str = f'=SUM(B2:B{last_row})' + writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) + + # Write additional files to the output file as new sheets + for file in additional_files: + additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None) + for sheet_name, df in additional_df.items(): + df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) + # Add total sum formula to the end of the last row + if file not in additional_files: + last_row = df.shape[0] + 1 + formula_str = f'=SUM(B2:B{last_row})' + writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) + +# List of additional files to merge as new sheets +additional_files = ['cat-spendings.xlsx', 'Analysis_spending.xlsx'] + +# Merge additional sheets with merged_income_data.xlsx +merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx') From 1c1443461978eeda22151be42c1fdcba79bf295f Mon Sep 17 00:00:00 2001 From: br34th5 Date: Tue, 7 May 2024 22:30:46 +0300 Subject: [PATCH 06/23] cleaned some junk --- src/personal_finance/v2.2.py | 68 ------------------------------------ 1 file changed, 68 deletions(-) diff --git a/src/personal_finance/v2.2.py b/src/personal_finance/v2.2.py index ac55d17..edd63b6 100644 --- a/src/personal_finance/v2.2.py +++ b/src/personal_finance/v2.2.py @@ -21,9 +21,6 @@ """ """ -#next: in 4. modify Excel file: at the end of the entries in column "SUMA" make a formula to add sum all values of that column in order to calculate total income/spending. -or if it's not possible then simply add sum function to B80 cell. - #next: in 8: create DF for each found excel spending(-) file(ignore the last row with total sum) and join into one DF. now iterate each unique value and sum all "SUMA" values. if that's not possible, convert DF to set and then sum key values. """ @@ -350,71 +347,6 @@ def extract_first_2_words(text): - -""" -#turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx -taipogi unikaliu moketoju/gaveju sarasus is pajamu ir islaidu. rankiniu budu priskiriu kiekvienam gavejui/moketojui -kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx. -priedo, manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo. -belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile. -""" -""" - # Save the merged workbook to the output directory - output_filename = os.path.join(output_directory, "merged_income_data.xlsx") - merged_workbook.save(output_filename) -""" -#INCOME -def merge_additional_sheets(base_file, additional_files, output_file): - # Create the output directory if it doesn't exist - output_directory_analysis = '/home/eikov/fin/analysis' - os.makedirs(output_directory_analysis, exist_ok=True) -""" - # Create a Pandas ExcelWriter object - with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer: - # Write the base file to the output file - base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None) - for sheet_name, df in base_df.items(): - df.to_excel(writer, sheet_name=sheet_name, index=False) - - # Write additional files to the output file as new sheets - for file in additional_files: - additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None) - for sheet_name, df in additional_df.items(): - df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) -""" -# List of additional files to merge as new sheets -additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx'] - -# Merge additional sheets with merged_income_data.xlsx -merge_additional_sheets('merged_income_data.xlsx', additional_files, 'Galutinis_Income.xlsx') - - -#SPENDINGS -def merge_additional_sheets(base_file, additional_files, output_file): - # Create the output directory if it doesn't exist - output_directory_analysis_s = '/home/eikov/fin/analysis' - os.makedirs(output_directory_analysis_s, exist_ok=True) - - # Create a Pandas ExcelWriter object - with pd.ExcelWriter(f'{output_directory_analysis_s}/{output_file}', engine='xlsxwriter') as writer: - # Write the base file to the output file - base_df = pd.read_excel(f'{output_directory_analysis_s}/{base_file}', sheet_name=None) - for sheet_name, df in base_df.items(): - df.to_excel(writer, sheet_name=sheet_name, index=False) - - # Write additional files to the output file as new sheets - for file in additional_files: - additional_df = pd.read_excel(f'{output_directory_analysis_s}/{file}', sheet_name=None) - for sheet_name, df in additional_df.items(): - df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) - -# List of additional files to merge as new sheets -additional_files = ['cat-spendings.xlsx', 'Analysis_spending.xlsx'] - -# Merge additional sheets with merged_income_data.xlsx -merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx') - - """ #turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx taipogi unikaliu moketoju/gaveju sarasus is pajamu ir islaidu. rankiniu budu priskiriu kiekvienam gavejui/moketojui From e9a07ef68f81794afc3e286213e79b9885712d93 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Wed, 8 May 2024 20:36:54 +0300 Subject: [PATCH 07/23] added functionality to smoothly merge new posts with old entries --- .../discord_extract/disc_extract_c.py | 44 +++++++++++++------ .../discord_extract/disc_extract_s.py | 42 +++++++++++++----- .../discord_extract/json_to_txt.py | 2 +- .../discord_extract/json_to_txt2.py | 2 + 4 files changed, 64 insertions(+), 26 deletions(-) diff --git a/src/personal_notes/discord_extract/disc_extract_c.py b/src/personal_notes/discord_extract/disc_extract_c.py index b93b3ab..11051c9 100644 --- a/src/personal_notes/discord_extract/disc_extract_c.py +++ b/src/personal_notes/discord_extract/disc_extract_c.py @@ -33,7 +33,7 @@ # Define rate limit settings RATE_LIMIT_THRESHOLD = 3 # Maximum number of requests per second RATE_LIMIT_BUCKET = RATE_LIMIT_THRESHOLD # Initial token bucket capacity -categories_to_monitor = ['Archived'] #!!! manually choose categories +categories_to_monitor = ['Aktualijos'] #!!! manually choose categories # Initialize request counter request_count = 0 @@ -145,19 +145,37 @@ def remove_duplicates(json_file): # Drop duplicates based on message ID clean_df = df.drop_duplicates(subset=['id']) - # Open the new file to save cleaned messages - with open('clean_messages.json', 'w') as file: - # Serialize each message individually and write them to the file + # Open the file to append cleaned messages + # Set to store unique message IDs encountered so far + unique_message_ids = set() + + # Open the file to append cleaned messages + with open('clean_messages.json', 'a') as file: + # Read existing messages to populate unique_message_ids set + with open('clean_messages.json', 'r') as existing_file: + for line in existing_file: + try: + existing_message = json.loads(line) + unique_message_ids.add(existing_message['id']) + except json.JSONDecodeError as e: + print(f"Error loading JSON data: {e}. Skipping this line and continuing.") + + # Serialize each message individually and write them to the file if the ID is not already present for _, message_row in clean_df.iterrows(): - message_data = { - 'id': message_row['id'], - 'category': message_row['category'], - 'channel': message_row['channel'], - 'content': message_row['content'], - 'timestamp': message_row['timestamp'] - } - json.dump(message_data, file) - file.write('\n') # Add newline for readability + message_id = message_row['id'] + if message_id not in unique_message_ids: + message_data = { + 'id': message_id, + 'category': message_row['category'], + 'channel': message_row['channel'], + 'content': message_row['content'], + 'timestamp': message_row['timestamp'] + } + json.dump(message_data, file) + file.write('\n') # Add newline for readability + unique_message_ids.add(message_id) # Add the ID to the set of unique IDs + + # Example usage: remove_duplicates('messages.json') diff --git a/src/personal_notes/discord_extract/disc_extract_s.py b/src/personal_notes/discord_extract/disc_extract_s.py index b62de7d..457106e 100644 --- a/src/personal_notes/discord_extract/disc_extract_s.py +++ b/src/personal_notes/discord_extract/disc_extract_s.py @@ -141,19 +141,37 @@ def remove_duplicates(json_file): # Drop duplicates based on message ID clean_df = df.drop_duplicates(subset=['id']) - # Open the new file to save cleaned messages - with open('clean_messages.json', 'w') as file: - # Serialize each message individually and write them to the file + # Open the file to append cleaned messages + # Set to store unique message IDs encountered so far + unique_message_ids = set() + + # Open the file to append cleaned messages + with open('clean_messages.json', 'a') as file: + # Read existing messages to populate unique_message_ids set + with open('clean_messages.json', 'r') as existing_file: + for line in existing_file: + try: + existing_message = json.loads(line) + unique_message_ids.add(existing_message['id']) + except json.JSONDecodeError as e: + print(f"Error loading JSON data: {e}. Skipping this line and continuing.") + + # Serialize each message individually and write them to the file if the ID is not already present for _, message_row in clean_df.iterrows(): - message_data = { - 'id': message_row['id'], - 'category': message_row['category'], - 'channel': message_row['channel'], - 'content': message_row['content'], - 'timestamp': message_row['timestamp'] - } - json.dump(message_data, file) - file.write('\n') # Add newline for readability + message_id = message_row['id'] + if message_id not in unique_message_ids: + message_data = { + 'id': message_id, + 'category': message_row['category'], + 'channel': message_row['channel'], + 'content': message_row['content'], + 'timestamp': message_row['timestamp'] + } + json.dump(message_data, file) + file.write('\n') # Add newline for readability + unique_message_ids.add(message_id) # Add the ID to the set of unique IDs + + # Example usage: remove_duplicates('messages.json') \ No newline at end of file diff --git a/src/personal_notes/discord_extract/json_to_txt.py b/src/personal_notes/discord_extract/json_to_txt.py index f7633f2..9f6fcd7 100644 --- a/src/personal_notes/discord_extract/json_to_txt.py +++ b/src/personal_notes/discord_extract/json_to_txt.py @@ -2,7 +2,7 @@ import json import pandas as pd -#next: use regex for <#832925562611302400> format. either show these numbers as letters and tags or don't show at all + # Load the messages from the JSON file with open('clean_messages.json', 'r', encoding='utf-8') as file: diff --git a/src/personal_notes/discord_extract/json_to_txt2.py b/src/personal_notes/discord_extract/json_to_txt2.py index 154b6b9..bba8115 100644 --- a/src/personal_notes/discord_extract/json_to_txt2.py +++ b/src/personal_notes/discord_extract/json_to_txt2.py @@ -3,6 +3,8 @@ import pandas as pd import re +#next: use regex for <#832925562611302400> format. either show these numbers as letters and tags or don't show at all + # Load the messages from the JSON file with open('clean_messages.json', 'r', encoding='utf-8') as file: messages = [json.loads(line) for line in file] From f873c6b8a1d125637044e7515923de9c78c66726 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Fri, 18 Jul 2025 14:45:56 +0300 Subject: [PATCH 08/23] comment --- Data-Engineering | 1 + __pycache__/pandas.cpython-311.pyc | Bin 0 -> 453 bytes .../__pycache__/config.cpython-311.pyc | Bin .../__pycache__/config.cpython-312.pyc | Bin src/LoL-support/Database/config.py | 0 src/LoL-support/Database/main.py | 9 - .../Leage_workspace.code-workspace | 8 + src/LoL-support/archive/adc_names.json | 0 src/LoL-support/archive/jungler_names.json | 0 src/LoL-support/archive/mid_names.json | 0 src/LoL-support/archive/step5-regex.py | 0 src/LoL-support/champion_names_test.json | 0 src/LoL-support/counters_processed.json | 148 +- src/LoL-support/data.json | 84 +- src/LoL-support/final_data.json | 1839 +++++++---------- src/LoL-support/how-to-start.md | 0 src/LoL-support/lolchamps.sh | 24 +- src/LoL-support/pick_rates.json | 158 +- src/LoL-support/sorted_final_data.json | 1833 +++++++--------- src/LoL-support/step1-lolchamps.py | 0 src/LoL-support/step2-lolchamps.py | 0 src/LoL-support/step3-lolchamps.py | 0 src/LoL-support/step4-lolchamps.py | 0 src/LoL-support/step5-lolchamps.py | 0 src/LoL-support/step6-lolchamps.py | 0 src/LoL-support/step7-lolchamps.py | 0 src/LoL-support/step8-db.py | 0 src/LoL-support/support_combined.json | 842 +++----- src/LoL-support/support_names.json | 2 +- src/LoL-support/synergies_list.json | 973 ++++----- src/personal_finance/.txt | 0 .../2024_02-04_spendings.xlsx | Bin 0 -> 26172 bytes .../Manually polished/Galutinis_Income.xlsx | Bin 0 -> 14338 bytes src/personal_finance/fin/scrape.py | 230 +++ src/personal_finance/run_script.sh | 0 src/personal_finance/scheme.txt | 8 +- src/personal_finance/v1.7.py | 0 src/personal_finance/v1.8.py | 0 src/personal_finance/v1.9.py | 0 src/personal_finance/v2.1.py | 0 src/personal_finance/v2.2.py | 0 src/personal_finance/v2.py | 0 src/personal_notes/clickup_extract/plan.txt | 11 + src/personal_notes/clickup_extract/tempy.py | 104 + .../{clickup_notes_split.py => v1clean.py} | 5 +- .../clickup_extract/v2sorted_messy.py | 74 + .../clickup_extract/v3.0clean_sorted.py | 99 + src/personal_notes/clickup_extract/v3.1.py | 110 + src/personal_notes/clickup_extract/v3.2.py | 84 + src/personal_notes/clickup_extract/v3.3.py | 109 + src/personal_notes/clickup_extract/v3.4.0.py | 117 ++ .../clickup_extract/v3.4.1.ALPHA.py | 119 ++ .../clickup_extract/v4.1.1.ALPHA.py | 118 ++ .../clickup_extract/v4.1.2.FINAL.py | 119 ++ .../discord_extract/2024-05-extract.txt | 4 +- .../__pycache__/json.cpython-311.pyc | Bin .../__pycache__/responses.cpython-311.pyc | Bin .../discord_extract/cleaning.py | 6 +- .../discord_extract/disc_del_channels.py | 0 .../discord_extract/disc_del_only.py | 64 + .../discord_extract/disc_extract_c.py | 0 .../discord_extract/disc_extract_s.py | 0 src/personal_notes/discord_extract/json3.py | 49 + .../discord_extract/json_to_txt.py | 0 .../discord_extract/json_to_txt2.py | 0 .../discord_extract/migration.txt | 11 - .../discord_extract/responses.py | 0 src/personal_notes/discord_extract/scheme.txt | 5 - .../.py => w-automation/plan.txt} | 0 69 files changed, 3867 insertions(+), 3500 deletions(-) create mode 160000 Data-Engineering create mode 100755 __pycache__/pandas.cpython-311.pyc mode change 100644 => 100755 src/LoL-support/Database/__pycache__/config.cpython-311.pyc mode change 100644 => 100755 src/LoL-support/Database/__pycache__/config.cpython-312.pyc mode change 100644 => 100755 src/LoL-support/Database/config.py mode change 100644 => 100755 src/LoL-support/Database/main.py create mode 100755 src/LoL-support/Leage_workspace.code-workspace mode change 100644 => 100755 src/LoL-support/archive/adc_names.json mode change 100644 => 100755 src/LoL-support/archive/jungler_names.json mode change 100644 => 100755 src/LoL-support/archive/mid_names.json mode change 100644 => 100755 src/LoL-support/archive/step5-regex.py mode change 100644 => 100755 src/LoL-support/champion_names_test.json mode change 100644 => 100755 src/LoL-support/counters_processed.json mode change 100644 => 100755 src/LoL-support/data.json mode change 100644 => 100755 src/LoL-support/final_data.json mode change 100644 => 100755 src/LoL-support/how-to-start.md mode change 100644 => 100755 src/LoL-support/pick_rates.json mode change 100644 => 100755 src/LoL-support/sorted_final_data.json mode change 100644 => 100755 src/LoL-support/step1-lolchamps.py mode change 100644 => 100755 src/LoL-support/step2-lolchamps.py mode change 100644 => 100755 src/LoL-support/step3-lolchamps.py mode change 100644 => 100755 src/LoL-support/step4-lolchamps.py mode change 100644 => 100755 src/LoL-support/step5-lolchamps.py mode change 100644 => 100755 src/LoL-support/step6-lolchamps.py mode change 100644 => 100755 src/LoL-support/step7-lolchamps.py mode change 100644 => 100755 src/LoL-support/step8-db.py mode change 100644 => 100755 src/LoL-support/support_combined.json mode change 100644 => 100755 src/LoL-support/support_names.json mode change 100644 => 100755 src/LoL-support/synergies_list.json mode change 100644 => 100755 src/personal_finance/.txt create mode 100755 src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx create mode 100755 src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx create mode 100755 src/personal_finance/fin/scrape.py mode change 100644 => 100755 src/personal_finance/run_script.sh mode change 100644 => 100755 src/personal_finance/scheme.txt mode change 100644 => 100755 src/personal_finance/v1.7.py mode change 100644 => 100755 src/personal_finance/v1.8.py mode change 100644 => 100755 src/personal_finance/v1.9.py mode change 100644 => 100755 src/personal_finance/v2.1.py mode change 100644 => 100755 src/personal_finance/v2.2.py mode change 100644 => 100755 src/personal_finance/v2.py create mode 100755 src/personal_notes/clickup_extract/plan.txt create mode 100755 src/personal_notes/clickup_extract/tempy.py rename src/personal_notes/clickup_extract/{clickup_notes_split.py => v1clean.py} (88%) mode change 100644 => 100755 create mode 100755 src/personal_notes/clickup_extract/v2sorted_messy.py create mode 100755 src/personal_notes/clickup_extract/v3.0clean_sorted.py create mode 100755 src/personal_notes/clickup_extract/v3.1.py create mode 100755 src/personal_notes/clickup_extract/v3.2.py create mode 100755 src/personal_notes/clickup_extract/v3.3.py create mode 100755 src/personal_notes/clickup_extract/v3.4.0.py create mode 100755 src/personal_notes/clickup_extract/v3.4.1.ALPHA.py create mode 100755 src/personal_notes/clickup_extract/v4.1.1.ALPHA.py create mode 100755 src/personal_notes/clickup_extract/v4.1.2.FINAL.py mode change 100644 => 100755 src/personal_notes/discord_extract/2024-05-extract.txt mode change 100644 => 100755 src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc mode change 100644 => 100755 src/personal_notes/discord_extract/__pycache__/responses.cpython-311.pyc mode change 100644 => 100755 src/personal_notes/discord_extract/cleaning.py mode change 100644 => 100755 src/personal_notes/discord_extract/disc_del_channels.py create mode 100755 src/personal_notes/discord_extract/disc_del_only.py mode change 100644 => 100755 src/personal_notes/discord_extract/disc_extract_c.py mode change 100644 => 100755 src/personal_notes/discord_extract/disc_extract_s.py create mode 100755 src/personal_notes/discord_extract/json3.py mode change 100644 => 100755 src/personal_notes/discord_extract/json_to_txt.py mode change 100644 => 100755 src/personal_notes/discord_extract/json_to_txt2.py delete mode 100644 src/personal_notes/discord_extract/migration.txt mode change 100644 => 100755 src/personal_notes/discord_extract/responses.py mode change 100644 => 100755 src/personal_notes/discord_extract/scheme.txt rename src/{personal_notes/discord_extract/.py => w-automation/plan.txt} (100%) mode change 100644 => 100755 diff --git a/Data-Engineering b/Data-Engineering new file mode 160000 index 0000000..55c20f1 --- /dev/null +++ b/Data-Engineering @@ -0,0 +1 @@ +Subproject commit 55c20f1bc3adc060f63dd41b74939eeddfad8604 diff --git a/__pycache__/pandas.cpython-311.pyc b/__pycache__/pandas.cpython-311.pyc new file mode 100755 index 0000000000000000000000000000000000000000..2adffa1940e6d9bd5c68db80e984367581e05173 GIT binary patch literal 453 zcmY*VyH3L}6m`-Tg-0bM7Sx@gL*hP|5JG@j35HgPvQUP~jc?nuPHe?VP+{r72e2`* zf)GCg5P(E6)90{ulp=v}P^nrW&vP(JmW$ok!LF9?J@z}W7XHV=IX(xfCHmMmMLv~0#h zYVvj;?7)-(_emElA_V0walAzupP$w$5CP)?{KmjxOx7W|<^I6DxGoe6#cJLB7siaV zC9ZKAGT18>_Hkl}uefLcV~*E7VeQaW-ZIK(;x$1$N@T1H8XGFq5+vxwh7W|cl#F%S zc|+6WGqW??<1S!u20X&9<2g20rDY6WB@D+)0MY)>QWklKHg_9+QGvm;m zzwXH2R2L_`la|^hDNWPHXy;=!Cuy`b&_~8Nvpu?exOv?D%;df@xsjd>e_fUQ0c53z Ab^rhX literal 0 HcmV?d00001 diff --git a/src/LoL-support/Database/__pycache__/config.cpython-311.pyc b/src/LoL-support/Database/__pycache__/config.cpython-311.pyc old mode 100644 new mode 100755 diff --git a/src/LoL-support/Database/__pycache__/config.cpython-312.pyc b/src/LoL-support/Database/__pycache__/config.cpython-312.pyc old mode 100644 new mode 100755 diff --git a/src/LoL-support/Database/config.py b/src/LoL-support/Database/config.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/Database/main.py b/src/LoL-support/Database/main.py old mode 100644 new mode 100755 index 2d79050..6654d6f --- a/src/LoL-support/Database/main.py +++ b/src/LoL-support/Database/main.py @@ -2,15 +2,6 @@ from config import config # Connect to the PostgreSQL database -"""conn = psycopg2.connect( - host="127.0.0.1", - port="5432", - dbname="league", - user="postgres", - password="0809" -) -""" - def connect(): connection = None try: diff --git a/src/LoL-support/Leage_workspace.code-workspace b/src/LoL-support/Leage_workspace.code-workspace new file mode 100755 index 0000000..407c760 --- /dev/null +++ b/src/LoL-support/Leage_workspace.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "../.." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/src/LoL-support/archive/adc_names.json b/src/LoL-support/archive/adc_names.json old mode 100644 new mode 100755 diff --git a/src/LoL-support/archive/jungler_names.json b/src/LoL-support/archive/jungler_names.json old mode 100644 new mode 100755 diff --git a/src/LoL-support/archive/mid_names.json b/src/LoL-support/archive/mid_names.json old mode 100644 new mode 100755 diff --git a/src/LoL-support/archive/step5-regex.py b/src/LoL-support/archive/step5-regex.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/champion_names_test.json b/src/LoL-support/champion_names_test.json old mode 100644 new mode 100755 diff --git a/src/LoL-support/counters_processed.json b/src/LoL-support/counters_processed.json old mode 100644 new mode 100755 index 43dbd0d..ae49a71 --- a/src/LoL-support/counters_processed.json +++ b/src/LoL-support/counters_processed.json @@ -1,166 +1,86 @@ [ - { - "champion_name": "Taric", - "counters": "Soraka(55.26%), Rell(52.91%), Vel'Koz(52.75%), Janna(50.88%), Zilean(50.55%), Rakan(49.62%), Maokai(49.58%), Nami(49.42%), Xerath(49.28%), Senna(49.24%)" - }, - { - "champion_name": "Janna", - "counters": "Senna(50.79%), Sona(50.1%), Soraka(50.01%), Seraphine(49.87%), Milio(49.71%), Neeko(49.67%), Rell(49.5%), Bard(49.44%), Rakan(49.42%), Blitzcrank(49.4%)" - }, { "champion_name": "Maokai", - "counters": "Shaco(58.18%), Braum(54.8%), Sona(53.99%), Janna(52.72%), Renata Glasc(52.39%), Zyra(51.46%), Alistar(51.45%), Soraka(50.8%), Taric(50.42%), Neeko(50.29%)" - }, - { - "champion_name": "Bard", - "counters": "Taric(52.2%), Janna(50.56%), Maokai(50.28%), Zilean(49.95%), Senna(49.94%), Blitzcrank(49.89%), Renata Glasc(49.52%), Soraka(49.45%), Rell(49.3%), Pyke(49.06%)" - }, - { - "champion_name": "Rakan", - "counters": "Senna(52.31%), Bard(51.42%), Soraka(51.41%), Neeko(51.37%), Renata Glasc(51.09%), Sona(50.93%), Maokai(50.87%), Rell(50.69%), Zilean(50.64%), Janna(50.58%)" - }, - { - "champion_name": "Sona", - "counters": "Taric(56.83%), Leona(55.04%), Seraphine(53.49%), Blitzcrank(52.56%), Senna(51.71%), Bard(51.4%), Nami(50.59%), Milio(50.43%), Zilean(50.26%), Pyke(50.19%)" - }, - { - "champion_name": "Senna", - "counters": "Pyke(52.31%), Blitzcrank(52.25%), Xerath(51.66%), Maokai(51.62%), Zyra(51.1%), Taric(50.76%), Leona(50.22%), Bard(50.06%), Lux(49.9%), Rell(49.8%)" + "counters": "Taric(50.48%), Alistar(49.83%), Braum(49.49%), Leona(49.02%), Janna(48.98%), Renata Glasc(48.48%), Rell(47.97%), Shaco(47.18%), Camille(47.04%), Zyra(47.02%)" }, { - "champion_name": "Rell", - "counters": "Poppy(53.79%), Seraphine(53.57%), Soraka(52.57%), Zilean(52.54%), Alistar(52.45%), Shaco(51.5%), Maokai(50.89%), Sona(50.75%), Bard(50.7%), Janna(50.5%)" - }, - { - "champion_name": "Neeko", - "counters": "Vel'Koz(60.45%), Sona(53.26%), Bard(52.61%), Brand(52.38%), Taric(52.17%), Alistar(51%), Shaco(50.68%), Senna(50.42%), Renata Glasc(50.38%), Morgana(50.35%)" + "champion_name": "Janna", + "counters": "Camille(53.25%), Sona(51.89%), Blitzcrank(51.2%), Maokai(51.02%), Braum(49.67%), Pyke(49.38%), Renata Glasc(49.3%), Senna(49.25%), Ashe(49.07%), Nami(48.64%)" }, { - "champion_name": "Soraka", - "counters": "Blitzcrank(54.17%), Neeko(51.31%), Sona(51.25%), Zyra(51.13%), Vel'Koz(51.05%), Bard(50.55%), Senna(50.38%), Yuumi(50.06%), Janna(49.99%), Pyke(49.98%)" + "champion_name": "Taric", + "counters": "Swain(57.63%), Sona(57.32%), Janna(54.88%), Nami(54.27%), Lux(53.54%), Seraphine(53.45%), Bard(52.97%), Soraka(52.32%), Thresh(52.21%), Braum(51.67%)" }, { "champion_name": "Blitzcrank", - "counters": "Taric(56.85%), Rakan(52.88%), Leona(52.47%), Braum(52.28%), Renata Glasc(51.05%), Maokai(51.04%), Zyra(50.98%), Rell(50.78%), Alistar(50.73%), Zilean(50.7%)" + "counters": "Pantheon(54.85%), Leona(54.08%), Maokai(53.66%), Braum(53.61%), Rakan(53.26%), Taric(53.13%), Alistar(52.33%), Shaco(51.5%), Rell(50.9%), Zilean(50.57%)" }, { - "champion_name": "Zyra", - "counters": "Sona(53.56%), Pyke(53.25%), Heimerdinger(53.11%), Bard(53.07%), Leona(51.91%), Janna(51.88%), Shaco(51.56%), Taric(51.43%), Rakan(50.98%), Nautilus(50.75%)" + "champion_name": "Camille", + "counters": "Taric(58.76%), Renata Glasc(58.12%), Braum(57.32%), Leona(54.48%), Brand(54.2%), Bard(53.76%), Zac(53.03%), Maokai(52.96%), Tahm Kench(52.94%), Zilean(52.78%)" }, { - "champion_name": "Pyke", - "counters": "Rakan(53.38%), Maokai(52.78%), Neeko(52.1%), Rell(52.08%), Taric(51.82%), Janna(51.79%), Blitzcrank(51.44%), Alistar(50.94%), Bard(50.94%), Thresh(50.49%)" + "champion_name": "Zac", + "counters": "Pantheon(61.36%), Taric(58.82%), Zilean(58.49%), Rell(56.82%), Bard(56.55%), Morgana(55.93%), Janna(54.44%), Maokai(53.44%), Milio(53.25%), Thresh(53.18%)" }, { - "champion_name": "Zilean", - "counters": "Janna(53.27%), Shaco(53.02%), Senna(51.99%), Neeko(51.67%), Maokai(51.41%), Xerath(51.07%), Milio(50.67%), Sylas(50.34%), Soraka(50.28%), Vel'Koz(50.23%)" + "champion_name": "Bard", + "counters": "Maokai(56.1%), Renata Glasc(53.77%), Janna(53.45%), Pyke(51.79%), Morgana(51.04%), Nami(50.92%), Pantheon(50.72%), Blitzcrank(50.43%), Brand(50.34%), Senna(50.21%)" }, { - "champion_name": "Braum", - "counters": "Zilean(55.95%), Soraka(54.2%), Bard(53.76%), Rell(53.74%), Taric(53.44%), Sona(53.2%), Senna(53.06%), Rakan(52.88%), Janna(52.49%), Renata Glasc(52.31%)" + "champion_name": "Sona", + "counters": "Zilean(61.68%), Rell(59.62%), Leona(57.21%), Neeko(55.71%), Thresh(55.03%), Maokai(54.7%), Bard(53.59%), Braum(51.81%), Blitzcrank(51.79%), Seraphine(51.2%)" }, { - "champion_name": "Vel'Koz", - "counters": "Pyke(55.14%), Zyra(54.76%), Seraphine(54.15%), Blitzcrank(53.89%), Janna(53.62%), Bard(53.22%), Sona(52.97%), Nami(52.1%), Maokai(51.85%), Renata Glasc(51.64%)" + "champion_name": "Zyra", + "counters": "Taric(54.73%), Leona(54.05%), Sona(53.31%), Maokai(52.98%), Renata Glasc(52.84%), Pyke(52.6%), Zac(52.59%), Janna(52.42%), Zilean(51.88%), Yuumi(51.63%)" }, { - "champion_name": "Thresh", - "counters": "Taric(53.39%), Janna(52.43%), Brand(52.12%), Rakan(52.02%), Neeko(51.75%), Sona(51.47%), Maokai(51.34%), Vel'Koz(51.27%), Bard(51.24%), Renata Glasc(51.24%)" + "champion_name": "Nautilus", + "counters": "Taric(60.99%), Leona(57.54%), Braum(56.73%), Renata Glasc(55.96%), Alistar(55.65%), Rell(55.38%), Camille(54.74%), Pantheon(54.53%), Swain(54.35%), Maokai(53.72%)" }, { "champion_name": "Milio", - "counters": "Taric(54.79%), Blitzcrank(53.66%), Braum(53.22%), Vel'Koz(53.08%), Senna(52.78%), Bard(52.75%), Neeko(52.17%), Rakan(51.91%), Soraka(51.52%), Thresh(51.33%)" - }, - { - "champion_name": "Alistar", - "counters": "Sona(54.51%), Janna(54.04%), Swain(53.24%), Vel'Koz(52.78%), Senna(52.64%), Zilean(52.39%), Soraka(52.22%), Seraphine(51.97%), Xerath(51.86%), Milio(51.85%)" - }, - { - "champion_name": "Zac", - "counters": "Poppy(67.39%), Maokai(57.14%), Neeko(56.72%), Swain(56%), Vel'Koz(54.1%), Taric(53.85%), Alistar(53.74%), Ashe(53.49%), Rakan(53.42%), Zilean(53.26%)" + "counters": "Blitzcrank(56.32%), Braum(55.45%), Maokai(54.37%), Camille(53.86%), Janna(53.43%), Thresh(53.37%), Zilean(53.04%), Tahm Kench(53.04%), Vel'Koz(52.98%), Leona(52.96%)" }, { - "champion_name": "Leona", - "counters": "Taric(56.7%), Janna(54.1%), Bard(54.03%), Shaco(53.67%), Maokai(53.55%), Rell(53.22%), Soraka(53.11%), Lux(53.09%), Morgana(53.02%), Braum(51.72%)" + "champion_name": "Nami", + "counters": "Morgana(56.08%), Blitzcrank(55.61%), Maokai(55.57%), Vel'Koz(54.47%), Rakan(54.44%), Neeko(54.21%), Rell(53.9%), Nautilus(53.32%), Thresh(53.28%), Senna(53.17%)" }, { "champion_name": "Xerath", - "counters": "Sylas(55%), Maokai(53.98%), Sona(53.77%), Janna(53.15%), Pyke(52.84%), Soraka(52.33%), Rakan(52.02%), Leona(51.76%), Zyra(51.72%), Nami(51.6%)" + "counters": "Blitzcrank(58.63%), Neeko(56.43%), Sona(56.36%), Maokai(56.26%), Pyke(56.15%), Leona(56.02%), Braum(55.06%), Rakan(54.83%), Nautilus(54.65%), Janna(53.86%)" }, { - "champion_name": "Nami", - "counters": "Maokai(56.37%), Neeko(53.14%), Blitzcrank(53.03%), Rakan(52.58%), Zyra(52.29%), Soraka(51.92%), Milio(51.67%), Pyke(51.56%), Janna(51.44%), Bard(51.33%)" + "champion_name": "Swain", + "counters": "Zac(58.97%), Sona(58.67%), Neeko(57.41%), Nami(57.14%), Milio(56.44%), Pantheon(55.95%), Zyra(55.49%), Morgana(55.24%), Leona(55%), Camille(54.79%)" }, { - "champion_name": "Shaco", - "counters": "Sona(60.95%), Swain(60.22%), Taric(59.68%), Heimerdinger(59.02%), Zac(56.14%), Janna(55.96%), Milio(54.49%), Pyke(54.23%), Rakan(53.2%), Bard(52.59%)" + "champion_name": "Lulu", + "counters": "Vel'Koz(57.35%), Blitzcrank(56.23%), Zilean(55.78%), Tahm Kench(55.43%), Renata Glasc(54.86%), Rell(54.7%), Janna(54.6%), Maokai(54.51%), Braum(53.79%), Ashe(53.65%)" }, { - "champion_name": "Heimerdinger", - "counters": "Maokai(61.64%), Neeko(56.82%), Seraphine(55.95%), Xerath(54.86%), Bard(54.55%), Leona(54.4%), Rell(53.15%), Rakan(52.67%), Lulu(52.56%), Janna(52.56%)" + "champion_name": "Ashe", + "counters": "Camille(58.72%), Maokai(57.34%), Pyke(56.14%), Blitzcrank(55.88%), Nautilus(55.46%), Taric(55.41%), Vel'Koz(54.55%), Zyra(54.13%), Soraka(53.87%), Xerath(53.83%)" }, { "champion_name": "Sylas", - "counters": "Sona(60%), Janna(57.91%), Vel'Koz(57.75%), Braum(57.52%), Morgana(57.5%), Taric(55.56%), Rell(54.55%), Rakan(53.89%), Blitzcrank(53.74%), Thresh(53.64%)" - }, - { - "champion_name": "Tahm Kench", - "counters": "Taric(62.22%), Braum(61.11%), Bard(57.4%), Zyra(57.27%), Pyke(55.38%), Maokai(54.9%), Brand(54.67%), Lulu(54.55%), Renata Glasc(54.17%), Rakan(54.12%)" - }, - { - "champion_name": "Amumu", - "counters": "Maokai(66.07%), Zyra(61.68%), Rell(58.9%), Rakan(58.55%), Janna(58.5%), Neeko(57.14%), Taric(56.1%), Braum(55.41%), Bard(54%), Leona(53.91%)" - }, - { - "champion_name": "Twitch", - "counters": "Zilean(61.97%), Taric(60.47%), Leona(57.92%), Soraka(57.87%), Shaco(57.38%), Nami(57.14%), Rakan(56.34%), Sylas(55.56%), Brand(55.26%), Pyke(54.65%)" - }, - { - "champion_name": "Nautilus", - "counters": "Taric(55.98%), Rell(55.66%), Renata Glasc(55.13%), Sylas(55.09%), Braum(54.08%), Rakan(54.06%), Vel'Koz(53.45%), Swain(53.09%), Neeko(52.97%), Alistar(52.87%)" + "counters": "Taric(70%), Shaco(70%), Pantheon(64.86%), Neeko(63.16%), Thresh(58.58%), Lulu(58.52%), Morgana(58.06%), Janna(57.66%), Leona(57.58%), Bard(57.45%)" }, { - "champion_name": "Ashe", - "counters": "Pyke(56.98%), Sylas(54.95%), Blitzcrank(54.83%), Heimerdinger(54.8%), Rell(54.67%), Nautilus(54.35%), Bard(53.44%), Milio(53.1%), Rakan(52.69%), Brand(52.5%)" + "champion_name": "Neeko", + "counters": "Zilean(60.66%), Taric(58.7%), Zac(57.78%), Leona(57.76%), Pantheon(57.14%), Janna(56.74%), Blitzcrank(56.38%), Bard(55.85%), Camille(55.41%), Rell(55.19%)" }, { "champion_name": "Lux", - "counters": "Blitzcrank(56.57%), Maokai(54.94%), Zyra(54.66%), Shaco(54.45%), Vel'Koz(54.1%), Sylas(53.87%), Zilean(53.51%), Pyke(53.5%), Xerath(52.75%), Nami(52.72%)" - }, - { - "champion_name": "Brand", - "counters": "Sona(58.11%), Maokai(57.24%), Xerath(57.03%), Janna(56.31%), Pyke(56.01%), Zyra(55.13%), Leona(54.96%), Zilean(54.92%), Soraka(54.73%), Senna(54.19%)" - }, - { - "champion_name": "Seraphine", - "counters": "Sylas(58.33%), Maokai(57.24%), Zilean(56.09%), Bard(55.97%), Pyke(54.69%), Soraka(53.72%), Blitzcrank(53.61%), Yuumi(53.6%), Milio(52.99%), Thresh(52.66%)" - }, - { - "champion_name": "Lulu", - "counters": "Taric(56.84%), Zilean(54.43%), Neeko(54.04%), Senna(54.04%), Sona(53.78%), Zyra(53.7%), Maokai(53.46%), Blitzcrank(52.96%), Bard(52.89%), Thresh(52.88%)" - }, - { - "champion_name": "Morgana", - "counters": "Zyra(56.73%), Senna(56.35%), Rell(56.29%), Janna(55.6%), Milio(55.2%), Vel'Koz(55.08%), Nami(54.67%), Taric(54.31%), Karma(54.24%), Maokai(53.2%)" - }, - { - "champion_name": "Karma", - "counters": "Pyke(57.3%), Maokai(55.33%), Zyra(54.97%), Blitzcrank(54.92%), Rakan(54.52%), Janna(54.36%), Sona(54.33%), Nautilus(54.21%), Soraka(53.61%), Neeko(52.83%)" - }, - { - "champion_name": "Swain", - "counters": "Zilean(57.77%), Sona(57.32%), Vel'Koz(57.25%), Janna(56.41%), Brand(55.87%), Yuumi(55.78%), Zyra(55.78%), Xerath(55.48%), Soraka(55.34%), Senna(55.32%)" - }, - { - "champion_name": "Pantheon", - "counters": "Neeko(59.74%), Maokai(58.43%), Karma(57.22%), Bard(57.14%), Swain(56.25%), Rakan(55.92%), Brand(55.83%), Soraka(55.74%), Sona(55.56%), Janna(55.42%)" + "counters": "Camille(62.93%), Pantheon(60%), Pyke(58.92%), Bard(57.24%), Shaco(56.3%), Blitzcrank(55.42%), Maokai(55.39%), Leona(55.08%), Alistar(54.69%), Zilean(54.61%)" }, { "champion_name": "Yuumi", - "counters": "Maokai(61.28%), Rell(59.85%), Taric(57.47%), Alistar(57.31%), Nautilus(56.64%), Rakan(55.78%), Pyke(55.71%), Leona(55.63%), Sylas(55.27%), Thresh(55.23%)" + "counters": "Rell(62.65%), Braum(58.82%), Blitzcrank(58.05%), Leona(57.77%), Pyke(57.61%), Taric(57.46%), Swain(56.85%), Rakan(56.72%), Maokai(56.12%), Janna(55.85%)" }, { "champion_name": "Hwei", - "counters": "Taric(66.91%), Sona(66.55%), Blitzcrank(65.66%), Janna(65.3%), Brand(65.18%), Pyke(63.85%), Xerath(63.02%), Vel'Koz(62.71%), Morgana(62.31%), Milio(62.01%)" + "counters": "Camille(63.76%), Pyke(60.47%), Maokai(58.72%), Xerath(58.7%), Rakan(58.47%), Taric(58.43%), Swain(58.1%), Zilean(57.69%), Vel'Koz(57.69%), Janna(57.32%)" } ] \ No newline at end of file diff --git a/src/LoL-support/data.json b/src/LoL-support/data.json old mode 100644 new mode 100755 index 78e746e..8cdf817 --- a/src/LoL-support/data.json +++ b/src/LoL-support/data.json @@ -1,46 +1,46 @@ { "div_elements": [ - "
1
\"supp\"
A
52.89%
1.4%
0.2%
14,268
", - "
2
\"supp\"
S+
52.13%
9.6%
4.5%
100,593
", - "
3
\"supp\"
A
51.99%
1.6%
0.6%
16,964
", - "
4
\"supp\"
S
51.82%
6.1%
0.9%
63,276
", - "
5
\"supp\"
S+
51.78%
12.8%
7.9%
133,770
", - "
6
\"supp\"
A
51.38%
2.4%
0.1%
25,396
", - "
7
\"supp\"
S+
51.36%
11.0%
10.8%
114,521
", - "
8
\"supp\"
S
51.35%
4.9%
4.3%
51,691
", - "
9
\"supp\"
A
51.22%
1.9%
5.4%
19,946
", - "
10
\"supp\"
S
51.19%
5.5%
2.1%
56,990
", - "
11
\"supp\"
S+
51.19%
8.8%
34.3%
92,387
", - "
12
\"supp\"
S
51.05%
3.8%
2.9%
39,963
", - "
13
\"supp\"
S+
51.02%
7.6%
16.6%
79,272
", - "
14
\"supp\"
A
51.02%
2.5%
0.7%
26,487
", - "
15
\"supp\"
A
50.60%
3.6%
0.7%
37,859
", - "
16
\"supp\"
A
50.54%
5.0%
2.0%
52,551
", - "
17
\"supp\"
B
50.24%
1.6%
0.2%
16,351
", - "
18
\"supp\"
S
50.23%
13.3%
5.1%
138,739
", - "
19
\"supp\"
A
50.22%
6.7%
2.3%
69,909
", - "
20
\"supp\"
A
50.19%
5.8%
2.8%
60,227
", - "
21
\"supp\"
B
50.16%
0.7%
2.2%
7,320
", - "
22
\"supp\"
A
50.15%
4.8%
1.8%
50,417
", - "
23
\"supp\"
A
49.97%
4.8%
8.9%
50,300
", - "
24
\"supp\"
B
49.93%
6.5%
0.2%
68,410
", - "
25
\"supp\"
B
49.83%
1.1%
10.7%
11,342
", - "
26
\"supp\"
B
49.66%
1.0%
1.1%
10,869
", - "
27
\"supp\"
B
49.54%
1.0%
20.0%
10,213
", - "
28
\"supp\"
B
49.40%
0.6%
0.5%
6,065
", - "
29
\"supp\"
B
49.35%
0.6%
0.4%
6,207
", - "
30
\"supp\"
B
49.26%
0.8%
4.5%
7,940
", - "
31
\"supp\"
D
49.09%
10.9%
16.7%
114,188
", - "
32
\"supp\"
D
48.74%
5.7%
9.0%
59,273
", - "
33
\"supp\"
D
48.70%
5.1%
2.5%
53,737
", - "
34
\"supp\"
B
48.62%
2.0%
4.7%
21,261
", - "
35
\"supp\"
B
48.60%
2.0%
0.4%
20,739
", - "
36
\"supp\"
D
48.58%
7.4%
4.4%
76,847
", - "
37
\"supp\"
D
48.54%
3.2%
11.9%
33,911
", - "
38
\"supp\"
D
48.09%
7.6%
2.1%
79,566
", - "
39
\"supp\"
C
47.52%
1.6%
0.7%
16,361
", - "
40
\"supp\"
B
47.01%
1.0%
2.4%
10,881
", - "
41
\"supp\"
D
46.73%
4.9%
5.4%
51,482
", - "
42
\"supp\"
D
39.39%
2.1%
29.4%
22,098
" + "
1
\"supp\"
S+
53.96%
12.7%
29.6%
69,959
", + "
2
\"supp\"
S+
52.51%
10.7%
5.9%
58,717
", + "
3
\"supp\"
A
52.05%
1.5%
0.2%
8,252
", + "
4
\"supp\"
A
52.00%
3.8%
0.5%
20,759
", + "
5
\"supp\"
S+
51.77%
8.1%
23.9%
44,423
", + "
6
\"supp\"
A
51.55%
2.3%
1.8%
12,418
", + "
7
\"supp\"
A
51.54%
4.4%
1.3%
24,495
", + "
8
\"supp\"
A
51.38%
4.1%
1.3%
22,625
", + "
9
\"supp\"
S
51.35%
7.7%
1.6%
42,551
", + "
10
\"supp\"
A
51.21%
2.8%
0.5%
15,658
", + "
11
\"supp\"
S+
51.20%
8.5%
18.2%
46,810
", + "
12
\"supp\"
A
50.84%
1.0%
5.3%
5,315
", + "
13
\"supp\"
A
50.61%
4.7%
1.7%
26,103
", + "
14
\"supp\"
A
50.56%
5.7%
2.5%
31,615
", + "
15
\"supp\"
A
50.50%
5.4%
1.8%
29,924
", + "
16
\"supp\"
A
50.48%
2.3%
0.1%
12,678
", + "
17
\"supp\"
A
50.42%
9.4%
2.5%
51,858
", + "
18
\"supp\"
A
50.41%
2.4%
0.4%
13,034
", + "
19
\"supp\"
A
50.17%
4.3%
3.9%
23,848
", + "
20
\"supp\"
B
50.13%
1.4%
0.2%
7,588
", + "
21
\"supp\"
A
49.97%
11.0%
18.8%
60,568
", + "
22
\"supp\"
B
49.89%
2.6%
10.8%
14,365
", + "
23
\"supp\"
B
49.88%
1.8%
1.8%
10,119
", + "
24
\"supp\"
B
49.33%
1.1%
0.3%
5,907
", + "
25
\"supp\"
D
49.05%
11.0%
10.6%
60,829
", + "
26
\"supp\"
D
49.02%
8.1%
6.1%
44,357
", + "
27
\"supp\"
C
48.81%
5.5%
0.2%
30,031
", + "
28
\"supp\"
B
48.62%
2.7%
4.0%
14,972
", + "
29
\"supp\"
B
48.57%
1.9%
7.8%
10,649
", + "
30
\"supp\"
B
48.50%
1.4%
0.4%
7,712
", + "
31
\"supp\"
D
48.43%
7.9%
3.8%
43,741
", + "
32
\"supp\"
D
48.05%
2.8%
9.4%
15,441
", + "
33
\"supp\"
B
47.94%
0.5%
0.4%
2,862
", + "
34
\"supp\"
D
47.88%
4.8%
4.1%
26,547
", + "
35
\"supp\"
B
47.86%
0.6%
6.6%
3,544
", + "
36
\"supp\"
B
47.80%
1.6%
2.5%
8,779
", + "
37
\"supp\"
D
47.79%
9.6%
24.8%
52,734
", + "
38
\"supp\"
C
47.77%
3.9%
1.4%
21,235
", + "
39
\"supp\"
B
47.70%
1.7%
1.7%
9,159
", + "
40
\"supp\"
D
47.02%
4.4%
4.0%
24,119
", + "
41
\"supp\"
B
46.82%
0.5%
3.1%
2,958
", + "
42
\"supp\"
D
45.48%
2.9%
18.5%
16,230
" ] } \ No newline at end of file diff --git a/src/LoL-support/final_data.json b/src/LoL-support/final_data.json old mode 100644 new mode 100755 index 019d059..b61a654 --- a/src/LoL-support/final_data.json +++ b/src/LoL-support/final_data.json @@ -1,1233 +1,934 @@ [ { - "champion_name": "Taric", - "pick_rate": "1.4%", - "counters": { - "Soraka": "55.26%", - "Rell": "52.91%", - "Vel'Koz": "52.75%", - "Janna": "50.88%", - "Zilean": "50.55%", - "Rakan": "49.62%", - "Maokai": "49.58%", - "Nami": "49.42%", - "Xerath": "49.28%", - "Senna": "49.24%" + "champion_name": "Maokai", + "pick_rate": "12.7%", + "counters": { + "Taric": "50.48%", + "Alistar": "49.83%", + "Braum": "49.49%", + "Leona": "49.02%", + "Janna": "48.98%", + "Renata Glasc": "48.48%", + "Rell": "47.97%", + "Shaco": "47.18%", + "Camille": "47.04%", + "Zyra": "47.02%" }, - "synergy": { - "Karthus": "70.83%", - "Yasuo": "68.91%", - "Lee Sin": "68.18%", - "Master Yi": "59.26%", - "Kog'Maw": "58.82%", - "Sivir": "58.62%", - "Varus": "56.63%", - "Swain": "56.45%", - "Seraphine": "56.41%", - "Kalista": "55.94%", - "Ziggs": "55.00%", - "Vayne": "54.01%", - "Jinx": "52.38%", - "Lucian": "51.91%", - "Kai'Sa": "51.81%", - "Ashe": "51.67%", - "Miss Fortune": "50.94%", - "Ezreal": "50.20%", - "Twitch": "50.00%" - } + "synergy": [ + "no data" + ] }, { "champion_name": "Janna", - "pick_rate": "9.6%", - "counters": { - "Senna": "50.79%", - "Sona": "50.1%", - "Soraka": "50.01%", - "Seraphine": "49.87%", - "Milio": "49.71%", - "Neeko": "49.67%", - "Rell": "49.5%", - "Bard": "49.44%", - "Rakan": "49.42%", - "Blitzcrank": "49.4%" - }, - "synergy": { - "Seraphine": "58.66%", - "Vayne": "55.43%", - "Yasuo": "54.91%", - "Ziggs": "54.85%", - "Jinx": "53.96%", - "Ashe": "53.85%", - "Tristana": "53.63%", - "Kalista": "53.47%", - "Kog'Maw": "52.89%", - "Karthus": "52.83%", - "Jhin": "52.65%", - "Ezreal": "52.58%", - "Lucian": "52.51%", - "Draven": "52.25%", - "Twitch": "52.00%", - "Samira": "51.88%", - "Varus": "51.73%", - "Zeri": "51.64%", - "Caitlyn": "51.53%", - "Kai'Sa": "50.95%" + "pick_rate": "10.7%", + "counters": { + "Camille": "53.25%", + "Sona": "51.89%", + "Blitzcrank": "51.2%", + "Maokai": "51.02%", + "Braum": "49.67%", + "Pyke": "49.38%", + "Renata Glasc": "49.3%", + "Senna": "49.25%", + "Ashe": "49.07%", + "Nami": "48.64%" + }, + "synergy": { + "Nilah": "57.38%", + "Twitch": "57.02%", + "Senna": "56.68%", + "Seraphine": "55.59%", + "Miss Fortune": "54.71%", + "Zeri": "54.30%", + "Tristana": "54.22%", + "Draven": "54.22%", + "Twisted Fate": "53.57%", + "Samira": "53.57%", + "Jhin": "53.03%", + "Lucian": "52.84%", + "Karthus": "52.73%", + "Ezreal": "52.60%", + "Yasuo": "52.58%", + "Kalista": "52.35%", + "Vayne": "51.97%", + "Ashe": "51.80%", + "Sivir": "51.10%", + "Caitlyn": "51.05%", + "Smolder": "50.23%", + "Aphelios": "50.23%" } }, { - "champion_name": "Maokai", - "pick_rate": "1.6%", + "champion_name": "Taric", + "pick_rate": "1.5%", "counters": { - "Shaco": "58.18%", - "Braum": "54.8%", - "Sona": "53.99%", - "Janna": "52.72%", - "Renata Glasc": "52.39%", - "Zyra": "51.46%", - "Alistar": "51.45%", - "Soraka": "50.8%", - "Taric": "50.42%", - "Neeko": "50.29%" - }, - "synergy": { - "Xayah": "60.00%", - "Ziggs": "58.33%", - "Karthus": "57.89%", - "Kai'Sa": "53.95%", - "Vayne": "53.51%", - "Sivir": "52.63%", - "Jhin": "52.46%", - "Aphelios": "52.46%", - "Draven": "52.38%", - "Ezreal": "52.29%", - "Samira": "51.81%", - "Varus": "51.53%", - "Zeri": "51.02%", - "Twitch": "50.00%", - "Ashe": "50.00%", - "Hwei": "50.00%" + "Swain": "57.63%", + "Sona": "57.32%", + "Janna": "54.88%", + "Nami": "54.27%", + "Lux": "53.54%", + "Seraphine": "53.45%", + "Bard": "52.97%", + "Soraka": "52.32%", + "Thresh": "52.21%", + "Braum": "51.67%" + }, + "synergy": { + "Vayne": "60.16%", + "Ashe": "58.46%", + "Yasuo": "58.33%", + "Twisted Fate": "57.89%", + "Pyke": "57.14%", + "Senna": "56.59%", + "Nilah": "56.32%", + "Kalista": "55.13%", + "Jinx": "54.39%", + "Aphelios": "53.57%", + "Seraphine": "53.13%", + "Zeri": "52.54%", + "Twitch": "52.05%", + "Smolder": "51.89%", + "Ezreal": "51.67%", + "Kai'Sa": "50.59%", + "Sivir": "50.00%", + "Lucian": "50.00%", + "Dr. Mundo": "50.00%", + "Xayah": "50.00%", + "Karthus": "50.00%" } }, { - "champion_name": "Bard", - "pick_rate": "6.1%", - "counters": { - "Taric": "52.2%", - "Janna": "50.56%", - "Maokai": "50.28%", - "Zilean": "49.95%", - "Senna": "49.94%", - "Blitzcrank": "49.89%", - "Renata Glasc": "49.52%", - "Soraka": "49.45%", - "Rell": "49.3%", - "Pyke": "49.06%" - }, - "synergy": { - "Nilah": "59.77%", - "Vayne": "55.15%", - "Caitlyn": "55.12%", - "Twitch": "55.06%", - "Ziggs": "54.37%", - "Tristana": "53.92%", - "Karthus": "53.62%", - "Seraphine": "53.05%", - "Lucian": "52.97%", - "Ezreal": "52.51%", - "Ashe": "52.44%", - "Jhin": "51.89%", - "Swain": "51.69%", - "Jinx": "51.34%", - "Zeri": "50.76%" - } + "champion_name": "Braum", + "pick_rate": "3.8%", + "counters": "no info", + "synergy": [ + "no data" + ] }, { - "champion_name": "Rakan", - "pick_rate": "12.8%", - "counters": { - "Senna": "52.31%", - "Bard": "51.42%", - "Soraka": "51.41%", - "Neeko": "51.37%", - "Renata Glasc": "51.09%", - "Sona": "50.93%", - "Maokai": "50.87%", - "Rell": "50.69%", - "Zilean": "50.64%", - "Janna": "50.58%" - }, - "synergy": { - "Lucian": "56.02%", - "Jinx": "54.48%", - "Twitch": "54.35%", - "Seraphine": "54.17%", - "Ziggs": "54.08%", - "Vayne": "54.03%", - "Sivir": "53.73%", - "Jhin": "52.40%", - "Yasuo": "51.97%", - "Karthus": "51.90%", - "Ezreal": "51.77%", - "Ashe": "51.55%", - "Miss Fortune": "50.65%", - "Nilah": "50.61%", - "Varus": "50.35%", - "Draven": "50.12%" + "champion_name": "Blitzcrank", + "pick_rate": "8.1%", + "counters": { + "Pantheon": "54.85%", + "Leona": "54.08%", + "Maokai": "53.66%", + "Braum": "53.61%", + "Rakan": "53.26%", + "Taric": "53.13%", + "Alistar": "52.33%", + "Shaco": "51.5%", + "Rell": "50.9%", + "Zilean": "50.57%" + }, + "synergy": { + "Seraphine": "65.22%", + "Karthus": "57.78%", + "Draven": "56.57%", + "Vayne": "55.39%", + "Varus": "54.74%", + "Jinx": "54.30%", + "Twisted Fate": "54.17%", + "Tristana": "53.18%", + "Sivir": "53.06%", + "Senna": "52.85%", + "Aphelios": "52.14%", + "Kai'Sa": "51.65%", + "Lucian": "50.78%", + "Nilah": "50.67%", + "Twitch": "50.00%", + "Ashe": "50.00%", + "Zeri": "50.00%" } }, { - "champion_name": "Sona", - "pick_rate": "2.4%", + "champion_name": "Camille", + "pick_rate": "2.3%", "counters": { - "Taric": "56.83%", - "Leona": "55.04%", - "Seraphine": "53.49%", - "Blitzcrank": "52.56%", - "Senna": "51.71%", - "Bard": "51.4%", - "Nami": "50.59%", - "Milio": "50.43%", - "Zilean": "50.26%", - "Pyke": "50.19%" - }, - "synergy": { - "Yasuo": "64.52%", - "Tristana": "60.29%", - "Ziggs": "58.51%", - "Samira": "57.45%", - "Sivir": "56.60%", - "Vayne": "56.55%", - "Nilah": "55.88%", - "Kog'Maw": "53.49%", - "Lucian": "53.04%", - "Xayah": "52.90%", - "Seraphine": "52.76%", - "Ezreal": "52.71%", - "Miss Fortune": "52.23%", - "Varus": "51.05%", - "Ashe": "50.93%", - "Caitlyn": "50.81%", - "Twitch": "50.21%" + "Taric": "58.76%", + "Renata Glasc": "58.12%", + "Braum": "57.32%", + "Leona": "54.48%", + "Brand": "54.2%", + "Bard": "53.76%", + "Zac": "53.03%", + "Maokai": "52.96%", + "Tahm Kench": "52.94%", + "Zilean": "52.78%" + }, + "synergy": { + "Twitch": "62.67%", + "Sivir": "58.33%", + "Zeri": "56.47%", + "Tristana": "56.20%", + "Senna": "55.81%", + "Xayah": "55.26%", + "Aphelios": "53.62%", + "Nilah": "53.13%", + "Seraphine": "52.94%", + "Varus": "52.89%", + "Karthus": "52.63%", + "Ashe": "52.50%", + "Smolder": "52.34%", + "Jhin": "52.09%", + "Draven": "52.06%", + "Ziggs": "52.00%", + "Twisted Fate": "50.94%", + "Vayne": "50.92%", + "Lucian": "50.35%", + "Swain": "50.00%" } }, { - "champion_name": "Senna", - "pick_rate": "11.0%", - "counters": { - "Pyke": "52.31%", - "Blitzcrank": "52.25%", - "Xerath": "51.66%", - "Maokai": "51.62%", - "Zyra": "51.1%", - "Taric": "50.76%", - "Leona": "50.22%", - "Bard": "50.06%", - "Lux": "49.9%", - "Rell": "49.8%" - }, + "champion_name": "Leona", + "pick_rate": "4.4%", + "counters": "no info", "synergy": { - "Kalista": "57.48%", - "Nilah": "56.48%", - "Karthus": "56.18%", - "Zeri": "55.11%", - "Seraphine": "55.06%", - "Miss Fortune": "54.98%", - "Cho'Gath": "54.69%", - "Vayne": "54.60%", - "Ashe": "54.54%", - "Tahm Kench": "54.51%", - "Yasuo": "54.49%", - "Brand": "54.26%", - "Twitch": "54.15%", - "Swain": "53.87%", - "Jhin": "52.87%", - "Ziggs": "52.59%", - "Sivir": "52.21%", - "Ezreal": "51.36%", - "Tristana": "51.36%", - "Varus": "50.75%" + "Swain": "68.09%", + "Senna": "60.41%", + "Jinx": "59.02%", + "Seraphine": "58.90%", + "Ashe": "57.67%", + "Aphelios": "56.67%", + "Nilah": "56.52%", + "Vayne": "55.20%", + "Lucian": "54.63%", + "Smolder": "54.42%", + "Miss Fortune": "53.39%", + "Karthus": "53.06%", + "Xayah": "51.61%", + "Ezreal": "51.04%", + "Kalista": "50.59%", + "Twisted Fate": "50.31%" } }, { "champion_name": "Rell", - "pick_rate": "4.9%", - "counters": { - "Poppy": "53.79%", - "Seraphine": "53.57%", - "Soraka": "52.57%", - "Zilean": "52.54%", - "Alistar": "52.45%", - "Shaco": "51.5%", - "Maokai": "50.89%", - "Sona": "50.75%", - "Bard": "50.7%", - "Janna": "50.5%" - }, + "pick_rate": "4.1%", + "counters": "no info", "synergy": { - "Ziggs": "61.31%", - "Karthus": "60.18%", - "Seraphine": "59.02%", - "Tristana": "58.16%", - "Lucian": "57.30%", - "Twitch": "56.30%", - "Miss Fortune": "54.44%", - "Ashe": "53.88%", - "Sivir": "53.49%", - "Jinx": "52.83%", - "Draven": "52.25%", - "Xayah": "52.10%", - "Ezreal": "52.08%", - "Kai'Sa": "51.33%", - "Samira": "51.15%", - "Zeri": "50.35%", - "Yasuo": "50.25%", - "Aphelios": "50.21%" + "Xayah": "58.33%", + "Twisted Fate": "56.72%", + "Twitch": "56.48%", + "Nilah": "56.25%", + "Miss Fortune": "55.88%", + "Zeri": "55.45%", + "Senna": "55.08%", + "Vayne": "53.65%", + "Jinx": "53.40%", + "Kalista": "52.46%", + "Samira": "52.46%", + "Aphelios": "52.17%", + "Draven": "52.15%", + "Tristana": "51.90%", + "Karthus": "51.43%", + "Ezreal": "51.32%", + "Jhin": "50.43%", + "Yasuo": "50.00%" } }, { - "champion_name": "Neeko", - "pick_rate": "1.9%", - "counters": { - "Vel'Koz": "60.45%", - "Sona": "53.26%", - "Bard": "52.61%", - "Brand": "52.38%", - "Taric": "52.17%", - "Alistar": "51%", - "Shaco": "50.68%", - "Senna": "50.42%", - "Renata Glasc": "50.38%", - "Morgana": "50.35%" - }, - "synergy": { - "Nilah": "77.78%", - "Yasuo": "59.46%", - "Xayah": "59.26%", - "Hwei": "58.82%", - "Lucian": "56.79%", - "Vayne": "53.85%", - "Twitch": "53.17%", - "Kalista": "50.75%", - "Ezreal": "50.72%", - "Ashe": "50.29%", - "Quinn": "50.00%", - "Samira": "50.00%", - "Varus": "50.00%" - } + "champion_name": "Rakan", + "pick_rate": "7.7%", + "counters": "no info", + "synergy": [ + "no data" + ] }, { - "champion_name": "Soraka", - "pick_rate": "5.5%", - "counters": { - "Blitzcrank": "54.17%", - "Neeko": "51.31%", - "Sona": "51.25%", - "Zyra": "51.13%", - "Vel'Koz": "51.05%", - "Bard": "50.55%", - "Senna": "50.38%", - "Yuumi": "50.06%", - "Janna": "49.99%", - "Pyke": "49.98%" - }, + "champion_name": "Zilean", + "pick_rate": "2.8%", + "counters": "no info", "synergy": { - "Yasuo": "63.54%", - "Swain": "59.70%", - "Samira": "58.41%", - "Vayne": "57.03%", - "Ashe": "53.73%", - "Jinx": "53.47%", - "Ziggs": "53.38%", - "Jhin": "53.17%", - "Tristana": "52.80%", - "Lucian": "52.34%", - "Miss Fortune": "51.96%", - "Twitch": "51.63%", - "Draven": "50.30%" + "Miss Fortune": "62.03%", + "Twisted Fate": "61.05%", + "Draven": "54.63%", + "Swain": "54.55%", + "Vayne": "54.47%", + "Ashe": "53.97%", + "Caitlyn": "53.13%", + "Seraphine": "52.44%", + "Smolder": "51.37%", + "Xayah": "51.22%", + "Sivir": "50.00%" } }, { - "champion_name": "Blitzcrank", - "pick_rate": "8.8%", - "counters": { - "Taric": "56.85%", - "Rakan": "52.88%", - "Leona": "52.47%", - "Braum": "52.28%", - "Renata Glasc": "51.05%", - "Maokai": "51.04%", - "Zyra": "50.98%", - "Rell": "50.78%", - "Alistar": "50.73%", - "Zilean": "50.7%" - }, + "champion_name": "Pyke", + "pick_rate": "8.5%", + "counters": "no info", "synergy": { - "Nilah": "58.59%", - "Xayah": "55.50%", - "Draven": "54.17%", - "Lucian": "53.85%", - "Ashe": "53.42%", - "Vayne": "52.91%", - "Twitch": "52.80%", - "Seraphine": "52.34%", - "Jinx": "51.90%", - "Karthus": "51.89%", - "Varus": "51.87%", - "Tristana": "51.72%", - "Miss Fortune": "51.53%", - "Zeri": "51.27%", - "Aphelios": "50.95%", - "Kalista": "50.84%", - "Jhin": "50.74%", - "Ezreal": "50.08%", - "Caitlyn": "50.00%" + "Karthus": "60.47%", + "Swain": "58.06%", + "Senna": "57.89%", + "Ashe": "55.02%", + "Twitch": "54.55%", + "Vayne": "53.90%", + "Jinx": "53.67%", + "Yasuo": "53.66%", + "Seraphine": "53.19%", + "Twisted Fate": "53.07%", + "Nilah": "53.00%", + "Smolder": "51.63%", + "Zeri": "50.56%", + "Ezreal": "50.22%", + "Draven": "50.00%" } }, { - "champion_name": "Zyra", - "pick_rate": "3.8%", + "champion_name": "Zac", + "pick_rate": "1.0%", "counters": { - "Sona": "53.56%", - "Pyke": "53.25%", - "Heimerdinger": "53.11%", - "Bard": "53.07%", - "Leona": "51.91%", - "Janna": "51.88%", - "Shaco": "51.56%", - "Taric": "51.43%", - "Rakan": "50.98%", - "Nautilus": "50.75%" - }, - "synergy": { - "Seraphine": "55.06%", - "Vayne": "54.84%", - "Tristana": "54.05%", - "Twitch": "52.96%", - "Samira": "51.98%", - "Jhin": "51.51%", - "Karthus": "51.16%", - "Draven": "50.65%", + "Pantheon": "61.36%", + "Taric": "58.82%", + "Zilean": "58.49%", + "Rell": "56.82%", + "Bard": "56.55%", + "Morgana": "55.93%", + "Janna": "54.44%", + "Maokai": "53.44%", + "Milio": "53.25%", + "Thresh": "53.18%" + }, + "synergy": { + "Aphelios": "83.33%", + "Caitlyn": "66.67%", + "Draven": "65.52%", + "Senna": "64.71%", + "Tristana": "61.54%", + "Twisted Fate": "61.29%", + "Zeri": "58.82%", + "Seraphine": "56.25%", + "Jinx": "55.00%", + "Ezreal": "52.38%", + "Smolder": "51.58%", + "Kalista": "51.52%", "Xayah": "50.00%" } }, { - "champion_name": "Pyke", - "pick_rate": "7.6%", - "counters": { - "Rakan": "53.38%", - "Maokai": "52.78%", - "Neeko": "52.1%", - "Rell": "52.08%", - "Taric": "51.82%", - "Janna": "51.79%", - "Blitzcrank": "51.44%", - "Alistar": "50.94%", - "Bard": "50.94%", - "Thresh": "50.49%" - }, - "synergy": { - "Karthus": "61.98%", - "Nilah": "60.43%", - "Yasuo": "57.30%", - "Tristana": "55.96%", - "Twitch": "55.08%", - "Ashe": "54.27%", - "Swain": "53.91%", - "Ziggs": "53.28%", - "Seraphine": "52.82%", - "Vayne": "52.64%", - "Varus": "51.58%", - "Jhin": "51.51%", - "Ezreal": "51.49%", - "Hwei": "51.43%", - "Draven": "50.95%" - } - }, - { - "champion_name": "Zilean", - "pick_rate": "2.5%", - "counters": { - "Janna": "53.27%", - "Shaco": "53.02%", - "Senna": "51.99%", - "Neeko": "51.67%", - "Maokai": "51.41%", - "Xerath": "51.07%", - "Milio": "50.67%", - "Sylas": "50.34%", - "Soraka": "50.28%", - "Vel'Koz": "50.23%" - }, + "champion_name": "Alistar", + "pick_rate": "4.7%", + "counters": "no info", "synergy": { - "Swain": "58.97%", - "Hwei": "55.56%", - "Samira": "54.12%", - "Kog'Maw": "54.05%", - "Twitch": "53.80%", - "Aphelios": "53.47%", - "Vayne": "51.88%", - "Caitlyn": "51.79%", - "Seraphine": "51.67%", - "Ezreal": "51.07%", - "Ashe": "50.12%" + "Karthus": "64.86%", + "Seraphine": "62.86%", + "Zeri": "60.83%", + "Sivir": "59.26%", + "Jinx": "58.42%", + "Yasuo": "58.09%", + "Tristana": "55.84%", + "Senna": "53.27%", + "Twitch": "52.99%", + "Draven": "51.74%", + "Xayah": "51.25%", + "Ezreal": "50.91%", + "Twisted Fate": "50.75%", + "Samira": "50.00%" } }, { - "champion_name": "Braum", - "pick_rate": "3.6%", + "champion_name": "Bard", + "pick_rate": "5.7%", "counters": { - "Zilean": "55.95%", - "Soraka": "54.2%", - "Bard": "53.76%", - "Rell": "53.74%", - "Taric": "53.44%", - "Sona": "53.2%", - "Senna": "53.06%", - "Rakan": "52.88%", - "Janna": "52.49%", - "Renata Glasc": "52.31%" - }, - "synergy": { - "Sivir": "60.82%", - "Twitch": "59.55%", - "Xayah": "54.40%", - "Varus": "52.69%", - "Kog'Maw": "52.03%", - "Ashe": "51.92%", - "Jinx": "51.32%", - "Tristana": "50.94%", - "Vayne": "50.83%", - "Draven": "50.75%", - "Ezreal": "50.35%" + "Maokai": "56.1%", + "Renata Glasc": "53.77%", + "Janna": "53.45%", + "Pyke": "51.79%", + "Morgana": "51.04%", + "Nami": "50.92%", + "Pantheon": "50.72%", + "Blitzcrank": "50.43%", + "Brand": "50.34%", + "Senna": "50.21%" + }, + "synergy": { + "Yasuo": "60.34%", + "Seraphine": "59.53%", + "Senna": "59.42%", + "Ziggs": "57.58%", + "Caitlyn": "55.43%", + "Xayah": "54.26%", + "Hwei": "54.17%", + "Twisted Fate": "53.66%", + "Jinx": "52.43%", + "Twitch": "51.68%", + "Aphelios": "51.54%", + "Karthus": "51.47%", + "Samira": "50.60%", + "Vayne": "50.34%" } }, { - "champion_name": "Renata Glasc", - "pick_rate": "5.0%", + "champion_name": "Soraka", + "pick_rate": "5.4%", "counters": "no info", "synergy": [ "no data" ] }, { - "champion_name": "Vel'Koz", - "pick_rate": "1.6%", - "counters": { - "Pyke": "55.14%", - "Zyra": "54.76%", - "Seraphine": "54.15%", - "Blitzcrank": "53.89%", - "Janna": "53.62%", - "Bard": "53.22%", - "Sona": "52.97%", - "Nami": "52.1%", - "Maokai": "51.85%", - "Renata Glasc": "51.64%" - }, - "synergy": { - "Karthus": "61.90%", - "Vayne": "55.34%", - "Sivir": "54.72%", - "Ashe": "54.44%", - "Aphelios": "53.25%", - "Seraphine": "52.38%", - "Jhin": "51.92%", - "Caitlyn": "51.43%", - "Ezreal": "51.22%", - "Twitch": "50.39%", - "Varus": "50.37%", - "Tristana": "50.00%", - "Miss Fortune": "50.00%" + "champion_name": "Sona", + "pick_rate": "2.3%", + "counters": { + "Zilean": "61.68%", + "Rell": "59.62%", + "Leona": "57.21%", + "Neeko": "55.71%", + "Thresh": "55.03%", + "Maokai": "54.7%", + "Bard": "53.59%", + "Braum": "51.81%", + "Blitzcrank": "51.79%", + "Seraphine": "51.2%" + }, + "synergy": { + "Nilah": "71.43%", + "Tristana": "63.16%", + "Twisted Fate": "56.19%", + "Jhin": "55.34%", + "Kai'Sa": "55.11%", + "Varus": "52.63%", + "Aphelios": "52.00%", + "Twitch": "51.46%", + "Vayne": "51.22%", + "Smolder": "50.72%", + "Lucian": "50.00%", + "Samira": "50.00%" } }, { "champion_name": "Thresh", - "pick_rate": "13.3%", - "counters": { - "Taric": "53.39%", - "Janna": "52.43%", - "Brand": "52.12%", - "Rakan": "52.02%", - "Neeko": "51.75%", - "Sona": "51.47%", - "Maokai": "51.34%", - "Vel'Koz": "51.27%", - "Bard": "51.24%", - "Renata Glasc": "51.24%" - }, + "pick_rate": "9.4%", + "counters": "no info", "synergy": { - "Tristana": "55.29%", - "Hwei": "54.01%", - "Draven": "53.28%", - "Jinx": "52.84%", - "Aphelios": "51.39%", - "Twitch": "51.28%", - "Kalista": "51.17%", - "Samira": "51.08%", - "Vayne": "51.01%", - "Karthus": "50.91%", - "Lucian": "50.61%", - "Ashe": "50.26%", - "Yasuo": "50.24%" + "Yasuo": "62.82%", + "Senna": "58.33%", + "Xayah": "56.52%", + "Sivir": "56.25%", + "Seraphine": "55.74%", + "Kalista": "54.41%", + "Twitch": "52.68%", + "Miss Fortune": "52.44%", + "Jinx": "52.43%", + "Draven": "52.37%", + "Aphelios": "52.12%", + "Smolder": "51.38%", + "Tristana": "51.11%", + "Samira": "50.59%", + "Varus": "50.32%", + "Vayne": "50.15%", + "Kai'Sa": "50.05%" } }, { - "champion_name": "Milio", - "pick_rate": "6.7%", - "counters": { - "Taric": "54.79%", - "Blitzcrank": "53.66%", - "Braum": "53.22%", - "Vel'Koz": "53.08%", - "Senna": "52.78%", - "Bard": "52.75%", - "Neeko": "52.17%", - "Rakan": "51.91%", - "Soraka": "51.52%", - "Thresh": "51.33%" - }, - "synergy": { - "Kog'Maw": "53.82%", - "Xayah": "52.76%", - "Lucian": "51.94%", - "Zeri": "51.56%", - "Vayne": "51.56%", - "Kalista": "51.56%", - "Ashe": "51.48%", - "Draven": "50.69%", - "Aphelios": "50.42%", - "Twitch": "50.00%" - } + "champion_name": "Renata Glasc", + "pick_rate": "2.4%", + "counters": "no info", + "synergy": [ + "no data" + ] }, { - "champion_name": "Alistar", - "pick_rate": "5.8%", - "counters": { - "Sona": "54.51%", - "Janna": "54.04%", - "Swain": "53.24%", - "Vel'Koz": "52.78%", - "Senna": "52.64%", - "Zilean": "52.39%", - "Soraka": "52.22%", - "Seraphine": "51.97%", - "Xerath": "51.86%", - "Milio": "51.85%" - }, - "synergy": { - "Seraphine": "59.43%", - "Ziggs": "57.86%", - "Sivir": "55.20%", - "Hwei": "54.65%", - "Zeri": "53.37%", - "Ashe": "52.55%", - "Yasuo": "52.42%", - "Vayne": "52.01%", - "Twitch": "51.83%", - "Xayah": "51.49%", - "Jinx": "50.82%", - "Jhin": "50.68%", - "Ezreal": "50.58%", - "Draven": "50.28%", - "Miss Fortune": "50.20%" + "champion_name": "Zyra", + "pick_rate": "4.3%", + "counters": { + "Taric": "54.73%", + "Leona": "54.05%", + "Sona": "53.31%", + "Maokai": "52.98%", + "Renata Glasc": "52.84%", + "Pyke": "52.6%", + "Zac": "52.59%", + "Janna": "52.42%", + "Zilean": "51.88%", + "Yuumi": "51.63%" + }, + "synergy": { + "Xayah": "66.07%", + "Sivir": "61.54%", + "Twisted Fate": "53.68%", + "Miss Fortune": "53.25%", + "Ezreal": "52.52%", + "Senna": "52.28%", + "Tristana": "50.94%", + "Vayne": "50.18%", + "Jinx": "50.00%" } }, { - "champion_name": "Zac", - "pick_rate": "0.7%", - "counters": { - "Poppy": "67.39%", - "Maokai": "57.14%", - "Neeko": "56.72%", - "Swain": "56%", - "Vel'Koz": "54.1%", - "Taric": "53.85%", - "Alistar": "53.74%", - "Ashe": "53.49%", - "Rakan": "53.42%", - "Zilean": "53.26%" - }, + "champion_name": "Vel'Koz", + "pick_rate": "1.4%", + "counters": "no info", "synergy": { - "Ziggs": "72.73%", - "Sivir": "71.43%", - "Zeri": "70.00%", - "Varus": "63.64%", - "Yasuo": "62.07%", - "Jhin": "59.32%", - "Kalista": "57.14%", - "Hwei": "57.14%", - "Twitch": "55.32%", - "Ezreal": "52.63%", - "Miss Fortune": "52.38%", - "Vayne": "50.75%", - "Swain": "50.00%", - "Tristana": "50.00%", - "Caitlyn": "50.00%" + "Samira": "65.00%", + "Nilah": "60.00%", + "Hwei": "58.82%", + "Seraphine": "54.55%", + "Draven": "53.66%", + "Ashe": "53.52%", + "Tristana": "53.33%", + "Zeri": "53.13%", + "Senna": "51.90%", + "Kai'Sa": "51.02%", + "Jhin": "50.71%", + "Smolder": "50.22%", + "Sivir": "50.00%", + "Karthus": "50.00%" } }, { - "champion_name": "Leona", - "pick_rate": "4.8%", - "counters": { - "Taric": "56.7%", - "Janna": "54.1%", - "Bard": "54.03%", - "Shaco": "53.67%", - "Maokai": "53.55%", - "Rell": "53.22%", - "Soraka": "53.11%", - "Lux": "53.09%", - "Morgana": "53.02%", - "Braum": "51.72%" - }, - "synergy": { - "Seraphine": "56.47%", - "Xayah": "55.00%", - "Zeri": "54.63%", - "Ezreal": "54.35%", - "Twitch": "54.27%", - "Caitlyn": "53.68%", - "Jhin": "53.21%", - "Yasuo": "53.19%", - "Lucian": "52.72%", - "Miss Fortune": "51.92%", - "Aphelios": "51.70%", - "Tristana": "51.48%", - "Kalista": "51.16%" - } + "champion_name": "Senna", + "pick_rate": "11.0%", + "counters": "no info", + "synergy": [ + "no data" + ] }, { - "champion_name": "Xerath", - "pick_rate": "4.8%", - "counters": { - "Sylas": "55%", - "Maokai": "53.98%", - "Sona": "53.77%", - "Janna": "53.15%", - "Pyke": "52.84%", - "Soraka": "52.33%", - "Rakan": "52.02%", - "Leona": "51.76%", - "Zyra": "51.72%", - "Nami": "51.6%" - }, + "champion_name": "Shaco", + "pick_rate": "2.6%", + "counters": "no info", "synergy": { - "Ziggs": "55.24%", - "Jinx": "53.95%", - "Vayne": "53.32%", - "Samira": "52.63%", - "Xayah": "52.57%", - "Ashe": "52.33%", - "Jhin": "51.81%", - "Caitlyn": "51.12%", - "Hwei": "50.88%", - "Tristana": "50.70%", - "Ezreal": "50.00%" + "Karthus": "76.92%", + "Twisted Fate": "62.64%", + "Tristana": "58.62%", + "Senna": "56.19%", + "Miss Fortune": "56.10%", + "Aphelios": "52.78%", + "Hwei": "52.63%", + "Ashe": "52.48%", + "Draven": "52.24%", + "Nilah": "50.00%", + "Caitlyn": "50.00%", + "Ziggs": "50.00%" } }, { - "champion_name": "Nami", - "pick_rate": "6.5%", - "counters": { - "Maokai": "56.37%", - "Neeko": "53.14%", - "Blitzcrank": "53.03%", - "Rakan": "52.58%", - "Zyra": "52.29%", - "Soraka": "51.92%", - "Milio": "51.67%", - "Pyke": "51.56%", - "Janna": "51.44%", - "Bard": "51.33%" - }, + "champion_name": "Pantheon", + "pick_rate": "1.8%", + "counters": "no info", "synergy": { - "Swain": "59.52%", - "Brand": "55.69%", - "Karthus": "55.65%", - "Twitch": "52.16%", - "Ashe": "51.88%", - "Seraphine": "51.43%", - "Jhin": "51.32%", - "Vayne": "51.29%", - "Ezreal": "51.27%" + "Karthus": "66.67%", + "Twisted Fate": "58.82%", + "Miss Fortune": "56.60%", + "Senna": "56.59%", + "Jhin": "55.67%", + "Nilah": "54.55%", + "Ezreal": "54.37%", + "Swain": "53.85%", + "Lucian": "53.06%", + "Ashe": "52.38%", + "Smolder": "52.07%", + "Samira": "51.97%", + "Jinx": "51.72%" } }, { - "champion_name": "Shaco", + "champion_name": "Tahm Kench", "pick_rate": "1.1%", - "counters": { - "Sona": "60.95%", - "Swain": "60.22%", - "Taric": "59.68%", - "Heimerdinger": "59.02%", - "Zac": "56.14%", - "Janna": "55.96%", - "Milio": "54.49%", - "Pyke": "54.23%", - "Rakan": "53.2%", - "Bard": "52.59%" - }, + "counters": "no info", "synergy": { - "Seraphine": "85.71%", - "Miss Fortune": "58.33%", - "Samira": "54.84%", - "Caitlyn": "54.43%", - "Jhin": "54.01%", - "Varus": "52.43%", - "Karthus": "50.00%" + "Kalista": "68.75%", + "Jinx": "65.00%", + "Nilah": "62.50%", + "Xayah": "62.50%", + "Twitch": "57.14%", + "Varus": "55.10%", + "Twisted Fate": "54.55%", + "Zeri": "53.85%", + "Senna": "52.51%", + "Seraphine": "50.00%" } }, { - "champion_name": "Heimerdinger", - "pick_rate": "1.0%", + "champion_name": "Nautilus", + "pick_rate": "11.0%", "counters": { - "Maokai": "61.64%", - "Neeko": "56.82%", - "Seraphine": "55.95%", - "Xerath": "54.86%", - "Bard": "54.55%", - "Leona": "54.4%", - "Rell": "53.15%", - "Rakan": "52.67%", - "Lulu": "52.56%", - "Janna": "52.56%" - }, - "synergy": { - "Zeri": "68.00%", - "Sivir": "57.89%", - "Draven": "54.29%", - "Jhin": "54.03%", - "Vayne": "53.49%", - "Miss Fortune": "52.78%", - "Ashe": "50.34%", - "Kalista": "50.00%", - "Twitch": "50.00%" + "Taric": "60.99%", + "Leona": "57.54%", + "Braum": "56.73%", + "Renata Glasc": "55.96%", + "Alistar": "55.65%", + "Rell": "55.38%", + "Camille": "54.74%", + "Pantheon": "54.53%", + "Swain": "54.35%", + "Maokai": "53.72%" + }, + "synergy": { + "Seraphine": "55.21%", + "Senna": "54.59%", + "Kalista": "52.55%", + "Xayah": "52.25%", + "Caitlyn": "51.40%", + "Karthus": "51.11%", + "Twitch": "50.96%", + "Lucian": "50.86%", + "Jhin": "50.64%", + "Draven": "50.62%", + "Vayne": "50.27%", + "Zeri": "50.21%", + "Samira": "50.09%", + "Nilah": "50.00%" } }, { - "champion_name": "Sylas", - "pick_rate": "1.0%", - "counters": { - "Sona": "60%", - "Janna": "57.91%", - "Vel'Koz": "57.75%", - "Braum": "57.52%", - "Morgana": "57.5%", - "Taric": "55.56%", - "Rell": "54.55%", - "Rakan": "53.89%", - "Blitzcrank": "53.74%", - "Thresh": "53.64%" + "champion_name": "Milio", + "pick_rate": "8.1%", + "counters": { + "Blitzcrank": "56.32%", + "Braum": "55.45%", + "Maokai": "54.37%", + "Camille": "53.86%", + "Janna": "53.43%", + "Thresh": "53.37%", + "Zilean": "53.04%", + "Tahm Kench": "53.04%", + "Vel'Koz": "52.98%", + "Leona": "52.96%" }, - "synergy": { - "Karthus": "60.00%", - "Aphelios": "58.06%", - "Nilah": "57.89%", - "Miss Fortune": "57.14%", - "Kalista": "55.36%", - "Lucian": "54.65%", - "Seraphine": "54.55%", - "Sivir": "52.94%", - "Zeri": "52.63%", - "Vayne": "52.53%", - "Jhin": "52.38%", - "Ezreal": "51.14%", - "Twitch": "50.77%", - "Yasuo": "50.00%" - } + "synergy": [ + "no data" + ] }, { - "champion_name": "Tahm Kench", - "pick_rate": "0.6%", + "champion_name": "Nami", + "pick_rate": "5.5%", "counters": { - "Taric": "62.22%", - "Braum": "61.11%", - "Bard": "57.4%", - "Zyra": "57.27%", - "Pyke": "55.38%", - "Maokai": "54.9%", - "Brand": "54.67%", - "Lulu": "54.55%", - "Renata Glasc": "54.17%", - "Rakan": "54.12%" + "Morgana": "56.08%", + "Blitzcrank": "55.61%", + "Maokai": "55.57%", + "Vel'Koz": "54.47%", + "Rakan": "54.44%", + "Neeko": "54.21%", + "Rell": "53.9%", + "Nautilus": "53.32%", + "Thresh": "53.28%", + "Senna": "53.17%" }, - "synergy": { - "Veigar": "80.00%", - "Ziggs": "75.00%", - "Karthus": "66.67%", - "Seraphine": "58.33%", - "Xayah": "58.33%", - "Jhin": "55.56%", - "Miss Fortune": "51.72%", - "Twitch": "51.28%", - "Kog'Maw": "50.00%", - "Kalista": "50.00%", - "Ashe": "50.00%", - "Ezreal": "50.00%" + "synergy": [ + "no data" + ] + }, + { + "champion_name": "Xerath", + "pick_rate": "2.7%", + "counters": { + "Blitzcrank": "58.63%", + "Neeko": "56.43%", + "Sona": "56.36%", + "Maokai": "56.26%", + "Pyke": "56.15%", + "Leona": "56.02%", + "Braum": "55.06%", + "Rakan": "54.83%", + "Nautilus": "54.65%", + "Janna": "53.86%" + }, + "synergy": { + "Samira": "70.00%", + "Twisted Fate": "61.36%", + "Caitlyn": "57.97%", + "Karthus": "55.56%", + "Ashe": "52.46%", + "Vayne": "50.55%", + "Zeri": "50.00%" } }, { - "champion_name": "Amumu", - "pick_rate": "0.6%", - "counters": { - "Maokai": "66.07%", - "Zyra": "61.68%", - "Rell": "58.9%", - "Rakan": "58.55%", - "Janna": "58.5%", - "Neeko": "57.14%", - "Taric": "56.1%", - "Braum": "55.41%", - "Bard": "54%", - "Leona": "53.91%" - }, + "champion_name": "Brand", + "pick_rate": "1.9%", + "counters": "no info", "synergy": { - "Yasuo": "71.43%", - "Ziggs": "66.67%", - "Kalista": "64.15%", - "Lucian": "60.00%", - "Karthus": "60.00%", - "Hwei": "57.14%", - "Ezreal": "55.88%", - "Nilah": "55.56%", - "Draven": "55.41%", - "Miss Fortune": "54.37%", - "Ashe": "54.17%", - "Jinx": "53.85%", - "Kai'Sa": "53.60%", - "Tristana": "52.63%", - "Zeri": "50.00%", - "Veigar": "50.00%" + "Samira": "59.09%", + "Yasuo": "57.89%", + "Twisted Fate": "57.14%", + "Xayah": "56.52%", + "Jinx": "55.88%", + "Jhin": "53.98%", + "Draven": "53.70%", + "Seraphine": "52.50%", + "Aphelios": "50.00%" } }, { - "champion_name": "Twitch", - "pick_rate": "0.8%", + "champion_name": "Swain", + "pick_rate": "1.4%", "counters": { - "Zilean": "61.97%", - "Taric": "60.47%", - "Leona": "57.92%", - "Soraka": "57.87%", - "Shaco": "57.38%", + "Zac": "58.97%", + "Sona": "58.67%", + "Neeko": "57.41%", "Nami": "57.14%", - "Rakan": "56.34%", - "Sylas": "55.56%", - "Brand": "55.26%", - "Pyke": "54.65%" + "Milio": "56.44%", + "Pantheon": "55.95%", + "Zyra": "55.49%", + "Morgana": "55.24%", + "Leona": "55%", + "Camille": "54.79%" }, - "synergy": { - "Sivir": "70.97%", - "Swain": "70.00%", - "Seraphine": "63.64%", - "Draven": "57.89%", - "Cassiopeia": "53.85%", - "Vayne": "53.54%", - "Jhin": "53.40%", - "Jinx": "52.27%", - "Ziggs": "51.72%", - "Zeri": "51.61%", - "Nilah": "50.00%", - "Kalista": "50.00%", - "Hwei": "50.00%", - "Tristana": "50.00%" - } + "synergy": [ + "no data" + ] }, { - "champion_name": "Nautilus", - "pick_rate": "10.9%", - "counters": { - "Taric": "55.98%", - "Rell": "55.66%", - "Renata Glasc": "55.13%", - "Sylas": "55.09%", - "Braum": "54.08%", - "Rakan": "54.06%", - "Vel'Koz": "53.45%", - "Swain": "53.09%", - "Neeko": "52.97%", - "Alistar": "52.87%" + "champion_name": "Lulu", + "pick_rate": "7.9%", + "counters": { + "Vel'Koz": "57.35%", + "Blitzcrank": "56.23%", + "Zilean": "55.78%", + "Tahm Kench": "55.43%", + "Renata Glasc": "54.86%", + "Rell": "54.7%", + "Janna": "54.6%", + "Maokai": "54.51%", + "Braum": "53.79%", + "Ashe": "53.65%" }, - "synergy": { - "Seraphine": "58.68%", - "Ziggs": "55.43%", - "Karthus": "55.21%", - "Lucian": "51.71%", - "Tristana": "51.50%", - "Samira": "51.14%", - "Kalista": "50.71%", - "Jinx": "50.13%" - } + "synergy": [ + "no data" + ] }, { - "champion_name": "Ashe", - "pick_rate": "5.7%", - "counters": { - "Pyke": "56.98%", - "Sylas": "54.95%", - "Blitzcrank": "54.83%", - "Heimerdinger": "54.8%", - "Rell": "54.67%", - "Nautilus": "54.35%", - "Bard": "53.44%", - "Milio": "53.1%", - "Rakan": "52.69%", - "Brand": "52.5%" - }, + "champion_name": "Morgana", + "pick_rate": "2.8%", + "counters": "no info", "synergy": { - "Karthus": "56.83%", - "Seraphine": "54.60%", - "Jinx": "54.17%", - "Nilah": "53.85%", - "Twitch": "53.55%", - "Ezreal": "51.69%", - "Swain": "51.32%", - "Ziggs": "50.79%", - "Lucian": "50.00%", - "Zeri": "50.00%" + "Xayah": "60.00%", + "Draven": "57.81%", + "Nilah": "54.55%", + "Samira": "51.61%", + "Sivir": "50.00%", + "Seraphine": "50.00%" } }, { - "champion_name": "Lux", - "pick_rate": "5.1%", - "counters": { - "Blitzcrank": "56.57%", - "Maokai": "54.94%", - "Zyra": "54.66%", - "Shaco": "54.45%", - "Vel'Koz": "54.1%", - "Sylas": "53.87%", - "Zilean": "53.51%", - "Pyke": "53.5%", - "Xerath": "52.75%", - "Nami": "52.72%" - }, + "champion_name": "Heimerdinger", + "pick_rate": "0.5%", + "counters": "no info", "synergy": { - "Karthus": "59.46%", - "Twitch": "53.57%", - "Xayah": "52.17%", - "Ashe": "51.05%", - "Varus": "50.88%", - "Tristana": "50.67%", + "Tristana": "80.00%", + "Aphelios": "80.00%", + "Draven": "64.29%", + "Twitch": "62.50%", + "Twisted Fate": "60.00%", + "Jhin": "55.56%", + "Varus": "53.85%", + "Senna": "51.28%", + "Miss Fortune": "50.00%", "Jinx": "50.00%", - "Zeri": "50.00%" + "Nilah": "50.00%" } }, { - "champion_name": "Brand", - "pick_rate": "2.0%", + "champion_name": "Ashe", + "pick_rate": "4.8%", "counters": { - "Sona": "58.11%", - "Maokai": "57.24%", - "Xerath": "57.03%", - "Janna": "56.31%", - "Pyke": "56.01%", - "Zyra": "55.13%", - "Leona": "54.96%", - "Zilean": "54.92%", - "Soraka": "54.73%", - "Senna": "54.19%" + "Camille": "58.72%", + "Maokai": "57.34%", + "Pyke": "56.14%", + "Blitzcrank": "55.88%", + "Nautilus": "55.46%", + "Taric": "55.41%", + "Vel'Koz": "54.55%", + "Zyra": "54.13%", + "Soraka": "53.87%", + "Xerath": "53.83%" }, "synergy": { - "Seraphine": "57.69%", - "Karthus": "55.17%", - "Yasuo": "55.17%", - "Kalista": "53.49%", - "Tristana": "52.31%", - "Xayah": "51.40%", - "Varus": "50.17%" + "Jinx": "53.73%", + "Jhin": "53.36%", + "Karthus": "52.31%", + "Kalista": "51.90%", + "Twisted Fate": "51.81%", + "Zeri": "50.65%" } }, { - "champion_name": "Seraphine", - "pick_rate": "2.0%", + "champion_name": "Sylas", + "pick_rate": "0.6%", "counters": { - "Sylas": "58.33%", - "Maokai": "57.24%", - "Zilean": "56.09%", - "Bard": "55.97%", - "Pyke": "54.69%", - "Soraka": "53.72%", - "Blitzcrank": "53.61%", - "Yuumi": "53.6%", - "Milio": "52.99%", - "Thresh": "52.66%" + "Taric": "70%", + "Shaco": "70%", + "Pantheon": "64.86%", + "Neeko": "63.16%", + "Thresh": "58.58%", + "Lulu": "58.52%", + "Morgana": "58.06%", + "Janna": "57.66%", + "Leona": "57.58%", + "Bard": "57.45%" }, "synergy": { - "Vayne": "59.62%", - "Tristana": "59.38%", - "Miss Fortune": "52.50%", - "Ashe": "52.20%", - "Jinx": "51.11%", - "Draven": "50.94%" + "Miss Fortune": "75.00%", + "Zeri": "72.73%", + "Twitch": "68.75%", + "Kalista": "68.42%", + "Draven": "65.22%", + "Tristana": "61.54%", + "Senna": "60.00%", + "Aphelios": "60.00%", + "Jinx": "55.56%", + "Jhin": "50.00%" } }, { - "champion_name": "Lulu", - "pick_rate": "7.4%", + "champion_name": "Neeko", + "pick_rate": "1.6%", "counters": { - "Taric": "56.84%", - "Zilean": "54.43%", - "Neeko": "54.04%", - "Senna": "54.04%", - "Sona": "53.78%", - "Zyra": "53.7%", - "Maokai": "53.46%", - "Blitzcrank": "52.96%", - "Bard": "52.89%", - "Thresh": "52.88%" - }, - "synergy": { - "Miss Fortune": "55.96%", - "Sivir": "55.62%", - "Tristana": "55.22%", - "Ashe": "51.59%", + "Zilean": "60.66%", + "Taric": "58.7%", + "Zac": "57.78%", + "Leona": "57.76%", + "Pantheon": "57.14%", + "Janna": "56.74%", + "Blitzcrank": "56.38%", + "Bard": "55.85%", + "Camille": "55.41%", + "Rell": "55.19%" + }, + "synergy": { + "Yasuo": "67.74%", + "Tristana": "66.67%", + "Twitch": "65.63%", + "Ashe": "60.87%", + "Senna": "59.22%", + "Nilah": "57.14%", + "Caitlyn": "54.55%", "Draven": "51.52%", - "Varus": "51.22%", - "Twitch": "51.14%", - "Jinx": "50.76%", - "Kog'Maw": "50.00%", - "Kalista": "50.00%" + "Twisted Fate": "50.94%", + "Vayne": "50.00%", + "Jinx": "50.00%", + "Miss Fortune": "50.00%" } }, { - "champion_name": "Morgana", - "pick_rate": "3.2%", - "counters": { - "Zyra": "56.73%", - "Senna": "56.35%", - "Rell": "56.29%", - "Janna": "55.6%", - "Milio": "55.2%", - "Vel'Koz": "55.08%", - "Nami": "54.67%", - "Taric": "54.31%", - "Karma": "54.24%", - "Maokai": "53.2%" - }, + "champion_name": "Karma", + "pick_rate": "9.6%", + "counters": "no info", "synergy": { - "Karthus": "55.56%", - "Nilah": "53.13%", - "Miss Fortune": "52.07%", - "Hwei": "51.72%", - "Vayne": "51.31%", - "Sivir": "50.91%", - "Draven": "50.63%", - "Tristana": "50.00%" + "Tristana": "54.41%", + "Ashe": "52.79%", + "Twisted Fate": "52.57%", + "Jinx": "52.21%", + "Miss Fortune": "51.67%", + "Kalista": "51.02%", + "Twitch": "50.19%", + "Caitlyn": "50.15%", + "Zeri": "50.00%" } }, { - "champion_name": "Karma", - "pick_rate": "7.6%", - "counters": { - "Pyke": "57.3%", - "Maokai": "55.33%", - "Zyra": "54.97%", - "Blitzcrank": "54.92%", - "Rakan": "54.52%", - "Janna": "54.36%", - "Sona": "54.33%", - "Nautilus": "54.21%", - "Soraka": "53.61%", - "Neeko": "52.83%" - }, - "synergy": { - "Draven": "53.57%", - "Vayne": "51.30%", - "Jhin": "50.49%", - "Ezreal": "50.43%", - "Tristana": "50.42%" + "champion_name": "Lux", + "pick_rate": "3.9%", + "counters": { + "Camille": "62.93%", + "Pantheon": "60%", + "Pyke": "58.92%", + "Bard": "57.24%", + "Shaco": "56.3%", + "Blitzcrank": "55.42%", + "Maokai": "55.39%", + "Leona": "55.08%", + "Alistar": "54.69%", + "Zilean": "54.61%" + }, + "synergy": { + "Ziggs": "57.14%", + "Twisted Fate": "56.82%", + "Samira": "52.63%", + "Senna": "52.05%", + "Ezreal": "50.65%", + "Nilah": "50.00%" } }, { - "champion_name": "Swain", - "pick_rate": "1.6%", - "counters": { - "Zilean": "57.77%", - "Sona": "57.32%", - "Vel'Koz": "57.25%", - "Janna": "56.41%", - "Brand": "55.87%", - "Yuumi": "55.78%", - "Zyra": "55.78%", - "Xerath": "55.48%", - "Soraka": "55.34%", - "Senna": "55.32%" - }, + "champion_name": "Seraphine", + "pick_rate": "1.7%", + "counters": "no info", "synergy": { - "Tristana": "64.29%", - "Karthus": "54.55%", - "Vayne": "54.01%", - "Jhin": "53.18%", - "Twitch": "50.70%", - "Nilah": "50.00%", - "Ziggs": "50.00%" + "Lucian": "61.54%", + "Tristana": "61.54%", + "Vayne": "58.90%", + "Varus": "57.69%", + "Jinx": "56.25%", + "Miss Fortune": "55.32%", + "Senna": "51.51%", + "Ezreal": "50.83%", + "Samira": "50.00%", + "Karthus": "50.00%" } }, { - "champion_name": "Pantheon", - "pick_rate": "1.0%", - "counters": { - "Neeko": "59.74%", - "Maokai": "58.43%", - "Karma": "57.22%", - "Bard": "57.14%", - "Swain": "56.25%", - "Rakan": "55.92%", - "Brand": "55.83%", - "Soraka": "55.74%", - "Sona": "55.56%", - "Janna": "55.42%" - }, - "synergy": { - "Miss Fortune": "63.41%", - "Seraphine": "56.25%", - "Yasuo": "54.55%", - "Swain": "52.63%", - "Varus": "52.05%", - "Zeri": "50.00%" + "champion_name": "Yuumi", + "pick_rate": "4.4%", + "counters": { + "Rell": "62.65%", + "Braum": "58.82%", + "Blitzcrank": "58.05%", + "Leona": "57.77%", + "Pyke": "57.61%", + "Taric": "57.46%", + "Swain": "56.85%", + "Rakan": "56.72%", + "Maokai": "56.12%", + "Janna": "55.85%" + }, + "synergy": { + "Rammus": "64.71%", + "Jinx": "55.32%", + "Vayne": "54.26%", + "Samira": "53.57%", + "Lucian": "53.52%", + "Tristana": "53.33%", + "Twitch": "53.31%", + "Jhin": "50.00%", + "Xayah": "50.00%" } }, { - "champion_name": "Yuumi", - "pick_rate": "4.9%", - "counters": { - "Maokai": "61.28%", - "Rell": "59.85%", - "Taric": "57.47%", - "Alistar": "57.31%", - "Nautilus": "56.64%", - "Rakan": "55.78%", - "Pyke": "55.71%", - "Leona": "55.63%", - "Sylas": "55.27%", - "Thresh": "55.23%" - }, + "champion_name": "Twitch", + "pick_rate": "0.5%", + "counters": "no info", "synergy": { - "Seraphine": "66.67%", - "Yasuo": "53.25%", - "Tristana": "52.55%", - "Vayne": "51.74%", - "Zeri": "51.45%", - "Twitch": "50.41%" + "Karma": "75.00%", + "Lucian": "66.67%", + "Cassiopeia": "66.67%", + "Janna": "66.67%", + "Aphelios": "66.67%", + "Jhin": "61.90%", + "Zeri": "60.87%", + "Varus": "60.00%", + "Kalista": "57.14%", + "Xayah": "53.85%", + "Ezreal": "53.52%", + "Vayne": "53.19%", + "Sivir": "50.00%", + "Jinx": "50.00%", + "Lulu": "50.00%" } }, { "champion_name": "Hwei", - "pick_rate": "2.1%", - "counters": { - "Taric": "66.91%", - "Sona": "66.55%", - "Blitzcrank": "65.66%", - "Janna": "65.3%", - "Brand": "65.18%", - "Pyke": "63.85%", - "Xerath": "63.02%", - "Vel'Koz": "62.71%", - "Morgana": "62.31%", - "Milio": "62.01%" - }, - "synergy": { - "Nilah": "51.61%" + "pick_rate": "2.9%", + "counters": { + "Camille": "63.76%", + "Pyke": "60.47%", + "Maokai": "58.72%", + "Xerath": "58.7%", + "Rakan": "58.47%", + "Taric": "58.43%", + "Swain": "58.1%", + "Zilean": "57.69%", + "Vel'Koz": "57.69%", + "Janna": "57.32%" + }, + "synergy": { + "Lucian": "57.14%", + "Tristana": "54.55%", + "Swain": "53.85%", + "Miss Fortune": "51.85%", + "Seraphine": "51.02%" } } ] \ No newline at end of file diff --git a/src/LoL-support/how-to-start.md b/src/LoL-support/how-to-start.md old mode 100644 new mode 100755 diff --git a/src/LoL-support/lolchamps.sh b/src/LoL-support/lolchamps.sh index 441b3b1..3f4b309 100755 --- a/src/LoL-support/lolchamps.sh +++ b/src/LoL-support/lolchamps.sh @@ -1,35 +1,37 @@ #!/bin/zsh -#python step1-lolchamps.py -#echo "Step 1 complete: Scraped targeted data from URL" +python step1-lolchamps.py echo "Step 1 Scrape targeted data from URL." -echo "TEMPORALY DISABLED, Data already extracted" +#echo "TEMPORALY DISABLED, Data already extracted" +#TBD: show the date when was the last time it scraped, python step2-lolchamps.py echo "Step 2 complete: extracted Champion names and their Pick rates" +#TBD: Winrates from diamond+ python step3-lolchamps.py echo "Step 3 complete: made data two-dimensional with Pandas, just for fun" -#python step4-lolchamps.py +python step4-lolchamps.py #enable only if list of champion names are lost echo "Step 4 complete: saved champion names in a list" -echo "TEMPORALY DISABLED, champ names already saved" +#echo "TEMPORALY DISABLED, champ names already saved" -#python step5-lolchamps.py +python step5-lolchamps.py echo "Step 5 in Beta mode: trying to iterate through champion names and scrape their counters." -echo "DISABLED BECAUSE COUNTERS ARE ALREADY SCRAPED AND SAVED" +#echo "DISABLED BECAUSE COUNTERS ARE ALREADY SCRAPED AND SAVED" #scraped only best counters. but script is unable to click for 'more counters' -#python step6-lolchamps.py +python step6-lolchamps.py echo "Step 6 in Beta mode: collecting synergies for each support champion" -echo "DISABLED BECAUSE SYNERGIES ARE ALREADY SCRAPED AND SAVED" +#echo "DISABLED BECAUSE SYNERGIES ARE ALREADY SCRAPED AND SAVED" #EUW and KR versions. Atm EUW works only. Currently it only shows DUO synergy, but I want ADC-SUPP-JUNGLER Synergy -#python step7-lolchamps.py +python step7-lolchamps.py echo "Step 7 complete: combining data into final_data" -#python step8-db.py +python step8-db.py echo "Step 8 in beta mode: sending final_data to Postgres Server tables" +#last extraction: 2024/03/04 chmod +x lolchamps.sh \ No newline at end of file diff --git a/src/LoL-support/pick_rates.json b/src/LoL-support/pick_rates.json old mode 100644 new mode 100755 index b8e55ed..f9a9a85 --- a/src/LoL-support/pick_rates.json +++ b/src/LoL-support/pick_rates.json @@ -1,170 +1,170 @@ [ { - "champion_name": "Taric", - "pick_rate": "1.4%" + "champion_name": "Maokai", + "pick_rate": "12.7%" }, { "champion_name": "Janna", - "pick_rate": "9.6%" + "pick_rate": "10.7%" }, { - "champion_name": "Maokai", - "pick_rate": "1.6%" + "champion_name": "Taric", + "pick_rate": "1.5%" }, { - "champion_name": "Bard", - "pick_rate": "6.1%" + "champion_name": "Braum", + "pick_rate": "3.8%" }, { - "champion_name": "Rakan", - "pick_rate": "12.8%" + "champion_name": "Blitzcrank", + "pick_rate": "8.1%" }, { - "champion_name": "Sona", - "pick_rate": "2.4%" + "champion_name": "Camille", + "pick_rate": "2.3%" }, { - "champion_name": "Senna", - "pick_rate": "11.0%" + "champion_name": "Leona", + "pick_rate": "4.4%" }, { "champion_name": "Rell", - "pick_rate": "4.9%" - }, - { - "champion_name": "Neeko", - "pick_rate": "1.9%" + "pick_rate": "4.1%" }, { - "champion_name": "Soraka", - "pick_rate": "5.5%" + "champion_name": "Rakan", + "pick_rate": "7.7%" }, { - "champion_name": "Blitzcrank", - "pick_rate": "8.8%" + "champion_name": "Zilean", + "pick_rate": "2.8%" }, { - "champion_name": "Zyra", - "pick_rate": "3.8%" + "champion_name": "Pyke", + "pick_rate": "8.5%" }, { - "champion_name": "Pyke", - "pick_rate": "7.6%" + "champion_name": "Zac", + "pick_rate": "1.0%" }, { - "champion_name": "Zilean", - "pick_rate": "2.5%" + "champion_name": "Alistar", + "pick_rate": "4.7%" }, { - "champion_name": "Braum", - "pick_rate": "3.6%" + "champion_name": "Bard", + "pick_rate": "5.7%" }, { - "champion_name": "Renata Glasc", - "pick_rate": "5.0%" + "champion_name": "Soraka", + "pick_rate": "5.4%" }, { - "champion_name": "Vel'Koz", - "pick_rate": "1.6%" + "champion_name": "Sona", + "pick_rate": "2.3%" }, { "champion_name": "Thresh", - "pick_rate": "13.3%" + "pick_rate": "9.4%" }, { - "champion_name": "Milio", - "pick_rate": "6.7%" + "champion_name": "Renata Glasc", + "pick_rate": "2.4%" }, { - "champion_name": "Alistar", - "pick_rate": "5.8%" + "champion_name": "Zyra", + "pick_rate": "4.3%" }, { - "champion_name": "Zac", - "pick_rate": "0.7%" + "champion_name": "Vel'Koz", + "pick_rate": "1.4%" }, { - "champion_name": "Leona", - "pick_rate": "4.8%" + "champion_name": "Senna", + "pick_rate": "11.0%" }, { - "champion_name": "Xerath", - "pick_rate": "4.8%" + "champion_name": "Shaco", + "pick_rate": "2.6%" }, { - "champion_name": "Nami", - "pick_rate": "6.5%" + "champion_name": "Pantheon", + "pick_rate": "1.8%" }, { - "champion_name": "Shaco", + "champion_name": "Tahm Kench", "pick_rate": "1.1%" }, { - "champion_name": "Heimerdinger", - "pick_rate": "1.0%" + "champion_name": "Nautilus", + "pick_rate": "11.0%" }, { - "champion_name": "Sylas", - "pick_rate": "1.0%" + "champion_name": "Milio", + "pick_rate": "8.1%" }, { - "champion_name": "Tahm Kench", - "pick_rate": "0.6%" + "champion_name": "Nami", + "pick_rate": "5.5%" }, { - "champion_name": "Amumu", - "pick_rate": "0.6%" + "champion_name": "Xerath", + "pick_rate": "2.7%" }, { - "champion_name": "Twitch", - "pick_rate": "0.8%" + "champion_name": "Brand", + "pick_rate": "1.9%" }, { - "champion_name": "Nautilus", - "pick_rate": "10.9%" + "champion_name": "Swain", + "pick_rate": "1.4%" }, { - "champion_name": "Ashe", - "pick_rate": "5.7%" + "champion_name": "Lulu", + "pick_rate": "7.9%" }, { - "champion_name": "Lux", - "pick_rate": "5.1%" + "champion_name": "Morgana", + "pick_rate": "2.8%" }, { - "champion_name": "Brand", - "pick_rate": "2.0%" + "champion_name": "Heimerdinger", + "pick_rate": "0.5%" }, { - "champion_name": "Seraphine", - "pick_rate": "2.0%" + "champion_name": "Ashe", + "pick_rate": "4.8%" }, { - "champion_name": "Lulu", - "pick_rate": "7.4%" + "champion_name": "Sylas", + "pick_rate": "0.6%" }, { - "champion_name": "Morgana", - "pick_rate": "3.2%" + "champion_name": "Neeko", + "pick_rate": "1.6%" }, { "champion_name": "Karma", - "pick_rate": "7.6%" + "pick_rate": "9.6%" }, { - "champion_name": "Swain", - "pick_rate": "1.6%" + "champion_name": "Lux", + "pick_rate": "3.9%" }, { - "champion_name": "Pantheon", - "pick_rate": "1.0%" + "champion_name": "Seraphine", + "pick_rate": "1.7%" }, { "champion_name": "Yuumi", - "pick_rate": "4.9%" + "pick_rate": "4.4%" + }, + { + "champion_name": "Twitch", + "pick_rate": "0.5%" }, { "champion_name": "Hwei", - "pick_rate": "2.1%" + "pick_rate": "2.9%" } ] \ No newline at end of file diff --git a/src/LoL-support/sorted_final_data.json b/src/LoL-support/sorted_final_data.json old mode 100644 new mode 100755 index 7517365..1a22c5a --- a/src/LoL-support/sorted_final_data.json +++ b/src/LoL-support/sorted_final_data.json @@ -1,1233 +1,934 @@ [ { - "champion_name": "Thresh", - "pick_rate": "13.3%", - "counters": { - "Taric": "53.39%", - "Janna": "52.43%", - "Brand": "52.12%", - "Rakan": "52.02%", - "Neeko": "51.75%", - "Sona": "51.47%", - "Maokai": "51.34%", - "Vel'Koz": "51.27%", - "Bard": "51.24%", - "Renata Glasc": "51.24%" - }, - "synergy": { - "Tristana": "55.29%", - "Hwei": "54.01%", - "Draven": "53.28%", - "Jinx": "52.84%", - "Aphelios": "51.39%", - "Twitch": "51.28%", - "Kalista": "51.17%", - "Samira": "51.08%", - "Vayne": "51.01%", - "Karthus": "50.91%", - "Lucian": "50.61%", - "Ashe": "50.26%", - "Yasuo": "50.24%" - } - }, - { - "champion_name": "Rakan", - "pick_rate": "12.8%", - "counters": { - "Senna": "52.31%", - "Bard": "51.42%", - "Soraka": "51.41%", - "Neeko": "51.37%", - "Renata Glasc": "51.09%", - "Sona": "50.93%", - "Maokai": "50.87%", - "Rell": "50.69%", - "Zilean": "50.64%", - "Janna": "50.58%" + "champion_name": "Maokai", + "pick_rate": "12.7%", + "counters": { + "Taric": "50.48%", + "Alistar": "49.83%", + "Braum": "49.49%", + "Leona": "49.02%", + "Janna": "48.98%", + "Renata Glasc": "48.48%", + "Rell": "47.97%", + "Shaco": "47.18%", + "Camille": "47.04%", + "Zyra": "47.02%" }, - "synergy": { - "Lucian": "56.02%", - "Jinx": "54.48%", - "Twitch": "54.35%", - "Seraphine": "54.17%", - "Ziggs": "54.08%", - "Vayne": "54.03%", - "Sivir": "53.73%", - "Jhin": "52.40%", - "Yasuo": "51.97%", - "Karthus": "51.90%", - "Ezreal": "51.77%", - "Ashe": "51.55%", - "Miss Fortune": "50.65%", - "Nilah": "50.61%", - "Varus": "50.35%", - "Draven": "50.12%" - } + "synergy": [ + "no data" + ] }, { "champion_name": "Senna", "pick_rate": "11.0%", - "counters": { - "Pyke": "52.31%", - "Blitzcrank": "52.25%", - "Xerath": "51.66%", - "Maokai": "51.62%", - "Zyra": "51.1%", - "Taric": "50.76%", - "Leona": "50.22%", - "Bard": "50.06%", - "Lux": "49.9%", - "Rell": "49.8%" - }, - "synergy": { - "Kalista": "57.48%", - "Nilah": "56.48%", - "Karthus": "56.18%", - "Zeri": "55.11%", - "Seraphine": "55.06%", - "Miss Fortune": "54.98%", - "Cho'Gath": "54.69%", - "Vayne": "54.60%", - "Ashe": "54.54%", - "Tahm Kench": "54.51%", - "Yasuo": "54.49%", - "Brand": "54.26%", - "Twitch": "54.15%", - "Swain": "53.87%", - "Jhin": "52.87%", - "Ziggs": "52.59%", - "Sivir": "52.21%", - "Ezreal": "51.36%", - "Tristana": "51.36%", - "Varus": "50.75%" - } + "counters": "no info", + "synergy": [ + "no data" + ] }, { "champion_name": "Nautilus", - "pick_rate": "10.9%", + "pick_rate": "11.0%", "counters": { - "Taric": "55.98%", - "Rell": "55.66%", - "Renata Glasc": "55.13%", - "Sylas": "55.09%", - "Braum": "54.08%", - "Rakan": "54.06%", - "Vel'Koz": "53.45%", - "Swain": "53.09%", - "Neeko": "52.97%", - "Alistar": "52.87%" - }, - "synergy": { - "Seraphine": "58.68%", - "Ziggs": "55.43%", - "Karthus": "55.21%", - "Lucian": "51.71%", - "Tristana": "51.50%", - "Samira": "51.14%", - "Kalista": "50.71%", - "Jinx": "50.13%" + "Taric": "60.99%", + "Leona": "57.54%", + "Braum": "56.73%", + "Renata Glasc": "55.96%", + "Alistar": "55.65%", + "Rell": "55.38%", + "Camille": "54.74%", + "Pantheon": "54.53%", + "Swain": "54.35%", + "Maokai": "53.72%" + }, + "synergy": { + "Seraphine": "55.21%", + "Senna": "54.59%", + "Kalista": "52.55%", + "Xayah": "52.25%", + "Caitlyn": "51.40%", + "Karthus": "51.11%", + "Twitch": "50.96%", + "Lucian": "50.86%", + "Jhin": "50.64%", + "Draven": "50.62%", + "Vayne": "50.27%", + "Zeri": "50.21%", + "Samira": "50.09%", + "Nilah": "50.00%" } }, { "champion_name": "Janna", - "pick_rate": "9.6%", - "counters": { - "Senna": "50.79%", - "Sona": "50.1%", - "Soraka": "50.01%", - "Seraphine": "49.87%", - "Milio": "49.71%", - "Neeko": "49.67%", - "Rell": "49.5%", - "Bard": "49.44%", - "Rakan": "49.42%", - "Blitzcrank": "49.4%" - }, - "synergy": { - "Seraphine": "58.66%", - "Vayne": "55.43%", - "Yasuo": "54.91%", - "Ziggs": "54.85%", - "Jinx": "53.96%", - "Ashe": "53.85%", - "Tristana": "53.63%", - "Kalista": "53.47%", - "Kog'Maw": "52.89%", - "Karthus": "52.83%", - "Jhin": "52.65%", - "Ezreal": "52.58%", - "Lucian": "52.51%", - "Draven": "52.25%", - "Twitch": "52.00%", - "Samira": "51.88%", - "Varus": "51.73%", - "Zeri": "51.64%", - "Caitlyn": "51.53%", - "Kai'Sa": "50.95%" + "pick_rate": "10.7%", + "counters": { + "Camille": "53.25%", + "Sona": "51.89%", + "Blitzcrank": "51.2%", + "Maokai": "51.02%", + "Braum": "49.67%", + "Pyke": "49.38%", + "Renata Glasc": "49.3%", + "Senna": "49.25%", + "Ashe": "49.07%", + "Nami": "48.64%" + }, + "synergy": { + "Nilah": "57.38%", + "Twitch": "57.02%", + "Senna": "56.68%", + "Seraphine": "55.59%", + "Miss Fortune": "54.71%", + "Zeri": "54.30%", + "Tristana": "54.22%", + "Draven": "54.22%", + "Twisted Fate": "53.57%", + "Samira": "53.57%", + "Jhin": "53.03%", + "Lucian": "52.84%", + "Karthus": "52.73%", + "Ezreal": "52.60%", + "Yasuo": "52.58%", + "Kalista": "52.35%", + "Vayne": "51.97%", + "Ashe": "51.80%", + "Sivir": "51.10%", + "Caitlyn": "51.05%", + "Smolder": "50.23%", + "Aphelios": "50.23%" } }, { - "champion_name": "Blitzcrank", - "pick_rate": "8.8%", - "counters": { - "Taric": "56.85%", - "Rakan": "52.88%", - "Leona": "52.47%", - "Braum": "52.28%", - "Renata Glasc": "51.05%", - "Maokai": "51.04%", - "Zyra": "50.98%", - "Rell": "50.78%", - "Alistar": "50.73%", - "Zilean": "50.7%" - }, + "champion_name": "Karma", + "pick_rate": "9.6%", + "counters": "no info", "synergy": { - "Nilah": "58.59%", - "Xayah": "55.50%", - "Draven": "54.17%", - "Lucian": "53.85%", - "Ashe": "53.42%", - "Vayne": "52.91%", - "Twitch": "52.80%", - "Seraphine": "52.34%", - "Jinx": "51.90%", - "Karthus": "51.89%", - "Varus": "51.87%", - "Tristana": "51.72%", - "Miss Fortune": "51.53%", - "Zeri": "51.27%", - "Aphelios": "50.95%", - "Kalista": "50.84%", - "Jhin": "50.74%", - "Ezreal": "50.08%", - "Caitlyn": "50.00%" + "Tristana": "54.41%", + "Ashe": "52.79%", + "Twisted Fate": "52.57%", + "Jinx": "52.21%", + "Miss Fortune": "51.67%", + "Kalista": "51.02%", + "Twitch": "50.19%", + "Caitlyn": "50.15%", + "Zeri": "50.00%" } }, { - "champion_name": "Pyke", - "pick_rate": "7.6%", - "counters": { - "Rakan": "53.38%", - "Maokai": "52.78%", - "Neeko": "52.1%", - "Rell": "52.08%", - "Taric": "51.82%", - "Janna": "51.79%", - "Blitzcrank": "51.44%", - "Alistar": "50.94%", - "Bard": "50.94%", - "Thresh": "50.49%" - }, + "champion_name": "Thresh", + "pick_rate": "9.4%", + "counters": "no info", "synergy": { - "Karthus": "61.98%", - "Nilah": "60.43%", - "Yasuo": "57.30%", - "Tristana": "55.96%", - "Twitch": "55.08%", - "Ashe": "54.27%", - "Swain": "53.91%", - "Ziggs": "53.28%", - "Seraphine": "52.82%", - "Vayne": "52.64%", - "Varus": "51.58%", - "Jhin": "51.51%", - "Ezreal": "51.49%", - "Hwei": "51.43%", - "Draven": "50.95%" + "Yasuo": "62.82%", + "Senna": "58.33%", + "Xayah": "56.52%", + "Sivir": "56.25%", + "Seraphine": "55.74%", + "Kalista": "54.41%", + "Twitch": "52.68%", + "Miss Fortune": "52.44%", + "Jinx": "52.43%", + "Draven": "52.37%", + "Aphelios": "52.12%", + "Smolder": "51.38%", + "Tristana": "51.11%", + "Samira": "50.59%", + "Varus": "50.32%", + "Vayne": "50.15%", + "Kai'Sa": "50.05%" } }, { - "champion_name": "Karma", - "pick_rate": "7.6%", - "counters": { - "Pyke": "57.3%", - "Maokai": "55.33%", - "Zyra": "54.97%", - "Blitzcrank": "54.92%", - "Rakan": "54.52%", - "Janna": "54.36%", - "Sona": "54.33%", - "Nautilus": "54.21%", - "Soraka": "53.61%", - "Neeko": "52.83%" - }, + "champion_name": "Pyke", + "pick_rate": "8.5%", + "counters": "no info", "synergy": { - "Draven": "53.57%", - "Vayne": "51.30%", - "Jhin": "50.49%", - "Ezreal": "50.43%", - "Tristana": "50.42%" + "Karthus": "60.47%", + "Swain": "58.06%", + "Senna": "57.89%", + "Ashe": "55.02%", + "Twitch": "54.55%", + "Vayne": "53.90%", + "Jinx": "53.67%", + "Yasuo": "53.66%", + "Seraphine": "53.19%", + "Twisted Fate": "53.07%", + "Nilah": "53.00%", + "Smolder": "51.63%", + "Zeri": "50.56%", + "Ezreal": "50.22%", + "Draven": "50.00%" } }, { - "champion_name": "Lulu", - "pick_rate": "7.4%", - "counters": { - "Taric": "56.84%", - "Zilean": "54.43%", - "Neeko": "54.04%", - "Senna": "54.04%", - "Sona": "53.78%", - "Zyra": "53.7%", - "Maokai": "53.46%", - "Blitzcrank": "52.96%", - "Bard": "52.89%", - "Thresh": "52.88%" - }, - "synergy": { - "Miss Fortune": "55.96%", - "Sivir": "55.62%", - "Tristana": "55.22%", - "Ashe": "51.59%", - "Draven": "51.52%", - "Varus": "51.22%", - "Twitch": "51.14%", - "Jinx": "50.76%", - "Kog'Maw": "50.00%", - "Kalista": "50.00%" + "champion_name": "Blitzcrank", + "pick_rate": "8.1%", + "counters": { + "Pantheon": "54.85%", + "Leona": "54.08%", + "Maokai": "53.66%", + "Braum": "53.61%", + "Rakan": "53.26%", + "Taric": "53.13%", + "Alistar": "52.33%", + "Shaco": "51.5%", + "Rell": "50.9%", + "Zilean": "50.57%" + }, + "synergy": { + "Seraphine": "65.22%", + "Karthus": "57.78%", + "Draven": "56.57%", + "Vayne": "55.39%", + "Varus": "54.74%", + "Jinx": "54.30%", + "Twisted Fate": "54.17%", + "Tristana": "53.18%", + "Sivir": "53.06%", + "Senna": "52.85%", + "Aphelios": "52.14%", + "Kai'Sa": "51.65%", + "Lucian": "50.78%", + "Nilah": "50.67%", + "Twitch": "50.00%", + "Ashe": "50.00%", + "Zeri": "50.00%" } }, { "champion_name": "Milio", - "pick_rate": "6.7%", - "counters": { - "Taric": "54.79%", - "Blitzcrank": "53.66%", - "Braum": "53.22%", - "Vel'Koz": "53.08%", - "Senna": "52.78%", - "Bard": "52.75%", - "Neeko": "52.17%", - "Rakan": "51.91%", - "Soraka": "51.52%", - "Thresh": "51.33%" + "pick_rate": "8.1%", + "counters": { + "Blitzcrank": "56.32%", + "Braum": "55.45%", + "Maokai": "54.37%", + "Camille": "53.86%", + "Janna": "53.43%", + "Thresh": "53.37%", + "Zilean": "53.04%", + "Tahm Kench": "53.04%", + "Vel'Koz": "52.98%", + "Leona": "52.96%" }, - "synergy": { - "Kog'Maw": "53.82%", - "Xayah": "52.76%", - "Lucian": "51.94%", - "Zeri": "51.56%", - "Vayne": "51.56%", - "Kalista": "51.56%", - "Ashe": "51.48%", - "Draven": "50.69%", - "Aphelios": "50.42%", - "Twitch": "50.00%" - } - }, - { - "champion_name": "Nami", - "pick_rate": "6.5%", - "counters": { - "Maokai": "56.37%", - "Neeko": "53.14%", - "Blitzcrank": "53.03%", - "Rakan": "52.58%", - "Zyra": "52.29%", - "Soraka": "51.92%", - "Milio": "51.67%", - "Pyke": "51.56%", - "Janna": "51.44%", - "Bard": "51.33%" - }, - "synergy": { - "Swain": "59.52%", - "Brand": "55.69%", - "Karthus": "55.65%", - "Twitch": "52.16%", - "Ashe": "51.88%", - "Seraphine": "51.43%", - "Jhin": "51.32%", - "Vayne": "51.29%", - "Ezreal": "51.27%" - } + "synergy": [ + "no data" + ] }, { - "champion_name": "Bard", - "pick_rate": "6.1%", - "counters": { - "Taric": "52.2%", - "Janna": "50.56%", - "Maokai": "50.28%", - "Zilean": "49.95%", - "Senna": "49.94%", - "Blitzcrank": "49.89%", - "Renata Glasc": "49.52%", - "Soraka": "49.45%", - "Rell": "49.3%", - "Pyke": "49.06%" + "champion_name": "Lulu", + "pick_rate": "7.9%", + "counters": { + "Vel'Koz": "57.35%", + "Blitzcrank": "56.23%", + "Zilean": "55.78%", + "Tahm Kench": "55.43%", + "Renata Glasc": "54.86%", + "Rell": "54.7%", + "Janna": "54.6%", + "Maokai": "54.51%", + "Braum": "53.79%", + "Ashe": "53.65%" }, - "synergy": { - "Nilah": "59.77%", - "Vayne": "55.15%", - "Caitlyn": "55.12%", - "Twitch": "55.06%", - "Ziggs": "54.37%", - "Tristana": "53.92%", - "Karthus": "53.62%", - "Seraphine": "53.05%", - "Lucian": "52.97%", - "Ezreal": "52.51%", - "Ashe": "52.44%", - "Jhin": "51.89%", - "Swain": "51.69%", - "Jinx": "51.34%", - "Zeri": "50.76%" - } + "synergy": [ + "no data" + ] }, { - "champion_name": "Alistar", - "pick_rate": "5.8%", - "counters": { - "Sona": "54.51%", - "Janna": "54.04%", - "Swain": "53.24%", - "Vel'Koz": "52.78%", - "Senna": "52.64%", - "Zilean": "52.39%", - "Soraka": "52.22%", - "Seraphine": "51.97%", - "Xerath": "51.86%", - "Milio": "51.85%" - }, - "synergy": { - "Seraphine": "59.43%", - "Ziggs": "57.86%", - "Sivir": "55.20%", - "Hwei": "54.65%", - "Zeri": "53.37%", - "Ashe": "52.55%", - "Yasuo": "52.42%", - "Vayne": "52.01%", - "Twitch": "51.83%", - "Xayah": "51.49%", - "Jinx": "50.82%", - "Jhin": "50.68%", - "Ezreal": "50.58%", - "Draven": "50.28%", - "Miss Fortune": "50.20%" - } + "champion_name": "Rakan", + "pick_rate": "7.7%", + "counters": "no info", + "synergy": [ + "no data" + ] }, { - "champion_name": "Ashe", + "champion_name": "Bard", "pick_rate": "5.7%", "counters": { - "Pyke": "56.98%", - "Sylas": "54.95%", - "Blitzcrank": "54.83%", - "Heimerdinger": "54.8%", - "Rell": "54.67%", - "Nautilus": "54.35%", - "Bard": "53.44%", - "Milio": "53.1%", - "Rakan": "52.69%", - "Brand": "52.5%" - }, - "synergy": { - "Karthus": "56.83%", - "Seraphine": "54.60%", - "Jinx": "54.17%", - "Nilah": "53.85%", - "Twitch": "53.55%", - "Ezreal": "51.69%", - "Swain": "51.32%", - "Ziggs": "50.79%", - "Lucian": "50.00%", - "Zeri": "50.00%" + "Maokai": "56.1%", + "Renata Glasc": "53.77%", + "Janna": "53.45%", + "Pyke": "51.79%", + "Morgana": "51.04%", + "Nami": "50.92%", + "Pantheon": "50.72%", + "Blitzcrank": "50.43%", + "Brand": "50.34%", + "Senna": "50.21%" + }, + "synergy": { + "Yasuo": "60.34%", + "Seraphine": "59.53%", + "Senna": "59.42%", + "Ziggs": "57.58%", + "Caitlyn": "55.43%", + "Xayah": "54.26%", + "Hwei": "54.17%", + "Twisted Fate": "53.66%", + "Jinx": "52.43%", + "Twitch": "51.68%", + "Aphelios": "51.54%", + "Karthus": "51.47%", + "Samira": "50.60%", + "Vayne": "50.34%" } }, { - "champion_name": "Soraka", + "champion_name": "Nami", "pick_rate": "5.5%", "counters": { - "Blitzcrank": "54.17%", - "Neeko": "51.31%", - "Sona": "51.25%", - "Zyra": "51.13%", - "Vel'Koz": "51.05%", - "Bard": "50.55%", - "Senna": "50.38%", - "Yuumi": "50.06%", - "Janna": "49.99%", - "Pyke": "49.98%" + "Morgana": "56.08%", + "Blitzcrank": "55.61%", + "Maokai": "55.57%", + "Vel'Koz": "54.47%", + "Rakan": "54.44%", + "Neeko": "54.21%", + "Rell": "53.9%", + "Nautilus": "53.32%", + "Thresh": "53.28%", + "Senna": "53.17%" }, - "synergy": { - "Yasuo": "63.54%", - "Swain": "59.70%", - "Samira": "58.41%", - "Vayne": "57.03%", - "Ashe": "53.73%", - "Jinx": "53.47%", - "Ziggs": "53.38%", - "Jhin": "53.17%", - "Tristana": "52.80%", - "Lucian": "52.34%", - "Miss Fortune": "51.96%", - "Twitch": "51.63%", - "Draven": "50.30%" - } - }, - { - "champion_name": "Lux", - "pick_rate": "5.1%", - "counters": { - "Blitzcrank": "56.57%", - "Maokai": "54.94%", - "Zyra": "54.66%", - "Shaco": "54.45%", - "Vel'Koz": "54.1%", - "Sylas": "53.87%", - "Zilean": "53.51%", - "Pyke": "53.5%", - "Xerath": "52.75%", - "Nami": "52.72%" - }, - "synergy": { - "Karthus": "59.46%", - "Twitch": "53.57%", - "Xayah": "52.17%", - "Ashe": "51.05%", - "Varus": "50.88%", - "Tristana": "50.67%", - "Jinx": "50.00%", - "Zeri": "50.00%" - } + "synergy": [ + "no data" + ] }, { - "champion_name": "Renata Glasc", - "pick_rate": "5.0%", + "champion_name": "Soraka", + "pick_rate": "5.4%", "counters": "no info", "synergy": [ "no data" ] }, { - "champion_name": "Rell", - "pick_rate": "4.9%", + "champion_name": "Ashe", + "pick_rate": "4.8%", "counters": { - "Poppy": "53.79%", - "Seraphine": "53.57%", - "Soraka": "52.57%", - "Zilean": "52.54%", - "Alistar": "52.45%", - "Shaco": "51.5%", - "Maokai": "50.89%", - "Sona": "50.75%", - "Bard": "50.7%", - "Janna": "50.5%" + "Camille": "58.72%", + "Maokai": "57.34%", + "Pyke": "56.14%", + "Blitzcrank": "55.88%", + "Nautilus": "55.46%", + "Taric": "55.41%", + "Vel'Koz": "54.55%", + "Zyra": "54.13%", + "Soraka": "53.87%", + "Xerath": "53.83%" }, "synergy": { - "Ziggs": "61.31%", - "Karthus": "60.18%", - "Seraphine": "59.02%", - "Tristana": "58.16%", - "Lucian": "57.30%", - "Twitch": "56.30%", - "Miss Fortune": "54.44%", - "Ashe": "53.88%", - "Sivir": "53.49%", - "Jinx": "52.83%", - "Draven": "52.25%", - "Xayah": "52.10%", - "Ezreal": "52.08%", - "Kai'Sa": "51.33%", - "Samira": "51.15%", - "Zeri": "50.35%", - "Yasuo": "50.25%", - "Aphelios": "50.21%" + "Jinx": "53.73%", + "Jhin": "53.36%", + "Karthus": "52.31%", + "Kalista": "51.90%", + "Twisted Fate": "51.81%", + "Zeri": "50.65%" } }, { - "champion_name": "Yuumi", - "pick_rate": "4.9%", - "counters": { - "Maokai": "61.28%", - "Rell": "59.85%", - "Taric": "57.47%", - "Alistar": "57.31%", - "Nautilus": "56.64%", - "Rakan": "55.78%", - "Pyke": "55.71%", - "Leona": "55.63%", - "Sylas": "55.27%", - "Thresh": "55.23%" - }, + "champion_name": "Alistar", + "pick_rate": "4.7%", + "counters": "no info", "synergy": { - "Seraphine": "66.67%", - "Yasuo": "53.25%", - "Tristana": "52.55%", - "Vayne": "51.74%", - "Zeri": "51.45%", - "Twitch": "50.41%" + "Karthus": "64.86%", + "Seraphine": "62.86%", + "Zeri": "60.83%", + "Sivir": "59.26%", + "Jinx": "58.42%", + "Yasuo": "58.09%", + "Tristana": "55.84%", + "Senna": "53.27%", + "Twitch": "52.99%", + "Draven": "51.74%", + "Xayah": "51.25%", + "Ezreal": "50.91%", + "Twisted Fate": "50.75%", + "Samira": "50.00%" } }, { "champion_name": "Leona", - "pick_rate": "4.8%", - "counters": { - "Taric": "56.7%", - "Janna": "54.1%", - "Bard": "54.03%", - "Shaco": "53.67%", - "Maokai": "53.55%", - "Rell": "53.22%", - "Soraka": "53.11%", - "Lux": "53.09%", - "Morgana": "53.02%", - "Braum": "51.72%" - }, + "pick_rate": "4.4%", + "counters": "no info", "synergy": { - "Seraphine": "56.47%", - "Xayah": "55.00%", - "Zeri": "54.63%", - "Ezreal": "54.35%", - "Twitch": "54.27%", - "Caitlyn": "53.68%", - "Jhin": "53.21%", - "Yasuo": "53.19%", - "Lucian": "52.72%", - "Miss Fortune": "51.92%", - "Aphelios": "51.70%", - "Tristana": "51.48%", - "Kalista": "51.16%" + "Swain": "68.09%", + "Senna": "60.41%", + "Jinx": "59.02%", + "Seraphine": "58.90%", + "Ashe": "57.67%", + "Aphelios": "56.67%", + "Nilah": "56.52%", + "Vayne": "55.20%", + "Lucian": "54.63%", + "Smolder": "54.42%", + "Miss Fortune": "53.39%", + "Karthus": "53.06%", + "Xayah": "51.61%", + "Ezreal": "51.04%", + "Kalista": "50.59%", + "Twisted Fate": "50.31%" } }, { - "champion_name": "Xerath", - "pick_rate": "4.8%", - "counters": { - "Sylas": "55%", - "Maokai": "53.98%", - "Sona": "53.77%", - "Janna": "53.15%", - "Pyke": "52.84%", - "Soraka": "52.33%", - "Rakan": "52.02%", - "Leona": "51.76%", - "Zyra": "51.72%", - "Nami": "51.6%" - }, - "synergy": { - "Ziggs": "55.24%", - "Jinx": "53.95%", - "Vayne": "53.32%", - "Samira": "52.63%", - "Xayah": "52.57%", - "Ashe": "52.33%", - "Jhin": "51.81%", - "Caitlyn": "51.12%", - "Hwei": "50.88%", - "Tristana": "50.70%", - "Ezreal": "50.00%" + "champion_name": "Yuumi", + "pick_rate": "4.4%", + "counters": { + "Rell": "62.65%", + "Braum": "58.82%", + "Blitzcrank": "58.05%", + "Leona": "57.77%", + "Pyke": "57.61%", + "Taric": "57.46%", + "Swain": "56.85%", + "Rakan": "56.72%", + "Maokai": "56.12%", + "Janna": "55.85%" + }, + "synergy": { + "Rammus": "64.71%", + "Jinx": "55.32%", + "Vayne": "54.26%", + "Samira": "53.57%", + "Lucian": "53.52%", + "Tristana": "53.33%", + "Twitch": "53.31%", + "Jhin": "50.00%", + "Xayah": "50.00%" } }, { "champion_name": "Zyra", - "pick_rate": "3.8%", - "counters": { - "Sona": "53.56%", - "Pyke": "53.25%", - "Heimerdinger": "53.11%", - "Bard": "53.07%", - "Leona": "51.91%", - "Janna": "51.88%", - "Shaco": "51.56%", - "Taric": "51.43%", - "Rakan": "50.98%", - "Nautilus": "50.75%" - }, - "synergy": { - "Seraphine": "55.06%", - "Vayne": "54.84%", - "Tristana": "54.05%", - "Twitch": "52.96%", - "Samira": "51.98%", - "Jhin": "51.51%", - "Karthus": "51.16%", - "Draven": "50.65%", - "Xayah": "50.00%" + "pick_rate": "4.3%", + "counters": { + "Taric": "54.73%", + "Leona": "54.05%", + "Sona": "53.31%", + "Maokai": "52.98%", + "Renata Glasc": "52.84%", + "Pyke": "52.6%", + "Zac": "52.59%", + "Janna": "52.42%", + "Zilean": "51.88%", + "Yuumi": "51.63%" + }, + "synergy": { + "Xayah": "66.07%", + "Sivir": "61.54%", + "Twisted Fate": "53.68%", + "Miss Fortune": "53.25%", + "Ezreal": "52.52%", + "Senna": "52.28%", + "Tristana": "50.94%", + "Vayne": "50.18%", + "Jinx": "50.00%" } }, { - "champion_name": "Braum", - "pick_rate": "3.6%", - "counters": { - "Zilean": "55.95%", - "Soraka": "54.2%", - "Bard": "53.76%", - "Rell": "53.74%", - "Taric": "53.44%", - "Sona": "53.2%", - "Senna": "53.06%", - "Rakan": "52.88%", - "Janna": "52.49%", - "Renata Glasc": "52.31%" - }, + "champion_name": "Rell", + "pick_rate": "4.1%", + "counters": "no info", "synergy": { - "Sivir": "60.82%", - "Twitch": "59.55%", - "Xayah": "54.40%", - "Varus": "52.69%", - "Kog'Maw": "52.03%", - "Ashe": "51.92%", - "Jinx": "51.32%", - "Tristana": "50.94%", - "Vayne": "50.83%", - "Draven": "50.75%", - "Ezreal": "50.35%" + "Xayah": "58.33%", + "Twisted Fate": "56.72%", + "Twitch": "56.48%", + "Nilah": "56.25%", + "Miss Fortune": "55.88%", + "Zeri": "55.45%", + "Senna": "55.08%", + "Vayne": "53.65%", + "Jinx": "53.40%", + "Kalista": "52.46%", + "Samira": "52.46%", + "Aphelios": "52.17%", + "Draven": "52.15%", + "Tristana": "51.90%", + "Karthus": "51.43%", + "Ezreal": "51.32%", + "Jhin": "50.43%", + "Yasuo": "50.00%" } }, { - "champion_name": "Morgana", - "pick_rate": "3.2%", + "champion_name": "Lux", + "pick_rate": "3.9%", + "counters": { + "Camille": "62.93%", + "Pantheon": "60%", + "Pyke": "58.92%", + "Bard": "57.24%", + "Shaco": "56.3%", + "Blitzcrank": "55.42%", + "Maokai": "55.39%", + "Leona": "55.08%", + "Alistar": "54.69%", + "Zilean": "54.61%" + }, + "synergy": { + "Ziggs": "57.14%", + "Twisted Fate": "56.82%", + "Samira": "52.63%", + "Senna": "52.05%", + "Ezreal": "50.65%", + "Nilah": "50.00%" + } + }, + { + "champion_name": "Braum", + "pick_rate": "3.8%", + "counters": "no info", + "synergy": [ + "no data" + ] + }, + { + "champion_name": "Hwei", + "pick_rate": "2.9%", "counters": { - "Zyra": "56.73%", - "Senna": "56.35%", - "Rell": "56.29%", - "Janna": "55.6%", - "Milio": "55.2%", - "Vel'Koz": "55.08%", - "Nami": "54.67%", - "Taric": "54.31%", - "Karma": "54.24%", - "Maokai": "53.2%" + "Camille": "63.76%", + "Pyke": "60.47%", + "Maokai": "58.72%", + "Xerath": "58.7%", + "Rakan": "58.47%", + "Taric": "58.43%", + "Swain": "58.1%", + "Zilean": "57.69%", + "Vel'Koz": "57.69%", + "Janna": "57.32%" }, "synergy": { - "Karthus": "55.56%", - "Nilah": "53.13%", - "Miss Fortune": "52.07%", - "Hwei": "51.72%", - "Vayne": "51.31%", - "Sivir": "50.91%", - "Draven": "50.63%", - "Tristana": "50.00%" + "Lucian": "57.14%", + "Tristana": "54.55%", + "Swain": "53.85%", + "Miss Fortune": "51.85%", + "Seraphine": "51.02%" } }, { "champion_name": "Zilean", - "pick_rate": "2.5%", - "counters": { - "Janna": "53.27%", - "Shaco": "53.02%", - "Senna": "51.99%", - "Neeko": "51.67%", - "Maokai": "51.41%", - "Xerath": "51.07%", - "Milio": "50.67%", - "Sylas": "50.34%", - "Soraka": "50.28%", - "Vel'Koz": "50.23%" - }, + "pick_rate": "2.8%", + "counters": "no info", "synergy": { - "Swain": "58.97%", - "Hwei": "55.56%", - "Samira": "54.12%", - "Kog'Maw": "54.05%", - "Twitch": "53.80%", - "Aphelios": "53.47%", - "Vayne": "51.88%", - "Caitlyn": "51.79%", - "Seraphine": "51.67%", - "Ezreal": "51.07%", - "Ashe": "50.12%" + "Miss Fortune": "62.03%", + "Twisted Fate": "61.05%", + "Draven": "54.63%", + "Swain": "54.55%", + "Vayne": "54.47%", + "Ashe": "53.97%", + "Caitlyn": "53.13%", + "Seraphine": "52.44%", + "Smolder": "51.37%", + "Xayah": "51.22%", + "Sivir": "50.00%" } }, { - "champion_name": "Sona", - "pick_rate": "2.4%", - "counters": { - "Taric": "56.83%", - "Leona": "55.04%", - "Seraphine": "53.49%", - "Blitzcrank": "52.56%", - "Senna": "51.71%", - "Bard": "51.4%", - "Nami": "50.59%", - "Milio": "50.43%", - "Zilean": "50.26%", - "Pyke": "50.19%" - }, + "champion_name": "Morgana", + "pick_rate": "2.8%", + "counters": "no info", "synergy": { - "Yasuo": "64.52%", - "Tristana": "60.29%", - "Ziggs": "58.51%", - "Samira": "57.45%", - "Sivir": "56.60%", - "Vayne": "56.55%", - "Nilah": "55.88%", - "Kog'Maw": "53.49%", - "Lucian": "53.04%", - "Xayah": "52.90%", - "Seraphine": "52.76%", - "Ezreal": "52.71%", - "Miss Fortune": "52.23%", - "Varus": "51.05%", - "Ashe": "50.93%", - "Caitlyn": "50.81%", - "Twitch": "50.21%" + "Xayah": "60.00%", + "Draven": "57.81%", + "Nilah": "54.55%", + "Samira": "51.61%", + "Sivir": "50.00%", + "Seraphine": "50.00%" } }, { - "champion_name": "Hwei", - "pick_rate": "2.1%", - "counters": { - "Taric": "66.91%", - "Sona": "66.55%", - "Blitzcrank": "65.66%", - "Janna": "65.3%", - "Brand": "65.18%", - "Pyke": "63.85%", - "Xerath": "63.02%", - "Vel'Koz": "62.71%", - "Morgana": "62.31%", - "Milio": "62.01%" - }, - "synergy": { - "Nilah": "51.61%" + "champion_name": "Xerath", + "pick_rate": "2.7%", + "counters": { + "Blitzcrank": "58.63%", + "Neeko": "56.43%", + "Sona": "56.36%", + "Maokai": "56.26%", + "Pyke": "56.15%", + "Leona": "56.02%", + "Braum": "55.06%", + "Rakan": "54.83%", + "Nautilus": "54.65%", + "Janna": "53.86%" + }, + "synergy": { + "Samira": "70.00%", + "Twisted Fate": "61.36%", + "Caitlyn": "57.97%", + "Karthus": "55.56%", + "Ashe": "52.46%", + "Vayne": "50.55%", + "Zeri": "50.00%" } }, { - "champion_name": "Brand", - "pick_rate": "2.0%", - "counters": { - "Sona": "58.11%", - "Maokai": "57.24%", - "Xerath": "57.03%", - "Janna": "56.31%", - "Pyke": "56.01%", - "Zyra": "55.13%", - "Leona": "54.96%", - "Zilean": "54.92%", - "Soraka": "54.73%", - "Senna": "54.19%" - }, + "champion_name": "Shaco", + "pick_rate": "2.6%", + "counters": "no info", "synergy": { - "Seraphine": "57.69%", - "Karthus": "55.17%", - "Yasuo": "55.17%", - "Kalista": "53.49%", - "Tristana": "52.31%", - "Xayah": "51.40%", - "Varus": "50.17%" + "Karthus": "76.92%", + "Twisted Fate": "62.64%", + "Tristana": "58.62%", + "Senna": "56.19%", + "Miss Fortune": "56.10%", + "Aphelios": "52.78%", + "Hwei": "52.63%", + "Ashe": "52.48%", + "Draven": "52.24%", + "Nilah": "50.00%", + "Caitlyn": "50.00%", + "Ziggs": "50.00%" } }, { - "champion_name": "Seraphine", - "pick_rate": "2.0%", + "champion_name": "Renata Glasc", + "pick_rate": "2.4%", + "counters": "no info", + "synergy": [ + "no data" + ] + }, + { + "champion_name": "Camille", + "pick_rate": "2.3%", "counters": { - "Sylas": "58.33%", - "Maokai": "57.24%", - "Zilean": "56.09%", - "Bard": "55.97%", - "Pyke": "54.69%", - "Soraka": "53.72%", - "Blitzcrank": "53.61%", - "Yuumi": "53.6%", - "Milio": "52.99%", - "Thresh": "52.66%" - }, - "synergy": { - "Vayne": "59.62%", - "Tristana": "59.38%", - "Miss Fortune": "52.50%", - "Ashe": "52.20%", - "Jinx": "51.11%", - "Draven": "50.94%" + "Taric": "58.76%", + "Renata Glasc": "58.12%", + "Braum": "57.32%", + "Leona": "54.48%", + "Brand": "54.2%", + "Bard": "53.76%", + "Zac": "53.03%", + "Maokai": "52.96%", + "Tahm Kench": "52.94%", + "Zilean": "52.78%" + }, + "synergy": { + "Twitch": "62.67%", + "Sivir": "58.33%", + "Zeri": "56.47%", + "Tristana": "56.20%", + "Senna": "55.81%", + "Xayah": "55.26%", + "Aphelios": "53.62%", + "Nilah": "53.13%", + "Seraphine": "52.94%", + "Varus": "52.89%", + "Karthus": "52.63%", + "Ashe": "52.50%", + "Smolder": "52.34%", + "Jhin": "52.09%", + "Draven": "52.06%", + "Ziggs": "52.00%", + "Twisted Fate": "50.94%", + "Vayne": "50.92%", + "Lucian": "50.35%", + "Swain": "50.00%" } }, { - "champion_name": "Neeko", + "champion_name": "Sona", + "pick_rate": "2.3%", + "counters": { + "Zilean": "61.68%", + "Rell": "59.62%", + "Leona": "57.21%", + "Neeko": "55.71%", + "Thresh": "55.03%", + "Maokai": "54.7%", + "Bard": "53.59%", + "Braum": "51.81%", + "Blitzcrank": "51.79%", + "Seraphine": "51.2%" + }, + "synergy": { + "Nilah": "71.43%", + "Tristana": "63.16%", + "Twisted Fate": "56.19%", + "Jhin": "55.34%", + "Kai'Sa": "55.11%", + "Varus": "52.63%", + "Aphelios": "52.00%", + "Twitch": "51.46%", + "Vayne": "51.22%", + "Smolder": "50.72%", + "Lucian": "50.00%", + "Samira": "50.00%" + } + }, + { + "champion_name": "Brand", "pick_rate": "1.9%", - "counters": { - "Vel'Koz": "60.45%", - "Sona": "53.26%", - "Bard": "52.61%", - "Brand": "52.38%", - "Taric": "52.17%", - "Alistar": "51%", - "Shaco": "50.68%", - "Senna": "50.42%", - "Renata Glasc": "50.38%", - "Morgana": "50.35%" - }, + "counters": "no info", "synergy": { - "Nilah": "77.78%", - "Yasuo": "59.46%", - "Xayah": "59.26%", - "Hwei": "58.82%", - "Lucian": "56.79%", - "Vayne": "53.85%", - "Twitch": "53.17%", - "Kalista": "50.75%", - "Ezreal": "50.72%", - "Ashe": "50.29%", - "Quinn": "50.00%", - "Samira": "50.00%", - "Varus": "50.00%" + "Samira": "59.09%", + "Yasuo": "57.89%", + "Twisted Fate": "57.14%", + "Xayah": "56.52%", + "Jinx": "55.88%", + "Jhin": "53.98%", + "Draven": "53.70%", + "Seraphine": "52.50%", + "Aphelios": "50.00%" } }, { - "champion_name": "Maokai", - "pick_rate": "1.6%", - "counters": { - "Shaco": "58.18%", - "Braum": "54.8%", - "Sona": "53.99%", - "Janna": "52.72%", - "Renata Glasc": "52.39%", - "Zyra": "51.46%", - "Alistar": "51.45%", - "Soraka": "50.8%", - "Taric": "50.42%", - "Neeko": "50.29%" - }, + "champion_name": "Pantheon", + "pick_rate": "1.8%", + "counters": "no info", "synergy": { - "Xayah": "60.00%", - "Ziggs": "58.33%", - "Karthus": "57.89%", - "Kai'Sa": "53.95%", - "Vayne": "53.51%", - "Sivir": "52.63%", - "Jhin": "52.46%", - "Aphelios": "52.46%", - "Draven": "52.38%", - "Ezreal": "52.29%", - "Samira": "51.81%", - "Varus": "51.53%", - "Zeri": "51.02%", - "Twitch": "50.00%", - "Ashe": "50.00%", - "Hwei": "50.00%" + "Karthus": "66.67%", + "Twisted Fate": "58.82%", + "Miss Fortune": "56.60%", + "Senna": "56.59%", + "Jhin": "55.67%", + "Nilah": "54.55%", + "Ezreal": "54.37%", + "Swain": "53.85%", + "Lucian": "53.06%", + "Ashe": "52.38%", + "Smolder": "52.07%", + "Samira": "51.97%", + "Jinx": "51.72%" } }, { - "champion_name": "Vel'Koz", - "pick_rate": "1.6%", - "counters": { - "Pyke": "55.14%", - "Zyra": "54.76%", - "Seraphine": "54.15%", - "Blitzcrank": "53.89%", - "Janna": "53.62%", - "Bard": "53.22%", - "Sona": "52.97%", - "Nami": "52.1%", - "Maokai": "51.85%", - "Renata Glasc": "51.64%" - }, + "champion_name": "Seraphine", + "pick_rate": "1.7%", + "counters": "no info", "synergy": { - "Karthus": "61.90%", - "Vayne": "55.34%", - "Sivir": "54.72%", - "Ashe": "54.44%", - "Aphelios": "53.25%", - "Seraphine": "52.38%", - "Jhin": "51.92%", - "Caitlyn": "51.43%", - "Ezreal": "51.22%", - "Twitch": "50.39%", - "Varus": "50.37%", - "Tristana": "50.00%", - "Miss Fortune": "50.00%" + "Lucian": "61.54%", + "Tristana": "61.54%", + "Vayne": "58.90%", + "Varus": "57.69%", + "Jinx": "56.25%", + "Miss Fortune": "55.32%", + "Senna": "51.51%", + "Ezreal": "50.83%", + "Samira": "50.00%", + "Karthus": "50.00%" } }, { - "champion_name": "Swain", + "champion_name": "Neeko", "pick_rate": "1.6%", "counters": { - "Zilean": "57.77%", - "Sona": "57.32%", - "Vel'Koz": "57.25%", - "Janna": "56.41%", - "Brand": "55.87%", - "Yuumi": "55.78%", - "Zyra": "55.78%", - "Xerath": "55.48%", - "Soraka": "55.34%", - "Senna": "55.32%" - }, - "synergy": { - "Tristana": "64.29%", - "Karthus": "54.55%", - "Vayne": "54.01%", - "Jhin": "53.18%", - "Twitch": "50.70%", - "Nilah": "50.00%", - "Ziggs": "50.00%" + "Zilean": "60.66%", + "Taric": "58.7%", + "Zac": "57.78%", + "Leona": "57.76%", + "Pantheon": "57.14%", + "Janna": "56.74%", + "Blitzcrank": "56.38%", + "Bard": "55.85%", + "Camille": "55.41%", + "Rell": "55.19%" + }, + "synergy": { + "Yasuo": "67.74%", + "Tristana": "66.67%", + "Twitch": "65.63%", + "Ashe": "60.87%", + "Senna": "59.22%", + "Nilah": "57.14%", + "Caitlyn": "54.55%", + "Draven": "51.52%", + "Twisted Fate": "50.94%", + "Vayne": "50.00%", + "Jinx": "50.00%", + "Miss Fortune": "50.00%" } }, { "champion_name": "Taric", - "pick_rate": "1.4%", + "pick_rate": "1.5%", "counters": { - "Soraka": "55.26%", - "Rell": "52.91%", - "Vel'Koz": "52.75%", - "Janna": "50.88%", - "Zilean": "50.55%", - "Rakan": "49.62%", - "Maokai": "49.58%", - "Nami": "49.42%", - "Xerath": "49.28%", - "Senna": "49.24%" - }, - "synergy": { - "Karthus": "70.83%", - "Yasuo": "68.91%", - "Lee Sin": "68.18%", - "Master Yi": "59.26%", - "Kog'Maw": "58.82%", - "Sivir": "58.62%", - "Varus": "56.63%", - "Swain": "56.45%", - "Seraphine": "56.41%", - "Kalista": "55.94%", - "Ziggs": "55.00%", - "Vayne": "54.01%", - "Jinx": "52.38%", - "Lucian": "51.91%", - "Kai'Sa": "51.81%", - "Ashe": "51.67%", - "Miss Fortune": "50.94%", - "Ezreal": "50.20%", - "Twitch": "50.00%" + "Swain": "57.63%", + "Sona": "57.32%", + "Janna": "54.88%", + "Nami": "54.27%", + "Lux": "53.54%", + "Seraphine": "53.45%", + "Bard": "52.97%", + "Soraka": "52.32%", + "Thresh": "52.21%", + "Braum": "51.67%" + }, + "synergy": { + "Vayne": "60.16%", + "Ashe": "58.46%", + "Yasuo": "58.33%", + "Twisted Fate": "57.89%", + "Pyke": "57.14%", + "Senna": "56.59%", + "Nilah": "56.32%", + "Kalista": "55.13%", + "Jinx": "54.39%", + "Aphelios": "53.57%", + "Seraphine": "53.13%", + "Zeri": "52.54%", + "Twitch": "52.05%", + "Smolder": "51.89%", + "Ezreal": "51.67%", + "Kai'Sa": "50.59%", + "Sivir": "50.00%", + "Lucian": "50.00%", + "Dr. Mundo": "50.00%", + "Xayah": "50.00%", + "Karthus": "50.00%" } }, { - "champion_name": "Shaco", - "pick_rate": "1.1%", - "counters": { - "Sona": "60.95%", - "Swain": "60.22%", - "Taric": "59.68%", - "Heimerdinger": "59.02%", - "Zac": "56.14%", - "Janna": "55.96%", - "Milio": "54.49%", - "Pyke": "54.23%", - "Rakan": "53.2%", - "Bard": "52.59%" - }, + "champion_name": "Vel'Koz", + "pick_rate": "1.4%", + "counters": "no info", "synergy": { - "Seraphine": "85.71%", - "Miss Fortune": "58.33%", - "Samira": "54.84%", - "Caitlyn": "54.43%", - "Jhin": "54.01%", - "Varus": "52.43%", + "Samira": "65.00%", + "Nilah": "60.00%", + "Hwei": "58.82%", + "Seraphine": "54.55%", + "Draven": "53.66%", + "Ashe": "53.52%", + "Tristana": "53.33%", + "Zeri": "53.13%", + "Senna": "51.90%", + "Kai'Sa": "51.02%", + "Jhin": "50.71%", + "Smolder": "50.22%", + "Sivir": "50.00%", "Karthus": "50.00%" } }, { - "champion_name": "Heimerdinger", - "pick_rate": "1.0%", + "champion_name": "Swain", + "pick_rate": "1.4%", "counters": { - "Maokai": "61.64%", - "Neeko": "56.82%", - "Seraphine": "55.95%", - "Xerath": "54.86%", - "Bard": "54.55%", - "Leona": "54.4%", - "Rell": "53.15%", - "Rakan": "52.67%", - "Lulu": "52.56%", - "Janna": "52.56%" + "Zac": "58.97%", + "Sona": "58.67%", + "Neeko": "57.41%", + "Nami": "57.14%", + "Milio": "56.44%", + "Pantheon": "55.95%", + "Zyra": "55.49%", + "Morgana": "55.24%", + "Leona": "55%", + "Camille": "54.79%" }, - "synergy": { - "Zeri": "68.00%", - "Sivir": "57.89%", - "Draven": "54.29%", - "Jhin": "54.03%", - "Vayne": "53.49%", - "Miss Fortune": "52.78%", - "Ashe": "50.34%", - "Kalista": "50.00%", - "Twitch": "50.00%" - } + "synergy": [ + "no data" + ] }, { - "champion_name": "Sylas", - "pick_rate": "1.0%", - "counters": { - "Sona": "60%", - "Janna": "57.91%", - "Vel'Koz": "57.75%", - "Braum": "57.52%", - "Morgana": "57.5%", - "Taric": "55.56%", - "Rell": "54.55%", - "Rakan": "53.89%", - "Blitzcrank": "53.74%", - "Thresh": "53.64%" - }, + "champion_name": "Tahm Kench", + "pick_rate": "1.1%", + "counters": "no info", "synergy": { - "Karthus": "60.00%", - "Aphelios": "58.06%", - "Nilah": "57.89%", - "Miss Fortune": "57.14%", - "Kalista": "55.36%", - "Lucian": "54.65%", - "Seraphine": "54.55%", - "Sivir": "52.94%", - "Zeri": "52.63%", - "Vayne": "52.53%", - "Jhin": "52.38%", - "Ezreal": "51.14%", - "Twitch": "50.77%", - "Yasuo": "50.00%" + "Kalista": "68.75%", + "Jinx": "65.00%", + "Nilah": "62.50%", + "Xayah": "62.50%", + "Twitch": "57.14%", + "Varus": "55.10%", + "Twisted Fate": "54.55%", + "Zeri": "53.85%", + "Senna": "52.51%", + "Seraphine": "50.00%" } }, { - "champion_name": "Pantheon", + "champion_name": "Zac", "pick_rate": "1.0%", "counters": { - "Neeko": "59.74%", - "Maokai": "58.43%", - "Karma": "57.22%", - "Bard": "57.14%", - "Swain": "56.25%", - "Rakan": "55.92%", - "Brand": "55.83%", - "Soraka": "55.74%", - "Sona": "55.56%", - "Janna": "55.42%" - }, - "synergy": { - "Miss Fortune": "63.41%", + "Pantheon": "61.36%", + "Taric": "58.82%", + "Zilean": "58.49%", + "Rell": "56.82%", + "Bard": "56.55%", + "Morgana": "55.93%", + "Janna": "54.44%", + "Maokai": "53.44%", + "Milio": "53.25%", + "Thresh": "53.18%" + }, + "synergy": { + "Aphelios": "83.33%", + "Caitlyn": "66.67%", + "Draven": "65.52%", + "Senna": "64.71%", + "Tristana": "61.54%", + "Twisted Fate": "61.29%", + "Zeri": "58.82%", "Seraphine": "56.25%", - "Yasuo": "54.55%", - "Swain": "52.63%", - "Varus": "52.05%", - "Zeri": "50.00%" - } - }, - { - "champion_name": "Twitch", - "pick_rate": "0.8%", - "counters": { - "Zilean": "61.97%", - "Taric": "60.47%", - "Leona": "57.92%", - "Soraka": "57.87%", - "Shaco": "57.38%", - "Nami": "57.14%", - "Rakan": "56.34%", - "Sylas": "55.56%", - "Brand": "55.26%", - "Pyke": "54.65%" - }, - "synergy": { - "Sivir": "70.97%", - "Swain": "70.00%", - "Seraphine": "63.64%", - "Draven": "57.89%", - "Cassiopeia": "53.85%", - "Vayne": "53.54%", - "Jhin": "53.40%", - "Jinx": "52.27%", - "Ziggs": "51.72%", - "Zeri": "51.61%", - "Nilah": "50.00%", - "Kalista": "50.00%", - "Hwei": "50.00%", - "Tristana": "50.00%" + "Jinx": "55.00%", + "Ezreal": "52.38%", + "Smolder": "51.58%", + "Kalista": "51.52%", + "Xayah": "50.00%" } }, { - "champion_name": "Zac", - "pick_rate": "0.7%", + "champion_name": "Sylas", + "pick_rate": "0.6%", "counters": { - "Poppy": "67.39%", - "Maokai": "57.14%", - "Neeko": "56.72%", - "Swain": "56%", - "Vel'Koz": "54.1%", - "Taric": "53.85%", - "Alistar": "53.74%", - "Ashe": "53.49%", - "Rakan": "53.42%", - "Zilean": "53.26%" + "Taric": "70%", + "Shaco": "70%", + "Pantheon": "64.86%", + "Neeko": "63.16%", + "Thresh": "58.58%", + "Lulu": "58.52%", + "Morgana": "58.06%", + "Janna": "57.66%", + "Leona": "57.58%", + "Bard": "57.45%" }, "synergy": { - "Ziggs": "72.73%", - "Sivir": "71.43%", - "Zeri": "70.00%", - "Varus": "63.64%", - "Yasuo": "62.07%", - "Jhin": "59.32%", - "Kalista": "57.14%", - "Hwei": "57.14%", - "Twitch": "55.32%", - "Ezreal": "52.63%", - "Miss Fortune": "52.38%", - "Vayne": "50.75%", - "Swain": "50.00%", - "Tristana": "50.00%", - "Caitlyn": "50.00%" + "Miss Fortune": "75.00%", + "Zeri": "72.73%", + "Twitch": "68.75%", + "Kalista": "68.42%", + "Draven": "65.22%", + "Tristana": "61.54%", + "Senna": "60.00%", + "Aphelios": "60.00%", + "Jinx": "55.56%", + "Jhin": "50.00%" } }, { - "champion_name": "Tahm Kench", - "pick_rate": "0.6%", - "counters": { - "Taric": "62.22%", - "Braum": "61.11%", - "Bard": "57.4%", - "Zyra": "57.27%", - "Pyke": "55.38%", - "Maokai": "54.9%", - "Brand": "54.67%", - "Lulu": "54.55%", - "Renata Glasc": "54.17%", - "Rakan": "54.12%" - }, + "champion_name": "Heimerdinger", + "pick_rate": "0.5%", + "counters": "no info", "synergy": { - "Veigar": "80.00%", - "Ziggs": "75.00%", - "Karthus": "66.67%", - "Seraphine": "58.33%", - "Xayah": "58.33%", + "Tristana": "80.00%", + "Aphelios": "80.00%", + "Draven": "64.29%", + "Twitch": "62.50%", + "Twisted Fate": "60.00%", "Jhin": "55.56%", - "Miss Fortune": "51.72%", - "Twitch": "51.28%", - "Kog'Maw": "50.00%", - "Kalista": "50.00%", - "Ashe": "50.00%", - "Ezreal": "50.00%" + "Varus": "53.85%", + "Senna": "51.28%", + "Miss Fortune": "50.00%", + "Jinx": "50.00%", + "Nilah": "50.00%" } }, { - "champion_name": "Amumu", - "pick_rate": "0.6%", - "counters": { - "Maokai": "66.07%", - "Zyra": "61.68%", - "Rell": "58.9%", - "Rakan": "58.55%", - "Janna": "58.5%", - "Neeko": "57.14%", - "Taric": "56.1%", - "Braum": "55.41%", - "Bard": "54%", - "Leona": "53.91%" - }, + "champion_name": "Twitch", + "pick_rate": "0.5%", + "counters": "no info", "synergy": { - "Yasuo": "71.43%", - "Ziggs": "66.67%", - "Kalista": "64.15%", - "Lucian": "60.00%", - "Karthus": "60.00%", - "Hwei": "57.14%", - "Ezreal": "55.88%", - "Nilah": "55.56%", - "Draven": "55.41%", - "Miss Fortune": "54.37%", - "Ashe": "54.17%", - "Jinx": "53.85%", - "Kai'Sa": "53.60%", - "Tristana": "52.63%", - "Zeri": "50.00%", - "Veigar": "50.00%" + "Karma": "75.00%", + "Lucian": "66.67%", + "Cassiopeia": "66.67%", + "Janna": "66.67%", + "Aphelios": "66.67%", + "Jhin": "61.90%", + "Zeri": "60.87%", + "Varus": "60.00%", + "Kalista": "57.14%", + "Xayah": "53.85%", + "Ezreal": "53.52%", + "Vayne": "53.19%", + "Sivir": "50.00%", + "Jinx": "50.00%", + "Lulu": "50.00%" } } ] \ No newline at end of file diff --git a/src/LoL-support/step1-lolchamps.py b/src/LoL-support/step1-lolchamps.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/step2-lolchamps.py b/src/LoL-support/step2-lolchamps.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/step3-lolchamps.py b/src/LoL-support/step3-lolchamps.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/step4-lolchamps.py b/src/LoL-support/step4-lolchamps.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/step5-lolchamps.py b/src/LoL-support/step5-lolchamps.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/step6-lolchamps.py b/src/LoL-support/step6-lolchamps.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/step7-lolchamps.py b/src/LoL-support/step7-lolchamps.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/step8-db.py b/src/LoL-support/step8-db.py old mode 100644 new mode 100755 diff --git a/src/LoL-support/support_combined.json b/src/LoL-support/support_combined.json old mode 100644 new mode 100755 index 4e4f71b..3c678d9 --- a/src/LoL-support/support_combined.json +++ b/src/LoL-support/support_combined.json @@ -1,663 +1,443 @@ [ { - "champion_name": "Taric", - "pick_rate": "1.4%", + "champion_name": "Maokai", + "pick_rate": "12.7%", "counters": { - "Soraka": "55.26%", - "Rell": "52.91%", - "Vel'Koz": "52.75%", - "Janna": "50.88%", - "Zilean": "50.55%", - "Rakan": "49.62%", - "Maokai": "49.58%", - "Nami": "49.42%", - "Xerath": "49.28%", - "Senna": "49.24%" + "Taric": "50.48%", + "Alistar": "49.83%", + "Braum": "49.49%", + "Leona": "49.02%", + "Janna": "48.98%", + "Renata Glasc": "48.48%", + "Rell": "47.97%", + "Shaco": "47.18%", + "Camille": "47.04%", + "Zyra": "47.02%" } }, { "champion_name": "Janna", - "pick_rate": "9.6%", + "pick_rate": "10.7%", "counters": { - "Senna": "50.79%", - "Sona": "50.1%", - "Soraka": "50.01%", - "Seraphine": "49.87%", - "Milio": "49.71%", - "Neeko": "49.67%", - "Rell": "49.5%", - "Bard": "49.44%", - "Rakan": "49.42%", - "Blitzcrank": "49.4%" + "Camille": "53.25%", + "Sona": "51.89%", + "Blitzcrank": "51.2%", + "Maokai": "51.02%", + "Braum": "49.67%", + "Pyke": "49.38%", + "Renata Glasc": "49.3%", + "Senna": "49.25%", + "Ashe": "49.07%", + "Nami": "48.64%" } }, { - "champion_name": "Maokai", - "pick_rate": "1.6%", + "champion_name": "Taric", + "pick_rate": "1.5%", "counters": { - "Shaco": "58.18%", - "Braum": "54.8%", - "Sona": "53.99%", - "Janna": "52.72%", - "Renata Glasc": "52.39%", - "Zyra": "51.46%", - "Alistar": "51.45%", - "Soraka": "50.8%", - "Taric": "50.42%", - "Neeko": "50.29%" + "Swain": "57.63%", + "Sona": "57.32%", + "Janna": "54.88%", + "Nami": "54.27%", + "Lux": "53.54%", + "Seraphine": "53.45%", + "Bard": "52.97%", + "Soraka": "52.32%", + "Thresh": "52.21%", + "Braum": "51.67%" } }, { - "champion_name": "Bard", - "pick_rate": "6.1%", - "counters": { - "Taric": "52.2%", - "Janna": "50.56%", - "Maokai": "50.28%", - "Zilean": "49.95%", - "Senna": "49.94%", - "Blitzcrank": "49.89%", - "Renata Glasc": "49.52%", - "Soraka": "49.45%", - "Rell": "49.3%", - "Pyke": "49.06%" - } + "champion_name": "Braum", + "pick_rate": "3.8%", + "counters": "no info" }, { - "champion_name": "Rakan", - "pick_rate": "12.8%", - "counters": { - "Senna": "52.31%", - "Bard": "51.42%", - "Soraka": "51.41%", - "Neeko": "51.37%", - "Renata Glasc": "51.09%", - "Sona": "50.93%", - "Maokai": "50.87%", - "Rell": "50.69%", - "Zilean": "50.64%", - "Janna": "50.58%" + "champion_name": "Blitzcrank", + "pick_rate": "8.1%", + "counters": { + "Pantheon": "54.85%", + "Leona": "54.08%", + "Maokai": "53.66%", + "Braum": "53.61%", + "Rakan": "53.26%", + "Taric": "53.13%", + "Alistar": "52.33%", + "Shaco": "51.5%", + "Rell": "50.9%", + "Zilean": "50.57%" } }, { - "champion_name": "Sona", - "pick_rate": "2.4%", + "champion_name": "Camille", + "pick_rate": "2.3%", "counters": { - "Taric": "56.83%", - "Leona": "55.04%", - "Seraphine": "53.49%", - "Blitzcrank": "52.56%", - "Senna": "51.71%", - "Bard": "51.4%", - "Nami": "50.59%", - "Milio": "50.43%", - "Zilean": "50.26%", - "Pyke": "50.19%" + "Taric": "58.76%", + "Renata Glasc": "58.12%", + "Braum": "57.32%", + "Leona": "54.48%", + "Brand": "54.2%", + "Bard": "53.76%", + "Zac": "53.03%", + "Maokai": "52.96%", + "Tahm Kench": "52.94%", + "Zilean": "52.78%" } }, { - "champion_name": "Senna", - "pick_rate": "11.0%", - "counters": { - "Pyke": "52.31%", - "Blitzcrank": "52.25%", - "Xerath": "51.66%", - "Maokai": "51.62%", - "Zyra": "51.1%", - "Taric": "50.76%", - "Leona": "50.22%", - "Bard": "50.06%", - "Lux": "49.9%", - "Rell": "49.8%" - } + "champion_name": "Leona", + "pick_rate": "4.4%", + "counters": "no info" }, { "champion_name": "Rell", - "pick_rate": "4.9%", - "counters": { - "Poppy": "53.79%", - "Seraphine": "53.57%", - "Soraka": "52.57%", - "Zilean": "52.54%", - "Alistar": "52.45%", - "Shaco": "51.5%", - "Maokai": "50.89%", - "Sona": "50.75%", - "Bard": "50.7%", - "Janna": "50.5%" - } - }, - { - "champion_name": "Neeko", - "pick_rate": "1.9%", - "counters": { - "Vel'Koz": "60.45%", - "Sona": "53.26%", - "Bard": "52.61%", - "Brand": "52.38%", - "Taric": "52.17%", - "Alistar": "51%", - "Shaco": "50.68%", - "Senna": "50.42%", - "Renata Glasc": "50.38%", - "Morgana": "50.35%" - } + "pick_rate": "4.1%", + "counters": "no info" }, { - "champion_name": "Soraka", - "pick_rate": "5.5%", - "counters": { - "Blitzcrank": "54.17%", - "Neeko": "51.31%", - "Sona": "51.25%", - "Zyra": "51.13%", - "Vel'Koz": "51.05%", - "Bard": "50.55%", - "Senna": "50.38%", - "Yuumi": "50.06%", - "Janna": "49.99%", - "Pyke": "49.98%" - } + "champion_name": "Rakan", + "pick_rate": "7.7%", + "counters": "no info" }, { - "champion_name": "Blitzcrank", - "pick_rate": "8.8%", - "counters": { - "Taric": "56.85%", - "Rakan": "52.88%", - "Leona": "52.47%", - "Braum": "52.28%", - "Renata Glasc": "51.05%", - "Maokai": "51.04%", - "Zyra": "50.98%", - "Rell": "50.78%", - "Alistar": "50.73%", - "Zilean": "50.7%" - } + "champion_name": "Zilean", + "pick_rate": "2.8%", + "counters": "no info" }, { - "champion_name": "Zyra", - "pick_rate": "3.8%", - "counters": { - "Sona": "53.56%", - "Pyke": "53.25%", - "Heimerdinger": "53.11%", - "Bard": "53.07%", - "Leona": "51.91%", - "Janna": "51.88%", - "Shaco": "51.56%", - "Taric": "51.43%", - "Rakan": "50.98%", - "Nautilus": "50.75%" - } + "champion_name": "Pyke", + "pick_rate": "8.5%", + "counters": "no info" }, { - "champion_name": "Pyke", - "pick_rate": "7.6%", + "champion_name": "Zac", + "pick_rate": "1.0%", "counters": { - "Rakan": "53.38%", - "Maokai": "52.78%", - "Neeko": "52.1%", - "Rell": "52.08%", - "Taric": "51.82%", - "Janna": "51.79%", - "Blitzcrank": "51.44%", - "Alistar": "50.94%", - "Bard": "50.94%", - "Thresh": "50.49%" + "Pantheon": "61.36%", + "Taric": "58.82%", + "Zilean": "58.49%", + "Rell": "56.82%", + "Bard": "56.55%", + "Morgana": "55.93%", + "Janna": "54.44%", + "Maokai": "53.44%", + "Milio": "53.25%", + "Thresh": "53.18%" } }, { - "champion_name": "Zilean", - "pick_rate": "2.5%", - "counters": { - "Janna": "53.27%", - "Shaco": "53.02%", - "Senna": "51.99%", - "Neeko": "51.67%", - "Maokai": "51.41%", - "Xerath": "51.07%", - "Milio": "50.67%", - "Sylas": "50.34%", - "Soraka": "50.28%", - "Vel'Koz": "50.23%" - } + "champion_name": "Alistar", + "pick_rate": "4.7%", + "counters": "no info" }, { - "champion_name": "Braum", - "pick_rate": "3.6%", + "champion_name": "Bard", + "pick_rate": "5.7%", "counters": { - "Zilean": "55.95%", - "Soraka": "54.2%", - "Bard": "53.76%", - "Rell": "53.74%", - "Taric": "53.44%", - "Sona": "53.2%", - "Senna": "53.06%", - "Rakan": "52.88%", - "Janna": "52.49%", - "Renata Glasc": "52.31%" + "Maokai": "56.1%", + "Renata Glasc": "53.77%", + "Janna": "53.45%", + "Pyke": "51.79%", + "Morgana": "51.04%", + "Nami": "50.92%", + "Pantheon": "50.72%", + "Blitzcrank": "50.43%", + "Brand": "50.34%", + "Senna": "50.21%" } }, { - "champion_name": "Renata Glasc", - "pick_rate": "5.0%", + "champion_name": "Soraka", + "pick_rate": "5.4%", "counters": "no info" }, { - "champion_name": "Vel'Koz", - "pick_rate": "1.6%", + "champion_name": "Sona", + "pick_rate": "2.3%", "counters": { - "Pyke": "55.14%", - "Zyra": "54.76%", - "Seraphine": "54.15%", - "Blitzcrank": "53.89%", - "Janna": "53.62%", - "Bard": "53.22%", - "Sona": "52.97%", - "Nami": "52.1%", - "Maokai": "51.85%", - "Renata Glasc": "51.64%" + "Zilean": "61.68%", + "Rell": "59.62%", + "Leona": "57.21%", + "Neeko": "55.71%", + "Thresh": "55.03%", + "Maokai": "54.7%", + "Bard": "53.59%", + "Braum": "51.81%", + "Blitzcrank": "51.79%", + "Seraphine": "51.2%" } }, { "champion_name": "Thresh", - "pick_rate": "13.3%", - "counters": { - "Taric": "53.39%", - "Janna": "52.43%", - "Brand": "52.12%", - "Rakan": "52.02%", - "Neeko": "51.75%", - "Sona": "51.47%", - "Maokai": "51.34%", - "Vel'Koz": "51.27%", - "Bard": "51.24%", - "Renata Glasc": "51.24%" - } + "pick_rate": "9.4%", + "counters": "no info" }, { - "champion_name": "Milio", - "pick_rate": "6.7%", - "counters": { - "Taric": "54.79%", - "Blitzcrank": "53.66%", - "Braum": "53.22%", - "Vel'Koz": "53.08%", - "Senna": "52.78%", - "Bard": "52.75%", - "Neeko": "52.17%", - "Rakan": "51.91%", - "Soraka": "51.52%", - "Thresh": "51.33%" - } + "champion_name": "Renata Glasc", + "pick_rate": "2.4%", + "counters": "no info" }, { - "champion_name": "Alistar", - "pick_rate": "5.8%", + "champion_name": "Zyra", + "pick_rate": "4.3%", "counters": { - "Sona": "54.51%", - "Janna": "54.04%", - "Swain": "53.24%", - "Vel'Koz": "52.78%", - "Senna": "52.64%", - "Zilean": "52.39%", - "Soraka": "52.22%", - "Seraphine": "51.97%", - "Xerath": "51.86%", - "Milio": "51.85%" + "Taric": "54.73%", + "Leona": "54.05%", + "Sona": "53.31%", + "Maokai": "52.98%", + "Renata Glasc": "52.84%", + "Pyke": "52.6%", + "Zac": "52.59%", + "Janna": "52.42%", + "Zilean": "51.88%", + "Yuumi": "51.63%" } }, { - "champion_name": "Zac", - "pick_rate": "0.7%", - "counters": { - "Poppy": "67.39%", - "Maokai": "57.14%", - "Neeko": "56.72%", - "Swain": "56%", - "Vel'Koz": "54.1%", - "Taric": "53.85%", - "Alistar": "53.74%", - "Ashe": "53.49%", - "Rakan": "53.42%", - "Zilean": "53.26%" - } + "champion_name": "Vel'Koz", + "pick_rate": "1.4%", + "counters": "no info" }, { - "champion_name": "Leona", - "pick_rate": "4.8%", - "counters": { - "Taric": "56.7%", - "Janna": "54.1%", - "Bard": "54.03%", - "Shaco": "53.67%", - "Maokai": "53.55%", - "Rell": "53.22%", - "Soraka": "53.11%", - "Lux": "53.09%", - "Morgana": "53.02%", - "Braum": "51.72%" - } + "champion_name": "Senna", + "pick_rate": "11.0%", + "counters": "no info" }, { - "champion_name": "Xerath", - "pick_rate": "4.8%", - "counters": { - "Sylas": "55%", - "Maokai": "53.98%", - "Sona": "53.77%", - "Janna": "53.15%", - "Pyke": "52.84%", - "Soraka": "52.33%", - "Rakan": "52.02%", - "Leona": "51.76%", - "Zyra": "51.72%", - "Nami": "51.6%" - } + "champion_name": "Shaco", + "pick_rate": "2.6%", + "counters": "no info" }, { - "champion_name": "Nami", - "pick_rate": "6.5%", - "counters": { - "Maokai": "56.37%", - "Neeko": "53.14%", - "Blitzcrank": "53.03%", - "Rakan": "52.58%", - "Zyra": "52.29%", - "Soraka": "51.92%", - "Milio": "51.67%", - "Pyke": "51.56%", - "Janna": "51.44%", - "Bard": "51.33%" - } + "champion_name": "Pantheon", + "pick_rate": "1.8%", + "counters": "no info" }, { - "champion_name": "Shaco", + "champion_name": "Tahm Kench", "pick_rate": "1.1%", - "counters": { - "Sona": "60.95%", - "Swain": "60.22%", - "Taric": "59.68%", - "Heimerdinger": "59.02%", - "Zac": "56.14%", - "Janna": "55.96%", - "Milio": "54.49%", - "Pyke": "54.23%", - "Rakan": "53.2%", - "Bard": "52.59%" - } + "counters": "no info" }, { - "champion_name": "Heimerdinger", - "pick_rate": "1.0%", + "champion_name": "Nautilus", + "pick_rate": "11.0%", "counters": { - "Maokai": "61.64%", - "Neeko": "56.82%", - "Seraphine": "55.95%", - "Xerath": "54.86%", - "Bard": "54.55%", - "Leona": "54.4%", - "Rell": "53.15%", - "Rakan": "52.67%", - "Lulu": "52.56%", - "Janna": "52.56%" + "Taric": "60.99%", + "Leona": "57.54%", + "Braum": "56.73%", + "Renata Glasc": "55.96%", + "Alistar": "55.65%", + "Rell": "55.38%", + "Camille": "54.74%", + "Pantheon": "54.53%", + "Swain": "54.35%", + "Maokai": "53.72%" } }, { - "champion_name": "Sylas", - "pick_rate": "1.0%", + "champion_name": "Milio", + "pick_rate": "8.1%", "counters": { - "Sona": "60%", - "Janna": "57.91%", - "Vel'Koz": "57.75%", - "Braum": "57.52%", - "Morgana": "57.5%", - "Taric": "55.56%", - "Rell": "54.55%", - "Rakan": "53.89%", - "Blitzcrank": "53.74%", - "Thresh": "53.64%" + "Blitzcrank": "56.32%", + "Braum": "55.45%", + "Maokai": "54.37%", + "Camille": "53.86%", + "Janna": "53.43%", + "Thresh": "53.37%", + "Zilean": "53.04%", + "Tahm Kench": "53.04%", + "Vel'Koz": "52.98%", + "Leona": "52.96%" } }, { - "champion_name": "Tahm Kench", - "pick_rate": "0.6%", + "champion_name": "Nami", + "pick_rate": "5.5%", "counters": { - "Taric": "62.22%", - "Braum": "61.11%", - "Bard": "57.4%", - "Zyra": "57.27%", - "Pyke": "55.38%", - "Maokai": "54.9%", - "Brand": "54.67%", - "Lulu": "54.55%", - "Renata Glasc": "54.17%", - "Rakan": "54.12%" + "Morgana": "56.08%", + "Blitzcrank": "55.61%", + "Maokai": "55.57%", + "Vel'Koz": "54.47%", + "Rakan": "54.44%", + "Neeko": "54.21%", + "Rell": "53.9%", + "Nautilus": "53.32%", + "Thresh": "53.28%", + "Senna": "53.17%" } }, { - "champion_name": "Amumu", - "pick_rate": "0.6%", + "champion_name": "Xerath", + "pick_rate": "2.7%", "counters": { - "Maokai": "66.07%", - "Zyra": "61.68%", - "Rell": "58.9%", - "Rakan": "58.55%", - "Janna": "58.5%", - "Neeko": "57.14%", - "Taric": "56.1%", - "Braum": "55.41%", - "Bard": "54%", - "Leona": "53.91%" + "Blitzcrank": "58.63%", + "Neeko": "56.43%", + "Sona": "56.36%", + "Maokai": "56.26%", + "Pyke": "56.15%", + "Leona": "56.02%", + "Braum": "55.06%", + "Rakan": "54.83%", + "Nautilus": "54.65%", + "Janna": "53.86%" } }, { - "champion_name": "Twitch", - "pick_rate": "0.8%", - "counters": { - "Zilean": "61.97%", - "Taric": "60.47%", - "Leona": "57.92%", - "Soraka": "57.87%", - "Shaco": "57.38%", - "Nami": "57.14%", - "Rakan": "56.34%", - "Sylas": "55.56%", - "Brand": "55.26%", - "Pyke": "54.65%" - } + "champion_name": "Brand", + "pick_rate": "1.9%", + "counters": "no info" }, { - "champion_name": "Nautilus", - "pick_rate": "10.9%", + "champion_name": "Swain", + "pick_rate": "1.4%", "counters": { - "Taric": "55.98%", - "Rell": "55.66%", - "Renata Glasc": "55.13%", - "Sylas": "55.09%", - "Braum": "54.08%", - "Rakan": "54.06%", - "Vel'Koz": "53.45%", - "Swain": "53.09%", - "Neeko": "52.97%", - "Alistar": "52.87%" + "Zac": "58.97%", + "Sona": "58.67%", + "Neeko": "57.41%", + "Nami": "57.14%", + "Milio": "56.44%", + "Pantheon": "55.95%", + "Zyra": "55.49%", + "Morgana": "55.24%", + "Leona": "55%", + "Camille": "54.79%" } }, { - "champion_name": "Ashe", - "pick_rate": "5.7%", + "champion_name": "Lulu", + "pick_rate": "7.9%", "counters": { - "Pyke": "56.98%", - "Sylas": "54.95%", - "Blitzcrank": "54.83%", - "Heimerdinger": "54.8%", - "Rell": "54.67%", - "Nautilus": "54.35%", - "Bard": "53.44%", - "Milio": "53.1%", - "Rakan": "52.69%", - "Brand": "52.5%" + "Vel'Koz": "57.35%", + "Blitzcrank": "56.23%", + "Zilean": "55.78%", + "Tahm Kench": "55.43%", + "Renata Glasc": "54.86%", + "Rell": "54.7%", + "Janna": "54.6%", + "Maokai": "54.51%", + "Braum": "53.79%", + "Ashe": "53.65%" } }, { - "champion_name": "Lux", - "pick_rate": "5.1%", - "counters": { - "Blitzcrank": "56.57%", - "Maokai": "54.94%", - "Zyra": "54.66%", - "Shaco": "54.45%", - "Vel'Koz": "54.1%", - "Sylas": "53.87%", - "Zilean": "53.51%", - "Pyke": "53.5%", - "Xerath": "52.75%", - "Nami": "52.72%" - } + "champion_name": "Morgana", + "pick_rate": "2.8%", + "counters": "no info" }, { - "champion_name": "Brand", - "pick_rate": "2.0%", - "counters": { - "Sona": "58.11%", - "Maokai": "57.24%", - "Xerath": "57.03%", - "Janna": "56.31%", - "Pyke": "56.01%", - "Zyra": "55.13%", - "Leona": "54.96%", - "Zilean": "54.92%", - "Soraka": "54.73%", - "Senna": "54.19%" - } + "champion_name": "Heimerdinger", + "pick_rate": "0.5%", + "counters": "no info" }, { - "champion_name": "Seraphine", - "pick_rate": "2.0%", + "champion_name": "Ashe", + "pick_rate": "4.8%", "counters": { - "Sylas": "58.33%", - "Maokai": "57.24%", - "Zilean": "56.09%", - "Bard": "55.97%", - "Pyke": "54.69%", - "Soraka": "53.72%", - "Blitzcrank": "53.61%", - "Yuumi": "53.6%", - "Milio": "52.99%", - "Thresh": "52.66%" + "Camille": "58.72%", + "Maokai": "57.34%", + "Pyke": "56.14%", + "Blitzcrank": "55.88%", + "Nautilus": "55.46%", + "Taric": "55.41%", + "Vel'Koz": "54.55%", + "Zyra": "54.13%", + "Soraka": "53.87%", + "Xerath": "53.83%" } }, { - "champion_name": "Lulu", - "pick_rate": "7.4%", + "champion_name": "Sylas", + "pick_rate": "0.6%", "counters": { - "Taric": "56.84%", - "Zilean": "54.43%", - "Neeko": "54.04%", - "Senna": "54.04%", - "Sona": "53.78%", - "Zyra": "53.7%", - "Maokai": "53.46%", - "Blitzcrank": "52.96%", - "Bard": "52.89%", - "Thresh": "52.88%" + "Taric": "70%", + "Shaco": "70%", + "Pantheon": "64.86%", + "Neeko": "63.16%", + "Thresh": "58.58%", + "Lulu": "58.52%", + "Morgana": "58.06%", + "Janna": "57.66%", + "Leona": "57.58%", + "Bard": "57.45%" } }, { - "champion_name": "Morgana", - "pick_rate": "3.2%", + "champion_name": "Neeko", + "pick_rate": "1.6%", "counters": { - "Zyra": "56.73%", - "Senna": "56.35%", - "Rell": "56.29%", - "Janna": "55.6%", - "Milio": "55.2%", - "Vel'Koz": "55.08%", - "Nami": "54.67%", - "Taric": "54.31%", - "Karma": "54.24%", - "Maokai": "53.2%" + "Zilean": "60.66%", + "Taric": "58.7%", + "Zac": "57.78%", + "Leona": "57.76%", + "Pantheon": "57.14%", + "Janna": "56.74%", + "Blitzcrank": "56.38%", + "Bard": "55.85%", + "Camille": "55.41%", + "Rell": "55.19%" } }, { "champion_name": "Karma", - "pick_rate": "7.6%", - "counters": { - "Pyke": "57.3%", - "Maokai": "55.33%", - "Zyra": "54.97%", - "Blitzcrank": "54.92%", - "Rakan": "54.52%", - "Janna": "54.36%", - "Sona": "54.33%", - "Nautilus": "54.21%", - "Soraka": "53.61%", - "Neeko": "52.83%" - } + "pick_rate": "9.6%", + "counters": "no info" }, { - "champion_name": "Swain", - "pick_rate": "1.6%", + "champion_name": "Lux", + "pick_rate": "3.9%", "counters": { - "Zilean": "57.77%", - "Sona": "57.32%", - "Vel'Koz": "57.25%", - "Janna": "56.41%", - "Brand": "55.87%", - "Yuumi": "55.78%", - "Zyra": "55.78%", - "Xerath": "55.48%", - "Soraka": "55.34%", - "Senna": "55.32%" + "Camille": "62.93%", + "Pantheon": "60%", + "Pyke": "58.92%", + "Bard": "57.24%", + "Shaco": "56.3%", + "Blitzcrank": "55.42%", + "Maokai": "55.39%", + "Leona": "55.08%", + "Alistar": "54.69%", + "Zilean": "54.61%" } }, { - "champion_name": "Pantheon", - "pick_rate": "1.0%", - "counters": { - "Neeko": "59.74%", - "Maokai": "58.43%", - "Karma": "57.22%", - "Bard": "57.14%", - "Swain": "56.25%", - "Rakan": "55.92%", - "Brand": "55.83%", - "Soraka": "55.74%", - "Sona": "55.56%", - "Janna": "55.42%" - } + "champion_name": "Seraphine", + "pick_rate": "1.7%", + "counters": "no info" }, { "champion_name": "Yuumi", - "pick_rate": "4.9%", + "pick_rate": "4.4%", "counters": { - "Maokai": "61.28%", - "Rell": "59.85%", - "Taric": "57.47%", - "Alistar": "57.31%", - "Nautilus": "56.64%", - "Rakan": "55.78%", - "Pyke": "55.71%", - "Leona": "55.63%", - "Sylas": "55.27%", - "Thresh": "55.23%" + "Rell": "62.65%", + "Braum": "58.82%", + "Blitzcrank": "58.05%", + "Leona": "57.77%", + "Pyke": "57.61%", + "Taric": "57.46%", + "Swain": "56.85%", + "Rakan": "56.72%", + "Maokai": "56.12%", + "Janna": "55.85%" } }, + { + "champion_name": "Twitch", + "pick_rate": "0.5%", + "counters": "no info" + }, { "champion_name": "Hwei", - "pick_rate": "2.1%", - "counters": { - "Taric": "66.91%", - "Sona": "66.55%", - "Blitzcrank": "65.66%", - "Janna": "65.3%", - "Brand": "65.18%", - "Pyke": "63.85%", - "Xerath": "63.02%", - "Vel'Koz": "62.71%", - "Morgana": "62.31%", - "Milio": "62.01%" + "pick_rate": "2.9%", + "counters": { + "Camille": "63.76%", + "Pyke": "60.47%", + "Maokai": "58.72%", + "Xerath": "58.7%", + "Rakan": "58.47%", + "Taric": "58.43%", + "Swain": "58.1%", + "Zilean": "57.69%", + "Vel'Koz": "57.69%", + "Janna": "57.32%" } } ] \ No newline at end of file diff --git a/src/LoL-support/support_names.json b/src/LoL-support/support_names.json old mode 100644 new mode 100755 index bafadeb..6655ed7 --- a/src/LoL-support/support_names.json +++ b/src/LoL-support/support_names.json @@ -1 +1 @@ -["Taric", "Janna", "Maokai", "Bard", "Rakan", "Sona", "Senna", "Rell", "Neeko", "Soraka", "Blitzcrank", "Zyra", "Pyke", "Zilean", "Braum", "Renata", "Vel'Koz", "Thresh", "Milio", "Alistar", "Zac", "Leona", "Xerath", "Nami", "Shaco", "Heimerdinger", "Sylas", "Tahm Kench", "Amumu", "Twitch", "Nautilus", "Ashe", "Lux", "Brand", "Seraphine", "Lulu", "Morgana", "Karma", "Swain", "Pantheon", "Yuumi", "Hwei"] \ No newline at end of file +["Maokai", "Janna", "Taric", "Braum", "Blitzcrank", "Camille", "Leona", "Rell", "Rakan", "Zilean", "Pyke", "Zac", "Alistar", "Bard", "Soraka", "Sona", "Thresh", "Renata Glasc", "Zyra", "Vel'Koz", "Senna", "Shaco", "Pantheon", "Tahm Kench", "Nautilus", "Milio", "Nami", "Xerath", "Brand", "Swain", "Lulu", "Morgana", "Heimerdinger", "Ashe", "Sylas", "Neeko", "Karma", "Lux", "Seraphine", "Yuumi", "Twitch", "Hwei"] \ No newline at end of file diff --git a/src/LoL-support/synergies_list.json b/src/LoL-support/synergies_list.json old mode 100644 new mode 100755 index 391b717..3d7539f --- a/src/LoL-support/synergies_list.json +++ b/src/LoL-support/synergies_list.json @@ -1,692 +1,559 @@ [ { - "champion_name": "Taric", + "champion_name": "Janna", "synergy": { - "Karthus": "70.83%", - "Yasuo": "68.91%", - "Lee Sin": "68.18%", - "Master Yi": "59.26%", - "Kog'Maw": "58.82%", - "Sivir": "58.62%", - "Varus": "56.63%", - "Swain": "56.45%", - "Seraphine": "56.41%", - "Kalista": "55.94%", - "Ziggs": "55.00%", - "Vayne": "54.01%", - "Jinx": "52.38%", - "Lucian": "51.91%", - "Kai'Sa": "51.81%", - "Ashe": "51.67%", - "Miss Fortune": "50.94%", - "Ezreal": "50.20%", - "Twitch": "50.00%" + "Nilah": "57.38%", + "Twitch": "57.02%", + "Senna": "56.68%", + "Seraphine": "55.59%", + "Miss Fortune": "54.71%", + "Zeri": "54.30%", + "Tristana": "54.22%", + "Draven": "54.22%", + "Twisted Fate": "53.57%", + "Samira": "53.57%", + "Jhin": "53.03%", + "Lucian": "52.84%", + "Karthus": "52.73%", + "Ezreal": "52.60%", + "Yasuo": "52.58%", + "Kalista": "52.35%", + "Vayne": "51.97%", + "Ashe": "51.80%", + "Sivir": "51.10%", + "Caitlyn": "51.05%", + "Smolder": "50.23%", + "Aphelios": "50.23%" } }, { - "champion_name": "Janna", + "champion_name": "Taric", "synergy": { - "Seraphine": "58.66%", - "Vayne": "55.43%", - "Yasuo": "54.91%", - "Ziggs": "54.85%", - "Jinx": "53.96%", - "Ashe": "53.85%", - "Tristana": "53.63%", - "Kalista": "53.47%", - "Kog'Maw": "52.89%", - "Karthus": "52.83%", - "Jhin": "52.65%", - "Ezreal": "52.58%", - "Lucian": "52.51%", - "Draven": "52.25%", - "Twitch": "52.00%", - "Samira": "51.88%", - "Varus": "51.73%", - "Zeri": "51.64%", - "Caitlyn": "51.53%", - "Kai'Sa": "50.95%" + "Vayne": "60.16%", + "Ashe": "58.46%", + "Yasuo": "58.33%", + "Twisted Fate": "57.89%", + "Pyke": "57.14%", + "Senna": "56.59%", + "Nilah": "56.32%", + "Kalista": "55.13%", + "Jinx": "54.39%", + "Aphelios": "53.57%", + "Seraphine": "53.13%", + "Zeri": "52.54%", + "Twitch": "52.05%", + "Smolder": "51.89%", + "Ezreal": "51.67%", + "Kai'Sa": "50.59%", + "Sivir": "50.00%", + "Lucian": "50.00%", + "Dr. Mundo": "50.00%", + "Xayah": "50.00%", + "Karthus": "50.00%" } }, { - "champion_name": "Maokai", + "champion_name": "Blitzcrank", "synergy": { - "Xayah": "60.00%", - "Ziggs": "58.33%", - "Karthus": "57.89%", - "Kai'Sa": "53.95%", - "Vayne": "53.51%", - "Sivir": "52.63%", - "Jhin": "52.46%", - "Aphelios": "52.46%", - "Draven": "52.38%", - "Ezreal": "52.29%", - "Samira": "51.81%", - "Varus": "51.53%", - "Zeri": "51.02%", + "Seraphine": "65.22%", + "Karthus": "57.78%", + "Draven": "56.57%", + "Vayne": "55.39%", + "Varus": "54.74%", + "Jinx": "54.30%", + "Twisted Fate": "54.17%", + "Tristana": "53.18%", + "Sivir": "53.06%", + "Senna": "52.85%", + "Aphelios": "52.14%", + "Kai'Sa": "51.65%", + "Lucian": "50.78%", + "Nilah": "50.67%", "Twitch": "50.00%", "Ashe": "50.00%", - "Hwei": "50.00%" + "Zeri": "50.00%" } }, { - "champion_name": "Bard", + "champion_name": "Camille", "synergy": { - "Nilah": "59.77%", - "Vayne": "55.15%", - "Caitlyn": "55.12%", - "Twitch": "55.06%", - "Ziggs": "54.37%", - "Tristana": "53.92%", - "Karthus": "53.62%", - "Seraphine": "53.05%", - "Lucian": "52.97%", - "Ezreal": "52.51%", - "Ashe": "52.44%", - "Jhin": "51.89%", - "Swain": "51.69%", - "Jinx": "51.34%", - "Zeri": "50.76%" - } - }, - { - "champion_name": "Rakan", - "synergy": { - "Lucian": "56.02%", - "Jinx": "54.48%", - "Twitch": "54.35%", - "Seraphine": "54.17%", - "Ziggs": "54.08%", - "Vayne": "54.03%", - "Sivir": "53.73%", - "Jhin": "52.40%", - "Yasuo": "51.97%", - "Karthus": "51.90%", - "Ezreal": "51.77%", - "Ashe": "51.55%", - "Miss Fortune": "50.65%", - "Nilah": "50.61%", - "Varus": "50.35%", - "Draven": "50.12%" + "Twitch": "62.67%", + "Sivir": "58.33%", + "Zeri": "56.47%", + "Tristana": "56.20%", + "Senna": "55.81%", + "Xayah": "55.26%", + "Aphelios": "53.62%", + "Nilah": "53.13%", + "Seraphine": "52.94%", + "Varus": "52.89%", + "Karthus": "52.63%", + "Ashe": "52.50%", + "Smolder": "52.34%", + "Jhin": "52.09%", + "Draven": "52.06%", + "Ziggs": "52.00%", + "Twisted Fate": "50.94%", + "Vayne": "50.92%", + "Lucian": "50.35%", + "Swain": "50.00%" } }, { - "champion_name": "Sona", + "champion_name": "Leona", "synergy": { - "Yasuo": "64.52%", - "Tristana": "60.29%", - "Ziggs": "58.51%", - "Samira": "57.45%", - "Sivir": "56.60%", - "Vayne": "56.55%", - "Nilah": "55.88%", - "Kog'Maw": "53.49%", - "Lucian": "53.04%", - "Xayah": "52.90%", - "Seraphine": "52.76%", - "Ezreal": "52.71%", - "Miss Fortune": "52.23%", - "Varus": "51.05%", - "Ashe": "50.93%", - "Caitlyn": "50.81%", - "Twitch": "50.21%" - } - }, - { - "champion_name": "Senna", - "synergy": { - "Kalista": "57.48%", - "Nilah": "56.48%", - "Karthus": "56.18%", - "Zeri": "55.11%", - "Seraphine": "55.06%", - "Miss Fortune": "54.98%", - "Cho'Gath": "54.69%", - "Vayne": "54.60%", - "Ashe": "54.54%", - "Tahm Kench": "54.51%", - "Yasuo": "54.49%", - "Brand": "54.26%", - "Twitch": "54.15%", - "Swain": "53.87%", - "Jhin": "52.87%", - "Ziggs": "52.59%", - "Sivir": "52.21%", - "Ezreal": "51.36%", - "Tristana": "51.36%", - "Varus": "50.75%" + "Swain": "68.09%", + "Senna": "60.41%", + "Jinx": "59.02%", + "Seraphine": "58.90%", + "Ashe": "57.67%", + "Aphelios": "56.67%", + "Nilah": "56.52%", + "Vayne": "55.20%", + "Lucian": "54.63%", + "Smolder": "54.42%", + "Miss Fortune": "53.39%", + "Karthus": "53.06%", + "Xayah": "51.61%", + "Ezreal": "51.04%", + "Kalista": "50.59%", + "Twisted Fate": "50.31%" } }, { "champion_name": "Rell", "synergy": { - "Ziggs": "61.31%", - "Karthus": "60.18%", - "Seraphine": "59.02%", - "Tristana": "58.16%", - "Lucian": "57.30%", - "Twitch": "56.30%", - "Miss Fortune": "54.44%", - "Ashe": "53.88%", - "Sivir": "53.49%", - "Jinx": "52.83%", - "Draven": "52.25%", - "Xayah": "52.10%", - "Ezreal": "52.08%", - "Kai'Sa": "51.33%", - "Samira": "51.15%", - "Zeri": "50.35%", - "Yasuo": "50.25%", - "Aphelios": "50.21%" - } - }, - { - "champion_name": "Neeko", - "synergy": { - "Nilah": "77.78%", - "Yasuo": "59.46%", - "Xayah": "59.26%", - "Hwei": "58.82%", - "Lucian": "56.79%", - "Vayne": "53.85%", - "Twitch": "53.17%", - "Kalista": "50.75%", - "Ezreal": "50.72%", - "Ashe": "50.29%", - "Quinn": "50.00%", - "Samira": "50.00%", - "Varus": "50.00%" - } - }, - { - "champion_name": "Soraka", - "synergy": { - "Yasuo": "63.54%", - "Swain": "59.70%", - "Samira": "58.41%", - "Vayne": "57.03%", - "Ashe": "53.73%", - "Jinx": "53.47%", - "Ziggs": "53.38%", - "Jhin": "53.17%", - "Tristana": "52.80%", - "Lucian": "52.34%", - "Miss Fortune": "51.96%", - "Twitch": "51.63%", - "Draven": "50.30%" - } - }, - { - "champion_name": "Blitzcrank", - "synergy": { - "Nilah": "58.59%", - "Xayah": "55.50%", - "Draven": "54.17%", - "Lucian": "53.85%", - "Ashe": "53.42%", - "Vayne": "52.91%", - "Twitch": "52.80%", - "Seraphine": "52.34%", - "Jinx": "51.90%", - "Karthus": "51.89%", - "Varus": "51.87%", - "Tristana": "51.72%", - "Miss Fortune": "51.53%", - "Zeri": "51.27%", - "Aphelios": "50.95%", - "Kalista": "50.84%", - "Jhin": "50.74%", - "Ezreal": "50.08%", - "Caitlyn": "50.00%" - } - }, - { - "champion_name": "Zyra", - "synergy": { - "Seraphine": "55.06%", - "Vayne": "54.84%", - "Tristana": "54.05%", - "Twitch": "52.96%", - "Samira": "51.98%", - "Jhin": "51.51%", - "Karthus": "51.16%", - "Draven": "50.65%", - "Xayah": "50.00%" - } - }, - { - "champion_name": "Pyke", - "synergy": { - "Karthus": "61.98%", - "Nilah": "60.43%", - "Yasuo": "57.30%", - "Tristana": "55.96%", - "Twitch": "55.08%", - "Ashe": "54.27%", - "Swain": "53.91%", - "Ziggs": "53.28%", - "Seraphine": "52.82%", - "Vayne": "52.64%", - "Varus": "51.58%", - "Jhin": "51.51%", - "Ezreal": "51.49%", - "Hwei": "51.43%", - "Draven": "50.95%" + "Xayah": "58.33%", + "Twisted Fate": "56.72%", + "Twitch": "56.48%", + "Nilah": "56.25%", + "Miss Fortune": "55.88%", + "Zeri": "55.45%", + "Senna": "55.08%", + "Vayne": "53.65%", + "Jinx": "53.40%", + "Kalista": "52.46%", + "Samira": "52.46%", + "Aphelios": "52.17%", + "Draven": "52.15%", + "Tristana": "51.90%", + "Karthus": "51.43%", + "Ezreal": "51.32%", + "Jhin": "50.43%", + "Yasuo": "50.00%" } }, { "champion_name": "Zilean", "synergy": { - "Swain": "58.97%", - "Hwei": "55.56%", - "Samira": "54.12%", - "Kog'Maw": "54.05%", - "Twitch": "53.80%", - "Aphelios": "53.47%", - "Vayne": "51.88%", - "Caitlyn": "51.79%", - "Seraphine": "51.67%", - "Ezreal": "51.07%", - "Ashe": "50.12%" - } - }, - { - "champion_name": "Braum", - "synergy": { - "Sivir": "60.82%", - "Twitch": "59.55%", - "Xayah": "54.40%", - "Varus": "52.69%", - "Kog'Maw": "52.03%", - "Ashe": "51.92%", - "Jinx": "51.32%", - "Tristana": "50.94%", - "Vayne": "50.83%", - "Draven": "50.75%", - "Ezreal": "50.35%" - } - }, - { - "champion_name": "Vel'Koz", - "synergy": { - "Karthus": "61.90%", - "Vayne": "55.34%", - "Sivir": "54.72%", - "Ashe": "54.44%", - "Aphelios": "53.25%", - "Seraphine": "52.38%", - "Jhin": "51.92%", - "Caitlyn": "51.43%", - "Ezreal": "51.22%", - "Twitch": "50.39%", - "Varus": "50.37%", - "Tristana": "50.00%", - "Miss Fortune": "50.00%" + "Miss Fortune": "62.03%", + "Twisted Fate": "61.05%", + "Draven": "54.63%", + "Swain": "54.55%", + "Vayne": "54.47%", + "Ashe": "53.97%", + "Caitlyn": "53.13%", + "Seraphine": "52.44%", + "Smolder": "51.37%", + "Xayah": "51.22%", + "Sivir": "50.00%" } }, { - "champion_name": "Thresh", + "champion_name": "Pyke", "synergy": { - "Tristana": "55.29%", - "Hwei": "54.01%", - "Draven": "53.28%", - "Jinx": "52.84%", - "Aphelios": "51.39%", - "Twitch": "51.28%", - "Kalista": "51.17%", - "Samira": "51.08%", - "Vayne": "51.01%", - "Karthus": "50.91%", - "Lucian": "50.61%", - "Ashe": "50.26%", - "Yasuo": "50.24%" + "Karthus": "60.47%", + "Swain": "58.06%", + "Senna": "57.89%", + "Ashe": "55.02%", + "Twitch": "54.55%", + "Vayne": "53.90%", + "Jinx": "53.67%", + "Yasuo": "53.66%", + "Seraphine": "53.19%", + "Twisted Fate": "53.07%", + "Nilah": "53.00%", + "Smolder": "51.63%", + "Zeri": "50.56%", + "Ezreal": "50.22%", + "Draven": "50.00%" } }, { - "champion_name": "Milio", + "champion_name": "Zac", "synergy": { - "Kog'Maw": "53.82%", - "Xayah": "52.76%", - "Lucian": "51.94%", - "Zeri": "51.56%", - "Vayne": "51.56%", - "Kalista": "51.56%", - "Ashe": "51.48%", - "Draven": "50.69%", - "Aphelios": "50.42%", - "Twitch": "50.00%" + "Aphelios": "83.33%", + "Caitlyn": "66.67%", + "Draven": "65.52%", + "Senna": "64.71%", + "Tristana": "61.54%", + "Twisted Fate": "61.29%", + "Zeri": "58.82%", + "Seraphine": "56.25%", + "Jinx": "55.00%", + "Ezreal": "52.38%", + "Smolder": "51.58%", + "Kalista": "51.52%", + "Xayah": "50.00%" } }, { "champion_name": "Alistar", "synergy": { - "Seraphine": "59.43%", - "Ziggs": "57.86%", - "Sivir": "55.20%", - "Hwei": "54.65%", - "Zeri": "53.37%", - "Ashe": "52.55%", - "Yasuo": "52.42%", - "Vayne": "52.01%", - "Twitch": "51.83%", - "Xayah": "51.49%", - "Jinx": "50.82%", - "Jhin": "50.68%", - "Ezreal": "50.58%", - "Draven": "50.28%", - "Miss Fortune": "50.20%" + "Karthus": "64.86%", + "Seraphine": "62.86%", + "Zeri": "60.83%", + "Sivir": "59.26%", + "Jinx": "58.42%", + "Yasuo": "58.09%", + "Tristana": "55.84%", + "Senna": "53.27%", + "Twitch": "52.99%", + "Draven": "51.74%", + "Xayah": "51.25%", + "Ezreal": "50.91%", + "Twisted Fate": "50.75%", + "Samira": "50.00%" } }, { - "champion_name": "Zac", + "champion_name": "Bard", "synergy": { - "Ziggs": "72.73%", - "Sivir": "71.43%", - "Zeri": "70.00%", - "Varus": "63.64%", - "Yasuo": "62.07%", - "Jhin": "59.32%", - "Kalista": "57.14%", - "Hwei": "57.14%", - "Twitch": "55.32%", - "Ezreal": "52.63%", - "Miss Fortune": "52.38%", - "Vayne": "50.75%", - "Swain": "50.00%", - "Tristana": "50.00%", - "Caitlyn": "50.00%" + "Yasuo": "60.34%", + "Seraphine": "59.53%", + "Senna": "59.42%", + "Ziggs": "57.58%", + "Caitlyn": "55.43%", + "Xayah": "54.26%", + "Hwei": "54.17%", + "Twisted Fate": "53.66%", + "Jinx": "52.43%", + "Twitch": "51.68%", + "Aphelios": "51.54%", + "Karthus": "51.47%", + "Samira": "50.60%", + "Vayne": "50.34%" } }, { - "champion_name": "Leona", + "champion_name": "Sona", "synergy": { - "Seraphine": "56.47%", - "Xayah": "55.00%", - "Zeri": "54.63%", - "Ezreal": "54.35%", - "Twitch": "54.27%", - "Caitlyn": "53.68%", - "Jhin": "53.21%", - "Yasuo": "53.19%", - "Lucian": "52.72%", - "Miss Fortune": "51.92%", - "Aphelios": "51.70%", - "Tristana": "51.48%", - "Kalista": "51.16%" + "Nilah": "71.43%", + "Tristana": "63.16%", + "Twisted Fate": "56.19%", + "Jhin": "55.34%", + "Kai'Sa": "55.11%", + "Varus": "52.63%", + "Aphelios": "52.00%", + "Twitch": "51.46%", + "Vayne": "51.22%", + "Smolder": "50.72%", + "Lucian": "50.00%", + "Samira": "50.00%" } }, { - "champion_name": "Xerath", + "champion_name": "Thresh", "synergy": { - "Ziggs": "55.24%", - "Jinx": "53.95%", - "Vayne": "53.32%", - "Samira": "52.63%", - "Xayah": "52.57%", - "Ashe": "52.33%", - "Jhin": "51.81%", - "Caitlyn": "51.12%", - "Hwei": "50.88%", - "Tristana": "50.70%", - "Ezreal": "50.00%" + "Yasuo": "62.82%", + "Senna": "58.33%", + "Xayah": "56.52%", + "Sivir": "56.25%", + "Seraphine": "55.74%", + "Kalista": "54.41%", + "Twitch": "52.68%", + "Miss Fortune": "52.44%", + "Jinx": "52.43%", + "Draven": "52.37%", + "Aphelios": "52.12%", + "Smolder": "51.38%", + "Tristana": "51.11%", + "Samira": "50.59%", + "Varus": "50.32%", + "Vayne": "50.15%", + "Kai'Sa": "50.05%" } }, { - "champion_name": "Nami", + "champion_name": "Zyra", "synergy": { - "Swain": "59.52%", - "Brand": "55.69%", - "Karthus": "55.65%", - "Twitch": "52.16%", - "Ashe": "51.88%", - "Seraphine": "51.43%", - "Jhin": "51.32%", - "Vayne": "51.29%", - "Ezreal": "51.27%" + "Xayah": "66.07%", + "Sivir": "61.54%", + "Twisted Fate": "53.68%", + "Miss Fortune": "53.25%", + "Ezreal": "52.52%", + "Senna": "52.28%", + "Tristana": "50.94%", + "Vayne": "50.18%", + "Jinx": "50.00%" } }, { - "champion_name": "Shaco", + "champion_name": "Vel'Koz", "synergy": { - "Seraphine": "85.71%", - "Miss Fortune": "58.33%", - "Samira": "54.84%", - "Caitlyn": "54.43%", - "Jhin": "54.01%", - "Varus": "52.43%", + "Samira": "65.00%", + "Nilah": "60.00%", + "Hwei": "58.82%", + "Seraphine": "54.55%", + "Draven": "53.66%", + "Ashe": "53.52%", + "Tristana": "53.33%", + "Zeri": "53.13%", + "Senna": "51.90%", + "Kai'Sa": "51.02%", + "Jhin": "50.71%", + "Smolder": "50.22%", + "Sivir": "50.00%", "Karthus": "50.00%" } }, { - "champion_name": "Heimerdinger", + "champion_name": "Shaco", "synergy": { - "Zeri": "68.00%", - "Sivir": "57.89%", - "Draven": "54.29%", - "Jhin": "54.03%", - "Vayne": "53.49%", - "Miss Fortune": "52.78%", - "Ashe": "50.34%", - "Kalista": "50.00%", - "Twitch": "50.00%" + "Karthus": "76.92%", + "Twisted Fate": "62.64%", + "Tristana": "58.62%", + "Senna": "56.19%", + "Miss Fortune": "56.10%", + "Aphelios": "52.78%", + "Hwei": "52.63%", + "Ashe": "52.48%", + "Draven": "52.24%", + "Nilah": "50.00%", + "Caitlyn": "50.00%", + "Ziggs": "50.00%" } }, { - "champion_name": "Sylas", + "champion_name": "Pantheon", "synergy": { - "Karthus": "60.00%", - "Aphelios": "58.06%", - "Nilah": "57.89%", - "Miss Fortune": "57.14%", - "Kalista": "55.36%", - "Lucian": "54.65%", - "Seraphine": "54.55%", - "Sivir": "52.94%", - "Zeri": "52.63%", - "Vayne": "52.53%", - "Jhin": "52.38%", - "Ezreal": "51.14%", - "Twitch": "50.77%", - "Yasuo": "50.00%" + "Karthus": "66.67%", + "Twisted Fate": "58.82%", + "Miss Fortune": "56.60%", + "Senna": "56.59%", + "Jhin": "55.67%", + "Nilah": "54.55%", + "Ezreal": "54.37%", + "Swain": "53.85%", + "Lucian": "53.06%", + "Ashe": "52.38%", + "Smolder": "52.07%", + "Samira": "51.97%", + "Jinx": "51.72%" } }, { "champion_name": "Tahm Kench", "synergy": { - "Veigar": "80.00%", - "Ziggs": "75.00%", - "Karthus": "66.67%", - "Seraphine": "58.33%", - "Xayah": "58.33%", - "Jhin": "55.56%", - "Miss Fortune": "51.72%", - "Twitch": "51.28%", - "Kog'Maw": "50.00%", - "Kalista": "50.00%", - "Ashe": "50.00%", - "Ezreal": "50.00%" + "Kalista": "68.75%", + "Jinx": "65.00%", + "Nilah": "62.50%", + "Xayah": "62.50%", + "Twitch": "57.14%", + "Varus": "55.10%", + "Twisted Fate": "54.55%", + "Zeri": "53.85%", + "Senna": "52.51%", + "Seraphine": "50.00%" } }, { - "champion_name": "Amumu", - "synergy": { - "Yasuo": "71.43%", - "Ziggs": "66.67%", - "Kalista": "64.15%", - "Lucian": "60.00%", - "Karthus": "60.00%", - "Hwei": "57.14%", - "Ezreal": "55.88%", - "Nilah": "55.56%", - "Draven": "55.41%", - "Miss Fortune": "54.37%", - "Ashe": "54.17%", - "Jinx": "53.85%", - "Kai'Sa": "53.60%", - "Tristana": "52.63%", - "Zeri": "50.00%", - "Veigar": "50.00%" + "champion_name": "Nautilus", + "synergy": { + "Seraphine": "55.21%", + "Senna": "54.59%", + "Kalista": "52.55%", + "Xayah": "52.25%", + "Caitlyn": "51.40%", + "Karthus": "51.11%", + "Twitch": "50.96%", + "Lucian": "50.86%", + "Jhin": "50.64%", + "Draven": "50.62%", + "Vayne": "50.27%", + "Zeri": "50.21%", + "Samira": "50.09%", + "Nilah": "50.00%" } }, { - "champion_name": "Twitch", + "champion_name": "Xerath", "synergy": { - "Sivir": "70.97%", - "Swain": "70.00%", - "Seraphine": "63.64%", - "Draven": "57.89%", - "Cassiopeia": "53.85%", - "Vayne": "53.54%", - "Jhin": "53.40%", - "Jinx": "52.27%", - "Ziggs": "51.72%", - "Zeri": "51.61%", - "Nilah": "50.00%", - "Kalista": "50.00%", - "Hwei": "50.00%", - "Tristana": "50.00%" + "Samira": "70.00%", + "Twisted Fate": "61.36%", + "Caitlyn": "57.97%", + "Karthus": "55.56%", + "Ashe": "52.46%", + "Vayne": "50.55%", + "Zeri": "50.00%" } }, { - "champion_name": "Nautilus", + "champion_name": "Brand", "synergy": { - "Seraphine": "58.68%", - "Ziggs": "55.43%", - "Karthus": "55.21%", - "Lucian": "51.71%", - "Tristana": "51.50%", - "Samira": "51.14%", - "Kalista": "50.71%", - "Jinx": "50.13%" + "Samira": "59.09%", + "Yasuo": "57.89%", + "Twisted Fate": "57.14%", + "Xayah": "56.52%", + "Jinx": "55.88%", + "Jhin": "53.98%", + "Draven": "53.70%", + "Seraphine": "52.50%", + "Aphelios": "50.00%" } }, { - "champion_name": "Ashe", + "champion_name": "Morgana", "synergy": { - "Karthus": "56.83%", - "Seraphine": "54.60%", - "Jinx": "54.17%", - "Nilah": "53.85%", - "Twitch": "53.55%", - "Ezreal": "51.69%", - "Swain": "51.32%", - "Ziggs": "50.79%", - "Lucian": "50.00%", - "Zeri": "50.00%" + "Xayah": "60.00%", + "Draven": "57.81%", + "Nilah": "54.55%", + "Samira": "51.61%", + "Sivir": "50.00%", + "Seraphine": "50.00%" } }, { - "champion_name": "Lux", + "champion_name": "Heimerdinger", "synergy": { - "Karthus": "59.46%", - "Twitch": "53.57%", - "Xayah": "52.17%", - "Ashe": "51.05%", - "Varus": "50.88%", - "Tristana": "50.67%", + "Tristana": "80.00%", + "Aphelios": "80.00%", + "Draven": "64.29%", + "Twitch": "62.50%", + "Twisted Fate": "60.00%", + "Jhin": "55.56%", + "Varus": "53.85%", + "Senna": "51.28%", + "Miss Fortune": "50.00%", "Jinx": "50.00%", - "Zeri": "50.00%" + "Nilah": "50.00%" } }, { - "champion_name": "Brand", + "champion_name": "Ashe", "synergy": { - "Seraphine": "57.69%", - "Karthus": "55.17%", - "Yasuo": "55.17%", - "Kalista": "53.49%", - "Tristana": "52.31%", - "Xayah": "51.40%", - "Varus": "50.17%" + "Jinx": "53.73%", + "Jhin": "53.36%", + "Karthus": "52.31%", + "Kalista": "51.90%", + "Twisted Fate": "51.81%", + "Zeri": "50.65%" } }, { - "champion_name": "Seraphine", + "champion_name": "Sylas", "synergy": { - "Vayne": "59.62%", - "Tristana": "59.38%", - "Miss Fortune": "52.50%", - "Ashe": "52.20%", - "Jinx": "51.11%", - "Draven": "50.94%" + "Miss Fortune": "75.00%", + "Zeri": "72.73%", + "Twitch": "68.75%", + "Kalista": "68.42%", + "Draven": "65.22%", + "Tristana": "61.54%", + "Senna": "60.00%", + "Aphelios": "60.00%", + "Jinx": "55.56%", + "Jhin": "50.00%" } }, { - "champion_name": "Lulu", + "champion_name": "Neeko", "synergy": { - "Miss Fortune": "55.96%", - "Sivir": "55.62%", - "Tristana": "55.22%", - "Ashe": "51.59%", + "Yasuo": "67.74%", + "Tristana": "66.67%", + "Twitch": "65.63%", + "Ashe": "60.87%", + "Senna": "59.22%", + "Nilah": "57.14%", + "Caitlyn": "54.55%", "Draven": "51.52%", - "Varus": "51.22%", - "Twitch": "51.14%", - "Jinx": "50.76%", - "Kog'Maw": "50.00%", - "Kalista": "50.00%" + "Twisted Fate": "50.94%", + "Vayne": "50.00%", + "Jinx": "50.00%", + "Miss Fortune": "50.00%" } }, { - "champion_name": "Morgana", + "champion_name": "Karma", "synergy": { - "Karthus": "55.56%", - "Nilah": "53.13%", - "Miss Fortune": "52.07%", - "Hwei": "51.72%", - "Vayne": "51.31%", - "Sivir": "50.91%", - "Draven": "50.63%", - "Tristana": "50.00%" + "Tristana": "54.41%", + "Ashe": "52.79%", + "Twisted Fate": "52.57%", + "Jinx": "52.21%", + "Miss Fortune": "51.67%", + "Kalista": "51.02%", + "Twitch": "50.19%", + "Caitlyn": "50.15%", + "Zeri": "50.00%" } }, { - "champion_name": "Karma", + "champion_name": "Lux", "synergy": { - "Draven": "53.57%", - "Vayne": "51.30%", - "Jhin": "50.49%", - "Ezreal": "50.43%", - "Tristana": "50.42%" + "Ziggs": "57.14%", + "Twisted Fate": "56.82%", + "Samira": "52.63%", + "Senna": "52.05%", + "Ezreal": "50.65%", + "Nilah": "50.00%" } }, { - "champion_name": "Swain", + "champion_name": "Seraphine", "synergy": { - "Tristana": "64.29%", - "Karthus": "54.55%", - "Vayne": "54.01%", - "Jhin": "53.18%", - "Twitch": "50.70%", - "Nilah": "50.00%", - "Ziggs": "50.00%" + "Lucian": "61.54%", + "Tristana": "61.54%", + "Vayne": "58.90%", + "Varus": "57.69%", + "Jinx": "56.25%", + "Miss Fortune": "55.32%", + "Senna": "51.51%", + "Ezreal": "50.83%", + "Samira": "50.00%", + "Karthus": "50.00%" } }, { - "champion_name": "Pantheon", + "champion_name": "Yuumi", "synergy": { - "Miss Fortune": "63.41%", - "Seraphine": "56.25%", - "Yasuo": "54.55%", - "Swain": "52.63%", - "Varus": "52.05%", - "Zeri": "50.00%" + "Rammus": "64.71%", + "Jinx": "55.32%", + "Vayne": "54.26%", + "Samira": "53.57%", + "Lucian": "53.52%", + "Tristana": "53.33%", + "Twitch": "53.31%", + "Jhin": "50.00%", + "Xayah": "50.00%" } }, { - "champion_name": "Yuumi", + "champion_name": "Twitch", "synergy": { - "Seraphine": "66.67%", - "Yasuo": "53.25%", - "Tristana": "52.55%", - "Vayne": "51.74%", - "Zeri": "51.45%", - "Twitch": "50.41%" + "Karma": "75.00%", + "Lucian": "66.67%", + "Cassiopeia": "66.67%", + "Janna": "66.67%", + "Aphelios": "66.67%", + "Jhin": "61.90%", + "Zeri": "60.87%", + "Varus": "60.00%", + "Kalista": "57.14%", + "Xayah": "53.85%", + "Ezreal": "53.52%", + "Vayne": "53.19%", + "Sivir": "50.00%", + "Jinx": "50.00%", + "Lulu": "50.00%" } }, { "champion_name": "Hwei", "synergy": { - "Nilah": "51.61%" + "Lucian": "57.14%", + "Tristana": "54.55%", + "Swain": "53.85%", + "Miss Fortune": "51.85%", + "Seraphine": "51.02%" } } ] \ No newline at end of file diff --git a/src/personal_finance/.txt b/src/personal_finance/.txt old mode 100644 new mode 100755 diff --git a/src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx b/src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..509952dfa9945fa2fcc6c7fdc0add079649bd685 GIT binary patch literal 26172 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 literal 0 HcmV?d00001 diff --git a/src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx b/src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..967c16cd43545a41e48d59495623bab9b874fca5 GIT binary patch literal 14338 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 vJf(EMc>d&bf97*f@lU<:"/\\|?*]', '', str(name)) + return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary + +# Process each unique List Name +for value in unique_values: + sanitized_list_name = sanitize_name(value) # Sanitize List Name + list_folder = os.path.join(output_dir, sanitized_list_name) + os.makedirs(list_folder, exist_ok=True) + + # Filter DataFrame based on the current List Name value + filtered_df = df[df['List Name'] == value] + + # Drop columns 'Space Name' and 'List Name' + filtered_df = filtered_df.drop(columns=['Space Name', 'List Name']) + + # Prepare a list to hold sorted rows + sorted_rows = [] + + # Define function to add tasks and their children + def add_task_with_children(task_id): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task) + + # Find top-level tasks (no parent) and create folders for each + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()] + + for _, task_row in top_level_tasks.iterrows(): + task_id = task_row['Task ID'] + task_name = task_row['Task Name'] + + # Sanitize the task name for folder and file naming + sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name + + # Create folder for the top-level task using the sanitized name + task_folder = os.path.join(list_folder, sanitized_task_name) + os.makedirs(task_folder, exist_ok=True) + + # Collect children of the top-level task + sorted_rows.clear() + add_task_with_children(task_id) + + # Convert to DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + + # Name the text file based on the parent task's name + txt_output_file = os.path.join(task_folder, f'{sanitized_task_name}.txt') + + # Check for filename length and truncate if necessary + if len(txt_output_file) > 255: + txt_output_file = os.path.join(task_folder, f'{sanitized_task_name[:255]}.txt') + + txt_columns_to_exclude = ['Task ID', 'Parent ID'] + sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude) + sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '') + + # Write to the text file + with open(txt_output_file, 'w', encoding='utf-8') as f: + for _, row in sorted_df_txt.iterrows(): + cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else '' + for col, val in zip(sorted_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + f.write("----------------------------------------------------\n") # Add separator line after each child's content + +print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.') diff --git a/src/personal_notes/clickup_extract/clickup_notes_split.py b/src/personal_notes/clickup_extract/v1clean.py old mode 100644 new mode 100755 similarity index 88% rename from src/personal_notes/clickup_extract/clickup_notes_split.py rename to src/personal_notes/clickup_extract/v1clean.py index 3cc2281..4dec753 --- a/src/personal_notes/clickup_extract/clickup_notes_split.py +++ b/src/personal_notes/clickup_extract/v1clean.py @@ -3,13 +3,14 @@ #preparing clickup exported CSV file for cleaning unnecessary columns # Read the CSV file into a DataFrame -csv_file = 'export_copy.csv' # Replace 'your_file.csv' with the path to your CSV file +csv_file = 'clickup2.csv' # Replace 'your_file.csv' with the path to your CSV file df = pd.read_csv(csv_file) # Specify the columns you want to delete columns_to_delete = ['Task ID', 'Attachments', 'Date Created', 'Date Created Text','Due Date', 'Due Date Text','Start Date', 'Start Date Text','Assignees', 'Folder Name', 'Time Estimated', 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', 'Time Spent', 'Time Spent Text', - 'Rolled Up Time', 'Rolled Up Time Text'] + 'Rolled Up Time', 'Rolled Up Time Text', + 'Parent ID', 'Tags', 'Priority', 'Status'] # Replace with the names of columns you want to delete # Drop the specified columns from the DataFrame diff --git a/src/personal_notes/clickup_extract/v2sorted_messy.py b/src/personal_notes/clickup_extract/v2sorted_messy.py new file mode 100755 index 0000000..ff20c3e --- /dev/null +++ b/src/personal_notes/clickup_extract/v2sorted_messy.py @@ -0,0 +1,74 @@ +import os +import pandas as pd + +# Preparing clickup exported CSV file for cleaning unnecessary columns +csv_file = 'clickup2.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Specify the columns you want to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop the specified columns from the DataFrame +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get all the unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Print or use the unique values as needed +print(unique_values) + +# Write the modified DataFrame back to a CSV and TXT files +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +for value in unique_values: + # Sanitize the string to replace problematic characters + sanitized_value = value.replace('/', '_') # Replace '/' with '_' + csv_output_file = os.path.join(output_dir, f'{sanitized_value}.csv') + txt_output_file = os.path.join(output_dir, f'{sanitized_value}.txt') + + # Filter the DataFrame based on the current value + filtered_df = df[df['List Name'] == value] + + # Drop additional columns from the filtered DataFrame + additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop + filtered_df = filtered_df.drop(columns=additional_columns_to_drop) + + # Create a list to hold the final sorted rows + sorted_rows = [] + + # Function to add tasks and their children + def add_task_with_children(task_id): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + sorted_rows.append(['---'] * len(row)) # Add separator row + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task) + + # Find all tasks that do not have a parent (top-level tasks) + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]['Task ID'].unique() + + # Add top-level tasks and their children to the sorted rows + for task_id in top_level_tasks: + add_task_with_children(task_id) + + # Convert the list of sorted rows back to a DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + + # Write the sorted DataFrame to a CSV file + sorted_df.to_csv(csv_output_file, index=False) + + # Write the sorted DataFrame to a TXT file + with open(txt_output_file, 'w', encoding='utf-8') as f: + for index, row in sorted_df.iterrows(): + f.write('\t'.join(map(str, row.values)) + '\n') + if all(cell == '---' for cell in row.values): + f.write('\n') # Add an extra newline after separator rows diff --git a/src/personal_notes/clickup_extract/v3.0clean_sorted.py b/src/personal_notes/clickup_extract/v3.0clean_sorted.py new file mode 100755 index 0000000..29f276b --- /dev/null +++ b/src/personal_notes/clickup_extract/v3.0clean_sorted.py @@ -0,0 +1,99 @@ +import os +import pandas as pd +import re + +#output looksl ike: unnecessary folders, hard to read data. + + +# Preparing clickup exported CSV file for cleaning unnecessary columns +csv_file = 'clickup2.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Specify the columns you want to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop the specified columns from the DataFrame +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get all unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Print or use the unique values as needed +print(unique_values) + +# Find all unique tags +all_tags = set() +for tags in df_sorted['Tags'].dropna(): + all_tags.update(tags.split(',')) + +# Write the modified DataFrame back to CSV and TXT files +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +for value in unique_values: + # Sanitize the string to replace problematic characters + sanitized_value = value.replace('/', '_') # Replace '/' with '_' + csv_output_file = os.path.join(output_dir, f'{sanitized_value}.csv') + txt_output_file = os.path.join(output_dir, f'{sanitized_value}.txt') + + # Filter the DataFrame based on the current value + filtered_df = df[df['List Name'] == value] + + # Drop additional columns from the filtered DataFrame + additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop + filtered_df = filtered_df.drop(columns=additional_columns_to_drop) + + # Create a list to hold the final sorted rows + sorted_rows = [] + + # Function to add tasks and their children + def add_task_with_children(task_id): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task) + + # Find all tasks that do not have a parent (top-level tasks) + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]['Task ID'].unique() + + # Add top-level tasks and their children to the sorted rows + for task_id in top_level_tasks: + add_task_with_children(task_id) + + # Convert the list of sorted rows back to a DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + + # Write the sorted DataFrame to a CSV file + sorted_df.to_csv(csv_output_file, index=False) + + # Write the sorted DataFrame to a TXT file + with open(txt_output_file, 'w', encoding='utf-8') as f: + for index, row in sorted_df.iterrows(): + f.write('\t'.join(map(str, row.values)) + '\n') + + # Write the DataFrame to tag-specific folders + for tag in all_tags: + # Escape special characters in the tag + escaped_tag = re.escape(tag.strip()) + tag_filtered_df = sorted_df[sorted_df['Tags'].str.contains(escaped_tag, na=False)] + if not tag_filtered_df.empty: + tag_dir = os.path.join(output_dir, tag.strip()) + os.makedirs(tag_dir, exist_ok=True) + tag_csv_output_file = os.path.join(tag_dir, f'{sanitized_value}.csv') + tag_txt_output_file = os.path.join(tag_dir, f'{sanitized_value}.txt') + + tag_filtered_df.to_csv(tag_csv_output_file, index=False) + + with open(tag_txt_output_file, 'w', encoding='utf-8') as f: + for index, row in tag_filtered_df.iterrows(): + f.write('\t'.join(map(str, row.values)) + '\n') + +print(f'Processed {len(df)} rows and saved them as .csv and .txt files in the "{output_dir}" directory and tag-specific subdirectories.') diff --git a/src/personal_notes/clickup_extract/v3.1.py b/src/personal_notes/clickup_extract/v3.1.py new file mode 100755 index 0000000..68fb915 --- /dev/null +++ b/src/personal_notes/clickup_extract/v3.1.py @@ -0,0 +1,110 @@ +import os +import pandas as pd +import re + +#output looks like: with unnecessary folders by the tags, clean data. + + +# Preparing clickup exported CSV file for cleaning unnecessary columns +csv_file = 'clickup2.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Specify the columns you want to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop the specified columns from the DataFrame +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get all unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Print or use the unique values as needed +print(unique_values) + +# Find all unique tags +all_tags = set() +for tags in df_sorted['Tags'].dropna(): + all_tags.update(tags.split(',')) + +# Write the modified DataFrame back to CSV and TXT files +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +for value in unique_values: + # Sanitize the string to replace problematic characters + sanitized_value = value.replace('/', '_') # Replace '/' with '_' + csv_output_file = os.path.join(output_dir, f'{sanitized_value}.csv') + txt_output_file = os.path.join(output_dir, f'{sanitized_value}.txt') + + # Filter the DataFrame based on the current value + filtered_df = df[df['List Name'] == value] + + # Drop additional columns from the filtered DataFrame + additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop + filtered_df = filtered_df.drop(columns=additional_columns_to_drop) + + # Create a list to hold the final sorted rows + sorted_rows = [] + + # Function to add tasks and their children + def add_task_with_children(task_id): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task) + + # Find all tasks that do not have a parent (top-level tasks) + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]['Task ID'].unique() + + # Add top-level tasks and their children to the sorted rows + for task_id in top_level_tasks: + add_task_with_children(task_id) + + # Convert the list of sorted rows back to a DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + + # Write the sorted DataFrame to a CSV file + sorted_df.to_csv(csv_output_file, index=False) + + # Write the sorted DataFrame to a TXT file without "Task ID" and "Parent ID" columns and removing NaN and [] + txt_columns_to_exclude = ['Task ID', 'Parent ID'] + sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude) + sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '') + + with open(txt_output_file, 'w', encoding='utf-8') as f: + for index, row in sorted_df_txt.iterrows(): + cleaned_row = [f"(({value}))" if pd.notna(value) and col == 'Task Content' else str(value) if pd.notna(value) else '' + for col, value in zip(sorted_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + + # Write the DataFrame to tag-specific folders + for tag in all_tags: + # Escape special characters in the tag + escaped_tag = re.escape(tag.strip()) + tag_filtered_df = sorted_df[sorted_df['Tags'].str.contains(escaped_tag, na=False)] + if not tag_filtered_df.empty: + tag_dir = os.path.join(output_dir, tag.strip()) + os.makedirs(tag_dir, exist_ok=True) + tag_csv_output_file = os.path.join(tag_dir, f'{sanitized_value}.csv') + tag_txt_output_file = os.path.join(tag_dir, f'{sanitized_value}.txt') + + tag_filtered_df.to_csv(tag_csv_output_file, index=False) + + tag_filtered_df_txt = tag_filtered_df.drop(columns=txt_columns_to_exclude) + tag_filtered_df_txt = tag_filtered_df_txt.replace(['NaN', '[]'], '') + + with open(tag_txt_output_file, 'w', encoding='utf-8') as f: + for index, row in tag_filtered_df_txt.iterrows(): + cleaned_row = [f"(({value}))" if pd.notna(value) and col == 'Task Content' else str(value) if pd.notna(value) else '' + for col, value in zip(tag_filtered_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + +print(f'Processed {len(df)} rows and saved them as .csv and .txt files in the "{output_dir}" directory and tag-specific subdirectories.') diff --git a/src/personal_notes/clickup_extract/v3.2.py b/src/personal_notes/clickup_extract/v3.2.py new file mode 100755 index 0000000..776ea18 --- /dev/null +++ b/src/personal_notes/clickup_extract/v3.2.py @@ -0,0 +1,84 @@ +import os +import pandas as pd +import re + +#output looks like: clean data. +# NEXT TBD improvement: create a folder for each parent ID, and txt file for each children that has attached parent ID + + +# Preparing clickup exported CSV file for cleaning unnecessary columns +csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Specify the columns you want to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop the specified columns from the DataFrame +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get all unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Print or use the unique values as needed +print(unique_values) + +# Write the modified DataFrame back to CSV and TXT files +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +for value in unique_values: + # Sanitize the string to replace problematic characters + sanitized_value = value.replace('/', '_') # Replace '/' with '_' + csv_output_file = os.path.join(output_dir, f'{sanitized_value}.csv') + txt_output_file = os.path.join(output_dir, f'{sanitized_value}.txt') + + # Filter the DataFrame based on the current value + filtered_df = df[df['List Name'] == value] + + # Drop additional columns from the filtered DataFrame + additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop + filtered_df = filtered_df.drop(columns=additional_columns_to_drop) + + # Create a list to hold the final sorted rows + sorted_rows = [] + + # Function to add tasks and their children + def add_task_with_children(task_id): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task) + + # Find all tasks that do not have a parent (top-level tasks) + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]['Task ID'].unique() + + # Add top-level tasks and their children to the sorted rows + for task_id in top_level_tasks: + add_task_with_children(task_id) + + # Convert the list of sorted rows back to a DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + + # Write the sorted DataFrame to a CSV file + sorted_df.to_csv(csv_output_file, index=False) + + # Write the sorted DataFrame to a TXT file without "Task ID" and "Parent ID" columns and removing NaN and [] + txt_columns_to_exclude = ['Task ID', 'Parent ID'] + sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude) + sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '') + + with open(txt_output_file, 'w', encoding='utf-8') as f: + for index, row in sorted_df_txt.iterrows(): + cleaned_row = [f"(({value}))" if pd.notna(value) and col == 'Task Content' else str(value) if pd.notna(value) else '' + for col, value in zip(sorted_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + +print(f'Processed {len(df)} rows and saved them as .csv and .txt files in the "{output_dir}" directory.') diff --git a/src/personal_notes/clickup_extract/v3.3.py b/src/personal_notes/clickup_extract/v3.3.py new file mode 100755 index 0000000..de6697b --- /dev/null +++ b/src/personal_notes/clickup_extract/v3.3.py @@ -0,0 +1,109 @@ +import os +import pandas as pd +import re + +#!!!!!!!!!!!!!!!!!!! QUITE neat. for the v3.4 version solve this problem: \n in txt files + +# Specify the CSV file path +csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Strip whitespace from column names immediately after loading +df.columns = df.columns.str.strip() + +# Columns to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop specified columns +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Define output directory +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +# Define a maximum length for folder and file names (adjust as needed) +MAX_NAME_LENGTH = 100 + +# Utility function to sanitize names +def sanitize_name(name): + # Remove invalid characters and truncate + sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name)) + return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary + +# Process each unique List Name +for value in unique_values: + sanitized_list_name = sanitize_name(value) # Sanitize List Name + list_folder = os.path.join(output_dir, sanitized_list_name) + os.makedirs(list_folder, exist_ok=True) + + # Filter DataFrame based on the current List Name value + filtered_df = df[df['List Name'] == value] + + # Drop columns 'Space Name' and 'List Name' + filtered_df = filtered_df.drop(columns=['Space Name', 'List Name']) + + # Prepare a list to hold sorted rows + sorted_rows = [] + + # Define function to add tasks and their children + def add_task_with_children(task_id): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task) + + # Find top-level tasks (no parent) and create folders for each + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()] + + for _, task_row in top_level_tasks.iterrows(): + task_id = task_row['Task ID'] + task_name = task_row['Task Name'] + + # Sanitize the task name for folder and file naming + sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name + + # Create folder for the top-level task using the sanitized name + task_folder = os.path.join(list_folder, sanitized_task_name) + os.makedirs(task_folder, exist_ok=True) + + # Collect children of the top-level task + sorted_rows.clear() + add_task_with_children(task_id) + + # Convert to DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + + # Name the text file based on the parent task's name + txt_output_file = os.path.join(task_folder, f'{sanitized_task_name}.txt') + + # Check for filename length and truncate if necessary + if len(txt_output_file) > 255: + txt_output_file = os.path.join(task_folder, f'{sanitized_task_name[:255]}.txt') + + txt_columns_to_exclude = ['Task ID', 'Parent ID'] + sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude) + sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '') + + # Write to the text file + with open(txt_output_file, 'w', encoding='utf-8') as f: + for _, row in sorted_df_txt.iterrows(): + cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else '' + for col, val in zip(sorted_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content + +print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.') diff --git a/src/personal_notes/clickup_extract/v3.4.0.py b/src/personal_notes/clickup_extract/v3.4.0.py new file mode 100755 index 0000000..c8b3ddb --- /dev/null +++ b/src/personal_notes/clickup_extract/v3.4.0.py @@ -0,0 +1,117 @@ +import os +import pandas as pd +import re +import glob + +# Specify the CSV file path +csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Strip whitespace from column names immediately after loading +df.columns = df.columns.str.strip() + +# Columns to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop specified columns +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Define output directory +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +# Define a maximum length for folder and file names (adjust as needed) +MAX_NAME_LENGTH = 100 + +# Utility function to sanitize names +def sanitize_name(name): + # Remove invalid characters and truncate + sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name)) + return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary + +# Process each unique List Name +for value in unique_values: + sanitized_list_name = sanitize_name(value) # Sanitize List Name + list_folder = os.path.join(output_dir, sanitized_list_name) + os.makedirs(list_folder, exist_ok=True) + + # Filter DataFrame based on the current List Name value + filtered_df = df[df['List Name'] == value] + + # Drop columns 'Space Name' and 'List Name' + filtered_df = filtered_df.drop(columns=['Space Name', 'List Name']) + + # Prepare a list to hold sorted rows + sorted_rows = [] + + # Define function to add tasks and their children + def add_task_with_children(task_id): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task) + + # Find top-level tasks (no parent) and create folders for each + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()] + + for _, task_row in top_level_tasks.iterrows(): + task_id = task_row['Task ID'] + task_name = task_row['Task Name'] + + # Sanitize the task name for folder and file naming + sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name + + # Create folder for the top-level task using the sanitized name + task_folder = os.path.join(list_folder, sanitized_task_name) + os.makedirs(task_folder, exist_ok=True) + + # Collect children of the top-level task + sorted_rows.clear() + add_task_with_children(task_id) + + # Convert to DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + + # Name the text file based on the parent task's name + txt_output_file = os.path.join(task_folder, f'{sanitized_task_name}.txt') + + # Check for filename length and truncate if necessary + if len(txt_output_file) > 255: + txt_output_file = os.path.join(task_folder, f'{sanitized_task_name[:255]}.txt') + + txt_columns_to_exclude = ['Task ID', 'Parent ID'] + sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude) + + # Clean up specific values without regex + sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '') + + # Remove any newline characters or multiple consecutive newlines that may still be present in each column + sorted_df_txt = sorted_df_txt.apply( + lambda col: col.map(lambda x: re.sub(r'\n+', ' ', str(x)) if pd.notna(x) else x) + ) + + + # Write to the text file + with open(txt_output_file, 'w', encoding='utf-8') as f: + for _, row in sorted_df_txt.iterrows(): + cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else '' + for col, val in zip(sorted_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + + # Add separator line after each child's content for clarity + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content +print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.') diff --git a/src/personal_notes/clickup_extract/v3.4.1.ALPHA.py b/src/personal_notes/clickup_extract/v3.4.1.ALPHA.py new file mode 100755 index 0000000..c6f54e2 --- /dev/null +++ b/src/personal_notes/clickup_extract/v3.4.1.ALPHA.py @@ -0,0 +1,119 @@ +import os +import pandas as pd +import re +import glob + +# Specify the CSV file path +csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Strip whitespace from column names immediately after loading +df.columns = df.columns.str.strip() + +# Columns to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop specified columns +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Define output directory +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +# Define a maximum length for folder and file names (adjust as needed) +MAX_NAME_LENGTH = 100 + +# Utility function to sanitize names +def sanitize_name(name): + # Remove invalid characters and truncate + sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name)).strip() # Remove invalid chars and leading/trailing whitespace + sanitized = sanitized.rstrip('.') # Remove trailing dots for cross-platform compatibility + return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary + + +# Process each unique List Name +for value in unique_values: + sanitized_list_name = sanitize_name(value) # Sanitize List Name + list_folder = os.path.join(output_dir, sanitized_list_name) + os.makedirs(list_folder, exist_ok=True) + + # Filter DataFrame based on the current List Name value + filtered_df = df[df['List Name'] == value] + + # Drop columns 'Space Name' and 'List Name' + filtered_df = filtered_df.drop(columns=['Space Name', 'List Name']) + + # Prepare a list to hold sorted rows + sorted_rows = [] + + # Define function to add tasks and their children + def add_task_with_children(task_id): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task) + + # Find top-level tasks (no parent) and create folders for each + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()] + + for _, task_row in top_level_tasks.iterrows(): + task_id = task_row['Task ID'] + task_name = task_row['Task Name'] + + # Sanitize the task name for folder and file naming + sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name + + # Create folder for the top-level task using the sanitized name + task_folder = os.path.join(list_folder, sanitized_task_name) + os.makedirs(task_folder, exist_ok=True) + + # Collect children of the top-level task + sorted_rows.clear() + add_task_with_children(task_id) + + # Convert to DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + + # Name the text file based on the parent task's name + txt_output_file = os.path.join(task_folder, f'{sanitized_task_name}.txt') + + # Check for filename length and truncate if necessary + if len(txt_output_file) > 255: + txt_output_file = os.path.join(task_folder, f'{sanitized_task_name[:255]}.txt') + + txt_columns_to_exclude = ['Task ID', 'Parent ID'] + sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude) + + # Clean up specific values without regex + sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '') + + # Remove any newline characters or multiple consecutive newlines that may still be present in each column + sorted_df_txt = sorted_df_txt.apply( + lambda col: col.map(lambda x: re.sub(r'\n+', ' ', str(x)) if pd.notna(x) else x) + ) + + + # Write to the text file + with open(txt_output_file, 'w', encoding='utf-8') as f: + for _, row in sorted_df_txt.iterrows(): + cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else '' + for col, val in zip(sorted_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + + # Add separator line after each child's content for clarity + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content + f.write(" \n") # Add separator line after each child's content +print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.') diff --git a/src/personal_notes/clickup_extract/v4.1.1.ALPHA.py b/src/personal_notes/clickup_extract/v4.1.1.ALPHA.py new file mode 100755 index 0000000..df125dd --- /dev/null +++ b/src/personal_notes/clickup_extract/v4.1.1.ALPHA.py @@ -0,0 +1,118 @@ +import os +import pandas as pd +import re +import glob + +#TBD: radau neatitikima: sub_task folderiuose esantys txt failai turi ne tik sau priklausanti turini, bet ir tarsi i juos appendintas is kitu sub_tasku turinys + +# Specify the CSV file path +csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Strip whitespace from column names immediately after loading +df.columns = df.columns.str.strip() + +# Columns to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop specified columns +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Define output directory +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +# Define a maximum length for folder and file names (adjust as needed) +MAX_NAME_LENGTH = 100 + +# Utility function to sanitize names +def sanitize_name(name): + # Remove invalid characters and truncate + sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name)).strip() # Remove invalid chars and leading/trailing whitespace + sanitized = sanitized.rstrip('.') # Remove trailing dots for cross-platform compatibility + sanitized = sanitized.replace('.', '') # Remove all periods for compatibility + return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary + + +# Process each unique List Name +for value in unique_values: + sanitized_list_name = sanitize_name(value) # Sanitize List Name + list_folder = os.path.join(output_dir, sanitized_list_name) + os.makedirs(list_folder, exist_ok=True) + + # Filter DataFrame based on the current List Name value + filtered_df = df[df['List Name'] == value] + + # Drop columns 'Space Name' and 'List Name' + filtered_df = filtered_df.drop(columns=['Space Name', 'List Name']) + + # Prepare a list to hold sorted rows + sorted_rows = [] + + # Define function to add tasks and their children + def add_task_with_children(task_id, parent_folder, is_child=False): + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + task_name = row[filtered_df.columns.get_loc("Task Name")] + sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name + + # If it's a child task, create a subfolder; if it's top-level, use the parent_folder directly + if is_child: + subtask_folder = os.path.join(parent_folder, sanitized_task_name) + os.makedirs(subtask_folder, exist_ok=True) + target_folder = subtask_folder + else: + target_folder = parent_folder + + # Collect children of the current task + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task, target_folder, is_child=True) + + # Create and write to the text file inside the target folder + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + txt_output_file = os.path.join(target_folder, f'{sanitized_task_name}.txt') + + # Exclude specific columns and clean the content + txt_columns_to_exclude = ['Task ID', 'Parent ID'] + sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude) + sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '') + sorted_df_txt = sorted_df_txt.apply( + lambda col: col.map(lambda x: re.sub(r'\n+', ' ', str(x)) if pd.notna(x) else x) + ) + + with open(txt_output_file, 'w', encoding='utf-8') as f: + for _, row in sorted_df_txt.iterrows(): + cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else '' + for col, val in zip(sorted_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + # Separator lines for clarity + f.write(" \n") # Add separator line for clarity + + # Find top-level tasks (no parent) and create folders for each + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()] + for _, task_row in top_level_tasks.iterrows(): + task_id = task_row['Task ID'] + task_name = task_row['Task Name'] + + # Sanitize and create folder for the top-level task + sanitized_task_name = sanitize_name(task_name) + task_folder = os.path.join(list_folder, sanitized_task_name) + os.makedirs(task_folder, exist_ok=True) + + # Add tasks with the new folder structure + sorted_rows.clear() + add_task_with_children(task_id, task_folder) + +print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.') + diff --git a/src/personal_notes/clickup_extract/v4.1.2.FINAL.py b/src/personal_notes/clickup_extract/v4.1.2.FINAL.py new file mode 100755 index 0000000..d30a0d7 --- /dev/null +++ b/src/personal_notes/clickup_extract/v4.1.2.FINAL.py @@ -0,0 +1,119 @@ +import os +import pandas as pd +import re +import glob + +#TBD: radau neatitikima: sub_task folderiuose esantys txt failai turi ne tik sau priklausanti turini, bet ir tarsi i juos appendintas is kitu sub_tasku turinys + +# Specify the CSV file path +csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file +df = pd.read_csv(csv_file) + +# Strip whitespace from column names immediately after loading +df.columns = df.columns.str.strip() + +# Columns to delete (excluding "Task ID" and "Parent ID") +columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text', + 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated', + 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', + 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status'] + +# Drop specified columns +df = df.drop(columns=columns_to_delete) + +# Sort the DataFrame by the "Space Name" column +df_sorted = df.sort_values(by='Space Name') + +# Get unique values in the "List Name" column +unique_values = df_sorted['List Name'].unique() + +# Define output directory +output_dir = 'output' +os.makedirs(output_dir, exist_ok=True) + +# Define a maximum length for folder and file names (adjust as needed) +MAX_NAME_LENGTH = 100 + +# Utility function to sanitize names +def sanitize_name(name): + # Remove invalid characters and truncate + sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name)).strip() # Remove invalid chars and leading/trailing whitespace + sanitized = sanitized.rstrip('.') # Remove trailing dots for cross-platform compatibility + sanitized = sanitized.replace('.', '') # Remove all periods for compatibility + return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary + + +# Process each unique List Name +for value in unique_values: + sanitized_list_name = sanitize_name(value) # Sanitize List Name + list_folder = os.path.join(output_dir, sanitized_list_name) + os.makedirs(list_folder, exist_ok=True) + + # Filter DataFrame based on the current List Name value + filtered_df = df[df['List Name'] == value] + + # Drop columns 'Space Name' and 'List Name' + filtered_df = filtered_df.drop(columns=['Space Name', 'List Name']) + + # Define function to add tasks and their children + def add_task_with_children(task_id, parent_folder, is_child=False): + # Clear sorted_rows to isolate data for each task or subtask + sorted_rows = [] + + # Collect rows for the current task + task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist() + for row in task_rows: + sorted_rows.append(row) + task_name = row[filtered_df.columns.get_loc("Task Name")] + sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name + + # If it's a child task, create a subfolder; if it's top-level, use the parent_folder directly + if is_child: + subtask_folder = os.path.join(parent_folder, sanitized_task_name) + os.makedirs(subtask_folder, exist_ok=True) + target_folder = subtask_folder + else: + target_folder = parent_folder + + # Collect children of the current task + child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique() + for child_task in child_tasks: + add_task_with_children(child_task, target_folder, is_child=True) + + # Convert to DataFrame + sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns) + txt_output_file = os.path.join(target_folder, f'{sanitized_task_name}.txt') + + # Exclude specific columns and clean the content + txt_columns_to_exclude = ['Task ID', 'Parent ID'] + sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude) + sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '') + sorted_df_txt = sorted_df_txt.apply( + lambda col: col.map(lambda x: re.sub(r'\n+', ' ', str(x)) if pd.notna(x) else x) + ) + + # Write data for the current task/subtask to its respective text file + with open(txt_output_file, 'w', encoding='utf-8') as f: + for _, row in sorted_df_txt.iterrows(): + cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else '' + for col, val in zip(sorted_df_txt.columns, row.values)] + f.write('\t'.join(cleaned_row) + '\n') + # Separator lines for clarity + f.write(" \n") # Add separator line for clarity + + # Find top-level tasks (no parent) and create folders for each + top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()] + for _, task_row in top_level_tasks.iterrows(): + task_id = task_row['Task ID'] + task_name = task_row['Task Name'] + + # Sanitize and create folder for the top-level task + sanitized_task_name = sanitize_name(task_name) + task_folder = os.path.join(list_folder, sanitized_task_name) + os.makedirs(task_folder, exist_ok=True) + + # Add tasks with the new folder structure + add_task_with_children(task_id, task_folder) + +print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.') + diff --git a/src/personal_notes/discord_extract/2024-05-extract.txt b/src/personal_notes/discord_extract/2024-05-extract.txt old mode 100644 new mode 100755 index 885c114..f4d7883 --- a/src/personal_notes/discord_extract/2024-05-extract.txt +++ b/src/personal_notes/discord_extract/2024-05-extract.txt @@ -4,11 +4,11 @@ Life Politics Health Noble -Novelty [not deleting from disc, reason: pictures] +Novelty Culture Social Entertainment -Friendships-Family [not deleting from disc, reason: pictures] +Friendships-Family Relationships Attraction Self-care diff --git a/src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc b/src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc old mode 100644 new mode 100755 diff --git a/src/personal_notes/discord_extract/__pycache__/responses.cpython-311.pyc b/src/personal_notes/discord_extract/__pycache__/responses.cpython-311.pyc old mode 100644 new mode 100755 diff --git a/src/personal_notes/discord_extract/cleaning.py b/src/personal_notes/discord_extract/cleaning.py old mode 100644 new mode 100755 index eaeafb8..093d4e9 --- a/src/personal_notes/discord_extract/cleaning.py +++ b/src/personal_notes/discord_extract/cleaning.py @@ -1,6 +1,7 @@ import json import pandas as pd +""" # After posts are scraped, time to look for duplicates and remove them: def remove_duplicates(json_file): # Read the contents of the JSON file into a list @@ -42,4 +43,7 @@ def remove_duplicates(json_file): file.write('\n') # Add newline for readability # Example usage: -remove_duplicates('messages.json') \ No newline at end of file +remove_duplicates('messages.json') +""" + +# working with final data \ No newline at end of file diff --git a/src/personal_notes/discord_extract/disc_del_channels.py b/src/personal_notes/discord_extract/disc_del_channels.py old mode 100644 new mode 100755 diff --git a/src/personal_notes/discord_extract/disc_del_only.py b/src/personal_notes/discord_extract/disc_del_only.py new file mode 100755 index 0000000..176c2bc --- /dev/null +++ b/src/personal_notes/discord_extract/disc_del_only.py @@ -0,0 +1,64 @@ +import os +from dotenv import load_dotenv +import discord +from discord import Intents, Client +from discord.errors import Forbidden +import asyncio + +# Load bot token from .env file +load_dotenv() +TOKEN = os.getenv('DISCORD_TOKEN') + +# Set up intents +intents = Intents.default() +intents.messages = True + +# Define permission flags +PERMISSION_READ_MESSAGES = 1 << 6 # 65536 uneccessary? +PERMISSION_MANAGE_MESSAGES = 1 << 13 # 8192 uneccessary? +PERMISSION_READ_MESSAGE_HISTORY = 1 << 11 # 73728 uneccessary? +PERMISSION_MANAGE_CHANNELS = 1 << 4 # 16 + +# Calculate permissions integer +permissions = ( + PERMISSION_READ_MESSAGES | PERMISSION_MANAGE_MESSAGES | + PERMISSION_READ_MESSAGE_HISTORY | PERMISSION_MANAGE_CHANNELS +) + +# Initialize the Discord client with permissions +client = Client(intents=intents, permissions=permissions) + +async def delete_channels(category): + try: + # Fetch the channels in the specified category + channels = category.channels + for channel in channels: + # Delete the channel + await channel.delete() + except Forbidden as e: + print(f"Permission error: {e}") + except discord.HTTPException as e: + if e.status == 429: + retry_after = e.retry_after + print(f"We are being rate limited. Retrying in {retry_after} seconds.") + await asyncio.sleep(retry_after) + await delete_channels(category) + else: + print(f"HTTP error: {e}") + except Exception as e: + print(f"An error occurred: {e}") + +@client.event +async def on_ready(): + print(f'{client.user} has connected to Discord!') + print('Monitoring channels...') + for guild in client.guilds: + print(f'Guild: {guild.name}') + for category in guild.categories: + if category.name == 'Noble': # Manually insert category name in the code + print(f'Category: {category.name}') + await delete_channels(category) + await client.close() + +# Run the bot +client.run(TOKEN) diff --git a/src/personal_notes/discord_extract/disc_extract_c.py b/src/personal_notes/discord_extract/disc_extract_c.py old mode 100644 new mode 100755 diff --git a/src/personal_notes/discord_extract/disc_extract_s.py b/src/personal_notes/discord_extract/disc_extract_s.py old mode 100644 new mode 100755 diff --git a/src/personal_notes/discord_extract/json3.py b/src/personal_notes/discord_extract/json3.py new file mode 100755 index 0000000..9c46927 --- /dev/null +++ b/src/personal_notes/discord_extract/json3.py @@ -0,0 +1,49 @@ +import os +import json +import pandas as pd + +# Function to export data into both txt and json files +def export_data(channel, category, content_list): + # Create a folder for the category if it doesn't exist + category_folder = os.path.join('categories', category) + os.makedirs(category_folder, exist_ok=True) + + # Create a folder for the channel + channel_folder = os.path.join(category_folder, channel) + os.makedirs(channel_folder, exist_ok=True) + + # Export content to text file + text_file_path = os.path.join(channel_folder, 'content.txt') + with open(text_file_path, 'w', encoding='utf-8') as text_file: + for content in content_list: + if content and content != ".": + text_file.write(content + '\n') + + # Export content to json file + json_file_path = os.path.join(channel_folder, 'content.json') + with open(json_file_path, 'w', encoding='utf-8') as json_file: + json.dump(content_list, json_file, indent=4) + +# Load the messages from the JSON file +with open('clean_messages.json', 'r', encoding='utf-8') as file: + messages = [json.loads(line) for line in file] + +# Create a DataFrame from the messages +df = pd.DataFrame(messages) + +# Sort the DataFrame by 'channel' and 'timestamp' +df.sort_values(by=['channel', 'timestamp'], inplace=True) + +# Group the messages by the 'channel' column +grouped = df.groupby('channel') + +# Iterate over each group (channel) +for channel, group_df in grouped: + # Get the category of the channel + category = group_df['category'].iloc[0] # Assuming 'category' is a column in the DataFrame + + # Extract content from the group dataframe + content_list = list(group_df['content']) + + # Export data to text and json files + export_data(channel, category, content_list) diff --git a/src/personal_notes/discord_extract/json_to_txt.py b/src/personal_notes/discord_extract/json_to_txt.py old mode 100644 new mode 100755 diff --git a/src/personal_notes/discord_extract/json_to_txt2.py b/src/personal_notes/discord_extract/json_to_txt2.py old mode 100644 new mode 100755 diff --git a/src/personal_notes/discord_extract/migration.txt b/src/personal_notes/discord_extract/migration.txt deleted file mode 100644 index b188b9a..0000000 --- a/src/personal_notes/discord_extract/migration.txt +++ /dev/null @@ -1,11 +0,0 @@ -from clickup to Anytype -1. paziuret kokius 10 columns pasilikt, nes anytype tik tiek columns gali turet -2. split csv by spaces(R) [p.s tikslai, uzduotys ir projektai tures but splitinami i: 1) tikslai ir uzduotys 2) projektai nes virs 1k eiluciu] -2. then scplit also by (P) -import splited spaces to Anytype spaces - -from discord to Logseq -1. Extract and delete posts from discord -2. Create channels as pages in Logseq -3. Convert to EDN/JSON/OPML -4. Import to Logseq \ No newline at end of file diff --git a/src/personal_notes/discord_extract/responses.py b/src/personal_notes/discord_extract/responses.py old mode 100644 new mode 100755 diff --git a/src/personal_notes/discord_extract/scheme.txt b/src/personal_notes/discord_extract/scheme.txt old mode 100644 new mode 100755 index e8ce428..7fc729d --- a/src/personal_notes/discord_extract/scheme.txt +++ b/src/personal_notes/discord_extract/scheme.txt @@ -1,8 +1,3 @@ -Discord zjbs, convenient del tags ir siaip galima betka share'int lengvai per discord. -Taciau.. -Kas savaite/menesi reik extractint visus post'us ir juos store'int kazkur Open Source Privacy app'e. -Kodel: nes pradeda laggint discord ir siaip ten not cool. - The extraction code scheme: 1. setting up a bot in the server https://www.youtube.com/watch?v=UYJDKSah-Ww diff --git a/src/personal_notes/discord_extract/.py b/src/w-automation/plan.txt old mode 100644 new mode 100755 similarity index 100% rename from src/personal_notes/discord_extract/.py rename to src/w-automation/plan.txt From 8319610699381d8ef928b5f3e913fb5dace81bbe Mon Sep 17 00:00:00 2001 From: br34th5 Date: Fri, 18 Jul 2025 14:51:30 +0300 Subject: [PATCH 09/23] Ignore .env files --- .gitignore | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ea6a851..1a40319 100755 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,16 @@ database.ini settings.json check.py -step8-priv.py -__pycache__/ data Barbora notebooks first scrape tries +personal_finance/fin/analysis/ +personal_finance/income +personal_finance/OGCSV +personal_finance/spending +personal_notes/clickup_extract/old_output +personal_notes/clickup_extract/source data +personal_notes/discord_extract/categories +src/personal_notes/discord_extract/.env.env +*.env From 5d69c80023359f86f35e6d363b95dee32aead8aa Mon Sep 17 00:00:00 2001 From: br34th5 Date: Fri, 18 Jul 2025 15:02:17 +0300 Subject: [PATCH 10/23] Update .gitignore --- .gitignore | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 1a40319..7f61ec9 100755 --- a/.gitignore +++ b/.gitignore @@ -6,12 +6,12 @@ data Barbora notebooks first scrape tries -personal_finance/fin/analysis/ -personal_finance/income -personal_finance/OGCSV -personal_finance/spending -personal_notes/clickup_extract/old_output -personal_notes/clickup_extract/source data -personal_notes/discord_extract/categories -src/personal_notes/discord_extract/.env.env +*personal_finance/fin/analysis/Manually polished +*personal_finance/income +*personal_finance/OGCSV +*personal_finance/spending +*personal_notes/clickup_extract/old_output +*personal_notes/clickup_extract/source data +*personal_notes/discord_extract/categories +*src/personal_notes/discord_extract/.env.env *.env From 25ffe71f45f21660fbaff4ab5d1360ed8c55ec03 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Fri, 18 Jul 2025 15:06:02 +0300 Subject: [PATCH 11/23] Remove 'Manually polished' folder from Git tracking --- .../Manually polished/2024_02-04_spendings.xlsx | Bin 26172 -> 0 bytes .../Manually polished/Galutinis_Income.xlsx | Bin 14338 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100755 src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx delete mode 100755 src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx diff --git a/src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx b/src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx deleted file mode 100755 index 509952dfa9945fa2fcc6c7fdc0add079649bd685..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26172 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 diff --git a/src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx b/src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx deleted file mode 100755 index 967c16cd43545a41e48d59495623bab9b874fca5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14338 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 vJf(EMc>d&bf97*f@lU< Date: Mon, 28 Jul 2025 13:16:53 +0300 Subject: [PATCH 12/23] Update .gitignore --- .gitignore | 5 ++- src/personal_finance/fin/OG-CSV/1+.csv | 9 ++++ src/personal_finance/fin/OG-CSV/1-.csv | 41 ++++++++++++++++++ .../2024_02-04_spendings.xlsx | Bin 0 -> 26172 bytes .../Manually polished/Galutinis_Income.xlsx | Bin 0 -> 14338 bytes .../fin/analysis/merged_income_data.xlsx | Bin 0 -> 4785 bytes .../fin/analysis/merged_spending_data.xlsx | Bin 0 -> 4785 bytes src/personal_finance/fin/scrape.py | 10 ++--- 8 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 src/personal_finance/fin/OG-CSV/1+.csv create mode 100644 src/personal_finance/fin/OG-CSV/1-.csv create mode 100755 src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx create mode 100755 src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx create mode 100644 src/personal_finance/fin/analysis/merged_income_data.xlsx create mode 100644 src/personal_finance/fin/analysis/merged_spending_data.xlsx diff --git a/.gitignore b/.gitignore index 7f61ec9..f61e99f 100755 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ data Barbora notebooks first scrape tries -*personal_finance/fin/analysis/Manually polished +*personal_finance/fin/analysis *personal_finance/income *personal_finance/OGCSV *personal_finance/spending @@ -15,3 +15,6 @@ first scrape tries *personal_notes/discord_extract/categories *src/personal_notes/discord_extract/.env.env *.env +*Data-Engineering +__pycache__ + diff --git a/src/personal_finance/fin/OG-CSV/1+.csv b/src/personal_finance/fin/OG-CSV/1+.csv new file mode 100644 index 0000000..4aab18d --- /dev/null +++ b/src/personal_finance/fin/OG-CSV/1+.csv @@ -0,0 +1,9 @@ +"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-06-01-2025-06-30)"; +"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; +"CLR10735426";2025-06-10;"EUR";2,35;"UAB Barbora";"";"";"";"";"09/06/2025 09:52 kortelė...961856 UAB Barbora/Vilnius/LTU #642007, dok. nr. CLR10735426, operacijos nr. RO1892038992L02";"RO1892038992L02";2025-06-10;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";2,35;"LT807044060008146755";"EUR"; +"104";2025-06-12;"EUR";20,00;"KLIM EDGAR";"";"LT657044090101048434";"AB SEB BANKAS";"CBVILT2X";"Dekui, dok. nr. 104, operacijos nr. RO1895393402L02";"RO1895393402L02";2025-06-12;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";20,00;"LT807044060008146755";"EUR"; +"";2025-06-12;"EUR";951,81;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Darbo užmokestis už 05 mėn., unik. mokėj. kodas 8B7C3D00A69A47529C30DCBBDF258E55, mok. ident. kodas 122590280, operacijos nr. RO1895057909L02";"RO1895057909L02";2025-06-12;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";951,81;"LT807044060008146755";"EUR"; +"CLR10801349";2025-06-21;"EUR";0,57;"UAB Barbora";"";"";"";"";"20/06/2025 13:40 kortelė...961856 UAB Barbora/Vilnius/LTU #772650, dok. nr. CLR10801349, operacijos nr. RO1905946572L02";"RO1905946572L02";2025-06-21;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,57;"LT807044060008146755";"EUR"; +"CLR10829531";2025-06-25;"EUR";0,13;"UAB Barbora";"";"";"";"";"25/06/2025 07:48 kortelė...961856 UAB Barbora/Vilnius/LTU #353223, dok. nr. CLR10829531, operacijos nr. RO1910099306L02";"RO1910099306L02";2025-06-25;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,13;"LT807044060008146755";"EUR"; +"";2025-06-26;"EUR";100,00;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Avansas už 2025 - 06-mėn., unik. mokėj. kodas FA9AED68797D4B4FBBA0502715680D90, mok. ident. kodas 122590280, operacijos nr. RO1911238409L02";"RO1911238409L02";2025-06-26;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";100,00;"LT807044060008146755";"EUR"; +"151125372";2025-06-29;"EUR";32,50;"OKAITĖ VILMA";"";"LT217044060008154316";"AB SEB BANKAS";"CBVILT2X";"gimtadienis, dok. nr. 151125372, operacijos nr. RO1914686186L02";"RO1914686186L02";2025-06-29;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";32,50;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/1-.csv b/src/personal_finance/fin/OG-CSV/1-.csv new file mode 100644 index 0000000..005234b --- /dev/null +++ b/src/personal_finance/fin/OG-CSV/1-.csv @@ -0,0 +1,41 @@ +"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-06-01-2025-06-30)"; +"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; +"147202086";2025-06-01;"EUR";20,00;"OKAITĖ VILMA";"";"LT217044060008154316";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 147202086, operacijos nr. RO1882152382L01";"RO1882152382L01";2025-06-01;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; +"147140671";2025-06-01;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 147140671, operacijos nr. RO1881509737L01";"RO1881509737L01";2025-06-01;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; +"147234481";2025-06-01;"EUR";100,00;"ERNEST MATIJEVSKI";"";"LT627300010183670574";"SWEDBANK AB";"HABALT22";"Eldaro bernvakariui, dok. nr. 147234481, operacijos nr. RD251048722";"RD251048722";2025-06-01;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";100,00;"LT807044060008146755";"EUR"; +"";2025-06-02;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 01/06/2025. Sutartis Nr 120420, operacijos nr. RO1882397963L01";"RO1882397963L01";2025-06-02;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; +"21637";2025-06-02;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT517044090105709148";"AB SEB BANKAS";"CBVILT2X";"Protingoms investicijoms, dok. nr. 21637, operacijos nr. RO1882518490L01";"RO1882518490L01";2025-06-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; +"21639";2025-06-02;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"Emergencies, dok. nr. 21639, operacijos nr. RO1882518492L01";"RO1882518492L01";2025-06-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; +"21639";2025-06-02;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"Tam, kas džiugina, dok. nr. 21639, operacijos nr. RO1882518493L01";"RO1882518493L01";2025-06-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; +"";2025-06-03;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 02/06/2025. Sutartis Nr 120420, operacijos nr. RO1883453471L01";"RO1883453471L01";2025-06-03;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; +"";2025-06-04;"EUR";2,00;"SEB bankas";"";"";"";"";"1839445-KASDIENIS paslaugų planas - mėnesio mokestis už gegužės mėn. , operacijos nr. RO1884527808L01";"RO1884527808L01";2025-06-04;"ACMTMDOPFEES-Paslaugų plano KASDIENIS mėnesio mokestis";"";"D";2,00;"LT807044060008146755";"EUR"; +"";2025-06-07;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 06/06/2025. Sutartis Nr 120420, operacijos nr. RO1888594783L01";"RO1888594783L01";2025-06-07;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"CLR10717500";2025-06-07;"EUR";62,58;"UAB Barbora";"";"";"";"";"06/06/2025 10:40 kortelė...961856 UAB Barbora/Vilnius/LTU #642007, dok. nr. CLR10717500, operacijos nr. RO1888720572L01";"RO1888720572L01";2025-06-06;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";62,58;"LT807044060008146755";"EUR"; +"";2025-06-11;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 10/06/2025. Sutartis Nr 120420, operacijos nr. RO1893349345L01";"RO1893349345L01";2025-06-11;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"148857595";2025-06-11;"EUR";20,00;"KLIM EDGAR";"";"LT657044090101048434";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 148857595, operacijos nr. RO1894642890L01";"RO1894642890L01";2025-06-11;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; +"CLR10740581";2025-06-11;"EUR";30,92;"UAB Barbora";"";"";"";"";"10/06/2025 18:43 kortelė...961856 UAB Barbora/Vilnius/LTU #071010, dok. nr. CLR10740581, operacijos nr. RO1893673262L01";"RO1893673262L01";2025-06-10;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";30,92;"LT807044060008146755";"EUR"; +"";2025-06-13;"EUR";27,20;"UAB ""TELE2""";"";"LT647044060001223571";"AB SEB BANKAS";"CBVILT2X";"mano.tele2.lt/LT, st803487, Tele2 saskaita .868647647., unik. mokėj. kodas 803487, operacijos nr. RO1896792472L01";"RO1896792472L01";2025-06-13;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";27,20;"LT807044060008146755";"EUR"; +"155";2025-06-13;"EUR";87,81;"UAB ELEKTRUM LIETUVA";"";"LT847044060007346724";"AB SEB BANKAS";"CBVILT2X";"Sutarties Nr.: 21247465979, dok. nr. 155, operacijos nr. RO1896799410L01";"RO1896799410L01";2025-06-13;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";87,81;"LT807044060008146755";"EUR"; +"";2025-06-14;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 13/06/2025. Sutartis Nr 120420, operacijos nr. RO1897582367L01";"RO1897582367L01";2025-06-14;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"149264703";2025-06-14;"EUR";20,00;"EDGAR KLIM";"";"BE14905422151683";"TRANSFERWISE EUROPE SA/NV";"TRWIBEB1";"Mokėjimas mobiliąja programėle, dok. nr. 149264703, operacijos nr. RMO22636032";"RMO22636032";2025-06-14;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";20,00;"LT807044060008146755";"EUR"; +"149256257";2025-06-14;"EUR";28,50;"Chmylko Edgar";"";"LT137044060008001006";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 149256257, operacijos nr. RO1897959986L01";"RO1897959986L01";2025-06-14;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";28,50;"LT807044060008146755";"EUR"; +"41516";2025-06-15;"EUR";21,00;"TELE2 UAB";"";"LT647044060001223571";"AB SEB BANKAS";"CBVILT2X";"Mokėtojo kodas: 859309211, dok. nr. 41516, operacijos nr. RO1898207373L01";"RO1898207373L01";2025-06-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";21,00;"LT807044060008146755";"EUR"; +"149488785";2025-06-16;"EUR";30,00;"ERNEST MATIJEVSKI";"";"LT627300010183670574";"SWEDBANK AB";"HABALT22";"Eldaro bernvakario alkui, dok. nr. 149488785, operacijos nr. RD252766195";"RD252766195";2025-06-16;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";30,00;"LT807044060008146755";"EUR"; +"";2025-06-16;"EUR";130,09;"Nacionalinis bilietų platintojas UAB";"";"LT107300010126193869";"SWEDBANK AB";"HABALT22";"NIPS15100-199646722407845 458384, unik. mokėj. kodas NIPS15100-43a4c8e93e85f851cb360b28f, operacijos nr. RD252759840";"RD252759840";2025-06-16;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";130,09;"LT807044060008146755";"EUR"; +"";2025-06-17;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 16/06/2025. Sutartis Nr 120420, operacijos nr. RO1900738026L01";"RO1900738026L01";2025-06-17;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; +"149579437";2025-06-17;"EUR";50,00;"OKAS DOMINYKAS";"";"LT047044060008039580";"AB SEB BANKAS";"CBVILT2X";"Algirdo gimse, dok. nr. 149579437, operacijos nr. RO1900840892L01";"RO1900840892L01";2025-06-17;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; +"";2025-06-18;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 17/06/2025. Sutartis Nr 120420, operacijos nr. RO1901953326L01";"RO1901953326L01";2025-06-18;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; +"CLR10783959";2025-06-18;"EUR";50,14;"UAB Barbora";"";"";"";"";"17/06/2025 19:11 kortelė...961856 UAB Barbora/Vilnius/LTU #772650, dok. nr. CLR10783959, operacijos nr. RO1902301169L01";"RO1902301169L01";2025-06-17;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";50,14;"LT807044060008146755";"EUR"; +"149853036";2025-06-18;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 149853036, operacijos nr. RO1903142909L01";"RO1903142909L01";2025-06-18;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; +"";2025-06-19;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 18/06/2025. Sutartis Nr 120420, operacijos nr. RO1903201036L01";"RO1903201036L01";2025-06-19;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"149974737";2025-06-19;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 149974737, operacijos nr. RO1904314631L01";"RO1904314631L01";2025-06-19;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; +"";2025-06-20;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 19/06/2025. Sutartis Nr 120420, operacijos nr. RO1904393365L01";"RO1904393365L01";2025-06-20;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"150402322";2025-06-23;"EUR";5,50;"ALGIRDAS SALIAMONAS";"";"LT574010051001213325";"LUMINOR BANK AS LITHUANIAN BRANCH";"AGBLLT2X";"taxas, dok. nr. 150402322, operacijos nr. RD253466405";"RD253466405";2025-06-23;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";5,50;"LT807044060008146755";"EUR"; +"";2025-06-24;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 23/06/2025. Sutartis Nr 120420, operacijos nr. RO1908556867L01";"RO1908556867L01";2025-06-24;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"CLR10820874";2025-06-24;"EUR";46,88;"UAB Barbora";"";"";"";"";"23/06/2025 13:47 kortelė...961856 UAB Barbora/Vilnius/LTU #353223, dok. nr. CLR10820874, operacijos nr. RO1908784917L01";"RO1908784917L01";2025-06-23;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";46,88;"LT807044060008146755";"EUR"; +"";2025-06-25;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 24/06/2025. Sutartis Nr 120420, operacijos nr. RO1909494558L01";"RO1909494558L01";2025-06-25;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"150676613";2025-06-25;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 150676613, operacijos nr. RO1910478465L01";"RO1910478465L01";2025-06-25;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; +"CLR10850074";2025-06-29;"EUR";38,41;"AMAZON* 5H1756U75";"";"";"";"";"28/06/2025 01:51 kortelė...961856 AMAZON* 5H1756U75/LUXEMBOURG/LUX #475225, dok. nr. CLR10850074, operacijos nr. RO1913977382L01";"RO1913977382L01";2025-06-28;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";38,41;"LT807044060008146755";"EUR"; +"151101327";2025-06-29;"EUR";50,00;"ALGIRDAS SALIAMONAS";"";"LT574010051001213325";"LUMINOR BANK AS LITHUANIAN BRANCH";"AGBLLT2X";"domui, dok. nr. 151101327, operacijos nr. RD254019443";"RD254019443";2025-06-29;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";50,00;"LT807044060008146755";"EUR"; +"151084479";2025-06-29;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 151084479, operacijos nr. RO1914321658L01";"RO1914321658L01";2025-06-29;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; +"";2025-06-30;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 29/06/2025. Sutartis Nr 120420, operacijos nr. RO1914795792L01";"RO1914795792L01";2025-06-30;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx b/src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..509952dfa9945fa2fcc6c7fdc0add079649bd685 GIT binary patch literal 26172 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 literal 0 HcmV?d00001 diff --git a/src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx b/src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..967c16cd43545a41e48d59495623bab9b874fca5 GIT binary patch literal 14338 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 Date: Mon, 28 Jul 2025 13:19:12 +0300 Subject: [PATCH 13/23] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f61e99f..6862593 100755 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,9 @@ Barbora notebooks first scrape tries *personal_finance/fin/analysis +*personal_finance/fin/analysis/Manually polished *personal_finance/income -*personal_finance/OGCSV +*personal_finance/OG-CSV *personal_finance/spending *personal_notes/clickup_extract/old_output *personal_notes/clickup_extract/source data From e88c9c14dc8ca42762e8c34a4226617ab60fb192 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Mon, 28 Jul 2025 13:56:51 +0300 Subject: [PATCH 14/23] Update .gitignore --- .gitignore | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 6862593..fc38adb 100755 --- a/.gitignore +++ b/.gitignore @@ -6,16 +6,16 @@ data Barbora notebooks first scrape tries -*personal_finance/fin/analysis -*personal_finance/fin/analysis/Manually polished -*personal_finance/income -*personal_finance/OG-CSV -*personal_finance/spending -*personal_notes/clickup_extract/old_output -*personal_notes/clickup_extract/source data -*personal_notes/discord_extract/categories -*src/personal_notes/discord_extract/.env.env -*.env -*Data-Engineering +personal_finance/fin/analysis +personal_finance/fin/analysis/Manually polished +personal_finance/income +personal_finance/OG-CSV +personal_finance/spending +personal_notes/clickup_extract/old_output +personal_notes/clickup_extract/source data +personal_notes/discord_extract/categories +src/personal_notes/discord_extract/.env.env +.env +Data-Engineering __pycache__ From 61f06d755df30824b9987876b8b4f860779e84b6 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Mon, 28 Jul 2025 15:06:53 +0300 Subject: [PATCH 15/23] fin scrape.py paths adjusted, everything works --- src/personal_finance/fin/OG-CSV/4+.csv | 12 +++++++ src/personal_finance/fin/OG-CSV/4-.csv | 31 ++++++++++++++++ src/personal_finance/fin/OG-CSV/5+.csv | 13 +++++++ src/personal_finance/fin/OG-CSV/5-.csv | 36 +++++++++++++++++++ .../fin/OG-CSV/{1+.csv => 6+.csv} | 0 .../fin/OG-CSV/{1-.csv => 6-.csv} | 0 src/personal_finance/fin/scrape.py | 8 ++--- 7 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 src/personal_finance/fin/OG-CSV/4+.csv create mode 100644 src/personal_finance/fin/OG-CSV/4-.csv create mode 100644 src/personal_finance/fin/OG-CSV/5+.csv create mode 100644 src/personal_finance/fin/OG-CSV/5-.csv rename src/personal_finance/fin/OG-CSV/{1+.csv => 6+.csv} (100%) rename src/personal_finance/fin/OG-CSV/{1-.csv => 6-.csv} (100%) diff --git a/src/personal_finance/fin/OG-CSV/4+.csv b/src/personal_finance/fin/OG-CSV/4+.csv new file mode 100644 index 0000000..2b78edc --- /dev/null +++ b/src/personal_finance/fin/OG-CSV/4+.csv @@ -0,0 +1,12 @@ +"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-04-01-2025-04-30)"; +"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; +"CLR10309395";2025-04-01;"EUR";3,05;"UAB Barbora";"";"";"";"";"31/03/2025 13:23 kortelė...961856 UAB Barbora/Vilnius/LTU #924417, dok. nr. CLR10309395, operacijos nr. RO1811817725L02";"RO1811817725L02";2025-04-01;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";3,05;"LT807044060008146755";"EUR"; +"138701506";2025-04-01;"EUR";250,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 138701506, operacijos nr. RO1812398126L02";"RO1812398126L02";2025-04-01;"PMNTRCDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"C";250,00;"LT807044060008146755";"EUR"; +"139043768";2025-04-04;"EUR";60,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 139043768, operacijos nr. RO1814748018L02";"RO1814748018L02";2025-04-04;"PMNTRCDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"C";60,00;"LT807044060008146755";"EUR"; +"139290471";2025-04-05;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 139290471, operacijos nr. RO1817246201L02";"RO1817246201L02";2025-04-05;"PMNTRCDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"C";50,00;"LT807044060008146755";"EUR"; +"CLR10352015";2025-04-08;"EUR";2,48;"UAB Barbora";"";"";"";"";"07/04/2025 10:11 kortelė...961856 UAB Barbora/Vilnius/LTU #400627, dok. nr. CLR10352015, operacijos nr. RO1819712436L02";"RO1819712436L02";2025-04-08;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";2,48;"LT807044060008146755";"EUR"; +"";2025-04-11;"EUR";1340,26;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Darbo užmokestis už 03 mėn., unik. mokėj. kodas 12BF40983F5745B5BCB6935CCBC7460C, mok. ident. kodas 122590280, operacijos nr. RO1823921784L02";"RO1823921784L02";2025-04-11;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";1340,26;"LT807044060008146755";"EUR"; +"CLR10401005";2025-04-16;"EUR";5,54;"UAB Barbora";"";"";"";"";"15/04/2025 09:25 kortelė...961856 UAB Barbora/Vilnius/LTU #079446, dok. nr. CLR10401005, operacijos nr. RO1829785833L02";"RO1829785833L02";2025-04-16;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";5,54;"LT807044060008146755";"EUR"; +"CLR10437903";2025-04-22;"EUR";0,34;"UAB Barbora";"";"";"";"";"22/04/2025 08:15 kortelė...961856 UAB Barbora/Vilnius/LTU #148348, dok. nr. CLR10437903, operacijos nr. RO1836519859L02";"RO1836519859L02";2025-04-22;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,34;"LT807044060008146755";"EUR"; +"";2025-04-25;"EUR";100,00;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Avansas už 2025 - 04-mėn., unik. mokėj. kodas 26CA6BBE5E4B4304B6A7C27E166531C2, mok. ident. kodas 122590280, operacijos nr. RO1839985214L02";"RO1839985214L02";2025-04-25;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";100,00;"LT807044060008146755";"EUR"; +"CLR10481267";2025-04-29;"EUR";0,53;"UAB Barbora";"";"";"";"";"29/04/2025 08:16 kortelė...961856 UAB Barbora/Vilnius/LTU #476648, dok. nr. CLR10481267, operacijos nr. RO1844083169L02";"RO1844083169L02";2025-04-29;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,53;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/4-.csv b/src/personal_finance/fin/OG-CSV/4-.csv new file mode 100644 index 0000000..b497d81 --- /dev/null +++ b/src/personal_finance/fin/OG-CSV/4-.csv @@ -0,0 +1,31 @@ +"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-04-01-2025-04-30)"; +"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; +"";2025-04-01;"EUR";300,00;"BRINK'S ATM LITHUANIA";"";"";"";"";"Lėšų nurašymas 01/04/2025 17:01 card...1856 LTSE0177 #692729, operacijos nr. RO1812441372L01";"RO1812441372L01";2025-04-01;"PMNTCCRDCWDL-Grynųjų išmokėjimas Debit MC Mylimiausia";"";"D";300,00;"LT807044060008146755";"EUR"; +"";2025-04-02;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 01/04/2025. Sutartis Nr 120420, operacijos nr. RO1812654155L01";"RO1812654155L01";2025-04-02;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"21637";2025-04-02;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT517044090105709148";"AB SEB BANKAS";"CBVILT2X";"Protingoms investicijoms, dok. nr. 21637, operacijos nr. RO1812759171L01";"RO1812759171L01";2025-04-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; +"21639";2025-04-02;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"Emergencies, dok. nr. 21639, operacijos nr. RO1812759176L01";"RO1812759176L01";2025-04-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; +"21639";2025-04-02;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"Tam, kas džiugina, dok. nr. 21639, operacijos nr. RO1812759179L01";"RO1812759179L01";2025-04-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; +"";2025-04-04;"EUR";2,00;"SEB bankas";"";"";"";"";"1839445-KASDIENIS paslaugų planas - mėnesio mokestis už kovo mėn. , operacijos nr. RO1814898101L01";"RO1814898101L01";2025-04-04;"ACMTMDOPFEES-Paslaugų plano KASDIENIS mėnesio mokestis";"";"D";2,00;"LT807044060008146755";"EUR"; +"CLR10327884";2025-04-04;"EUR";52,74;"UAB Barbora";"";"";"";"";"03/04/2025 23:11 kortelė...961856 UAB Barbora/Vilnius/LTU #400627, dok. nr. CLR10327884, operacijos nr. RO1815674995L01";"RO1815674995L01";2025-04-03;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";52,74;"LT807044060008146755";"EUR"; +"";2025-04-05;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 04/04/2025. Sutartis Nr 120420, operacijos nr. RO1816537094L01";"RO1816537094L01";2025-04-05;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"140141858";2025-04-10;"EUR";15,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 140141858, operacijos nr. RO1823441282L01";"RO1823441282L01";2025-04-10;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";15,00;"LT807044060008146755";"EUR"; +"";2025-04-11;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 10/04/2025. Sutartis Nr 120420, operacijos nr. RO1823539057L01";"RO1823539057L01";2025-04-11;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"CLR10369885";2025-04-11;"EUR";2,99;"UAB Barbora";"";"";"";"";"10/04/2025 21:21 kortelė...961856 UAB Barbora/Vilnius/LTU #080390, dok. nr. CLR10369885, operacijos nr. RO1823901658L01";"RO1823901658L01";2025-04-10;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";2,99;"LT807044060008146755";"EUR"; +"CLR10369885";2025-04-11;"EUR";45,17;"UAB Barbora";"";"";"";"";"10/04/2025 21:05 kortelė...961856 UAB Barbora/Vilnius/LTU #079446, dok. nr. CLR10369885, operacijos nr. RO1823901655L01";"RO1823901655L01";2025-04-10;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";45,17;"LT807044060008146755";"EUR"; +"151";2025-04-11;"EUR";171,46;"UAB ELEKTRUM LIETUVA";"";"LT847044060007346724";"AB SEB BANKAS";"CBVILT2X";"Sutarties Nr.: 21247465979, dok. nr. 151, operacijos nr. RO1824709101L01";"RO1824709101L01";2025-04-11;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";171,46;"LT807044060008146755";"EUR"; +"";2025-04-12;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 11/04/2025. Sutartis Nr 120420, operacijos nr. RO1824861119L01";"RO1824861119L01";2025-04-12;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; +"140342915";2025-04-12;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 140342915, operacijos nr. RO1824981290L01";"RO1824981290L01";2025-04-12;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; +"140533893";2025-04-13;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 140533893, operacijos nr. RO1826777751L01";"RO1826777751L01";2025-04-13;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; +"CLR10382816";2025-04-13;"EUR";161,42;"AMAZON* RH5GE5OJ4";"";"";"";"";"12/04/2025 00:56 kortelė...961856 AMAZON* RH5GE5OJ4/LUXEMBOURG/LUX #136850, dok. nr. CLR10382816, operacijos nr. RO1825882716L01";"RO1825882716L01";2025-04-12;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";161,42;"LT807044060008146755";"EUR"; +"";2025-04-14;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 13/04/2025. Sutartis Nr 120420, operacijos nr. RO1826862702L01";"RO1826862702L01";2025-04-14;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"41516";2025-04-15;"EUR";21,00;"TELE2 UAB";"";"LT647044060001223571";"AB SEB BANKAS";"CBVILT2X";"Mokėtojo kodas: 859309211, dok. nr. 41516, operacijos nr. RO1828398084L01";"RO1828398084L01";2025-04-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";21,00;"LT807044060008146755";"EUR"; +"140999422";2025-04-16;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 140999422, operacijos nr. RO1830738559L01";"RO1830738559L01";2025-04-16;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; +"";2025-04-18;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 17/04/2025. Sutartis Nr 120420, operacijos nr. RO1832132019L01";"RO1832132019L01";2025-04-18;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"141180162";2025-04-18;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 141180162, operacijos nr. RO1832096481L01";"RO1832096481L01";2025-04-18;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; +"141346387";2025-04-19;"EUR";40,00;"OKAITĖ VILMA";"";"LT217044060008154316";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 141346387, operacijos nr. RO1833880786L01";"RO1833880786L01";2025-04-19;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";40,00;"LT807044060008146755";"EUR"; +"";2025-04-20;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 19/04/2025. Sutartis Nr 120420, operacijos nr. RO1834483311L01";"RO1834483311L01";2025-04-20;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"";2025-04-22;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 21/04/2025. Sutartis Nr 120420, operacijos nr. RO1835864001L01";"RO1835864001L01";2025-04-22;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"CLR10436353";2025-04-22;"EUR";44,94;"UAB Barbora";"";"";"";"";"21/04/2025 12:25 kortelė...961856 UAB Barbora/Vilnius/LTU #148348, dok. nr. CLR10436353, operacijos nr. RO1836090879L01";"RO1836090879L01";2025-04-21;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";44,94;"LT807044060008146755";"EUR"; +"";2025-04-25;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 24/04/2025. Sutartis Nr 120420, operacijos nr. RO1839124592L01";"RO1839124592L01";2025-04-25;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"CLR10454056";2025-04-25;"EUR";66,14;"UAB Barbora";"";"";"";"";"24/04/2025 20:17 kortelė...961856 UAB Barbora/Vilnius/LTU #476648, dok. nr. CLR10454056, operacijos nr. RO1839421016L01";"RO1839421016L01";2025-04-24;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";66,14;"LT807044060008146755";"EUR"; +"142330310";2025-04-28;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 142330310, operacijos nr. RO1842391412L01";"RO1842391412L01";2025-04-28;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/5+.csv b/src/personal_finance/fin/OG-CSV/5+.csv new file mode 100644 index 0000000..aea8b9e --- /dev/null +++ b/src/personal_finance/fin/OG-CSV/5+.csv @@ -0,0 +1,13 @@ +"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-05-01-2025-05-31)"; +"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; +"CLR10517180";2025-05-05;"EUR";1,87;"UAB Barbora";"";"";"";"";"05/05/2025 08:31 kortelė...961856 UAB Barbora/Vilnius/LTU #182967, dok. nr. CLR10517180, operacijos nr. RO1850862356L02";"RO1850862356L02";2025-05-05;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";1,87;"LT807044060008146755";"EUR"; +"";2025-05-12;"EUR";1315,20;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Darbo užmokestis už 04 mėn., unik. mokėj. kodas 5A89A726E5B14D29928A44D6F49CEF4B, mok. ident. kodas 122590280, operacijos nr. RO1859046145L02";"RO1859046145L02";2025-05-12;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";1315,20;"LT807044060008146755";"EUR"; +"CLR10566207";2025-05-13;"EUR";0,89;"UAB Barbora";"";"";"";"";"13/05/2025 08:00 kortelė...961856 UAB Barbora/Vilnius/LTU #949656, dok. nr. CLR10566207, operacijos nr. RO1860536182L02";"RO1860536182L02";2025-05-13;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,89;"LT807044060008146755";"EUR"; +"2505153984";2025-05-16;"EUR";0,21;"UAB BARBORA";"302908069";"LT827044060008168932";"AB SEB BANKAS";"CBVILT2X";"Permokos grąžinimas. Užsakymo Nr.:#X631-DI5DX1R16QU4KXB, dok. nr. 2505153984, unik. mokėj. kodas NOTPROVIDED, mok. ident. kodas 302908069, operacijos nr. RO1864639375L02";"RO1864639375L02";2025-05-16;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";0,21;"LT807044060008146755";"EUR"; +"145458935";2025-05-19;"EUR";11,00;"OKAS DOMINYKAS";"";"LT047044060008039580";"AB SEB BANKAS";"CBVILT2X";"bolt, dok. nr. 145458935, operacijos nr. RO1867647335L02";"RO1867647335L02";2025-05-19;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";11,00;"LT807044060008146755";"EUR"; +"CLR10626191";2025-05-23;"EUR";0,07;"UAB Barbora";"";"";"";"";"22/05/2025 10:30 kortelė...961856 UAB Barbora/Vilnius/LTU #983490, dok. nr. CLR10626191, operacijos nr. RO1871834652L02";"RO1871834652L02";2025-05-23;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,07;"LT807044060008146755";"EUR"; +"";2025-05-23;"EUR";100,00;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Avansas už 2025 - 05-mėn., unik. mokėj. kodas 85C61B2200194105AEA23D6D8FF4D5CE, mok. ident. kodas 122590280, operacijos nr. RO1872231400L02";"RO1872231400L02";2025-05-23;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";100,00;"LT807044060008146755";"EUR"; +"CLR10645746";2025-05-26;"EUR";0,72;"UAB Barbora";"";"";"";"";"26/05/2025 08:37 kortelė...961856 UAB Barbora/Vilnius/LTU #464483, dok. nr. CLR10645746, operacijos nr. RO1875340509L02";"RO1875340509L02";2025-05-26;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,72;"LT807044060008146755";"EUR"; +"LDLT25052918DNXK";2025-05-29;"EUR";6,00;"Robertas Kivyta";"";"LT414010042403869261";"LUMINOR BANK AS LITHUANIAN BRANCH";"AGBLLT2X";"Taxas, dok. nr. LDLT25052918DNXK, unik. mokėj. kodas NOTPROVIDED, operacijos nr. RO1878651230L02";"RO1878651230L02";2025-05-29;"PMNTRCDTESCT-Lėšų įskaitymas - Momentinis mokėjimas";"";"C";6,00;"LT807044060008146755";"EUR"; +"6250432045";2025-05-29;"EUR";385,17;"VALST. SOC. DRAUD. FONDO VALD. PRIE SOCIAL. APS. IR DARBO MINISTERIJOS";"191630223";"LT807044060000909294";"AB SEB BANKAS";"CBVILT2X";"P.P.MOK.S6250432045 Data 2025-05-01 SoDros išmoka byla 1337467 (EINARAS KOVALIŪNAS), dok. nr. 6250432045, unik. mokėj. kodas S10162504320452025, gav. ident. kodas 39401210122, mok. ident. kodas 191630223, prad. mok. VSDF prie SADM, galut. gav. EINARAS KOVALIŪNAS, operacijos nr. RO1878214227L02";"RO1878214227L02";2025-05-29;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";385,17;"LT807044060008146755";"EUR"; +"CLR10674169";2025-05-31;"EUR";0,06;"UAB Barbora";"";"";"";"";"30/05/2025 13:33 kortelė...961856 UAB Barbora/Vilnius/LTU #885707, dok. nr. CLR10674169, operacijos nr. RO1880665209L02";"RO1880665209L02";2025-05-31;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,06;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/5-.csv b/src/personal_finance/fin/OG-CSV/5-.csv new file mode 100644 index 0000000..0e995a4 --- /dev/null +++ b/src/personal_finance/fin/OG-CSV/5-.csv @@ -0,0 +1,36 @@ +"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-05-01-2025-05-31)"; +"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; +"142754494";2025-05-01;"EUR";10,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 142754494, operacijos nr. RO1845785169L01";"RO1845785169L01";2025-05-01;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";10,00;"LT807044060008146755";"EUR"; +"61836";2025-05-01;"EUR";530,00;"KOVALIŪNAS EINARAS";"";"LT037044090105763168";"AB SEB BANKAS";"CBVILT2X";"Dideliems norams, dok. nr. 61836, operacijos nr. RO1845964553L01";"RO1845964553L01";2025-05-01;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";530,00;"LT807044060008146755";"EUR"; +"";2025-05-02;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 01/05/2025. Sutartis Nr 120420, operacijos nr. RO1846805466L01";"RO1846805466L01";2025-05-02;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; +"21637";2025-05-02;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT517044090105709148";"AB SEB BANKAS";"CBVILT2X";"Protingoms investicijoms, dok. nr. 21637, operacijos nr. RO1846930544L01";"RO1846930544L01";2025-05-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; +"21639";2025-05-02;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"Emergencies, dok. nr. 21639, operacijos nr. RO1846930549L01";"RO1846930549L01";2025-05-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; +"CLR10497028";2025-05-02;"EUR";44,84;"UAB Barbora";"";"";"";"";"01/05/2025 22:03 kortelė...961856 UAB Barbora/Vilnius/LTU #182967, dok. nr. CLR10497028, operacijos nr. RO1847077516L01";"RO1847077516L01";2025-05-01;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";44,84;"LT807044060008146755";"EUR"; +"21639";2025-05-02;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"Tam, kas džiugina, dok. nr. 21639, operacijos nr. RO1846930556L01";"RO1846930556L01";2025-05-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; +"";2025-05-04;"EUR";2,00;"SEB bankas";"";"";"";"";"1839445-KASDIENIS paslaugų planas - mėnesio mokestis už balandžio mėn. , operacijos nr. RO1848744639L01";"RO1848744639L01";2025-05-04;"ACMTMDOPFEES-Paslaugų plano KASDIENIS mėnesio mokestis";"";"D";2,00;"LT807044060008146755";"EUR"; +"143617594";2025-05-07;"EUR";11,00;"Marcinkevičius Donatas";"";"LT187044060007852797";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 143617594, operacijos nr. RO1853034664L01";"RO1853034664L01";2025-05-07;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";11,00;"LT807044060008146755";"EUR"; +"CLR10535138";2025-05-08;"EUR";5,24;"UAB EUROVAISTINE V0373";"";"";"";"";"07/05/2025 14:10 kortelė...961856 UAB EUROVAISTINE V0373/VILNIUS/LTU #751072, dok. nr. CLR10535138, operacijos nr. RO1854864252L01";"RO1854864252L01";2025-05-07;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";5,24;"LT807044060008146755";"EUR"; +"";2025-05-10;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 09/05/2025. Sutartis Nr 120420, operacijos nr. RO1856415848L01";"RO1856415848L01";2025-05-10;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"CLR10545589";2025-05-10;"EUR";58,83;"UAB Barbora";"";"";"";"";"09/05/2025 12:47 kortelė...961856 UAB Barbora/Vilnius/LTU #949656, dok. nr. CLR10545589, operacijos nr. RO1856716206L01";"RO1856716206L01";2025-05-09;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";58,83;"LT807044060008146755";"EUR"; +"";2025-05-15;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 14/05/2025. Sutartis Nr 120420, operacijos nr. RO1862301395L01";"RO1862301395L01";2025-05-15;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; +"41516";2025-05-15;"EUR";21,00;"TELE2 UAB";"";"LT647044060001223571";"AB SEB BANKAS";"CBVILT2X";"Mokėtojo kodas: 859309211, dok. nr. 41516, operacijos nr. RO1862647571L01";"RO1862647571L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";21,00;"LT807044060008146755";"EUR"; +"";2025-05-15;"EUR";27,90;"UAB PAYSERA LT";"";"LT067044060004556274";"AB SEB BANKAS";"CBVILT2X";"R638256077 (tag)39401210122(tag) (tag)EINARAS KOVALIŪNAS(tag) (tag)(tag) Ref:1441 No:74647519 (https://www.epaslaugos.lt), operacijos nr. RO1862267026L01";"RO1862267026L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";27,90;"LT807044060008146755";"EUR"; +"";2025-05-15;"EUR";43,27;"UAB BARBORA";"";"LT507044060007917017";"AB SEB BANKAS";"CBVILT2X";"Order Payment -DI5DX1R16QU4KXB, unik. mokėj. kodas NIPS16192-4bbb4048c17d3be5d6030f198, operacijos nr. RO1863338307L01";"RO1863338307L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";43,27;"LT807044060008146755";"EUR"; +"152";2025-05-15;"EUR";81,78;"UAB ELEKTRUM LIETUVA";"";"LT847044060007346724";"AB SEB BANKAS";"CBVILT2X";"Sutarties Nr.: 21247465979, dok. nr. 152, operacijos nr. RO1862264724L01";"RO1862264724L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";81,78;"LT807044060008146755";"EUR"; +"153";2025-05-15;"EUR";500,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 153, operacijos nr. RO1862264882L01";"RO1862264882L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";500,00;"LT807044060008146755";"EUR"; +"";2025-05-16;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 15/05/2025. Sutartis Nr 120420, operacijos nr. RO1863862856L01";"RO1863862856L01";2025-05-16;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"145296466";2025-05-17;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 145296466, operacijos nr. RO1865933568L01";"RO1865933568L01";2025-05-17;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; +"";2025-05-18;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 17/05/2025. Sutartis Nr 120420, operacijos nr. RO1865966429L01";"RO1865966429L01";2025-05-18;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"145366323";2025-05-18;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 145366323, operacijos nr. RO1866936419L01";"RO1866936419L01";2025-05-18;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; +"";2025-05-19;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 18/05/2025. Sutartis Nr 120420, operacijos nr. RO1867074177L01";"RO1867074177L01";2025-05-19;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"154";2025-05-19;"EUR";44,00;"Algirdas Saliamonas";"";"LT574010051001213325";"LUMINOR BANK AS LITHUANIAN BRANCH";"AGBLLT2X";"skola, dok. nr. 154, operacijos nr. RD249716323";"RD249716323";2025-05-19;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";44,00;"LT807044060008146755";"EUR"; +"";2025-05-19;"EUR";234,54;"UAB KESKO SENUKAI DIGITAL";"";"LT587300010172678121";"SWEDBANK AB";"HABALT22";"PSD2 - EP01423769, unik. mokėj. kodas 57984952, operacijos nr. RD249726165";"RD249726165";2025-05-19;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";234,54;"LT807044060008146755";"EUR"; +"";2025-05-20;"EUR";0,45;"SEB bankas";"";"";"";"";"SMS banko mokestis 19/05/2025. Sutartis Nr 120420, operacijos nr. RO1868216111L01";"RO1868216111L01";2025-05-20;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,45;"LT807044060008146755";"EUR"; +"CLR10606555";2025-05-20;"EUR";35,27;"UAB Barbora";"";"";"";"";"19/05/2025 22:11 kortelė...961856 UAB Barbora/Vilnius/LTU #983490, dok. nr. CLR10606555, operacijos nr. RO1868534122L01";"RO1868534122L01";2025-05-19;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";35,27;"LT807044060008146755";"EUR"; +"";2025-05-25;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 24/05/2025. Sutartis Nr 120420, operacijos nr. RO1873785811L01";"RO1873785811L01";2025-05-25;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"CLR10637194";2025-05-25;"EUR";1,34;"UAB Barbora";"";"";"";"";"24/05/2025 17:20 kortelė...961856 UAB Barbora/Vilnius/LTU #465994, dok. nr. CLR10637194, operacijos nr. RO1873979355L01";"RO1873979355L01";2025-05-24;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";1,34;"LT807044060008146755";"EUR"; +"CLR10637194";2025-05-25;"EUR";55,30;"UAB Barbora";"";"";"";"";"24/05/2025 16:54 kortelė...961856 UAB Barbora/Vilnius/LTU #464483, dok. nr. CLR10637194, operacijos nr. RO1873979350L01";"RO1873979350L01";2025-05-24;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";55,30;"LT807044060008146755";"EUR"; +"";2025-05-29;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 28/05/2025. Sutartis Nr 120420, operacijos nr. RO1878119005L01";"RO1878119005L01";2025-05-29;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; +"CLR10661723";2025-05-29;"EUR";41,39;"UAB Barbora";"";"";"";"";"28/05/2025 22:48 kortelė...961856 UAB Barbora/Vilnius/LTU #885707, dok. nr. CLR10661723, operacijos nr. RO1878383054L01";"RO1878383054L01";2025-05-28;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";41,39;"LT807044060008146755";"EUR"; +"146908242";2025-05-30;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 146908242, operacijos nr. RO1879741544L01";"RO1879741544L01";2025-05-30;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; +"";2025-05-31;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 30/05/2025. Sutartis Nr 120420, operacijos nr. RO1880463089L01";"RO1880463089L01";2025-05-31;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/1+.csv b/src/personal_finance/fin/OG-CSV/6+.csv similarity index 100% rename from src/personal_finance/fin/OG-CSV/1+.csv rename to src/personal_finance/fin/OG-CSV/6+.csv diff --git a/src/personal_finance/fin/OG-CSV/1-.csv b/src/personal_finance/fin/OG-CSV/6-.csv similarity index 100% rename from src/personal_finance/fin/OG-CSV/1-.csv rename to src/personal_finance/fin/OG-CSV/6-.csv diff --git a/src/personal_finance/fin/scrape.py b/src/personal_finance/fin/scrape.py index 391768b..0e02b0b 100755 --- a/src/personal_finance/fin/scrape.py +++ b/src/personal_finance/fin/scrape.py @@ -155,8 +155,8 @@ def extract_first_4_words(text): #Merge spending seperate xlsx files to one -directory = '/home/eikov/scraping_env/Data-Engineering/src/personal_finance/fin/spendings' -output_directory = '/home/eikov/scraping_env/Data-Engineering/src/personal_finance/fin/analysis' +directory = '/home/eikov/fin/spendings' +output_directory = '/home/eikov/fin/analysis' # List all files in the directory files = os.listdir(directory) @@ -193,8 +193,8 @@ def extract_first_4_words(text): #Merge income seperate xlsx files to one -directory = '/home/eikov/scraping_env/Data-Engineering/src/personal_finance/fin/income' -output_directory = '/home/eikov/scraping_env/Data-Engineering/src/personal_finance/fin/analysis' +directory = '/home/eikov/fin/income' +output_directory = '/home/eikov/fin/analysis' # List all files in the directory files = os.listdir(directory) From 32f44403e84a924652a3a8e697aaa6a611691460 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Mon, 28 Jul 2025 15:07:42 +0300 Subject: [PATCH 16/23] fin scrape.py paths adjusted, everything works --- src/personal_finance/fin/OG-CSV/4+.csv | 12 -------- src/personal_finance/fin/OG-CSV/4-.csv | 31 ------------------- src/personal_finance/fin/OG-CSV/5+.csv | 13 -------- src/personal_finance/fin/OG-CSV/5-.csv | 36 ---------------------- src/personal_finance/fin/OG-CSV/6+.csv | 9 ------ src/personal_finance/fin/OG-CSV/6-.csv | 41 -------------------------- 6 files changed, 142 deletions(-) delete mode 100644 src/personal_finance/fin/OG-CSV/4+.csv delete mode 100644 src/personal_finance/fin/OG-CSV/4-.csv delete mode 100644 src/personal_finance/fin/OG-CSV/5+.csv delete mode 100644 src/personal_finance/fin/OG-CSV/5-.csv delete mode 100644 src/personal_finance/fin/OG-CSV/6+.csv delete mode 100644 src/personal_finance/fin/OG-CSV/6-.csv diff --git a/src/personal_finance/fin/OG-CSV/4+.csv b/src/personal_finance/fin/OG-CSV/4+.csv deleted file mode 100644 index 2b78edc..0000000 --- a/src/personal_finance/fin/OG-CSV/4+.csv +++ /dev/null @@ -1,12 +0,0 @@ -"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-04-01-2025-04-30)"; -"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; -"CLR10309395";2025-04-01;"EUR";3,05;"UAB Barbora";"";"";"";"";"31/03/2025 13:23 kortelė...961856 UAB Barbora/Vilnius/LTU #924417, dok. nr. CLR10309395, operacijos nr. RO1811817725L02";"RO1811817725L02";2025-04-01;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";3,05;"LT807044060008146755";"EUR"; -"138701506";2025-04-01;"EUR";250,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 138701506, operacijos nr. RO1812398126L02";"RO1812398126L02";2025-04-01;"PMNTRCDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"C";250,00;"LT807044060008146755";"EUR"; -"139043768";2025-04-04;"EUR";60,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 139043768, operacijos nr. RO1814748018L02";"RO1814748018L02";2025-04-04;"PMNTRCDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"C";60,00;"LT807044060008146755";"EUR"; -"139290471";2025-04-05;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 139290471, operacijos nr. RO1817246201L02";"RO1817246201L02";2025-04-05;"PMNTRCDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"C";50,00;"LT807044060008146755";"EUR"; -"CLR10352015";2025-04-08;"EUR";2,48;"UAB Barbora";"";"";"";"";"07/04/2025 10:11 kortelė...961856 UAB Barbora/Vilnius/LTU #400627, dok. nr. CLR10352015, operacijos nr. RO1819712436L02";"RO1819712436L02";2025-04-08;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";2,48;"LT807044060008146755";"EUR"; -"";2025-04-11;"EUR";1340,26;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Darbo užmokestis už 03 mėn., unik. mokėj. kodas 12BF40983F5745B5BCB6935CCBC7460C, mok. ident. kodas 122590280, operacijos nr. RO1823921784L02";"RO1823921784L02";2025-04-11;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";1340,26;"LT807044060008146755";"EUR"; -"CLR10401005";2025-04-16;"EUR";5,54;"UAB Barbora";"";"";"";"";"15/04/2025 09:25 kortelė...961856 UAB Barbora/Vilnius/LTU #079446, dok. nr. CLR10401005, operacijos nr. RO1829785833L02";"RO1829785833L02";2025-04-16;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";5,54;"LT807044060008146755";"EUR"; -"CLR10437903";2025-04-22;"EUR";0,34;"UAB Barbora";"";"";"";"";"22/04/2025 08:15 kortelė...961856 UAB Barbora/Vilnius/LTU #148348, dok. nr. CLR10437903, operacijos nr. RO1836519859L02";"RO1836519859L02";2025-04-22;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,34;"LT807044060008146755";"EUR"; -"";2025-04-25;"EUR";100,00;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Avansas už 2025 - 04-mėn., unik. mokėj. kodas 26CA6BBE5E4B4304B6A7C27E166531C2, mok. ident. kodas 122590280, operacijos nr. RO1839985214L02";"RO1839985214L02";2025-04-25;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";100,00;"LT807044060008146755";"EUR"; -"CLR10481267";2025-04-29;"EUR";0,53;"UAB Barbora";"";"";"";"";"29/04/2025 08:16 kortelė...961856 UAB Barbora/Vilnius/LTU #476648, dok. nr. CLR10481267, operacijos nr. RO1844083169L02";"RO1844083169L02";2025-04-29;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,53;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/4-.csv b/src/personal_finance/fin/OG-CSV/4-.csv deleted file mode 100644 index b497d81..0000000 --- a/src/personal_finance/fin/OG-CSV/4-.csv +++ /dev/null @@ -1,31 +0,0 @@ -"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-04-01-2025-04-30)"; -"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; -"";2025-04-01;"EUR";300,00;"BRINK'S ATM LITHUANIA";"";"";"";"";"Lėšų nurašymas 01/04/2025 17:01 card...1856 LTSE0177 #692729, operacijos nr. RO1812441372L01";"RO1812441372L01";2025-04-01;"PMNTCCRDCWDL-Grynųjų išmokėjimas Debit MC Mylimiausia";"";"D";300,00;"LT807044060008146755";"EUR"; -"";2025-04-02;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 01/04/2025. Sutartis Nr 120420, operacijos nr. RO1812654155L01";"RO1812654155L01";2025-04-02;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"21637";2025-04-02;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT517044090105709148";"AB SEB BANKAS";"CBVILT2X";"Protingoms investicijoms, dok. nr. 21637, operacijos nr. RO1812759171L01";"RO1812759171L01";2025-04-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; -"21639";2025-04-02;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"Emergencies, dok. nr. 21639, operacijos nr. RO1812759176L01";"RO1812759176L01";2025-04-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; -"21639";2025-04-02;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"Tam, kas džiugina, dok. nr. 21639, operacijos nr. RO1812759179L01";"RO1812759179L01";2025-04-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; -"";2025-04-04;"EUR";2,00;"SEB bankas";"";"";"";"";"1839445-KASDIENIS paslaugų planas - mėnesio mokestis už kovo mėn. , operacijos nr. RO1814898101L01";"RO1814898101L01";2025-04-04;"ACMTMDOPFEES-Paslaugų plano KASDIENIS mėnesio mokestis";"";"D";2,00;"LT807044060008146755";"EUR"; -"CLR10327884";2025-04-04;"EUR";52,74;"UAB Barbora";"";"";"";"";"03/04/2025 23:11 kortelė...961856 UAB Barbora/Vilnius/LTU #400627, dok. nr. CLR10327884, operacijos nr. RO1815674995L01";"RO1815674995L01";2025-04-03;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";52,74;"LT807044060008146755";"EUR"; -"";2025-04-05;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 04/04/2025. Sutartis Nr 120420, operacijos nr. RO1816537094L01";"RO1816537094L01";2025-04-05;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"140141858";2025-04-10;"EUR";15,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 140141858, operacijos nr. RO1823441282L01";"RO1823441282L01";2025-04-10;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";15,00;"LT807044060008146755";"EUR"; -"";2025-04-11;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 10/04/2025. Sutartis Nr 120420, operacijos nr. RO1823539057L01";"RO1823539057L01";2025-04-11;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"CLR10369885";2025-04-11;"EUR";2,99;"UAB Barbora";"";"";"";"";"10/04/2025 21:21 kortelė...961856 UAB Barbora/Vilnius/LTU #080390, dok. nr. CLR10369885, operacijos nr. RO1823901658L01";"RO1823901658L01";2025-04-10;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";2,99;"LT807044060008146755";"EUR"; -"CLR10369885";2025-04-11;"EUR";45,17;"UAB Barbora";"";"";"";"";"10/04/2025 21:05 kortelė...961856 UAB Barbora/Vilnius/LTU #079446, dok. nr. CLR10369885, operacijos nr. RO1823901655L01";"RO1823901655L01";2025-04-10;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";45,17;"LT807044060008146755";"EUR"; -"151";2025-04-11;"EUR";171,46;"UAB ELEKTRUM LIETUVA";"";"LT847044060007346724";"AB SEB BANKAS";"CBVILT2X";"Sutarties Nr.: 21247465979, dok. nr. 151, operacijos nr. RO1824709101L01";"RO1824709101L01";2025-04-11;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";171,46;"LT807044060008146755";"EUR"; -"";2025-04-12;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 11/04/2025. Sutartis Nr 120420, operacijos nr. RO1824861119L01";"RO1824861119L01";2025-04-12;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; -"140342915";2025-04-12;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 140342915, operacijos nr. RO1824981290L01";"RO1824981290L01";2025-04-12;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; -"140533893";2025-04-13;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 140533893, operacijos nr. RO1826777751L01";"RO1826777751L01";2025-04-13;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; -"CLR10382816";2025-04-13;"EUR";161,42;"AMAZON* RH5GE5OJ4";"";"";"";"";"12/04/2025 00:56 kortelė...961856 AMAZON* RH5GE5OJ4/LUXEMBOURG/LUX #136850, dok. nr. CLR10382816, operacijos nr. RO1825882716L01";"RO1825882716L01";2025-04-12;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";161,42;"LT807044060008146755";"EUR"; -"";2025-04-14;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 13/04/2025. Sutartis Nr 120420, operacijos nr. RO1826862702L01";"RO1826862702L01";2025-04-14;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"41516";2025-04-15;"EUR";21,00;"TELE2 UAB";"";"LT647044060001223571";"AB SEB BANKAS";"CBVILT2X";"Mokėtojo kodas: 859309211, dok. nr. 41516, operacijos nr. RO1828398084L01";"RO1828398084L01";2025-04-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";21,00;"LT807044060008146755";"EUR"; -"140999422";2025-04-16;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 140999422, operacijos nr. RO1830738559L01";"RO1830738559L01";2025-04-16;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; -"";2025-04-18;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 17/04/2025. Sutartis Nr 120420, operacijos nr. RO1832132019L01";"RO1832132019L01";2025-04-18;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"141180162";2025-04-18;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 141180162, operacijos nr. RO1832096481L01";"RO1832096481L01";2025-04-18;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; -"141346387";2025-04-19;"EUR";40,00;"OKAITĖ VILMA";"";"LT217044060008154316";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 141346387, operacijos nr. RO1833880786L01";"RO1833880786L01";2025-04-19;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";40,00;"LT807044060008146755";"EUR"; -"";2025-04-20;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 19/04/2025. Sutartis Nr 120420, operacijos nr. RO1834483311L01";"RO1834483311L01";2025-04-20;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"";2025-04-22;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 21/04/2025. Sutartis Nr 120420, operacijos nr. RO1835864001L01";"RO1835864001L01";2025-04-22;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"CLR10436353";2025-04-22;"EUR";44,94;"UAB Barbora";"";"";"";"";"21/04/2025 12:25 kortelė...961856 UAB Barbora/Vilnius/LTU #148348, dok. nr. CLR10436353, operacijos nr. RO1836090879L01";"RO1836090879L01";2025-04-21;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";44,94;"LT807044060008146755";"EUR"; -"";2025-04-25;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 24/04/2025. Sutartis Nr 120420, operacijos nr. RO1839124592L01";"RO1839124592L01";2025-04-25;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"CLR10454056";2025-04-25;"EUR";66,14;"UAB Barbora";"";"";"";"";"24/04/2025 20:17 kortelė...961856 UAB Barbora/Vilnius/LTU #476648, dok. nr. CLR10454056, operacijos nr. RO1839421016L01";"RO1839421016L01";2025-04-24;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";66,14;"LT807044060008146755";"EUR"; -"142330310";2025-04-28;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 142330310, operacijos nr. RO1842391412L01";"RO1842391412L01";2025-04-28;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/5+.csv b/src/personal_finance/fin/OG-CSV/5+.csv deleted file mode 100644 index aea8b9e..0000000 --- a/src/personal_finance/fin/OG-CSV/5+.csv +++ /dev/null @@ -1,13 +0,0 @@ -"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-05-01-2025-05-31)"; -"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; -"CLR10517180";2025-05-05;"EUR";1,87;"UAB Barbora";"";"";"";"";"05/05/2025 08:31 kortelė...961856 UAB Barbora/Vilnius/LTU #182967, dok. nr. CLR10517180, operacijos nr. RO1850862356L02";"RO1850862356L02";2025-05-05;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";1,87;"LT807044060008146755";"EUR"; -"";2025-05-12;"EUR";1315,20;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Darbo užmokestis už 04 mėn., unik. mokėj. kodas 5A89A726E5B14D29928A44D6F49CEF4B, mok. ident. kodas 122590280, operacijos nr. RO1859046145L02";"RO1859046145L02";2025-05-12;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";1315,20;"LT807044060008146755";"EUR"; -"CLR10566207";2025-05-13;"EUR";0,89;"UAB Barbora";"";"";"";"";"13/05/2025 08:00 kortelė...961856 UAB Barbora/Vilnius/LTU #949656, dok. nr. CLR10566207, operacijos nr. RO1860536182L02";"RO1860536182L02";2025-05-13;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,89;"LT807044060008146755";"EUR"; -"2505153984";2025-05-16;"EUR";0,21;"UAB BARBORA";"302908069";"LT827044060008168932";"AB SEB BANKAS";"CBVILT2X";"Permokos grąžinimas. Užsakymo Nr.:#X631-DI5DX1R16QU4KXB, dok. nr. 2505153984, unik. mokėj. kodas NOTPROVIDED, mok. ident. kodas 302908069, operacijos nr. RO1864639375L02";"RO1864639375L02";2025-05-16;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";0,21;"LT807044060008146755";"EUR"; -"145458935";2025-05-19;"EUR";11,00;"OKAS DOMINYKAS";"";"LT047044060008039580";"AB SEB BANKAS";"CBVILT2X";"bolt, dok. nr. 145458935, operacijos nr. RO1867647335L02";"RO1867647335L02";2025-05-19;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";11,00;"LT807044060008146755";"EUR"; -"CLR10626191";2025-05-23;"EUR";0,07;"UAB Barbora";"";"";"";"";"22/05/2025 10:30 kortelė...961856 UAB Barbora/Vilnius/LTU #983490, dok. nr. CLR10626191, operacijos nr. RO1871834652L02";"RO1871834652L02";2025-05-23;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,07;"LT807044060008146755";"EUR"; -"";2025-05-23;"EUR";100,00;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Avansas už 2025 - 05-mėn., unik. mokėj. kodas 85C61B2200194105AEA23D6D8FF4D5CE, mok. ident. kodas 122590280, operacijos nr. RO1872231400L02";"RO1872231400L02";2025-05-23;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";100,00;"LT807044060008146755";"EUR"; -"CLR10645746";2025-05-26;"EUR";0,72;"UAB Barbora";"";"";"";"";"26/05/2025 08:37 kortelė...961856 UAB Barbora/Vilnius/LTU #464483, dok. nr. CLR10645746, operacijos nr. RO1875340509L02";"RO1875340509L02";2025-05-26;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,72;"LT807044060008146755";"EUR"; -"LDLT25052918DNXK";2025-05-29;"EUR";6,00;"Robertas Kivyta";"";"LT414010042403869261";"LUMINOR BANK AS LITHUANIAN BRANCH";"AGBLLT2X";"Taxas, dok. nr. LDLT25052918DNXK, unik. mokėj. kodas NOTPROVIDED, operacijos nr. RO1878651230L02";"RO1878651230L02";2025-05-29;"PMNTRCDTESCT-Lėšų įskaitymas - Momentinis mokėjimas";"";"C";6,00;"LT807044060008146755";"EUR"; -"6250432045";2025-05-29;"EUR";385,17;"VALST. SOC. DRAUD. FONDO VALD. PRIE SOCIAL. APS. IR DARBO MINISTERIJOS";"191630223";"LT807044060000909294";"AB SEB BANKAS";"CBVILT2X";"P.P.MOK.S6250432045 Data 2025-05-01 SoDros išmoka byla 1337467 (EINARAS KOVALIŪNAS), dok. nr. 6250432045, unik. mokėj. kodas S10162504320452025, gav. ident. kodas 39401210122, mok. ident. kodas 191630223, prad. mok. VSDF prie SADM, galut. gav. EINARAS KOVALIŪNAS, operacijos nr. RO1878214227L02";"RO1878214227L02";2025-05-29;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";385,17;"LT807044060008146755";"EUR"; -"CLR10674169";2025-05-31;"EUR";0,06;"UAB Barbora";"";"";"";"";"30/05/2025 13:33 kortelė...961856 UAB Barbora/Vilnius/LTU #885707, dok. nr. CLR10674169, operacijos nr. RO1880665209L02";"RO1880665209L02";2025-05-31;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,06;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/5-.csv b/src/personal_finance/fin/OG-CSV/5-.csv deleted file mode 100644 index 0e995a4..0000000 --- a/src/personal_finance/fin/OG-CSV/5-.csv +++ /dev/null @@ -1,36 +0,0 @@ -"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-05-01-2025-05-31)"; -"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; -"142754494";2025-05-01;"EUR";10,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 142754494, operacijos nr. RO1845785169L01";"RO1845785169L01";2025-05-01;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";10,00;"LT807044060008146755";"EUR"; -"61836";2025-05-01;"EUR";530,00;"KOVALIŪNAS EINARAS";"";"LT037044090105763168";"AB SEB BANKAS";"CBVILT2X";"Dideliems norams, dok. nr. 61836, operacijos nr. RO1845964553L01";"RO1845964553L01";2025-05-01;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";530,00;"LT807044060008146755";"EUR"; -"";2025-05-02;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 01/05/2025. Sutartis Nr 120420, operacijos nr. RO1846805466L01";"RO1846805466L01";2025-05-02;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; -"21637";2025-05-02;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT517044090105709148";"AB SEB BANKAS";"CBVILT2X";"Protingoms investicijoms, dok. nr. 21637, operacijos nr. RO1846930544L01";"RO1846930544L01";2025-05-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; -"21639";2025-05-02;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"Emergencies, dok. nr. 21639, operacijos nr. RO1846930549L01";"RO1846930549L01";2025-05-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; -"CLR10497028";2025-05-02;"EUR";44,84;"UAB Barbora";"";"";"";"";"01/05/2025 22:03 kortelė...961856 UAB Barbora/Vilnius/LTU #182967, dok. nr. CLR10497028, operacijos nr. RO1847077516L01";"RO1847077516L01";2025-05-01;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";44,84;"LT807044060008146755";"EUR"; -"21639";2025-05-02;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"Tam, kas džiugina, dok. nr. 21639, operacijos nr. RO1846930556L01";"RO1846930556L01";2025-05-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; -"";2025-05-04;"EUR";2,00;"SEB bankas";"";"";"";"";"1839445-KASDIENIS paslaugų planas - mėnesio mokestis už balandžio mėn. , operacijos nr. RO1848744639L01";"RO1848744639L01";2025-05-04;"ACMTMDOPFEES-Paslaugų plano KASDIENIS mėnesio mokestis";"";"D";2,00;"LT807044060008146755";"EUR"; -"143617594";2025-05-07;"EUR";11,00;"Marcinkevičius Donatas";"";"LT187044060007852797";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 143617594, operacijos nr. RO1853034664L01";"RO1853034664L01";2025-05-07;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";11,00;"LT807044060008146755";"EUR"; -"CLR10535138";2025-05-08;"EUR";5,24;"UAB EUROVAISTINE V0373";"";"";"";"";"07/05/2025 14:10 kortelė...961856 UAB EUROVAISTINE V0373/VILNIUS/LTU #751072, dok. nr. CLR10535138, operacijos nr. RO1854864252L01";"RO1854864252L01";2025-05-07;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";5,24;"LT807044060008146755";"EUR"; -"";2025-05-10;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 09/05/2025. Sutartis Nr 120420, operacijos nr. RO1856415848L01";"RO1856415848L01";2025-05-10;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"CLR10545589";2025-05-10;"EUR";58,83;"UAB Barbora";"";"";"";"";"09/05/2025 12:47 kortelė...961856 UAB Barbora/Vilnius/LTU #949656, dok. nr. CLR10545589, operacijos nr. RO1856716206L01";"RO1856716206L01";2025-05-09;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";58,83;"LT807044060008146755";"EUR"; -"";2025-05-15;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 14/05/2025. Sutartis Nr 120420, operacijos nr. RO1862301395L01";"RO1862301395L01";2025-05-15;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; -"41516";2025-05-15;"EUR";21,00;"TELE2 UAB";"";"LT647044060001223571";"AB SEB BANKAS";"CBVILT2X";"Mokėtojo kodas: 859309211, dok. nr. 41516, operacijos nr. RO1862647571L01";"RO1862647571L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";21,00;"LT807044060008146755";"EUR"; -"";2025-05-15;"EUR";27,90;"UAB PAYSERA LT";"";"LT067044060004556274";"AB SEB BANKAS";"CBVILT2X";"R638256077 (tag)39401210122(tag) (tag)EINARAS KOVALIŪNAS(tag) (tag)(tag) Ref:1441 No:74647519 (https://www.epaslaugos.lt), operacijos nr. RO1862267026L01";"RO1862267026L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";27,90;"LT807044060008146755";"EUR"; -"";2025-05-15;"EUR";43,27;"UAB BARBORA";"";"LT507044060007917017";"AB SEB BANKAS";"CBVILT2X";"Order Payment -DI5DX1R16QU4KXB, unik. mokėj. kodas NIPS16192-4bbb4048c17d3be5d6030f198, operacijos nr. RO1863338307L01";"RO1863338307L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";43,27;"LT807044060008146755";"EUR"; -"152";2025-05-15;"EUR";81,78;"UAB ELEKTRUM LIETUVA";"";"LT847044060007346724";"AB SEB BANKAS";"CBVILT2X";"Sutarties Nr.: 21247465979, dok. nr. 152, operacijos nr. RO1862264724L01";"RO1862264724L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";81,78;"LT807044060008146755";"EUR"; -"153";2025-05-15;"EUR";500,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 153, operacijos nr. RO1862264882L01";"RO1862264882L01";2025-05-15;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";500,00;"LT807044060008146755";"EUR"; -"";2025-05-16;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 15/05/2025. Sutartis Nr 120420, operacijos nr. RO1863862856L01";"RO1863862856L01";2025-05-16;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"145296466";2025-05-17;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 145296466, operacijos nr. RO1865933568L01";"RO1865933568L01";2025-05-17;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; -"";2025-05-18;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 17/05/2025. Sutartis Nr 120420, operacijos nr. RO1865966429L01";"RO1865966429L01";2025-05-18;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"145366323";2025-05-18;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 145366323, operacijos nr. RO1866936419L01";"RO1866936419L01";2025-05-18;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; -"";2025-05-19;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 18/05/2025. Sutartis Nr 120420, operacijos nr. RO1867074177L01";"RO1867074177L01";2025-05-19;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"154";2025-05-19;"EUR";44,00;"Algirdas Saliamonas";"";"LT574010051001213325";"LUMINOR BANK AS LITHUANIAN BRANCH";"AGBLLT2X";"skola, dok. nr. 154, operacijos nr. RD249716323";"RD249716323";2025-05-19;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";44,00;"LT807044060008146755";"EUR"; -"";2025-05-19;"EUR";234,54;"UAB KESKO SENUKAI DIGITAL";"";"LT587300010172678121";"SWEDBANK AB";"HABALT22";"PSD2 - EP01423769, unik. mokėj. kodas 57984952, operacijos nr. RD249726165";"RD249726165";2025-05-19;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";234,54;"LT807044060008146755";"EUR"; -"";2025-05-20;"EUR";0,45;"SEB bankas";"";"";"";"";"SMS banko mokestis 19/05/2025. Sutartis Nr 120420, operacijos nr. RO1868216111L01";"RO1868216111L01";2025-05-20;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,45;"LT807044060008146755";"EUR"; -"CLR10606555";2025-05-20;"EUR";35,27;"UAB Barbora";"";"";"";"";"19/05/2025 22:11 kortelė...961856 UAB Barbora/Vilnius/LTU #983490, dok. nr. CLR10606555, operacijos nr. RO1868534122L01";"RO1868534122L01";2025-05-19;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";35,27;"LT807044060008146755";"EUR"; -"";2025-05-25;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 24/05/2025. Sutartis Nr 120420, operacijos nr. RO1873785811L01";"RO1873785811L01";2025-05-25;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"CLR10637194";2025-05-25;"EUR";1,34;"UAB Barbora";"";"";"";"";"24/05/2025 17:20 kortelė...961856 UAB Barbora/Vilnius/LTU #465994, dok. nr. CLR10637194, operacijos nr. RO1873979355L01";"RO1873979355L01";2025-05-24;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";1,34;"LT807044060008146755";"EUR"; -"CLR10637194";2025-05-25;"EUR";55,30;"UAB Barbora";"";"";"";"";"24/05/2025 16:54 kortelė...961856 UAB Barbora/Vilnius/LTU #464483, dok. nr. CLR10637194, operacijos nr. RO1873979350L01";"RO1873979350L01";2025-05-24;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";55,30;"LT807044060008146755";"EUR"; -"";2025-05-29;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 28/05/2025. Sutartis Nr 120420, operacijos nr. RO1878119005L01";"RO1878119005L01";2025-05-29;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"CLR10661723";2025-05-29;"EUR";41,39;"UAB Barbora";"";"";"";"";"28/05/2025 22:48 kortelė...961856 UAB Barbora/Vilnius/LTU #885707, dok. nr. CLR10661723, operacijos nr. RO1878383054L01";"RO1878383054L01";2025-05-28;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";41,39;"LT807044060008146755";"EUR"; -"146908242";2025-05-30;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 146908242, operacijos nr. RO1879741544L01";"RO1879741544L01";2025-05-30;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; -"";2025-05-31;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 30/05/2025. Sutartis Nr 120420, operacijos nr. RO1880463089L01";"RO1880463089L01";2025-05-31;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/6+.csv b/src/personal_finance/fin/OG-CSV/6+.csv deleted file mode 100644 index 4aab18d..0000000 --- a/src/personal_finance/fin/OG-CSV/6+.csv +++ /dev/null @@ -1,9 +0,0 @@ -"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-06-01-2025-06-30)"; -"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; -"CLR10735426";2025-06-10;"EUR";2,35;"UAB Barbora";"";"";"";"";"09/06/2025 09:52 kortelė...961856 UAB Barbora/Vilnius/LTU #642007, dok. nr. CLR10735426, operacijos nr. RO1892038992L02";"RO1892038992L02";2025-06-10;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";2,35;"LT807044060008146755";"EUR"; -"104";2025-06-12;"EUR";20,00;"KLIM EDGAR";"";"LT657044090101048434";"AB SEB BANKAS";"CBVILT2X";"Dekui, dok. nr. 104, operacijos nr. RO1895393402L02";"RO1895393402L02";2025-06-12;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";20,00;"LT807044060008146755";"EUR"; -"";2025-06-12;"EUR";951,81;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Darbo užmokestis už 05 mėn., unik. mokėj. kodas 8B7C3D00A69A47529C30DCBBDF258E55, mok. ident. kodas 122590280, operacijos nr. RO1895057909L02";"RO1895057909L02";2025-06-12;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";951,81;"LT807044060008146755";"EUR"; -"CLR10801349";2025-06-21;"EUR";0,57;"UAB Barbora";"";"";"";"";"20/06/2025 13:40 kortelė...961856 UAB Barbora/Vilnius/LTU #772650, dok. nr. CLR10801349, operacijos nr. RO1905946572L02";"RO1905946572L02";2025-06-21;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,57;"LT807044060008146755";"EUR"; -"CLR10829531";2025-06-25;"EUR";0,13;"UAB Barbora";"";"";"";"";"25/06/2025 07:48 kortelė...961856 UAB Barbora/Vilnius/LTU #353223, dok. nr. CLR10829531, operacijos nr. RO1910099306L02";"RO1910099306L02";2025-06-25;"PMNTMCOPADJT-Gautinos sumos į sąskaitą Debit MC Mylimiausia";"";"C";0,13;"LT807044060008146755";"EUR"; -"";2025-06-26;"EUR";100,00;"UAB PAKMARKAS";"122590280";"LT577300010000039382";"SWEDBANK AB";"HABALT22";"Avansas už 2025 - 06-mėn., unik. mokėj. kodas FA9AED68797D4B4FBBA0502715680D90, mok. ident. kodas 122590280, operacijos nr. RO1911238409L02";"RO1911238409L02";2025-06-26;"PMNTRCDTESCT-Lėšų įskaitymas - Europinis pinigų pervedimas";"";"C";100,00;"LT807044060008146755";"EUR"; -"151125372";2025-06-29;"EUR";32,50;"OKAITĖ VILMA";"";"LT217044060008154316";"AB SEB BANKAS";"CBVILT2X";"gimtadienis, dok. nr. 151125372, operacijos nr. RO1914686186L02";"RO1914686186L02";2025-06-29;"PMNTRCDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"C";32,50;"LT807044060008146755";"EUR"; diff --git a/src/personal_finance/fin/OG-CSV/6-.csv b/src/personal_finance/fin/OG-CSV/6-.csv deleted file mode 100644 index 005234b..0000000 --- a/src/personal_finance/fin/OG-CSV/6-.csv +++ /dev/null @@ -1,41 +0,0 @@ -"SĄSKAITOS (LT807044060008146755) IŠRAŠAS (UŽ LAIKOTARPĮ: 2025-06-01-2025-06-30)"; -"DOK NR.";"DATA";"VALIUTA";"SUMA";"MOKĖTOJO ARBA GAVĖJO PAVADINIMAS";"MOKĖTOJO ARBA GAVĖJO IDENTIFIKACINIS KODAS";"SĄSKAITA";"KREDITO ĮSTAIGOS PAVADINIMAS";"KREDITO ĮSTAIGOS SWIFT KODAS";"MOKĖJIMO PASKIRTIS";"TRANSAKCIJOS KODAS";"DOKUMENTO DATA";"TRANSAKCIJOS TIPAS";"NUORODA";"DEBETAS/KREDITAS";"SUMA SĄSKAITOS VALIUTA";"SĄSKAITOS NR";"SĄSKAITOS VALIUTA"; -"147202086";2025-06-01;"EUR";20,00;"OKAITĖ VILMA";"";"LT217044060008154316";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 147202086, operacijos nr. RO1882152382L01";"RO1882152382L01";2025-06-01;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; -"147140671";2025-06-01;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 147140671, operacijos nr. RO1881509737L01";"RO1881509737L01";2025-06-01;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; -"147234481";2025-06-01;"EUR";100,00;"ERNEST MATIJEVSKI";"";"LT627300010183670574";"SWEDBANK AB";"HABALT22";"Eldaro bernvakariui, dok. nr. 147234481, operacijos nr. RD251048722";"RD251048722";2025-06-01;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";100,00;"LT807044060008146755";"EUR"; -"";2025-06-02;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 01/06/2025. Sutartis Nr 120420, operacijos nr. RO1882397963L01";"RO1882397963L01";2025-06-02;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; -"21637";2025-06-02;"EUR";20,00;"KOVALIŪNAS EINARAS";"";"LT517044090105709148";"AB SEB BANKAS";"CBVILT2X";"Protingoms investicijoms, dok. nr. 21637, operacijos nr. RO1882518490L01";"RO1882518490L01";2025-06-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; -"21639";2025-06-02;"EUR";30,00;"KOVALIŪNAS EINARAS";"";"LT267044090105735400";"AB SEB BANKAS";"CBVILT2X";"Emergencies, dok. nr. 21639, operacijos nr. RO1882518492L01";"RO1882518492L01";2025-06-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";30,00;"LT807044060008146755";"EUR"; -"21639";2025-06-02;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"Tam, kas džiugina, dok. nr. 21639, operacijos nr. RO1882518493L01";"RO1882518493L01";2025-06-02;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; -"";2025-06-03;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 02/06/2025. Sutartis Nr 120420, operacijos nr. RO1883453471L01";"RO1883453471L01";2025-06-03;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; -"";2025-06-04;"EUR";2,00;"SEB bankas";"";"";"";"";"1839445-KASDIENIS paslaugų planas - mėnesio mokestis už gegužės mėn. , operacijos nr. RO1884527808L01";"RO1884527808L01";2025-06-04;"ACMTMDOPFEES-Paslaugų plano KASDIENIS mėnesio mokestis";"";"D";2,00;"LT807044060008146755";"EUR"; -"";2025-06-07;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 06/06/2025. Sutartis Nr 120420, operacijos nr. RO1888594783L01";"RO1888594783L01";2025-06-07;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"CLR10717500";2025-06-07;"EUR";62,58;"UAB Barbora";"";"";"";"";"06/06/2025 10:40 kortelė...961856 UAB Barbora/Vilnius/LTU #642007, dok. nr. CLR10717500, operacijos nr. RO1888720572L01";"RO1888720572L01";2025-06-06;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";62,58;"LT807044060008146755";"EUR"; -"";2025-06-11;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 10/06/2025. Sutartis Nr 120420, operacijos nr. RO1893349345L01";"RO1893349345L01";2025-06-11;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"148857595";2025-06-11;"EUR";20,00;"KLIM EDGAR";"";"LT657044090101048434";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 148857595, operacijos nr. RO1894642890L01";"RO1894642890L01";2025-06-11;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";20,00;"LT807044060008146755";"EUR"; -"CLR10740581";2025-06-11;"EUR";30,92;"UAB Barbora";"";"";"";"";"10/06/2025 18:43 kortelė...961856 UAB Barbora/Vilnius/LTU #071010, dok. nr. CLR10740581, operacijos nr. RO1893673262L01";"RO1893673262L01";2025-06-10;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";30,92;"LT807044060008146755";"EUR"; -"";2025-06-13;"EUR";27,20;"UAB ""TELE2""";"";"LT647044060001223571";"AB SEB BANKAS";"CBVILT2X";"mano.tele2.lt/LT, st803487, Tele2 saskaita .868647647., unik. mokėj. kodas 803487, operacijos nr. RO1896792472L01";"RO1896792472L01";2025-06-13;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";27,20;"LT807044060008146755";"EUR"; -"155";2025-06-13;"EUR";87,81;"UAB ELEKTRUM LIETUVA";"";"LT847044060007346724";"AB SEB BANKAS";"CBVILT2X";"Sutarties Nr.: 21247465979, dok. nr. 155, operacijos nr. RO1896799410L01";"RO1896799410L01";2025-06-13;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";87,81;"LT807044060008146755";"EUR"; -"";2025-06-14;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 13/06/2025. Sutartis Nr 120420, operacijos nr. RO1897582367L01";"RO1897582367L01";2025-06-14;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"149264703";2025-06-14;"EUR";20,00;"EDGAR KLIM";"";"BE14905422151683";"TRANSFERWISE EUROPE SA/NV";"TRWIBEB1";"Mokėjimas mobiliąja programėle, dok. nr. 149264703, operacijos nr. RMO22636032";"RMO22636032";2025-06-14;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";20,00;"LT807044060008146755";"EUR"; -"149256257";2025-06-14;"EUR";28,50;"Chmylko Edgar";"";"LT137044060008001006";"AB SEB BANKAS";"CBVILT2X";"Mokėjimas mobiliąja programėle, dok. nr. 149256257, operacijos nr. RO1897959986L01";"RO1897959986L01";2025-06-14;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";28,50;"LT807044060008146755";"EUR"; -"41516";2025-06-15;"EUR";21,00;"TELE2 UAB";"";"LT647044060001223571";"AB SEB BANKAS";"CBVILT2X";"Mokėtojo kodas: 859309211, dok. nr. 41516, operacijos nr. RO1898207373L01";"RO1898207373L01";2025-06-15;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";21,00;"LT807044060008146755";"EUR"; -"149488785";2025-06-16;"EUR";30,00;"ERNEST MATIJEVSKI";"";"LT627300010183670574";"SWEDBANK AB";"HABALT22";"Eldaro bernvakario alkui, dok. nr. 149488785, operacijos nr. RD252766195";"RD252766195";2025-06-16;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";30,00;"LT807044060008146755";"EUR"; -"";2025-06-16;"EUR";130,09;"Nacionalinis bilietų platintojas UAB";"";"LT107300010126193869";"SWEDBANK AB";"HABALT22";"NIPS15100-199646722407845 458384, unik. mokėj. kodas NIPS15100-43a4c8e93e85f851cb360b28f, operacijos nr. RD252759840";"RD252759840";2025-06-16;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";130,09;"LT807044060008146755";"EUR"; -"";2025-06-17;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 16/06/2025. Sutartis Nr 120420, operacijos nr. RO1900738026L01";"RO1900738026L01";2025-06-17;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; -"149579437";2025-06-17;"EUR";50,00;"OKAS DOMINYKAS";"";"LT047044060008039580";"AB SEB BANKAS";"CBVILT2X";"Algirdo gimse, dok. nr. 149579437, operacijos nr. RO1900840892L01";"RO1900840892L01";2025-06-17;"PMNTICDTBOOK-Pinigų pervedimas į kito kliento sąskaitą SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; -"";2025-06-18;"EUR";0,30;"SEB bankas";"";"";"";"";"SMS banko mokestis 17/06/2025. Sutartis Nr 120420, operacijos nr. RO1901953326L01";"RO1901953326L01";2025-06-18;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,30;"LT807044060008146755";"EUR"; -"CLR10783959";2025-06-18;"EUR";50,14;"UAB Barbora";"";"";"";"";"17/06/2025 19:11 kortelė...961856 UAB Barbora/Vilnius/LTU #772650, dok. nr. CLR10783959, operacijos nr. RO1902301169L01";"RO1902301169L01";2025-06-17;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";50,14;"LT807044060008146755";"EUR"; -"149853036";2025-06-18;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 149853036, operacijos nr. RO1903142909L01";"RO1903142909L01";2025-06-18;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; -"";2025-06-19;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 18/06/2025. Sutartis Nr 120420, operacijos nr. RO1903201036L01";"RO1903201036L01";2025-06-19;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"149974737";2025-06-19;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 149974737, operacijos nr. RO1904314631L01";"RO1904314631L01";2025-06-19;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; -"";2025-06-20;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 19/06/2025. Sutartis Nr 120420, operacijos nr. RO1904393365L01";"RO1904393365L01";2025-06-20;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"150402322";2025-06-23;"EUR";5,50;"ALGIRDAS SALIAMONAS";"";"LT574010051001213325";"LUMINOR BANK AS LITHUANIAN BRANCH";"AGBLLT2X";"taxas, dok. nr. 150402322, operacijos nr. RD253466405";"RD253466405";2025-06-23;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";5,50;"LT807044060008146755";"EUR"; -"";2025-06-24;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 23/06/2025. Sutartis Nr 120420, operacijos nr. RO1908556867L01";"RO1908556867L01";2025-06-24;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"CLR10820874";2025-06-24;"EUR";46,88;"UAB Barbora";"";"";"";"";"23/06/2025 13:47 kortelė...961856 UAB Barbora/Vilnius/LTU #353223, dok. nr. CLR10820874, operacijos nr. RO1908784917L01";"RO1908784917L01";2025-06-23;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";46,88;"LT807044060008146755";"EUR"; -"";2025-06-25;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 24/06/2025. Sutartis Nr 120420, operacijos nr. RO1909494558L01";"RO1909494558L01";2025-06-25;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; -"150676613";2025-06-25;"EUR";50,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 150676613, operacijos nr. RO1910478465L01";"RO1910478465L01";2025-06-25;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";50,00;"LT807044060008146755";"EUR"; -"CLR10850074";2025-06-29;"EUR";38,41;"AMAZON* 5H1756U75";"";"";"";"";"28/06/2025 01:51 kortelė...961856 AMAZON* 5H1756U75/LUXEMBOURG/LUX #475225, dok. nr. CLR10850074, operacijos nr. RO1913977382L01";"RO1913977382L01";2025-06-28;"PMNTCCRDOTHR-Atsiskaitymas POS Debit MC Mylimiausia";"";"D";38,41;"LT807044060008146755";"EUR"; -"151101327";2025-06-29;"EUR";50,00;"ALGIRDAS SALIAMONAS";"";"LT574010051001213325";"LUMINOR BANK AS LITHUANIAN BRANCH";"AGBLLT2X";"domui, dok. nr. 151101327, operacijos nr. RD254019443";"RD254019443";2025-06-29;"PMNTICDTESCT-Momentinis mokėjimas, lėšų nurašymas";"";"D";50,00;"LT807044060008146755";"EUR"; -"151084479";2025-06-29;"EUR";100,00;"KOVALIŪNAS EINARAS";"";"LT357044090105676747";"AB SEB BANKAS";"CBVILT2X";"relokacija, dok. nr. 151084479, operacijos nr. RO1914321658L01";"RO1914321658L01";2025-06-29;"PMNTICDTBOOK-Pinigų pervedimas tarp savo sąskaitų SEB banke";"";"D";100,00;"LT807044060008146755";"EUR"; -"";2025-06-30;"EUR";0,15;"SEB bankas";"";"";"";"";"SMS banko mokestis 29/06/2025. Sutartis Nr 120420, operacijos nr. RO1914795792L01";"RO1914795792L01";2025-06-30;"XTNDNTAVNTAV-Aptarnavimo mokestis už pranešimų paslaugą";"";"D";0,15;"LT807044060008146755";"EUR"; From a7c3cabd73dc3d9a6d3d0abee3b18232bce34fde Mon Sep 17 00:00:00 2001 From: br34th5 Date: Mon, 28 Jul 2025 15:12:53 +0300 Subject: [PATCH 17/23] cleaning up --- .../automation}/plan.txt | 0 .../2024_02-04_spendings.xlsx | Bin 26172 -> 0 bytes .../Manually polished/Galutinis_Income.xlsx | Bin 14338 -> 0 bytes .../fin/analysis/merged_income_data.xlsx | Bin 4785 -> 0 bytes .../fin/analysis/merged_spending_data.xlsx | Bin 4785 -> 0 bytes src/personal_finance/run_script.sh | 3 ++ .../discord_extract/2024-05-extract.txt | 26 ------------------ 7 files changed, 3 insertions(+), 26 deletions(-) rename src/{w-automation => Archives/automation}/plan.txt (100%) delete mode 100755 src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx delete mode 100755 src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx delete mode 100644 src/personal_finance/fin/analysis/merged_income_data.xlsx delete mode 100644 src/personal_finance/fin/analysis/merged_spending_data.xlsx delete mode 100755 src/personal_notes/discord_extract/2024-05-extract.txt diff --git a/src/w-automation/plan.txt b/src/Archives/automation/plan.txt similarity index 100% rename from src/w-automation/plan.txt rename to src/Archives/automation/plan.txt diff --git a/src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx b/src/personal_finance/fin/analysis/Manually polished/2024_02-04_spendings.xlsx deleted file mode 100755 index 509952dfa9945fa2fcc6c7fdc0add079649bd685..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26172 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 diff --git a/src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx b/src/personal_finance/fin/analysis/Manually polished/Galutinis_Income.xlsx deleted file mode 100755 index 967c16cd43545a41e48d59495623bab9b874fca5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14338 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 Date: Mon, 28 Jul 2025 15:14:35 +0300 Subject: [PATCH 18/23] cleaning up --- Data-Engineering | 1 - __pycache__/pandas.cpython-311.pyc | Bin 453 -> 0 bytes 2 files changed, 1 deletion(-) delete mode 160000 Data-Engineering delete mode 100755 __pycache__/pandas.cpython-311.pyc diff --git a/Data-Engineering b/Data-Engineering deleted file mode 160000 index 55c20f1..0000000 --- a/Data-Engineering +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 55c20f1bc3adc060f63dd41b74939eeddfad8604 diff --git a/__pycache__/pandas.cpython-311.pyc b/__pycache__/pandas.cpython-311.pyc deleted file mode 100755 index 2adffa1940e6d9bd5c68db80e984367581e05173..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 453 zcmY*VyH3L}6m`-Tg-0bM7Sx@gL*hP|5JG@j35HgPvQUP~jc?nuPHe?VP+{r72e2`* zf)GCg5P(E6)90{ulp=v}P^nrW&vP(JmW$ok!LF9?J@z}W7XHV=IX(xfCHmMmMLv~0#h zYVvj;?7)-(_emElA_V0walAzupP$w$5CP)?{KmjxOx7W|<^I6DxGoe6#cJLB7siaV zC9ZKAGT18>_Hkl}uefLcV~*E7VeQaW-ZIK(;x$1$N@T1H8XGFq5+vxwh7W|cl#F%S zc|+6WGqW??<1S!u20X&9<2g20rDY6WB@D+)0MY)>QWklKHg_9+QGvm;m zzwXH2R2L_`la|^hDNWPHXy;=!Cuy`b&_~8Nvpu?exOv?D%;df@xsjd>e_fUQ0c53z Ab^rhX From 14aa062bfc82fc8257e6a3f60ba4a68fda2df3d2 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Mon, 28 Jul 2025 15:16:47 +0300 Subject: [PATCH 19/23] small change --- src/personal_finance/run_script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/personal_finance/run_script.sh b/src/personal_finance/run_script.sh index b9f9293..283fbf9 100755 --- a/src/personal_finance/run_script.sh +++ b/src/personal_finance/run_script.sh @@ -7,6 +7,6 @@ source ~/scraping_env/bin/activate cd /home/eikov/fin # Run your Python script -python scrape.py +python3 scrape.py chmod +x run_script.sh From 219a4ff04912ebab2607279a4302caddf92e5632 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Mon, 28 Jul 2025 19:22:19 +0300 Subject: [PATCH 20/23] fin v2.3 latest version now shows summ of unique strings --- src/personal_finance/fin/scrape.py | 4 +- src/personal_finance/v2.2.py | 33 +- src/personal_finance/v2.3.py | 474 +++++++++++++++++++++++++++++ 3 files changed, 496 insertions(+), 15 deletions(-) create mode 100755 src/personal_finance/v2.3.py diff --git a/src/personal_finance/fin/scrape.py b/src/personal_finance/fin/scrape.py index 0e02b0b..6998d00 100755 --- a/src/personal_finance/fin/scrape.py +++ b/src/personal_finance/fin/scrape.py @@ -4,8 +4,10 @@ import json import openpyxl +#simplified working version + # Define the directory containing the CSV files -directory = '/home/eikov/scraping_env/Data-Engineering/src/personal_finance/fin/OG-CSV' +directory = '/home/eikov/fin/OG-CSV' # Define the pattern for file names file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type diff --git a/src/personal_finance/v2.2.py b/src/personal_finance/v2.2.py index edd63b6..3295fd6 100755 --- a/src/personal_finance/v2.2.py +++ b/src/personal_finance/v2.2.py @@ -4,6 +4,12 @@ import json import openpyxl +#more advanced than scrape.py but not fully working yet: +# =sum() shows 0 +# galutinis spendings looks, but income does not merge Categories and Analysis_Income into galutinis_income.xlsx + + + """ This code is designed to process bank statement files and organize the data into separate DataFrames for income and spending transactions. It performs the following tasks: @@ -13,7 +19,7 @@ 4. Organizes the data into separate DataFrames for income and spending transactions, grouped by month. 5. Saves the DataFrames as JSON and Excel files in separate directories for income and spending. 6. Merges separate monthly Excel files into single "merged_income_data.xlsx" and "merged_spending_data.xlsx" files. -7. Extracts unique payer/payee names from the merged files and saves them as "categories_income.xlsx" and "categories_spendings.xlsx". +7. Extracts unique payer/payee names from the merged files and saves them as "unique-strings-income.xlsx" and "unique-strings-spendings.xlsx". 8. Allows manual categorization of payer/payee names by creating "cat-income.xlsx" and "cat-spendings.xlsx" files. 9. Merges the categorized data and analysis sheets into final "Galutinis_Income.xlsx" and "Galutinis_spendings.xlsx" files. @@ -302,9 +308,9 @@ def extract_first_2_words(text): # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2) unique_values.update(df.iloc[:, 2].unique()) -# Create a DataFrame with unique values and save it to 'categories.xlsx' +# Create a DataFrame with unique values and save it to 'unique-strings-income.xlsx' unique_df = pd.DataFrame({'Unique Values': list(unique_values)}) -unique_df.to_excel(f'{output_directory}/categories_income.xlsx', index=False) +unique_df.to_excel(f'{output_directory}/unique-strings-income.xlsx', index=False) @@ -338,27 +344,26 @@ def extract_first_2_words(text): # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2) unique_values2.update(df.iloc[:, 2].unique()) -# Create a DataFrame with unique values and save it to 'categories.xlsx' +# Create a DataFrame with unique values and save it to 'unique-strings-spendings.' unique_df = pd.DataFrame({'Unique Values': list(unique_values2)}) -unique_df.to_excel(f'{output_directory}/categories_spendings.xlsx', index=False) - +unique_df.to_excel(f'{output_directory}/unique-strings-spendings.xlsx', index=False) """ -#turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx -taipogi unikaliu moketoju/gaveju sarasus is pajamu ir islaidu. rankiniu budu priskiriu kiekvienam gavejui/moketojui +1) AUTO: turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx +2) MANUAL: turiu unikaliu moketoju/gaveju sarasus is pajamu ir islaidu[unique-strings-.xlsx]. rankiniu budu priskiriu kiekvienam gavejui/moketojui kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx. -priedo, manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo. -belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile. -""" -""" +3) MANUAL: manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo. +4) AUTO: belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile. + # Save the merged workbook to the output directory output_filename = os.path.join(output_directory, "merged_income_data.xlsx") merged_workbook.save(output_filename) """ + #INCOME def merge_additional_sheets(base_file, additional_files, output_file): # Create the output directory if it doesn't exist @@ -387,7 +392,7 @@ def merge_additional_sheets(base_file, additional_files, output_file): formula_str = f'=SUM(B2:B{last_row})' writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) -# List of additional files to merge as new sheets +# List of additional files to merge as new sheets. additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx'] # Merge additional sheets with merged_income_data.xlsx @@ -423,7 +428,7 @@ def merge_additional_sheets(base_file, additional_files, output_file): writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) # List of additional files to merge as new sheets -additional_files = ['cat-spendings.xlsx', 'Analysis_spending.xlsx'] +additional_files = ['cat-spendings.xlsx', 'Analysis_spendings.xlsx'] # Merge additional sheets with merged_income_data.xlsx merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx') diff --git a/src/personal_finance/v2.3.py b/src/personal_finance/v2.3.py new file mode 100755 index 0000000..e74be07 --- /dev/null +++ b/src/personal_finance/v2.3.py @@ -0,0 +1,474 @@ +import pandas as pd +import os +from io import StringIO +import json +import openpyxl + +#more advanced than scrape.py but not fully working yet: +# =sum() shows 0 +# galutinis spendings looks, but income does not merge Categories and Analysis_Income into galutinis_income.xlsx + +#TBD: +# a script scraped all entries and then gave me unique string values in .xlsx format. +# 1) I want to see the sum each unique string . What's the best approach to do this, in pandas before writing to .xlsx or somehow edit it after saving? +# 2) Then as a second [B] column I assigned each unique string value a sub-category and [C] column as Category. to do analysis on different Sheet named [Analysis] + + +""" +This code is designed to process bank statement files and organize the data into separate DataFrames for income and spending transactions. It performs the following tasks: + +1. Reads bank statement files from a specified directory. +2. Extracts relevant information from each file, such as transaction date, amount, payer/payee name, and purpose. +3. Applies data cleaning and preprocessing steps, including removing unnecessary columns and extracting the first two words from payer/payee names and payment purposes. +4. Organizes the data into separate DataFrames for income and spending transactions, grouped by month. +5. Saves the DataFrames as JSON and Excel files in separate directories for income and spending. +6. Merges separate monthly Excel files into single "merged_income_data.xlsx" and "merged_spending_data.xlsx" files. +7. Extracts unique payer/payee names from the merged files and saves them as "unique-strings-income.xlsx" and "unique-strings-spendings.xlsx". +8. Allows manual categorization of payer/payee names by creating "cat-income.xlsx" and "cat-spendings.xlsx" files. +9. Merges the categorized data and analysis sheets into final "Galutinis_Income.xlsx" and "Galutinis_spendings.xlsx" files. + +The code is designed to be modular and extensible, allowing for easy integration of additional data processing or analysis steps as needed. +""" + +""" +#next: in 8: create DF for each found excel spending(-) file(ignore the last row with total sum) and join into one DF. +now iterate each unique value and sum all "SUMA" values. if that's not possible, convert DF to set and then sum key values. +""" + + +# Define the directory containing the CSV files +directory = '/home/eikov/fin/OG-CSV' + +# Define the pattern for file names +file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type + +# Initialize empty dictionaries to store dataframes for each month +spending_dfs = {} +income_dfs = {} + +# Define the filters +filter1 = "KOVALIŪNAS EINARAS" +filter2 = "Einaras Kovaliūnas" # Add your second filter value here + + +# Function to extract first 2 words from a string +def extract_first_2_words(text): + if pd.isna(text): # Check if the value is NaN + return "" # Return an empty string if NaN + words = text.split()[:2] + return ' '.join(words) + + + +# Iterate over the file numbers from 1 to 12 +for file_num in range(1, 13): + month = str(file_num) + spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]) + for file_type in ['+', '-']: + filename = file_pattern.format(file_num, file_type) + filepath = os.path.join(directory, filename) + if os.path.exists(filepath): + print(f"Processing file: {filename}") + # Initialize an empty list to store the valid lines of the CSV file + valid_lines = [] + skipped_rows = [] + + # Read the CSV file line by line + with open(filepath, 'r', encoding='utf-8') as file: + for i, line in enumerate(file): + if i < 2: # Skip the first two lines (first row and header row) + continue + try: + # Attempt to parse the line as CSV + pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';' + valid_lines.append(line) # If successful, add the line to the list of valid lines + except pd.errors.ParserError: + print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}") + skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row + + # Read the valid lines into a DataFrame, skipping the header line + bank_data = None + try: + bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';') + except pd.errors.ParserError: + print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.") + if not valid_lines: + print("No valid data read due to errors.") + continue + + # If data was successfully read, extract specific columns and apply filter + if bank_data is not None: + bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns + + # Replace empty values with NaN + bank_data.replace('-', pd.NA, inplace=True) + + # Rename columns + bank_data.columns = ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"] + + # Apply filters + bank_data = bank_data[(bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter1) & + (bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] != filter2)] + + # Filter out rows that are the same as column headers or contain all empty values + bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKĖTOJO ARBA GAVĖJO PAVADINIMAS", "MOKĖJIMO PASKIRTIS"]), axis=1)] + + # Extract first 4 words from specified columns + bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"] = bank_data["MOKĖTOJO ARBA GAVĖJO PAVADINIMAS"].apply(extract_first_2_words) + bank_data["MOKĖJIMO PASKIRTIS"] = bank_data["MOKĖJIMO PASKIRTIS"].apply(extract_first_2_words) + + # Replace commas with periods in the "SUMA" column + bank_data["SUMA"] = bank_data["SUMA"].str.replace(',', '.') + + # Convert the values in the "SUMA" column to numeric data type + bank_data["SUMA"] = pd.to_numeric(bank_data["SUMA"], errors='coerce') + # Now the "SUMA" column contains numeric values + + + # Append the DataFrame to the appropriate dictionary based on file type + if file_type == '+': + income_dfs[month] = bank_data + elif file_type == '-': + spending_dfs[month] = bank_data + else: + print(f"No valid data read from file {filename}.") + + # Display the DataFrame + if file_type == '+': + print(f"\nIncome DataFrame for month {month}:") + print(income_dfs[month]) + elif file_type == '-': + print(f"\nSpending DataFrame for month {month}:") + print(spending_dfs[month]) + + # Specify the folder path + output_folder_income = "/home/eikov/fin/income" + output_folder_spending = "/home/eikov/fin/spendings" + + # Create the folder if it doesn't exist + os.makedirs(output_folder_income, exist_ok=True) + os.makedirs(output_folder_spending, exist_ok=True) + + # Save DataFrames to JSON files + income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json") + spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json") + + # Save DataFrames to Excel files + income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx") + spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx") + + # Drop rows with NaN values + income_dfs[month].dropna(inplace=True) + spending_dfs[month].dropna(inplace=True) + + # Convert DataFrames to JSON strings + income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str) + spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str) + + # Save DataFrames to Excel files + income_dfs[month].to_excel(income_excel_filename, index=False) + spending_dfs[month].to_excel(spending_excel_filename, index=False) + + print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}") + print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}") + + # Display skipped rows + if skipped_rows: + print(f"\nSkipped rows in file {filename}:") + for row_num, row_content in skipped_rows: + print(f"Row {row_num}: {row_content}") + else: + print(f"File {filename} not found. Skipping...") + +print("All files processed.") + + + + + + + + +#Merge spending seperate xlsx files to one +directory = '/home/eikov/fin/spendings' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + sheet = workbook.active + + if i == 0: + merged_sheet = merged_workbook.active + else: + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + for row in sheet.iter_rows(values_only=True): + if not any(row): # skip empty rows + continue + merged_sheet.append(row) + + # Add total formula + data_start_row = 2 + data_end_row = merged_sheet.max_row + formula_str = f'=SUM(B{data_start_row}:B{data_end_row})' + merged_sheet[f'A{data_end_row + 1}'] = 'TOTAL' + merged_sheet[f'B{data_end_row + 1}'] = formula_str + + +# Save the merged workbook to the output directory +output_filename = os.path.join(output_directory, "merged_spending_data.xlsx") +merged_workbook.save(output_filename) + + +#Merge income seperate xlsx files to one +directory = '/home/eikov/fin/income' +output_directory = '/home/eikov/fin/analysis' + +# List all files in the directory +files = os.listdir(directory) + +# Filter out only the Excel files (files ending with .xlsx) +excel_files = [file for file in files if file.endswith('.xlsx')] + +# Create a new workbook +merged_workbook = openpyxl.Workbook() + +# Loop through each file and add it as a new sheet to the workbook +for i, file_name in enumerate(excel_files): + # Load the workbook from file + workbook = openpyxl.load_workbook(os.path.join(directory, file_name)) + + # Get the active sheet from the loaded workbook + sheet = workbook.active + + # Copy the active sheet to the merged workbook + if i == 0: + # For the first sheet, use the default sheet name 'Sheet' + merged_sheet = merged_workbook.active + else: + # For subsequent sheets, create a new sheet with the file name as the sheet name + merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}") + + # Copy data from the original sheet to the merged sheet + for row in sheet.iter_rows(values_only=True): + merged_sheet.append(row) + + # Add total sum formula(B2:B100) at the end of the last row + last_row = merged_sheet.max_row + formula_str = f'=SUM(B2:B100)' + merged_sheet[f'A{last_row + 1}'] = formula_str + + + # Save the merged workbook to the output directory + output_filename = os.path.join(output_directory, "merged_income_data.xlsx") + merged_workbook.save(output_filename) + + + + + + + + + + + +# finding unique strings in SPENDINGS and categorise it +# Path to the directory containing the file +output_directory2 = '/home/eikov/fin/analysis' +filename2 = 'merged_income_data.xlsx' +file_path2 = f'{output_directory2}/{filename2}' + +# Read Excel file +xls = pd.ExcelFile(file_path2) + +# Create empty DataFrame to collect values +all_data = pd.DataFrame(columns=['SUMA', 'MOKĖTOJO ARBA GAVĖJO PAVADINIMAS']) + +# Iterate through each sheet +for sheet_name in xls.sheet_names: + df = pd.read_excel(xls, sheet_name) + + if df.shape[1] < 4: + print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.") + continue + + # Extract 'SUMA' and 'STRING' columns by index — adjust if needed + amounts = df.iloc[:, 1] # this is column B, the 'SUMA' + labels = df.iloc[:, 2] # this is column C, the 'MOKĖTOJO ARBA GAVĖJO PAVADINIMAS' + + + # Combine into one DataFrame + temp_df = pd.DataFrame({'SUMA': amounts, 'MOKĖTOJO ARBA GAVĖJO PAVADINIMAS': labels}) + + # Drop rows where either SUMA or STRING is missing + temp_df.dropna(subset=['SUMA', 'MOKĖTOJO ARBA GAVĖJO PAVADINIMAS'], inplace=True) + + # 🛠 Clean and convert SUMA properly + temp_df['SUMA'] = ( + temp_df['SUMA'] + .astype(str) + .str.replace(r'[^\d,.-]', '', regex=True) + .str.replace(',', '.', regex=False) + .astype(float) + ) + + + all_data = pd.concat([all_data, temp_df], ignore_index=True) + +# Group and sum +grouped = all_data.groupby('MOKĖTOJO ARBA GAVĖJO PAVADINIMAS', as_index=False)['SUMA'].sum() + +# Save results +grouped.to_excel(f'{output_directory2}/summed-strings-income.xlsx', index=False) + + + + + + + + +# finding unique strings in SPENDINGS and categorise it +# Path to the directory containing the file +output_directory2 = '/home/eikov/fin/analysis' +filename2 = 'merged_spending_data.xlsx' +file_path2 = f'{output_directory2}/{filename2}' + +# Read Excel file +xls = pd.ExcelFile(file_path2) + +# Create empty DataFrame to collect values +all_data = pd.DataFrame(columns=['SUMA', 'MOKĖTOJO ARBA GAVĖJO PAVADINIMAS']) + +# Iterate through each sheet +for sheet_name in xls.sheet_names: + df = pd.read_excel(xls, sheet_name) + + if df.shape[1] < 4: + print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.") + continue + + # Extract 'SUMA' and 'STRING' columns by index — adjust if needed + amounts = df.iloc[:, 1] # this is column B, the 'SUMA' + labels = df.iloc[:, 2] # this is column C, the 'MOKĖTOJO ARBA GAVĖJO PAVADINIMAS' + + + # Combine into one DataFrame + temp_df = pd.DataFrame({'SUMA': amounts, 'MOKĖTOJO ARBA GAVĖJO PAVADINIMAS': labels}) + + # Drop rows where either SUMA or STRING is missing + temp_df.dropna(subset=['SUMA', 'MOKĖTOJO ARBA GAVĖJO PAVADINIMAS'], inplace=True) + + # 🛠 Clean and convert SUMA properly + temp_df['SUMA'] = ( + temp_df['SUMA'] + .astype(str) + .str.replace(r'[^\d,.-]', '', regex=True) + .str.replace(',', '.', regex=False) + .astype(float) + ) + + all_data = pd.concat([all_data, temp_df], ignore_index=True) + +# Group and sum +grouped = all_data.groupby('MOKĖTOJO ARBA GAVĖJO PAVADINIMAS', as_index=False)['SUMA'].sum() + +# Save results +grouped.to_excel(f'{output_directory2}/summed-strings-spendings.xlsx', index=False) + + + + + +""" +1) AUTO: turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx +2) MANUAL: turiu unikaliu moketoju/gaveju sarasus is pajamu ir islaidu[summed-strings-income/spendings.xlsx]. rankiniu budu priskiriu kiekvienam gavejui/moketojui +kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx. +3) MANUAL: manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo. +4) AUTO: belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile. + + # Save the merged workbook to the output directory + output_filename = os.path.join(output_directory, "merged_income_data.xlsx") + merged_workbook.save(output_filename) +""" + +#INCOME +def merge_additional_sheets(base_file, additional_files, output_file): + # Create the output directory if it doesn't exist + output_directory_analysis = '/home/eikov/fin/analysis' + os.makedirs(output_directory_analysis, exist_ok=True) + + # Create a Pandas ExcelWriter object + with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer: + # Write the base file to the output file + base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None) + for sheet_name, df in base_df.items(): + df.to_excel(writer, sheet_name=sheet_name, index=False) + # Add total sum formula to the end of the last row + last_row = df.shape[0] + 1 + formula_str = f'=SUM(B2:B{last_row})' + writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) + + # Write additional files to the output file as new sheets + for file in additional_files: + additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None) + for sheet_name, df in additional_df.items(): + df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) + # Add total sum formula to the end of the last row + if file not in additional_files: + last_row = df.shape[0] + 1 + formula_str = f'=SUM(B2:B{last_row})' + writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) + +# List of additional files to merge as new sheets. +additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx'] + +# Merge additional sheets with merged_income_data.xlsx +merge_additional_sheets('merged_income_data.xlsx', additional_files, 'Galutinis_Income.xlsx') + + +#SPENDINGS +def merge_additional_sheets(base_file, additional_files, output_file): + # Create the output directory if it doesn't exist + output_directory_analysis = '/home/eikov/fin/analysis' + os.makedirs(output_directory_analysis, exist_ok=True) + + # Create a Pandas ExcelWriter object + with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer: + # Write the base file to the output file + base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None) + for sheet_name, df in base_df.items(): + df.to_excel(writer, sheet_name=sheet_name, index=False) + # Add total sum formula to the end of the last row + last_row = df.shape[0] + 1 + formula_str = f'=SUM(B2:B{last_row})' + writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) + + # Write additional files to the output file as new sheets + for file in additional_files: + additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None) + for sheet_name, df in additional_df.items(): + df.to_excel(writer, sheet_name=f'{sheet_name}', index=False) + # Add total sum formula to the end of the last row + if file not in additional_files: + last_row = df.shape[0] + 1 + formula_str = f'=SUM(B2:B{last_row})' + writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str) + +# List of additional files to merge as new sheets +additional_files = ['cat-spendings.xlsx', 'Analysis_spendings.xlsx'] + +# Merge additional sheets with merged_income_data.xlsx +merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx') From 86829076cde4b543169a4e0f1448ac1ab7334cd8 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Wed, 30 Jul 2025 20:41:51 +0300 Subject: [PATCH 21/23] Totals with manually assigned subcategories and categories, then running pandamat1.1 creates pie chart and saves it to Analysis sheet --- src/personal_finance/pandamat1.0.py | 28 +++++++++++++++++ src/personal_finance/pandamat1.1.py | 41 +++++++++++++++++++++++++ src/personal_finance/run_script.sh | 5 ++- src/personal_finance/scheme.txt | 2 ++ src/personal_finance/{v2.py => v2.0.py} | 0 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/personal_finance/pandamat1.0.py create mode 100644 src/personal_finance/pandamat1.1.py rename src/personal_finance/{v2.py => v2.0.py} (100%) diff --git a/src/personal_finance/pandamat1.0.py b/src/personal_finance/pandamat1.0.py new file mode 100644 index 0000000..da52511 --- /dev/null +++ b/src/personal_finance/pandamat1.0.py @@ -0,0 +1,28 @@ +import pandas as pd +import matplotlib.pyplot as plt + +# Load the Totals sheet +file_path = '/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx' # change filename +df = pd.read_excel(file_path, sheet_name='Totals') + +# Ensure SUMA is numeric (clean up bad values) +df['SUMA'] = pd.to_numeric(df['SUMA'], errors='coerce') # convert non-numeric to NaN +df = df.dropna(subset=['SUMA']) # drop rows where SUMA couldn't be converted + +# Group by Category (or Subcategory) +grouped = df.groupby('Category')['SUMA'].sum().sort_values(ascending=False) + +# Optional: filter out zero or very small values +grouped = grouped[grouped > 0] + +# Plot +plt.figure(figsize=(8, 8)) +grouped.plot.pie(autopct='%1.1f%%', startangle=90) +plt.title('Spending Distribution by Category') +plt.ylabel('') +plt.tight_layout() + +# Save pie chart +output_path = '/home/eikov/fin/analysis/spending_pie_by_category.png' +plt.savefig(output_path) +print(f"Saved pie chart to: {output_path}") diff --git a/src/personal_finance/pandamat1.1.py b/src/personal_finance/pandamat1.1.py new file mode 100644 index 0000000..fd9435f --- /dev/null +++ b/src/personal_finance/pandamat1.1.py @@ -0,0 +1,41 @@ +import pandas as pd +import matplotlib.pyplot as plt +from openpyxl import load_workbook +from openpyxl.drawing.image import Image as OpenpyxlImage + +# File paths +excel_file = '/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx' # Replace with actual file name +chart_image_path = '/home/eikov/fin/analysis/Manually-polished/2025/spending_pie_by_category.png' + +# 1. Load data from Totals sheet +df = pd.read_excel(excel_file, sheet_name='Totals') + +# 2. Clean and group +df['SUMA'] = pd.to_numeric(df['SUMA'], errors='coerce') +df = df.dropna(subset=['SUMA']) +grouped = df.groupby('Category')['SUMA'].sum().sort_values(ascending=False) +grouped = grouped[grouped > 0] + +# 3. Create and save pie chart +plt.figure(figsize=(8, 8)) +grouped.plot.pie(autopct='%1.1f%%', startangle=90) +plt.title('Spending Distribution by Category') +plt.ylabel('') +plt.tight_layout() +plt.savefig(chart_image_path) +plt.close() + +# 4. Insert image into a new sheet in the Excel file +workbook = load_workbook(excel_file) +if 'Analysis' in workbook.sheetnames: + del workbook['Analysis'] # remove if it already exists (optional) +sheet = workbook.create_sheet('Analysis') + +# Insert image +img = OpenpyxlImage(chart_image_path) +img.anchor = 'A1' # Position in the sheet +sheet.add_image(img) + +# Save workbook +workbook.save(excel_file) +print(f"✅ Pie chart inserted into 'Analysis' sheet of: {excel_file}") diff --git a/src/personal_finance/run_script.sh b/src/personal_finance/run_script.sh index 283fbf9..1fab98f 100755 --- a/src/personal_finance/run_script.sh +++ b/src/personal_finance/run_script.sh @@ -7,6 +7,9 @@ source ~/scraping_env/bin/activate cd /home/eikov/fin # Run your Python script -python3 scrape.py +python3 scrape.py #or latest version like v2.3.py + +# optional: for better analysis create visual charts with pandas, mathplotlib +python3 pandamat.py chmod +x run_script.sh diff --git a/src/personal_finance/scheme.txt b/src/personal_finance/scheme.txt index 53abe34..77f3d4a 100755 --- a/src/personal_finance/scheme.txt +++ b/src/personal_finance/scheme.txt @@ -1,6 +1,8 @@ step1: download seb monthly csv israsus step2: rename them by month and add +(if parsing incomes) or -(if parsing spendings) step3: enjoy clean data in xlsx , showing Date/Sum/Moketojas/Paskirtis +step4: edit summed-strings-spendings.xlsx to assign each row sub-category and category, rename to a) cat-income.xlsx b) cat-spendings.xlsx then rerun .py again +step5: run pandamat.py for visuals, charts, analysis Scheme Step 0 DONE: Download monthly banko israsus diff --git a/src/personal_finance/v2.py b/src/personal_finance/v2.0.py similarity index 100% rename from src/personal_finance/v2.py rename to src/personal_finance/v2.0.py From dd2676081c909760c2ee14d92597a30745475562 Mon Sep 17 00:00:00 2001 From: br34th5 Date: Sat, 2 Aug 2025 18:04:31 +0300 Subject: [PATCH 22/23] better charts in Analysis sheet --- src/personal_finance/pandamat1.1.py | 2 + src/personal_finance/pandamat1.2.py | 84 +++++++++++++++++++++++++++++ src/personal_finance/pandamat1.3.py | 73 +++++++++++++++++++++++++ src/personal_finance/pandamat1.4.py | 84 +++++++++++++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 src/personal_finance/pandamat1.2.py create mode 100644 src/personal_finance/pandamat1.3.py create mode 100644 src/personal_finance/pandamat1.4.py diff --git a/src/personal_finance/pandamat1.1.py b/src/personal_finance/pandamat1.1.py index fd9435f..1042f4c 100644 --- a/src/personal_finance/pandamat1.1.py +++ b/src/personal_finance/pandamat1.1.py @@ -3,6 +3,8 @@ from openpyxl import load_workbook from openpyxl.drawing.image import Image as OpenpyxlImage +#basic + # File paths excel_file = '/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx' # Replace with actual file name chart_image_path = '/home/eikov/fin/analysis/Manually-polished/2025/spending_pie_by_category.png' diff --git a/src/personal_finance/pandamat1.2.py b/src/personal_finance/pandamat1.2.py new file mode 100644 index 0000000..37dca65 --- /dev/null +++ b/src/personal_finance/pandamat1.2.py @@ -0,0 +1,84 @@ +import pandas as pd +import matplotlib.pyplot as plt +from openpyxl import load_workbook +from openpyxl.drawing.image import Image as OpenpyxlImage + +#ok + +# File paths +excel_file = '/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx' # Replace with actual file name +chart_image_path = '/home/eikov/fin/analysis/Manually-polished/2025/spending_pie_by_category.png' + +# 1. Load data from Totals sheet +df = pd.read_excel(excel_file, sheet_name='Totals') + +# 2. Clean and group +df['SUMA'] = pd.to_numeric(df['SUMA'], errors='coerce') +df = df.dropna(subset=['SUMA']) +grouped = df.groupby('Category')['SUMA'].sum().sort_values(ascending=False) +grouped = grouped[grouped > 0] + + + + +# 3. Create and save pie chart +# Group by Category and Subcategory +df['SUMA'] = pd.to_numeric(df['SUMA'], errors='coerce') +df = df.dropna(subset=['SUMA']) +grouped = df.groupby(['Category', 'Subcategory'])['SUMA'].sum().sort_values(ascending=False) + +# Filter small values if chart gets too crowded (optional) +grouped = grouped[grouped > 0] + +# Build custom labels: "Category: Subcategory" +labels = [f"{cat}: {sub}" for cat, sub in grouped.index] + +# Plot with smaller font +plt.figure(figsize=(10, 10)) +patches, texts, autotexts = plt.pie( + grouped.values, + labels=labels, + autopct='%1.1f%%', + startangle=90, + textprops={'fontsize': 8} +) +plt.title('Spending Breakdown by Category and Subcategory', fontsize=12) +plt.tight_layout() + +# stacking small text +plt.figure(figsize=(10, 10)) +patches, texts, autotexts = plt.pie( + grouped.values, + labels=None, # Don't show labels on the pie + autopct='%1.1f%%', + startangle=90, + textprops={'fontsize': 8} +) + +# stacking small text: Add legend outside the pie +plt.legend(patches, labels, loc='center left', bbox_to_anchor=(1, 0.5), fontsize=8) +plt.title('Spending Breakdown by Category and Subcategory', fontsize=12) +plt.tight_layout() + + + + +# Save image +chart_image_path = '/home/eikov/fin/analysis/spending_pie_by_category_sub.png' +plt.savefig(chart_image_path) +plt.close() + +# 4. Insert image into a new sheet in the Excel file +workbook = load_workbook(excel_file) +if 'Analysis' in workbook.sheetnames: + del workbook['Analysis'] # remove if it already exists (optional) +sheet = workbook.create_sheet('Analysis') + +# Insert image +img = OpenpyxlImage(chart_image_path) +img.anchor = 'A1' # Position in the sheet +sheet.add_image(img) + +# Save workbook +workbook.save(excel_file) +print(f"✅ Pie chart inserted into 'Analysis' sheet of: {excel_file}") diff --git a/src/personal_finance/pandamat1.3.py b/src/personal_finance/pandamat1.3.py new file mode 100644 index 0000000..f07537c --- /dev/null +++ b/src/personal_finance/pandamat1.3.py @@ -0,0 +1,73 @@ +import pandas as pd +import matplotlib.pyplot as plt +from openpyxl import load_workbook +from openpyxl.drawing.image import Image as ExcelImage + +#so far best one + +# === Load data === +excel_file = "/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx" # <-- Replace with your actual file +sheet_name = "Totals" + +df = pd.read_excel(excel_file, sheet_name=sheet_name) + +# === Group by Category and Subcategory === +grouped = df.groupby(['Category', 'Subcategory'], as_index=False)['SUMA'].sum() + +# Remove rows with zero or negative sums (optional) +grouped = grouped[grouped['SUMA'] > 0] + +# Sort by SUMA descending +grouped = grouped.sort_values(by='SUMA', ascending=False) + +# === Create pie chart === +fig, ax = plt.subplots(figsize=(8, 8)) + +# Pie chart data +sizes = grouped['SUMA'] +labels_raw = grouped[['Category', 'Subcategory']].agg(' – '.join, axis=1) + +# Plot pie and get returned patches +patches, texts, autotexts = ax.pie( + sizes, + autopct='%1.1f%%', + startangle=90 +) + +# Create legend with formatted labels including % +total = sizes.sum() +percentages = (sizes / total * 100).round(1) +legend_labels = [f"{p:.1f}% – {cat}" for p, cat in zip(percentages, labels_raw)] + +# Sort legend entries by percentages +sorted_indices = percentages.argsort()[::-1] +sorted_labels = [legend_labels[i] for i in sorted_indices] +sorted_patches = [patches[i] for i in sorted_indices] + +# Display legend +ax.legend(sorted_patches, sorted_labels, loc="center left", bbox_to_anchor=(1, 0.5), fontsize='small') + +# Save figure +chart_path = "output_chart.png" +plt.tight_layout() +plt.savefig(chart_path) +plt.close() + +# === Insert chart into Excel in sheet "Analysis" === +wb = load_workbook(excel_file) + +# Create or get the "Analysis" sheet +if "Analysis" in wb.sheetnames: + ws = wb["Analysis"] +else: + ws = wb.create_sheet("Analysis") + +# Add image to worksheet +img = ExcelImage(chart_path) +img.anchor = "A1" +ws.add_image(img) + +# Save updated Excel file +wb.save(excel_file) + +print("Chart generated and embedded into the 'Analysis' sheet.") diff --git a/src/personal_finance/pandamat1.4.py b/src/personal_finance/pandamat1.4.py new file mode 100644 index 0000000..394e1dc --- /dev/null +++ b/src/personal_finance/pandamat1.4.py @@ -0,0 +1,84 @@ +import pandas as pd +import matplotlib.pyplot as plt +from openpyxl import load_workbook +from openpyxl.drawing.image import Image as ExcelImage + +#so far the best one + +# === Load data === +excel_file = "/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx" # <-- Replace with your actual file +sheet_name = "Totals" + +df = pd.read_excel(excel_file, sheet_name=sheet_name) + +# === Group by Category and Subcategory === +grouped = df.groupby(['Category', 'Subcategory'], as_index=False)['SUMA'].sum() + +# Remove zero or negative values +grouped = grouped[grouped['SUMA'] > 0] + +# Sort by SUMA descending +grouped = grouped.sort_values(by='SUMA', ascending=False) + +# === Create pie chart === +fig, ax = plt.subplots(figsize=(8, 8)) + +# Pie data +sizes = grouped['SUMA'] +labels_raw = grouped[['Category', 'Subcategory']].agg(' – '.join, axis=1) + +# Pie chart +patches, texts, autotexts = ax.pie( + sizes, + autopct='%1.1f%%', + startangle=90 +) + +# Generate custom labels with % + numeric sum + category info +total = sizes.sum() +percentages = (sizes / total * 100).round(1) +sums_formatted = sizes.round(2) + +legend_labels = [ + f"{p:.1f}% – {s:.2f} – {label}" + for p, s, label in zip(percentages, sums_formatted, labels_raw) +] + +# Sort legend entries by % descending +sorted_indices = percentages.argsort()[::-1] +sorted_labels = [legend_labels[i] for i in sorted_indices] +sorted_patches = [patches[i] for i in sorted_indices] + +# Add legend +ax.legend( + sorted_patches, + sorted_labels, + loc="center left", + bbox_to_anchor=(1, 0.5), + fontsize='small' +) + +# Save the pie chart +chart_path = "output_chart.png" +plt.tight_layout() +plt.savefig(chart_path) +plt.close() + +# === Insert chart into Excel === +wb = load_workbook(excel_file) + +# Create or get the "Analysis" sheet +if "Analysis" in wb.sheetnames: + ws = wb["Analysis"] +else: + ws = wb.create_sheet("Analysis") + +# Add image +img = ExcelImage(chart_path) +img.anchor = "A1" +ws.add_image(img) + +# Save updated Excel file +wb.save(excel_file) + +print("Chart with enhanced legend saved and embedded into 'Analysis' sheet.") From 179d148e3882ef39ef8f5f46085f432aa8aaeafd Mon Sep 17 00:00:00 2001 From: br34th5 Date: Sat, 2 Aug 2025 18:06:02 +0300 Subject: [PATCH 23/23] better charts in Analysis sheet --- src/personal_finance/pandamat1.3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/personal_finance/pandamat1.3.py b/src/personal_finance/pandamat1.3.py index f07537c..550fd46 100644 --- a/src/personal_finance/pandamat1.3.py +++ b/src/personal_finance/pandamat1.3.py @@ -3,7 +3,7 @@ from openpyxl import load_workbook from openpyxl.drawing.image import Image as ExcelImage -#so far best one +#good # === Load data === excel_file = "/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx" # <-- Replace with your actual file