<jupyter_start><jupyter_text>Meeting Summary<jupyter_code># You will need pandas/numpy to run the first part of the script. Then plotly for the second part. You can install plotly via pip (pip install plotly)
# If the PNG export is not working, most probably orca is not installed. You can install it by executing in the Anaconda cmd: conda install -c plotly plotly-orca
import pandas as pd
import numpy as np
import plotly.express as px
import re
import pickle
import html2text
import traceback
from IPython.display import Markdown as md
import plotly.express as px
import plotly.io as pio
import plotly.graph_objects as go
import os
import os.path
import math
# Create variables
email_dump    = meeting["file"]
title         = meeting["title"]
meeting_dates = meeting["dates"]

# Create DataFrame for analysis
df = pd.read_csv(
    email_dump,
    index_col=0,
    dtype=object)
df['date']    = pd.to_datetime(df['date'], errors='coerce')

# Filter out NaTs (datetime conversion errors)
conversion_oks = pd.notnull(df['date'])
df = df[conversion_oks]

df['day']     = df['date'].dt.floor('d')
df['hour']    = df['date'].dt.floor('h')

def parse_email_company(x):
    try:
        email_address_split = x.split('@')
        company_domain      = email_address_split[-1]
        company_name_split  = company_domain.split('.')
        
        if 'COM.CN' in x or 'COM.TR' in x:
            company_name = company_name_split[0]
        else:
            company_name = company_name_split[-2]
        
        if len(company_name) <= 4:
            company_name = company_name.upper()
        else:
            company_name = company_name.capitalize()
            
        if company_name=='COM':
            print(x)
            
        # Some substitutions for big companies that show in rankings
        if company_name=='Ztetx':
            company_name = 'ZTE'
        elif company_name=='Nokia-sbell' or company_name=='Nokia-bell-labs':
            company_name = 'Nokia'
        elif company_name=='Docomolab-euro':
            company_name = 'NTT Docomo'
        elif company_name=='Magenta':
            company_name = 'Deutsche Telekom'
            
        return company_name
    except:
        # print('Error parsing email. Sender address: {0}'.format(x))
        return ''
df['company'] = df['sender address'].apply(parse_email_company)

# display(df.company.unique())

# Dataframe grouping
email_count_per_ai   = df.groupby('ai').count()
email_count_per_day  = df.groupby('day').count()
email_count_per_hour = df.groupby('hour').count()
# Pre-process DataFrame
max_rows = pd.options.display.max_rows
pd.options.display.max_rows = None

# Ranking based on emails per company
emails_count = df.groupby('sender name').count().sort_values(by='date', ascending=False)
# See https://stackoverflow.com/questions/49574817/fill-a-new-pandas-column-with-row-numbers
emails_count['ranking'] = np.arange(len(emails_count)) + 1
emails_count['emails'] = emails_count['date']

emails_count_company = df.groupby('company').count().sort_values(by='date', ascending=False)

# See https://stackoverflow.com/questions/49574817/fill-a-new-pandas-column-with-row-numbers
emails_count_company['ranking'] = np.arange(len(emails_count_company)) + 1
emails_count_company['emails'] = emails_count_company['date']

# Ranking based on emails per delegate
active_delegate_list = pd.DataFrame()
grouped_df = df.groupby(['company', 'sender name']).count()
active_delegate_list['company']       = grouped_df.index.get_level_values(0).copy()
active_delegate_list['delegate name'] = grouped_df.index.get_level_values(1).copy()
# display(active_delegate_list)
active_delegate_count = active_delegate_list.groupby('company').count()
# display(active_delegate_count)
# display(emails_count_company)

