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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|