Spaces:
Running
Running
James McCool
commited on
Commit
·
b09e7cf
1
Parent(s):
60809b9
Add subscription notice in Player ROO section and include a demo video link for enhanced user engagement
Browse files
app.py
CHANGED
|
@@ -396,6 +396,7 @@ with tab1:
|
|
| 396 |
with tab2:
|
| 397 |
st.header("Player ROO")
|
| 398 |
with st.expander("Info and Filters"):
|
|
|
|
| 399 |
with st.container():
|
| 400 |
slate_type_var2 = st.radio("Which slate type are you loading?", ('Regular', 'Showdown'), key='slate_type_var2')
|
| 401 |
slate_var2 = st.radio("Which slate data are you loading?", ('Main', 'Secondary', 'Auxiliary'), key='slate_var2')
|
|
@@ -628,314 +629,5 @@ with tab2:
|
|
| 628 |
|
| 629 |
with tab3:
|
| 630 |
st.header("Optimals")
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
with col1:
|
| 634 |
-
slate_type_var3 = st.radio("Which slate type are you loading?", ('Regular', 'Showdown'), key='slate_type_var3')
|
| 635 |
-
if slate_type_var3 == 'Regular':
|
| 636 |
-
raw_baselines = roo_data
|
| 637 |
-
elif slate_type_var3 == 'Showdown':
|
| 638 |
-
raw_baselines = sd_roo_data
|
| 639 |
-
slate_var3 = st.radio("Which slate data are you loading?", ('Main', 'Secondary', 'Auxiliary'), key='slate_var3')
|
| 640 |
-
if slate_type_var3 == 'Regular':
|
| 641 |
-
if site_var == 'Draftkings':
|
| 642 |
-
dk_lineups = init_DK_lineups(slate_type_var3, slate_var3)
|
| 643 |
-
elif site_var == 'Fanduel':
|
| 644 |
-
fd_lineups = init_FD_lineups(slate_type_var3, slate_var3)
|
| 645 |
-
elif slate_type_var3 == 'Showdown':
|
| 646 |
-
if site_var == 'Draftkings':
|
| 647 |
-
dk_lineups = init_DK_lineups(slate_type_var3, slate_var3)
|
| 648 |
-
elif site_var == 'Fanduel':
|
| 649 |
-
fd_lineups = init_FD_lineups(slate_type_var3, slate_var3)
|
| 650 |
-
with col2:
|
| 651 |
-
lineup_num_var = st.number_input("How many lineups do you want to display?", min_value=1, max_value=1000, value=150, step=1)
|
| 652 |
-
player_var1 = st.radio("Do you want a frame with specific Players?", ('Full Slate', 'Specific Players'), key='player_var1')
|
| 653 |
-
if player_var1 == 'Specific Players':
|
| 654 |
-
player_var2 = st.multiselect('Which players do you want?', options = raw_baselines['Player'].unique())
|
| 655 |
-
elif player_var1 == 'Full Slate':
|
| 656 |
-
player_var2 = raw_baselines.Player.values.tolist()
|
| 657 |
-
with col3:
|
| 658 |
-
if site_var == 'Draftkings':
|
| 659 |
-
salary_min_var = st.number_input("Minimum salary used", min_value = 0, max_value = 50000, value = 49000, step = 100, key = 'salary_min_var')
|
| 660 |
-
salary_max_var = st.number_input("Maximum salary used", min_value = 0, max_value = 50000, value = 50000, step = 100, key = 'salary_max_var')
|
| 661 |
-
elif site_var == 'Fanduel':
|
| 662 |
-
salary_min_var = st.number_input("Minimum salary used", min_value = 0, max_value = 35000, value = 34000, step = 100, key = 'salary_min_var')
|
| 663 |
-
salary_max_var = st.number_input("Maximum salary used", min_value = 0, max_value = 35000, value = 35000, step = 100, key = 'salary_max_var')
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
if site_var == 'Draftkings':
|
| 667 |
-
if slate_type_var3 == 'Regular':
|
| 668 |
-
ROO_slice = raw_baselines[raw_baselines['Site'] == 'Draftkings']
|
| 669 |
-
player_salaries = dict(zip(ROO_slice['Player'], ROO_slice['Salary']))
|
| 670 |
-
column_names = dk_columns
|
| 671 |
-
elif slate_type_var3 == 'Showdown':
|
| 672 |
-
player_salaries = dict(zip(raw_baselines['Player'], raw_baselines['Salary']))
|
| 673 |
-
column_names = dk_sd_columns
|
| 674 |
-
# Get the minimum and maximum ownership values from dk_lineups
|
| 675 |
-
min_own = np.min(dk_lineups[:,12])
|
| 676 |
-
max_own = np.max(dk_lineups[:,12])
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
elif site_var == 'Fanduel':
|
| 680 |
-
raw_baselines = hold_display
|
| 681 |
-
if slate_type_var3 == 'Regular':
|
| 682 |
-
ROO_slice = raw_baselines[raw_baselines['Site'] == 'Fanduel']
|
| 683 |
-
player_salaries = dict(zip(ROO_slice['Player'], ROO_slice['Salary']))
|
| 684 |
-
column_names = fd_columns
|
| 685 |
-
elif slate_type_var3 == 'Showdown':
|
| 686 |
-
player_salaries = dict(zip(raw_baselines['Player'], raw_baselines['Salary']))
|
| 687 |
-
column_names = fd_sd_columns
|
| 688 |
-
# Get the minimum and maximum ownership values from dk_lineups
|
| 689 |
-
min_own = np.min(fd_lineups[:,11])
|
| 690 |
-
max_own = np.max(fd_lineups[:,11])
|
| 691 |
-
|
| 692 |
-
if st.button("Prepare full data export", key='data_export'):
|
| 693 |
-
name_export = pd.DataFrame(st.session_state.working_seed.copy(), columns=column_names)
|
| 694 |
-
data_export = pd.DataFrame(st.session_state.working_seed.copy(), columns=column_names)
|
| 695 |
-
if site_var == 'Draftkings':
|
| 696 |
-
if slate_type_var3 == 'Regular':
|
| 697 |
-
map_columns = ['SP1', 'SP2', 'C', '1B', '2B', '3B', 'SS', 'OF1', 'OF2', 'OF3']
|
| 698 |
-
elif slate_type_var3 == 'Showdown':
|
| 699 |
-
map_columns = ['CPT', 'FLEX1', 'FLEX2', 'FLEX3', 'FLEX4', 'FLEX5']
|
| 700 |
-
for col_idx in map_columns:
|
| 701 |
-
data_export[col_idx] = data_export[col_idx].map(dk_id_map)
|
| 702 |
-
elif site_var == 'Fanduel':
|
| 703 |
-
if slate_type_var3 == 'Regular':
|
| 704 |
-
map_columns = ['P', 'C_1B', '2B', '3B', 'SS', 'OF1', 'OF2', 'OF3', 'UTIL']
|
| 705 |
-
elif slate_type_var3 == 'Showdown':
|
| 706 |
-
map_columns = ['CPT', 'FLEX1', 'FLEX2', 'FLEX3', 'FLEX4']
|
| 707 |
-
for col_idx in map_columns:
|
| 708 |
-
data_export[col_idx] = data_export[col_idx].map(fd_id_map)
|
| 709 |
-
st.download_button(
|
| 710 |
-
label="Export optimals set (IDs)",
|
| 711 |
-
data=convert_df(data_export),
|
| 712 |
-
file_name='MLB_optimals_export.csv',
|
| 713 |
-
mime='text/csv',
|
| 714 |
-
)
|
| 715 |
-
st.download_button(
|
| 716 |
-
label="Export optimals set (Names)",
|
| 717 |
-
data=convert_df(name_export),
|
| 718 |
-
file_name='MLB_optimals_export.csv',
|
| 719 |
-
mime='text/csv',
|
| 720 |
-
)
|
| 721 |
-
|
| 722 |
-
if site_var == 'Draftkings':
|
| 723 |
-
if 'working_seed' in st.session_state:
|
| 724 |
-
st.session_state.working_seed = st.session_state.working_seed
|
| 725 |
-
if player_var1 == 'Specific Players':
|
| 726 |
-
st.session_state.working_seed = st.session_state.working_seed[np.equal.outer(st.session_state.working_seed, player_var2).any(axis=1).all(axis=1)]
|
| 727 |
-
elif player_var1 == 'Full Slate':
|
| 728 |
-
st.session_state.working_seed = dk_lineups.copy()
|
| 729 |
-
st.session_state.data_export_display = pd.DataFrame(st.session_state.working_seed[0:lineup_num_var], columns=column_names)
|
| 730 |
-
elif 'working_seed' not in st.session_state:
|
| 731 |
-
st.session_state.working_seed = dk_lineups.copy()
|
| 732 |
-
st.session_state.working_seed = st.session_state.working_seed
|
| 733 |
-
if player_var1 == 'Specific Players':
|
| 734 |
-
st.session_state.working_seed = st.session_state.working_seed[np.equal.outer(st.session_state.working_seed, player_var2).any(axis=1).all(axis=1)]
|
| 735 |
-
elif player_var1 == 'Full Slate':
|
| 736 |
-
st.session_state.working_seed = dk_lineups.copy()
|
| 737 |
-
st.session_state.data_export_display = pd.DataFrame(st.session_state.working_seed[0:lineup_num_var], columns=column_names)
|
| 738 |
-
|
| 739 |
-
elif site_var == 'Fanduel':
|
| 740 |
-
if 'working_seed' in st.session_state:
|
| 741 |
-
st.session_state.working_seed = st.session_state.working_seed
|
| 742 |
-
if player_var1 == 'Specific Players':
|
| 743 |
-
st.session_state.working_seed = st.session_state.working_seed[np.equal.outer(st.session_state.working_seed, player_var2).any(axis=1).all(axis=1)]
|
| 744 |
-
elif player_var1 == 'Full Slate':
|
| 745 |
-
st.session_state.working_seed = fd_lineups.copy()
|
| 746 |
-
st.session_state.data_export_display = pd.DataFrame(st.session_state.working_seed[0:lineup_num_var], columns=column_names)
|
| 747 |
-
elif 'working_seed' not in st.session_state:
|
| 748 |
-
st.session_state.working_seed = fd_lineups.copy()
|
| 749 |
-
st.session_state.working_seed = st.session_state.working_seed
|
| 750 |
-
if player_var1 == 'Specific Players':
|
| 751 |
-
st.session_state.working_seed = st.session_state.working_seed[np.equal.outer(st.session_state.working_seed, player_var2).any(axis=1).all(axis=1)]
|
| 752 |
-
elif player_var1 == 'Full Slate':
|
| 753 |
-
st.session_state.working_seed = fd_lineups.copy()
|
| 754 |
-
st.session_state.data_export_display = pd.DataFrame(st.session_state.working_seed[0:lineup_num_var], columns=column_names)
|
| 755 |
-
st.session_state.data_export_display = st.session_state.data_export_display[st.session_state.data_export_display['salary'] >= salary_min_var]
|
| 756 |
-
st.session_state.data_export_display = st.session_state.data_export_display[st.session_state.data_export_display['salary'] <= salary_max_var]
|
| 757 |
-
export_file = st.session_state.data_export_display.copy()
|
| 758 |
-
name_export = st.session_state.data_export_display.copy()
|
| 759 |
-
if site_var == 'Draftkings':
|
| 760 |
-
if slate_type_var3 == 'Regular':
|
| 761 |
-
map_columns = ['SP1', 'SP2', 'C', '1B', '2B', '3B', 'SS', 'OF1', 'OF2', 'OF3']
|
| 762 |
-
elif slate_type_var3 == 'Showdown':
|
| 763 |
-
map_columns = ['CPT', 'FLEX1', 'FLEX2', 'FLEX3', 'FLEX4', 'FLEX5']
|
| 764 |
-
for col_idx in map_columns:
|
| 765 |
-
export_file[col_idx] = export_file[col_idx].map(dk_id_map)
|
| 766 |
-
elif site_var == 'Fanduel':
|
| 767 |
-
if slate_type_var3 == 'Regular':
|
| 768 |
-
map_columns = ['P', 'C_1B', '2B', '3B', 'SS', 'OF1', 'OF2', 'OF3', 'UTIL']
|
| 769 |
-
elif slate_type_var3 == 'Showdown':
|
| 770 |
-
map_columns = ['CPT', 'FLEX1', 'FLEX2', 'FLEX3', 'FLEX4']
|
| 771 |
-
for col_idx in map_columns:
|
| 772 |
-
export_file[col_idx] = export_file[col_idx].map(fd_id_map)
|
| 773 |
-
|
| 774 |
-
with st.container():
|
| 775 |
-
if st.button("Reset Optimals", key='reset3'):
|
| 776 |
-
for key in st.session_state.keys():
|
| 777 |
-
del st.session_state[key]
|
| 778 |
-
if site_var == 'Draftkings':
|
| 779 |
-
st.session_state.working_seed = dk_lineups.copy()
|
| 780 |
-
elif site_var == 'Fanduel':
|
| 781 |
-
st.session_state.working_seed = fd_lineups.copy()
|
| 782 |
-
if 'data_export_display' in st.session_state:
|
| 783 |
-
st.dataframe(st.session_state.data_export_display.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(precision=2), height=500, use_container_width = True)
|
| 784 |
-
st.download_button(
|
| 785 |
-
label="Export display optimals (IDs)",
|
| 786 |
-
data=convert_df(export_file),
|
| 787 |
-
file_name='MLB_display_optimals.csv',
|
| 788 |
-
mime='text/csv',
|
| 789 |
-
)
|
| 790 |
-
st.download_button(
|
| 791 |
-
label="Export display optimals (Names)",
|
| 792 |
-
data=convert_df(name_export),
|
| 793 |
-
file_name='MLB_display_optimals.csv',
|
| 794 |
-
mime='text/csv',
|
| 795 |
-
)
|
| 796 |
-
|
| 797 |
-
with st.container():
|
| 798 |
-
if slate_type_var3 == 'Regular':
|
| 799 |
-
if 'working_seed' in st.session_state:
|
| 800 |
-
# Create a new dataframe with summary statistics
|
| 801 |
-
if site_var == 'Draftkings':
|
| 802 |
-
summary_df = pd.DataFrame({
|
| 803 |
-
'Metric': ['Min', 'Average', 'Max', 'STDdev'],
|
| 804 |
-
'Salary': [
|
| 805 |
-
np.min(st.session_state.working_seed[:,10]),
|
| 806 |
-
np.mean(st.session_state.working_seed[:,10]),
|
| 807 |
-
np.max(st.session_state.working_seed[:,10]),
|
| 808 |
-
np.std(st.session_state.working_seed[:,10])
|
| 809 |
-
],
|
| 810 |
-
'Proj': [
|
| 811 |
-
np.min(st.session_state.working_seed[:,11]),
|
| 812 |
-
np.mean(st.session_state.working_seed[:,11]),
|
| 813 |
-
np.max(st.session_state.working_seed[:,11]),
|
| 814 |
-
np.std(st.session_state.working_seed[:,11])
|
| 815 |
-
],
|
| 816 |
-
'Own': [
|
| 817 |
-
np.min(st.session_state.working_seed[:,16]),
|
| 818 |
-
np.mean(st.session_state.working_seed[:,16]),
|
| 819 |
-
np.max(st.session_state.working_seed[:,16]),
|
| 820 |
-
np.std(st.session_state.working_seed[:,16])
|
| 821 |
-
]
|
| 822 |
-
})
|
| 823 |
-
elif site_var == 'Fanduel':
|
| 824 |
-
summary_df = pd.DataFrame({
|
| 825 |
-
'Metric': ['Min', 'Average', 'Max', 'STDdev'],
|
| 826 |
-
'Salary': [
|
| 827 |
-
np.min(st.session_state.working_seed[:,9]),
|
| 828 |
-
np.mean(st.session_state.working_seed[:,9]),
|
| 829 |
-
np.max(st.session_state.working_seed[:,9]),
|
| 830 |
-
np.std(st.session_state.working_seed[:,9])
|
| 831 |
-
],
|
| 832 |
-
'Proj': [
|
| 833 |
-
np.min(st.session_state.working_seed[:,10]),
|
| 834 |
-
np.mean(st.session_state.working_seed[:,10]),
|
| 835 |
-
np.max(st.session_state.working_seed[:,10]),
|
| 836 |
-
np.std(st.session_state.working_seed[:,10])
|
| 837 |
-
],
|
| 838 |
-
'Own': [
|
| 839 |
-
np.min(st.session_state.working_seed[:,15]),
|
| 840 |
-
np.mean(st.session_state.working_seed[:,15]),
|
| 841 |
-
np.max(st.session_state.working_seed[:,15]),
|
| 842 |
-
np.std(st.session_state.working_seed[:,15])
|
| 843 |
-
]
|
| 844 |
-
})
|
| 845 |
-
|
| 846 |
-
# Set the index of the summary dataframe as the "Metric" column
|
| 847 |
-
summary_df = summary_df.set_index('Metric')
|
| 848 |
-
|
| 849 |
-
# Display the summary dataframe
|
| 850 |
-
st.subheader("Optimal Statistics")
|
| 851 |
-
st.dataframe(summary_df.style.format({
|
| 852 |
-
'Salary': '{:.2f}',
|
| 853 |
-
'Proj': '{:.2f}',
|
| 854 |
-
'Own': '{:.2f}'
|
| 855 |
-
}).background_gradient(cmap='RdYlGn', axis=0, subset=['Salary', 'Proj', 'Own']), use_container_width=True)
|
| 856 |
-
|
| 857 |
-
with st.container():
|
| 858 |
-
tab1, tab2 = st.tabs(["Display Frequency", "Seed Frame Frequency"])
|
| 859 |
-
with tab1:
|
| 860 |
-
if 'data_export_display' in st.session_state:
|
| 861 |
-
if site_var == 'Draftkings':
|
| 862 |
-
if slate_type_var3 == 'Regular':
|
| 863 |
-
player_columns = st.session_state.data_export_display.iloc[:, :10]
|
| 864 |
-
elif slate_type_var3 == 'Showdown':
|
| 865 |
-
player_columns = st.session_state.data_export_display.iloc[:, :6]
|
| 866 |
-
elif site_var == 'Fanduel':
|
| 867 |
-
if slate_type_var3 == 'Regular':
|
| 868 |
-
player_columns = st.session_state.data_export_display.iloc[:, :9]
|
| 869 |
-
elif slate_type_var3 == 'Showdown':
|
| 870 |
-
player_columns = st.session_state.data_export_display.iloc[:, :5]
|
| 871 |
-
|
| 872 |
-
# Flatten the DataFrame and count unique values
|
| 873 |
-
value_counts = player_columns.values.flatten().tolist()
|
| 874 |
-
value_counts = pd.Series(value_counts).value_counts()
|
| 875 |
-
|
| 876 |
-
percentages = (value_counts / lineup_num_var * 100).round(2)
|
| 877 |
-
|
| 878 |
-
# Create a DataFrame with the results
|
| 879 |
-
summary_df = pd.DataFrame({
|
| 880 |
-
'Player': value_counts.index,
|
| 881 |
-
'Frequency': value_counts.values,
|
| 882 |
-
'Percentage': percentages.values
|
| 883 |
-
})
|
| 884 |
-
|
| 885 |
-
# Sort by frequency in descending order
|
| 886 |
-
summary_df['Salary'] = summary_df['Player'].map(player_salaries)
|
| 887 |
-
summary_df = summary_df[['Player', 'Salary', 'Frequency', 'Percentage']]
|
| 888 |
-
summary_df = summary_df.sort_values('Frequency', ascending=False)
|
| 889 |
-
summary_df = summary_df.set_index('Player')
|
| 890 |
-
|
| 891 |
-
# Display the table
|
| 892 |
-
st.write("Player Frequency Table:")
|
| 893 |
-
st.dataframe(summary_df.style.format({'Percentage': '{:.2f}%'}), height=500, use_container_width=True)
|
| 894 |
-
|
| 895 |
-
st.download_button(
|
| 896 |
-
label="Export player frequency",
|
| 897 |
-
data=convert_df_to_csv(summary_df),
|
| 898 |
-
file_name='MLB_player_frequency.csv',
|
| 899 |
-
mime='text/csv',
|
| 900 |
-
)
|
| 901 |
-
with tab2:
|
| 902 |
-
if 'working_seed' in st.session_state:
|
| 903 |
-
if site_var == 'Draftkings':
|
| 904 |
-
if slate_type_var3 == 'Regular':
|
| 905 |
-
player_columns = st.session_state.working_seed[:, :10]
|
| 906 |
-
elif slate_type_var3 == 'Showdown':
|
| 907 |
-
player_columns = st.session_state.working_seed[:, :7]
|
| 908 |
-
elif site_var == 'Fanduel':
|
| 909 |
-
if slate_type_var3 == 'Regular':
|
| 910 |
-
player_columns = st.session_state.working_seed[:, :9]
|
| 911 |
-
elif slate_type_var3 == 'Showdown':
|
| 912 |
-
player_columns = st.session_state.working_seed[:, :6]
|
| 913 |
-
|
| 914 |
-
# Flatten the DataFrame and count unique values
|
| 915 |
-
value_counts = player_columns.flatten().tolist()
|
| 916 |
-
value_counts = pd.Series(value_counts).value_counts()
|
| 917 |
-
|
| 918 |
-
percentages = (value_counts / len(st.session_state.working_seed) * 100).round(2)
|
| 919 |
-
# Create a DataFrame with the results
|
| 920 |
-
summary_df = pd.DataFrame({
|
| 921 |
-
'Player': value_counts.index,
|
| 922 |
-
'Frequency': value_counts.values,
|
| 923 |
-
'Percentage': percentages.values
|
| 924 |
-
})
|
| 925 |
-
|
| 926 |
-
# Sort by frequency in descending order
|
| 927 |
-
summary_df['Salary'] = summary_df['Player'].map(player_salaries)
|
| 928 |
-
summary_df = summary_df[['Player', 'Salary', 'Frequency', 'Percentage']]
|
| 929 |
-
summary_df = summary_df.sort_values('Frequency', ascending=False)
|
| 930 |
-
summary_df = summary_df.set_index('Player')
|
| 931 |
-
|
| 932 |
-
# Display the table
|
| 933 |
-
st.write("Seed Frame Frequency Table:")
|
| 934 |
-
st.dataframe(summary_df.style.format({'Percentage': '{:.2f}%'}), height=500, use_container_width=True)
|
| 935 |
-
|
| 936 |
-
st.download_button(
|
| 937 |
-
label="Export seed frame frequency",
|
| 938 |
-
data=convert_df_to_csv(summary_df),
|
| 939 |
-
file_name='MLB_seed_frame_frequency.csv',
|
| 940 |
-
mime='text/csv',
|
| 941 |
-
)
|
|
|
|
| 396 |
with tab2:
|
| 397 |
st.header("Player ROO")
|
| 398 |
with st.expander("Info and Filters"):
|
| 399 |
+
st.info("In this demo you'll be able to see information and statistics, but names/teams have been redacted. To see more information, grab a subscription! (https://paydirtdfs.com/subscriptions-choices/)")
|
| 400 |
with st.container():
|
| 401 |
slate_type_var2 = st.radio("Which slate type are you loading?", ('Regular', 'Showdown'), key='slate_type_var2')
|
| 402 |
slate_var2 = st.radio("Which slate data are you loading?", ('Main', 'Secondary', 'Auxiliary'), key='slate_var2')
|
|
|
|
| 629 |
|
| 630 |
with tab3:
|
| 631 |
st.header("Optimals")
|
| 632 |
+
VIDEO_URL = "https://www.youtube.com/watch?v=nCr-XKdXm0c"
|
| 633 |
+
st.video(VIDEO_URL)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|