# Taking into account number of delegates per company
emails_per_active_delegate_count = emails_count_company['emails']/active_delegate_count['delegate name']
emails_count_company['emails/active delegate'] = emails_per_active_delegate_count
emails_count_company['active delegates'] = active_delegate_count['delegate name']
emails_count_company['total emails'] = emails_count_company['emails']
emails_count_company_resort = emails_count_company.sort_values(by='emails/active delegate', ascending=False)
emails_count_company_resort['ranking'] = np.arange(len(emails_count_company)) + 1
# display(emails_count_company_resort[['ranking', 'emails/active delegate', 'active delegates', 'total emails']].iloc[0:20,:])<jupyter_output><empty_output><jupyter_text>Overall stats<jupyter_code>total_emails = email_count_per_ai['date'].sum()
total_delegates = len(emails_count.index)
total_companies = len(emails_count_company.index)
display(md("Total emails: {0}".format(total_emails)))
display(md("Number of active delegates: {0}".format(total_delegates)))
display(md("Number of active companies: {0}".format(total_companies)))
# Where to save plots
plot_folders = meeting['title']
plot_folder_html = os.path.join(plot_folders, 'html')
plot_folder_png  = os.path.join(plot_folders, 'png')

if not os.path.exists(plot_folders):
    os.mkdir(plot_folders)
if not os.path.exists(plot_folder_html):
    os.mkdir(plot_folder_html)
if not os.path.exists(plot_folder_png):
    os.mkdir(plot_folder_png)
company_sizes_list_length = 10
# display(emails_count_company.sort_values(by=['active delegates', 'total emails'], ascending=False).iloc[0:(company_sizes_list_length+1),:].loc[:,['active delegates', 'total emails']])

pie_threshold = 3
emails_count_company_percent = emails_count_company.sort_values(by=['total emails'], ascending=False).loc[:,['active delegates', 'total emails']]
emails_count_company_percent['delegation percent'] = emails_count_company_percent['active delegates'] / total_delegates
emails_count_company_percent = emails_count_company_percent.loc[emails_count_company_percent['active delegates'] >= pie_threshold,:]

extra_row = pd.DataFrame.from_dict(
    {
        'Others (<{0} delegates)'.format(pie_threshold): [
            total_delegates - emails_count_company_percent['active delegates'].sum(),
            total_emails - emails_count_company_percent['total emails'].sum(),
            1 - emails_count_company_percent['delegation percent'].sum()
        ]
    }, 
    orient='index',
    columns=['active delegates', 'total emails', 'delegation percent'])

emails_count_company_percent = emails_count_company_percent.append(extra_row)
# display(emails_count_company_percent)

fig = px.pie(
    emails_count_company_percent, 
    title='Delegation distribution (total {0} active delegates)'.format(total_delegates),
    names=emails_count_company_percent.index,
    values='active delegates',
    labels={
        'index': 'Company'
    }
)

fig.update_traces(textposition='inside', textinfo='percent+label')

fig.show()

fig_title = "{0} {1}".format(title, "Delegate distribution")

fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))
pie_threshold = 0.02
company_ranking_per_emails = emails_count_company.sort_values(by=['total emails'], ascending=False).loc[:,['active delegates', 'total emails']]
company_ranking_per_emails['email percent'] = company_ranking_per_emails['total emails'] / total_emails
company_ranking_per_emails = company_ranking_per_emails.loc[company_ranking_per_emails['email percent'] > pie_threshold,:]

extra_row = pd.DataFrame.from_dict(
    {
        'Others (<{0}%)'.format(pie_threshold*100): [
            total_delegates - company_ranking_per_emails['active delegates'].sum(),
            total_emails - company_ranking_per_emails['total emails'].sum(),
            1 - company_ranking_per_emails['email percent'].sum()
        ]
    }, 
    orient='index',
    columns=['active delegates', 'total emails', 'email percent'])

company_ranking_per_emails = company_ranking_per_emails.append(extra_row)
# display(company_ranking_per_emails)

fig = px.pie(
    company_ranking_per_emails, 
    title='Biggest email contributors (companies)',
    names=company_ranking_per_emails.index,
    values='total emails',
    labels={
        'index': 'Company'
    }
)

fig.update_traces(textposition='inside', textinfo='percent+label')

fig.show()

fig_title = "{0} {1}".format(title, "Emails per company")
fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))<jupyter_output><empty_output><jupyter_text>Emails/delegateNot sure whether it is good to be in this list...<jupyter_code>display(emails_count[['ranking', 'emails']].iloc[0:(ranking_length+1),:])
pd.options.display.max_rows = 60<jupyter_output><empty_output><jupyter_text>Emails per AI<jupyter_code>data = go.Bar(
    x=email_count_per_ai.index, 
    y=email_count_per_ai['date'])
layout = go.Layout(
    xaxis= {'type': 'category'})
fig = go.Figure(data=data, layout=layout)

fig.update_xaxes(title_text='AI')
fig.update_yaxes(title_text='Emails/AI')

fig.show()

fig_title = "{0} {1}".format(title, "Emails per AI")
fig.write_html(os.path.join(plot_folder_html, fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))<jupyter_output><empty_output><jupyter_text>Emails per day<jupyter_code>data = go.Bar(
    x=email_count_per_day.index, 
    y=email_count_per_day['date'])
layout = go.Layout(
    xaxis_tickformat = '%d.%m',
    xaxis_range = meeting_dates,
    xaxis = { 'dtick': 1000*3600*24 })
fig = go.Figure(data=data, layout=layout)

fig.update_xaxes(title_text='Date')
fig.update_yaxes(title_text='Emails/day')

fig.show()

fig_title = "{0} {1}".format(title, "Emails per day")
fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))
# Data source for emails/day per AI
email_count_per_day_and_ai = df.groupby(['ai', 'day']).count()

ai_plot = {}
ais = df.ai.unique()
for ai in ais:
    ai_plot[ai] = email_count_per_day_and_ai[email_count_per_day_and_ai.index.isin([ai], level=0)]['date']

layout = go.Layout(
    xaxis_tickformat = '%d.%m',
    xaxis_range = meeting_dates,
    xaxis = { 'dtick': 1000*3600*24 } )

fig = go.Figure(layout=layout)

for ai in ais:
    bar_data = ai_plot[ai]
    bar_y = bar_data
    bar_x = bar_data.index.get_level_values(1)
    bar_title = ai
    data = go.Bar(
        x=bar_x, 
        y=bar_y,
        name=bar_title)
    fig.add_trace(data)

fig.update_layout(barmode='relative')

fig.update_xaxes(title_text='Date')
fig.update_yaxes(title_text='Emails/day')

fig.show()

fig_title = "{0} {1}".format(title, "Emails per day and AI")
fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))<jupyter_output><empty_output><jupyter_text>Emails per hour<jupyter_code>data = go.Bar(
    x=email_count_per_hour.index, 
    y=email_count_per_hour['date'])
layout = go.Layout(
    xaxis_tickformat = '%d.%m  %Hh',
    xaxis_range = meeting_dates)
fig = go.Figure(data=data, layout=layout)

fig.update_xaxes(title_text='Date')
fig.update_yaxes(title_text='Emails/hour')

fig.show()

fig_title = "{0} {1}".format(title, "Emails per hour")
fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))
# Data source for emails/h per AI
email_count_per_hour_and_ai = df.groupby(['ai', 'hour']).count()

ai_plot = {}
ais = df.ai.unique()
for ai in ais:
    ai_plot[ai] = email_count_per_hour_and_ai[email_count_per_hour_and_ai.index.isin([ai], level=0)]['date']

layout = go.Layout(
    xaxis_tickformat = '%d.%m %Hh',
    xaxis_range = meeting_dates)

fig = go.Figure(layout=layout)

for ai in ais:
    bar_data = ai_plot[ai]
    bar_y = bar_data
    bar_x = bar_data.index.get_level_values(1)
    bar_title = ai
    data = go.Bar(
        x=bar_x, 
        y=bar_y,
        name=bar_title)
    fig.add_trace(data)

fig.update_layout(barmode='relative')

fig.update_xaxes(title_text='Date')
fig.update_yaxes(title_text='Emails/hour')

fig.show()

fig_title = "{0} {1}".format(title, "Emails per hour and AI")
fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))
tdocs = []    
for email_subject in df['subject']:
    try:
        tdocs.append(re.search(r'S2-[\d]{7}', email_subject).group())
    except:
        tdocs.append('')
        # print(email_subject)

df['tdoc'] = tdocs
# display(df)
df_per_tdoc = df.groupby("tdoc")
emails_per_tdoc = df_per_tdoc.count()
emails_per_tdoc['Email Count'] = emails_per_tdoc['date']
emails_per_tdoc = emails_per_tdoc.loc[:,['Email Count']]
emails_per_tdoc.sort_values(by ='Email Count' , ascending=False, inplace=True)
emails_per_tdoc = emails_per_tdoc.reset_index()

# Not necessary if you have the pickle file
# display(emails_per_tdoc.iloc[0:21,:])
# Cross data with TdocsByAgenda data if data is available
try:
    with open(meeting['tdocs_cache'], 'rb') as f:
        # Unpickle the 'data' dictionary using the highest protocol available.
        cache         = pickle.load(f)
        meeting_tdocs = cache['tdocs']
        meeting_tdocs_original = meeting_tdocs
        contributor_columns = cache['contributor_columns']
        others_cosigners    = cache['others_cosigners']
        # Same order and TDocs as the ones in the emails
        meeting_tdocs = meeting_tdocs.reindex(index=list(emails_per_tdoc['tdoc']),columns=['AI', 'Title', 'Source'])
        # display(meeting_tdocs)
        # Need to convert to list. If not you get a list of NaNs because Pandas will try to align the indices
        pd.options.display.max_columns = None
        pd.options.display.max_rows    = 50
        pd.options.display.min_rows    = 20
        meeting_tdocs['Email Count'] = list(emails_per_tdoc['Email Count'])
        meeting_tdocs.sort_values(by ='Email Count' , ascending=False, inplace=True)
        
        # Search for NaNs
        nan_entries     = list(meeting_tdocs[meeting_tdocs['AI'].isnull()].index)
        # print(nan_entries)
        for nan_entry in nan_entries:
            try:
                email_subject = df[df['subject'].str.contains("{0}] ".format(nan_entry))].iloc[0]['subject']
                split1 = email_subject.split('] ')
                meeting_tdocs.at[nan_entry,'Title'] = split1[-1]
                split2 = split1[0].split(', ')[1]
                meeting_tdocs.at[nan_entry,'AI'] = split2.replace('AI#','')
                # print(emails_for_nans)
            except:
                pass
                # print("Error on {0}".format(nan_entry))
        
        # display(meeting_tdocs.iloc[0:21,:])
        # display(meeting_tdocs)
except:
    meeting_tdocs = None
    traceback.print_exc()
revisions_file = meeting['revisions_file']
with open(revisions_file, 'r') as file:
    tdoc_revisions_html = file.read()

h = html2text.HTML2Text()
# Ignore converting links from HTML
h.ignore_links = True
tdoc_revisions_text = h.handle(tdoc_revisions_html)
tdocs = emails = re.findall(r'S2-[\d]{7}r[\d]{2}',tdoc_revisions_text)
tdocs = list(set(tdocs))
revision_list = [(tdoc[0:-3], tdoc[-2:]) for tdoc in tdocs]

df = pd.DataFrame(revision_list, columns =['Tdoc', 'Revisions'])
df["Revisions"] = df[["Revisions"]].apply(pd.to_numeric)
# display(df)

df_per_tdoc = df.groupby("Tdoc")
maximums = df_per_tdoc.max()
maximums.sort_values(by ='Revisions' , ascending=False, inplace=True)
maximums = maximums.reset_index()
maximums = maximums.set_index('Tdoc')
# display(maximums)

meeting_tdocs = pd.concat([meeting_tdocs, maximums], axis=1, sort=False)
meeting_tdocs['Email Count'] = meeting_tdocs['Email Count'].fillna(0)
meeting_tdocs['Revisions']   = meeting_tdocs['Revisions'].fillna(0)
meeting_tdocs['Email Count'] = meeting_tdocs['Email Count'].astype(int)
meeting_tdocs['Revisions']   = meeting_tdocs['Revisions'].astype(int)

#meeting_tdocs.sort_values(by ='Revisions' , ascending=False, inplace=True)
# display(meeting_tdocs.iloc[0:21,:])<jupyter_output><empty_output><jupyter_text>Emails/contribution<jupyter_code>result = meeting_tdocs.sort_values(by ='Email Count' , ascending=False).iloc[0:(ranking_length+1),:]
display(result)
fig = px.histogram(
    meeting_tdocs, 
    x="Email Count",
    labels={
        "Email Count": "Number of emails",
        "count": "Number of TDocs"
    },
    marginal = 'box')

fig.update_yaxes(title_text='')

fig.show()

fig_title = "{0} {1}".format(title, "Emails per discussed TDoc")
fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))

# print('Mean: {0:.2f} emails/contribution'.format(meeting_tdocs['Email Count'].mean()))<jupyter_output><empty_output><jupyter_text>Revisions/contribution<jupyter_code>result = meeting_tdocs.sort_values(by ='Revisions' , ascending=False).iloc[0:(ranking_length+1),:]
display(result)

fig = px.histogram(
    meeting_tdocs, 
    x="Revisions",
    labels={
        "Revisions": "Number of revisions",
        "count": "Number of TDocs"
    },
    marginal = 'box')

fig.update_yaxes(title_text='')

fig.show()

fig_title = "{0} {1}".format(title, "Revisions per discussed TDoc")
fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))

# print('Mean: {0:.2f} revisions/contribution'.format(meeting_tdocs.Revisions.mean()))<jupyter_output><empty_output><jupyter_text>Contribution Statistics<jupyter_code># TDocs statistics

# Count sources per TDoc
tdocs = meeting_tdocs_original.copy()
tdocs['Source count'] = tdocs['Source (summary)'].apply(lambda x: len(x.split(',')))
tdocs['First source'] = tdocs['Source (summary)'].apply(lambda x: x.split(',')[0])
contributors = [e.replace('Contributed by ','') for e in contributor_columns]
contributors.append('Others')
pd.options.display.max_columns = None
pd.options.display.max_rows    = 50
pd.options.display.min_rows    = 7
# display(tdocs)

# Make the diagram simpler by plotting only companies
contributors_to_ignore_in_diagram = [
    'RAN WG1',
    'RAN WG2',
    'RAN WG3',
    'SA WG1',
    'SA WG2',
    'SA WG3',
    'SA WG4',
    'SA WG5',
    'SA WG6',
    'TSG SA',
    'TSG CT',
    'TSG RAN',
    'CT WG1',
    'CT WG2',
    'CT WG3',
    'CT WG4',
    'IETF',
    'IEEE',
    'BBF',
    'GSMA'
]

tdocs_with_multiple_sources = tdocs[tdocs['Source count']>1]

contributors_in_meeting = []
edge_sizes = {}
for idx,contributor in enumerate(contributors):
    # Each contributor is a node
    contributor_size = tdocs_with_multiple_sources[(tdocs_with_multiple_sources['Contributed by {0}'.format(contributor)]==True)].count()['AI']
    if contributor_size > 0:
        contributors_in_meeting.append(contributor)
        
# print('{0} contributors counted in this meeting'.format(len(contributors_in_meeting)))
contributors_in_meeting = [e for e in contributors_in_meeting if e not in contributors_to_ignore_in_diagram]
# print('{0} contributors counted in this meeting after filtering non-company entities'.format(len(contributors_in_meeting)))
        
# Generate co-signing matrix
# Initialize N (row) x M (columns)
N = len(contributors_in_meeting)
M = N
 
# using list comprehension 
# to initializing matrix
co_sign_numbers = [ [ 0 for i in range(M) ] for j in range(N) ]
 
# printing result 
# print("Relation matrix after initializing {0}x{1}: ".format(N,M))

total_contributors = len(contributors_in_meeting)
contributors = {}
for idx_1,contributor_1 in enumerate(contributors_in_meeting):
    # print('{0}: Checking {1}'.format(idx_1, contributor_1))
    contributors[contributor_1] = (tdocs['Contributed by {0}'.format(contributor_1)]==True).sum()
    for idx_2,contributor_2 in enumerate(contributors_in_meeting[idx_1+1:total_contributors]):
        tdoc_count = int(tdocs_with_multiple_sources[(tdocs_with_multiple_sources['Contributed by {0}'.format(contributor_1)]==True) & (tdocs_with_multiple_sources['Contributed by {0}'.format(contributor_2)]==True)].count()['AI'])
        if tdoc_count > 0:
            edge_sizes['{0}-{1}'.format(contributor_1,contributor_2)] = tdoc_count
            full_idx2 = idx_1+1+idx_2
            if idx_1 != full_idx2:
                co_sign_numbers[idx_1][full_idx2] = tdoc_count
                co_sign_numbers[full_idx2][idx_1] = tdoc_count
                # print('  and {0} ({1}): {2}'.format(contributor_2, contributors_in_meeting[full_idx2], tdoc_count))
                
# print("Contributors in meeting ({0})".format(len(contributors_in_meeting)))
# print(contributors_in_meeting)
# display(pd.DataFrame(co_sign_numbers))

# Number of co-signers per TDoc
tdocs_sorted_by_source_count = tdocs.sort_values(by ='Source count' , ascending=False, inplace=False)
tdocs_to_display = tdocs_sorted_by_source_count.iloc[0:21,:]
tdocs_to_display = tdocs_to_display.loc[:,['AI', 'Type', 'Doc For', 'Title', 'Source', 'Source count']]
# display(tdocs_to_display)
contributions_per_company = pd.DataFrame.from_dict(contributors, orient='index', columns=['contributions'])
contributions_per_company.loc[:,'First source'] = tdocs.groupby('First source').count()['AI'].reindex(contributions_per_company.index, fill_value=0)
contributions_per_company = contributions_per_company.sort_values(by=['First source', 'contributions'], ascending=False)#.iloc[0:(ranking_length+1),:]

# display(contributions_per_company.iloc[1:(ranking_length+1),:])
pie_threshold = 0.02
total_contributions = contributions_per_company['contributions'].sum()
total_first_source_contributions = contributions_per_company['First source'].sum()
pie_threshold_contribs = math.floor(total_first_source_contributions * pie_threshold)

contributions_per_company['First author percent'] = contributions_per_company['First source'] / total_first_source_contributions
contributions_per_company_to_plot = contributions_per_company.loc[contributions_per_company['First author percent'] > pie_threshold,['contributions', 'First source']]

extra_row = pd.DataFrame.from_dict(
    {
        'Others (<{0}%)'.format(pie_threshold*100, pie_threshold_contribs): [
            total_contributions - contributions_per_company_to_plot['contributions'].sum(),
            total_first_source_contributions - contributions_per_company_to_plot['First source'].sum(),
        ]
    }, 
    orient='index',
    columns=['contributions', 'First source'])

contributions_per_company_to_plot = contributions_per_company_to_plot.append(extra_row)

fig = px.pie(
    contributions_per_company_to_plot, 
    title='TDocs per first author (total {0})'.format(total_first_source_contributions),
    names=contributions_per_company_to_plot.index,
    values='First source',
    labels={
        'index': 'Company',
        'First source': 'Contributions as first source'
    }
)

fig.update_traces(textposition='inside', textinfo='percent+label')

fig.show()

fig_title = "{0} {1}".format(title, "First source TDoc distribution")
fig.write_html(os.path.join(plot_folder_html,  fig_title + ".html"))
# fig.write_image(os.path.join(plot_folder_png, fig_title + ".png"))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13830566932528562, 'fraction_numerical': 0.009571028238844578, 'mean_word_length': 4.929447852760736, 'pattern_counts': {'":': 4, '<': 41, '<?xml version=': 0, '>': 43, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '403611', 'license': 'MIT', 'n_tokens_mistral': 8286, 'n_tokens_neox': 7963, 'n_words': 1382, 'path': '3GPP Meeting Email Statistics/eMeeting Email statistics.ipynb', 'repo_name': 'telekom/3gpp-meeting-tools', 'size': '23195'}