Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
LHCData
lhc-sm-api
Commits
277eaa9b
Commit
277eaa9b
authored
Jun 23, 2022
by
Aleksandra Mnich
Browse files
Merge branch 'SIGMON-371_validation_ack' into 'dev'
[SIGMON-371] Validation comments ack See merge request
!187
parents
f8441d62
cb010f33
Pipeline
#4137367
failed with stages
in 16 minutes and 7 seconds
Changes
19
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
lhcsmapi/analysis/RbCircuitSchematic.py
View file @
277eaa9b
...
...
@@ -212,7 +212,8 @@ def get_magnets_visualisation(circuit_type: str, circuit_name: str, results_tabl
# join with results_table
if
'timestamp_iqps'
in
results_table
.
columns
:
sub_results_table
=
results_table
[[
'Position'
,
'I_Q_M'
,
'timestamp_iqps'
]]
sub_results_table
[
'datetime_iqps'
]
=
results_table
[
'timestamp_iqps'
].
apply
(
lambda
col
:
Time
.
to_string_short
(
col
))
sub_results_table
[
'datetime_iqps'
]
=
results_table
[
'timestamp_iqps'
].
apply
(
lambda
col
:
Time
.
to_string_short
(
col
))
else
:
sub_results_table
=
results_table
[[
'Position'
,
'I_Q_M'
]]
sub_results_table
[
'datetime_iqps'
]
=
np
.
nan
...
...
@@ -490,4 +491,4 @@ def draw_schematic(circuit_name: str,
fig
.
update_shapes
(
dict
(
xref
=
'x'
,
yref
=
'y'
))
fig
.
layout
.
update
(
showlegend
=
False
)
fig
.
show
()
\ No newline at end of file
fig
.
show
()
lhcsmapi/analysis/comparison.py
View file @
277eaa9b
...
...
@@ -18,7 +18,8 @@ def compare_features_to_reference(features_df: pd.DataFrame,
*
,
wildcard
=
None
,
timestamp
=
None
,
nominal_voltage
=
900
)
->
pd
.
DataFrame
:
nominal_voltage
=
900
,
precision
=
6
)
->
pd
.
DataFrame
:
""" Method comparing input features with reference features given as a [min, max] range of values for:
- Quench Heaters
...
...
@@ -39,6 +40,8 @@ def compare_features_to_reference(features_df: pd.DataFrame,
during the operation)
:param nominal_voltage: nominal QH voltage (10, 300, 900); 900 by default
:type nominal_voltage: int
:param precision: floating point precision to which values are compared
:type precision: int
:return: True if features are within min/max range from reference features, otherwise False
"""
...
...
@@ -61,8 +64,11 @@ def compare_features_to_reference(features_df: pd.DataFrame,
comparison_df
=
pd
.
concat
([
reference_df
[
common_columns
],
features_df
[
common_columns
]],
sort
=
True
)
comparison_df_t
=
comparison_df
.
T
comparison_df_t
[
'result'
]
=
comparison_df_t
.
apply
(
lambda
row
:
(
row
[
'act'
]
>=
row
[
'min'
])
and
(
row
[
'act'
]
<=
row
[
'max'
]),
def
_round
(
floating
):
return
round
(
floating
,
precision
)
comparison_df_t
[
'result'
]
=
comparison_df_t
.
apply
(
lambda
row
:
(
_round
(
row
[
'act'
])
>=
_round
(
row
[
'min'
]))
and
(
_round
(
row
[
'act'
])
<=
_round
(
row
[
'max'
])),
axis
=
1
)
return
comparison_df_t
...
...
@@ -73,7 +79,8 @@ def compare_difference_of_features_to_reference(features_df: pd.DataFrame,
circuit_type
:
str
,
system
:
str
,
wildcard
=
None
,
nominal_voltage
=
900
)
->
pd
.
DataFrame
:
nominal_voltage
=
900
,
precision
=
6
)
->
pd
.
DataFrame
:
""" Method comparing the difference of the input features and the reference ones to an acceptance range
:param features_df: input features
...
...
@@ -86,6 +93,8 @@ def compare_difference_of_features_to_reference(features_df: pd.DataFrame,
:param wildcard: wildcard in order to replace reference feature name
:param nominal_voltage: nominal QH voltage (10, 300, 900); 900 by default
:type nominal_voltage: int
:param precision: floating point precision to which values are compared
:type precision: int
:return: a DataFrame with comparison of features
"""
...
...
@@ -110,7 +119,7 @@ def compare_difference_of_features_to_reference(features_df: pd.DataFrame,
def
compare
(
row
):
if
np
.
isnan
(
row
[
'diff'
]):
return
np
.
nan
return
round
(
abs
(
row
[
'act'
]
-
row
[
'ref'
]),
6
)
<=
row
[
'diff'
]
return
round
(
abs
(
row
[
'act'
]
-
row
[
'ref'
]),
precision
)
<=
row
[
'diff'
]
comparison_df
=
comparison_df
.
T
comparison_df
[
'result'
]
=
comparison_df
.
apply
(
compare
,
axis
=
1
)
...
...
lhcsmapi/analysis/diode/DiodeLeadResistanceAnalysis.py
View file @
277eaa9b
...
...
@@ -309,7 +309,7 @@ def _filter_u_diode(dataframe: pd.DataFrame, circuit_name: str, source_qds: str)
if
circuit_name
.
startswith
(
'RQ'
):
crate_name
,
signal_name
=
MappingMetadata
.
get_crate_and_diode_signal_name_for_rq
(
circuit_name
,
magnet_name
)
if
signal_name
==
'U_REF_N1'
:
source_name
=
f
'DQQDS.
{
crate_name
}
.
{
magne
t_name
}
:U_REF_N1
'
source_name
=
f
'DQQDS.
{
crate_name
}
.
{
circui
t_name
}
'
return
dataframe
.
filter
(
regex
=
f
'
{
source_name
}
:
{
signal_name
}
'
)
...
...
lhcsmapi/analysis/qds/QdsAnalysis.py
View file @
277eaa9b
...
...
@@ -1237,9 +1237,14 @@ class QdsRqAnalysis(QdsAnalysis):
ax
[
1
][
1
].
set_xlim
((
-
0.1
,
0.1
))
plt
.
show
()
def
analyze_qds_run3
(
self
,
source_timestamp_qds_df
:
pd
.
DataFrame
,
circuit_names
:
List
[
str
],
iqps_analog_dfs
:
List
[
pd
.
DataFrame
],
iqps_digital_dfs
:
List
[
pd
.
DataFrame
],
u_nqps_rqd_dfs
:
List
[
pd
.
DataFrame
],
u_nqps_rqf_dfs
:
List
[
pd
.
DataFrame
],
start_point
=
100
)
->
None
:
def
analyze_qds_run3
(
self
,
source_timestamp_qds_df
:
pd
.
DataFrame
,
circuit_names
:
List
[
str
],
iqps_analog_dfs
:
List
[
pd
.
DataFrame
],
iqps_digital_dfs
:
List
[
pd
.
DataFrame
],
u_nqps_rqd_dfs
:
List
[
pd
.
DataFrame
],
u_nqps_rqf_dfs
:
List
[
pd
.
DataFrame
],
start_point
=
100
)
->
None
:
""" Method analyzing QDS signals in RQ circuit. It is plotting all signals and calculating:
- quench origin (RQD/RQF, INT/EXT)
- time derivative of the U_QS0 signal
...
...
lhcsmapi/analysis/qh/QuenchHeaterVoltageAnalysis.py
View file @
277eaa9b
...
...
@@ -215,7 +215,8 @@ class QuenchHeaterVoltageAnalysis(CircuitAnalysis):
circuit_type
,
'QH'
,
wildcard
=
{
wildcard_key
:
source_qh
},
nominal_voltage
=
nominal_voltage
)
nominal_voltage
=
nominal_voltage
,
precision
=
1
)
@
staticmethod
def
calculate_char_time
(
timestamp_qh
,
u_hds_dfs
:
List
[
pd
.
DataFrame
])
->
pd
.
DataFrame
:
...
...
@@ -256,7 +257,8 @@ class QuenchHeaterVoltageAnalysis(CircuitAnalysis):
circuit_type
,
'QH'
,
wildcard
=
{
wildcard_key
:
source_qh
},
nominal_voltage
=
nominal_voltage
)
nominal_voltage
=
nominal_voltage
,
precision
=
3
)
@
staticmethod
def
plot_voltage_with_ref
(
source_qh
:
str
,
timestamp_qh
:
int
,
timestamp_ref
:
int
,
u_hds_dfs
:
List
[
pd
.
DataFrame
],
...
...
@@ -294,13 +296,13 @@ class QuenchHeaterVoltageAnalysis(CircuitAnalysis):
:return: None
"""
row_color_dct
=
{
False
:
'background-color: red'
,
True
:
''
}
display_html
(
first_last_u_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
)
\
.
set_caption
(
'Comparison of the inititial and final voltage'
)
\
.
apply
(
lambda
s
:
first_last_u_comp_df
[
'result'
].
map
(
row_color_dct
)).
_repr_html_
()
\
+
tau_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
)
\
.
set_caption
(
'Comparison of the discharge characteristic time to the reference'
)
\
.
apply
(
lambda
s
:
tau_comp_df
[
'result'
].
map
(
row_color_dct
)).
_repr_html_
()
,
raw
=
True
)
display_html
(
first_last_u_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
)
.
set_caption
(
'Comparison of the inititial and final voltage'
)
.
apply
(
lambda
s
:
first_last_u_comp_df
[
'result'
].
map
(
row_color_dct
)).
set_precision
(
1
).
render
()
+
tau_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
)
.
set_caption
(
'Comparison of the discharge characteristic time to the reference'
)
.
apply
(
lambda
s
:
tau_comp_df
[
'result'
].
map
(
row_color_dct
)).
set_precision
(
3
).
render
()
,
raw
=
True
)
def
get_analysis_result
(
self
):
print
(
self
.
analysis_result
.
toJSON
())
...
...
lhcsmapi/analysis/qh/QuenchHeaterVoltageCurrentAnalysis.py
View file @
277eaa9b
...
...
@@ -170,8 +170,11 @@ class QuenchHeaterVoltageCurrentAnalysis(QuenchHeaterVoltageAnalysis):
capacitance_df
=
self
.
calculate_capacitance
(
tau_df
,
first_r_df
)
capacitance_ref_df
=
self
.
calculate_capacitance
(
tau_ref_df
,
first_r_ref_df
)
capacitance_comp_df
=
comparison
.
compare_difference_of_features_to_reference
(
capacitance_df
,
capacitance_ref_df
,
self
.
circuit_type
,
'QH'
)
capacitance_comp_df
=
comparison
.
compare_difference_of_features_to_reference
(
capacitance_df
,
capacitance_ref_df
,
self
.
circuit_type
,
'QH'
,
precision
=
3
)
# Display comparison
self
.
display_features
(
first_last_u_comp_df
,
first_r_comp_df
,
tau_comp_df
,
capacitance_comp_df
)
...
...
@@ -282,18 +285,18 @@ class QuenchHeaterVoltageCurrentAnalysis(QuenchHeaterVoltageAnalysis):
"""
row_color_dct
=
{
False
:
'background-color: red'
,
True
:
''
,
np
.
nan
:
''
}
display_html
(
first_last_u_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
).
set_caption
(
'Comparison of the inititial and final voltage'
).
apply
(
lambda
s
:
first_last_u_comp_df
[
'result'
]
\
.
map
(
row_color_dct
)).
_repr_html_
()
+
first_r_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
).
set_caption
(
'Comparison of the initial resis
i
tance to the reference'
).
apply
(
lambda
s
:
first_r_comp_df
[
'result'
]
\
.
map
(
row_color_dct
)).
_repr_html_
()
+
tau_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
).
set_caption
(
'Comparison of the discharge characteristic time to the reference'
).
apply
(
lambda
s
:
tau_comp_df
[
'result'
]
\
.
map
(
row_color_dct
)).
_repr_html_
()
+
capacitance_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
).
set_caption
(
'Comparison of the estimated capacitance to the reference'
).
apply
(
lambda
s
:
capacitance_comp_df
[
'result'
]
\
.
map
(
row_color_dct
)).
_repr_html_
()
,
raw
=
True
)
'Comparison of the inititial and final voltage'
).
apply
(
lambda
s
:
first_last_u_comp_df
[
'result'
].
map
(
row_color_dct
)).
set_precision
(
1
).
render
()
+
first_r_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
).
set_caption
(
'Comparison of the initial resistance to the reference'
).
apply
(
lambda
s
:
first_r_comp_df
[
'result'
].
map
(
row_color_dct
)).
set_precision
(
2
).
render
()
+
tau_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
).
set_caption
(
'Comparison of the discharge characteristic time to the reference'
).
apply
(
lambda
s
:
tau_comp_df
[
'result'
].
map
(
row_color_dct
)).
set_precision
(
3
).
render
()
+
capacitance_comp_df
.
style
.
set_table_attributes
(
"style='display:inline'"
).
set_caption
(
'Comparison of the estimated capacitance to the reference'
).
apply
(
lambda
s
:
capacitance_comp_df
[
'result'
].
map
(
row_color_dct
)).
set_precision
(
3
).
render
()
,
raw
=
True
)
@
staticmethod
def
synchronize_raw_signal_to_decay
(
hds_dfs
:
List
[
pd
.
DataFrame
],
index_to_sync
:
float
)
->
List
[
pd
.
DataFrame
]:
...
...
@@ -451,7 +454,8 @@ class QuenchHeaterVoltageCurrentAnalysis(QuenchHeaterVoltageAnalysis):
circuit_type
,
'QH'
,
wildcard
=
{
'CELL'
:
source_qh
},
nominal_voltage
=
nominal_voltage
)
nominal_voltage
=
nominal_voltage
,
precision
=
2
)
@
staticmethod
def
calculate_capacitance
(
tau_df
:
pd
.
DataFrame
,
first_r_df
:
pd
.
DataFrame
)
->
pd
.
DataFrame
:
...
...
lhcsmapi/api/analysis/__init__.py
View file @
277eaa9b
from
.analysis
import
Analysis
,
AnalysisManager
,
AnalysisError
from
.analysis
import
Analysis
,
AnalysisManager
,
AnalysisError
lhcsmapi/gui/pc/fgc_pm_find_button/RbFgcPmFindButtonBaseModule.py
View file @
277eaa9b
...
...
@@ -18,10 +18,7 @@ class RbFgcPmFindButtonBaseModule(FgcPmFindButtonBaseModule):
:param circuit_name: circuit name
:return: a list of FGC source and timestamp tuples
"""
metadata_fgc
=
signal_metadata
.
get_signal_metadata
(
'RB'
,
circuit_name
,
'PC'
,
'PM'
,
metadata_fgc
=
signal_metadata
.
get_signal_metadata
(
'RB'
,
circuit_name
,
'PC'
,
'PM'
,
Time
.
to_unix_timestamp
(
start_time
))
quotient
,
remainder
=
Time
.
modulo_day_in_unix_timestamp
(
start_time
,
end_time
)
...
...
test/test_api/test_query.py
View file @
277eaa9b
...
...
@@ -186,10 +186,7 @@ class DataQueryTest(unittest.TestCase):
variable
=
'v1'
input_dataframe
=
self
.
spark
.
createDataFrame
([],
StructType
([]))
expected_result
=
pd
.
DataFrame
(
pd
.
DataFrame
({
variable
:
pd
.
Series
(
dtype
=
'str'
)
}))
expected_result
=
pd
.
DataFrame
(
pd
.
DataFrame
({
variable
:
pd
.
Series
(
dtype
=
'str'
)}))
# act
actual_result
=
transform_variables_dataset_to_pandas
(
input_dataframe
,
variable
)
# assert
...
...
@@ -529,17 +526,19 @@ def test_query_pm_events_with_parameters(mock_get):
pd
.
testing
.
assert_frame_equal
(
source_timestamp_ref_df
,
source_timestamp_act_df
)
@
mock
.
patch
(
'requests.get'
,
side_effect
=
mocked_pmdbsignal_requests_get
)
def
test_query_raw_pm_signals
(
mock_get
):
system
=
'FGC'
class_name
=
'51_self_pmd'
source
=
'RPTE.UA47.RB.A45'
timestamp
=
1426220469520000000
signal_name
=
[
'STATUS.I_MEAS'
]
signal_name
=
[
'STATUS.I_MEAS'
]
response
=
query_raw_pm_data
(
system
,
class_name
,
source
,
timestamp
,
signal_name
)
assert
signal_name
[
0
]
in
response
def
_mocked_pmdata_requests_get
(
*
args
,
**
kwargs
):
path
=
Path
(
os
.
path
.
dirname
(
__file__
))
...
...
@@ -548,13 +547,11 @@ def _mocked_pmdata_requests_get(*args, **kwargs):
self
.
text
=
text
self
.
status_code
=
status_code
json_path
=
os
.
path
.
join
(
path
.
parent
,
'resources/dbsignal/post_mortem/'
'pm_data_mock.json'
)
json_path
=
os
.
path
.
join
(
path
.
parent
,
'resources/dbsignal/post_mortem/'
'pm_data_mock.json'
)
with
open
(
json_path
)
as
json_file
:
return
MockedRequest
(
json
.
dumps
(
json
.
load
(
json_file
)))
@
mock
.
patch
(
'requests.get'
,
side_effect
=
_mocked_pmdata_requests_get
)
def
test_query_raw_pm_data
(
mock_get
):
system
=
'LBDS'
...
...
@@ -563,4 +560,4 @@ def test_query_raw_pm_data(mock_get):
timestamp
=
1430431203968406625
response
=
query_raw_pm_data
(
system
,
class_name
,
source
,
timestamp
)
assert
response
[
'acquisitionStamp'
]
==
timestamp
\ No newline at end of file
assert
response
[
'acquisitionStamp'
]
==
timestamp
test/test_gui/pc/fgc_pm_event_select/test_FgcPmEventSelectBaseModule.py
View file @
277eaa9b
...
...
@@ -4,7 +4,6 @@ from lhcsmapi.gui.pc.fgc_pm_event_select.FgcPmEventSelectBaseModuleFactory impor
class
TestFgcPmEventSelectBaseModule
(
TestCase
):
def
test_create_pm_circuit_name_timestamp_text
(
self
):
# arrange
fgc_pm_event
=
(
'RCS.A45B1'
,
1611941460800000000
)
...
...
test/test_gui/pc/fgc_pm_event_select/test_IpdFgcPmEventSelectBaseModule.py
View file @
277eaa9b
...
...
@@ -6,7 +6,6 @@ from lhcsmapi.gui.pc.fgc_pm_event_select.FgcPmEventSelectBaseModuleFactory impor
class
TestIpdFgcPmEventSelectBaseModule
(
TestCase
):
def
test_display_qps_circuit_schematic
(
self
):
# arrange
ipd_fgc_pm_module
=
FgcPmEventSelectBaseModuleFactory
.
create
(
'IPD'
)
...
...
test/test_gui/pc/fgc_pm_event_select/test_IpqFgcPmEventSelectBaseModule.py
View file @
277eaa9b
...
...
@@ -6,7 +6,6 @@ from lhcsmapi.gui.pc.fgc_pm_event_select.FgcPmEventSelectBaseModuleFactory impor
class
TestIpqFgcPmEventSelectBaseModule
(
TestCase
):
def
test_display_qps_circuit_schematic
(
self
):
# arrange
ipq_fgc_pm_module
=
FgcPmEventSelectBaseModuleFactory
.
create
(
'IPQ'
)
...
...
test/test_gui/pc/fgc_pm_event_select/test_ItFgcPmEventSelectBaseModule.py
View file @
277eaa9b
...
...
@@ -4,7 +4,6 @@ from lhcsmapi.gui.pc.fgc_pm_event_select.FgcPmEventSelectBaseModuleFactory impor
class
TestItFgcPmEventSelectBaseModule
(
TestCase
):
def
test_convert_fgc_pm_events_to_options
(
self
):
# arrange
fgc_pm_events
=
[(
'RQX.L1'
,
1535558373560000000
)]
...
...
test/test_gui/pc/fgc_pm_event_select/test_R600AFgcPmEventSelectBaseModule.py
View file @
277eaa9b
...
...
@@ -4,7 +4,6 @@ from lhcsmapi.gui.pc.fgc_pm_event_select.FgcPmEventSelectBaseModuleFactory impor
class
TestR600ARcbxhvFgcPmEventSelectBaseModule
(
TestCase
):
def
test_get_selected_circuit_name
(
self
):
# arrange
fgc_pm_event
=
(
'RCBXV1.R1'
,
1493219269220000000
,
'RCBXH1.R1'
,
1493219269240000000
)
...
...
@@ -33,7 +32,6 @@ class TestR600ARcbxhvFgcPmEventSelectBaseModule(TestCase):
class
TestR600ARcdoFgcPmEventSelectBaseModule
(
TestCase
):
def
test_get_selected_circuit_name
(
self
):
# arrange
fgc_pm_event
=
(
'RCO.A81B2'
,
1521459746200000000
,
'RCD.A81B2'
,
1521459746260000000
)
...
...
test/test_gui/pc/fgc_pm_event_select/test_RbFgcPmEventSelectBaseModule.py
View file @
277eaa9b
...
...
@@ -4,7 +4,6 @@ from lhcsmapi.gui.pc.fgc_pm_event_select.FgcPmEventSelectBaseModuleFactory impor
class
TestRbFgcPmEventSelectBaseModule
(
TestCase
):
def
test_convert_fgc_pm_events_to_options
(
self
):
# arrange
fgc_pm_events
=
[[
'RB.A12'
,
1544419882440000000
],
[
'RB.A12'
,
1544477282220000000
],
...
...
test/test_gui/pc/fgc_pm_event_select/test_RqFgcPmEventSelectBaseModule.py
View file @
277eaa9b
...
...
@@ -4,7 +4,6 @@ from lhcsmapi.gui.pc.fgc_pm_event_select.FgcPmEventSelectBaseModuleFactory impor
class
TestRqFgcPmEventSelectBaseModule
(
TestCase
):
def
test_convert_fgc_pm_events_to_options
(
self
):
# arrange
fgc_pm_events
=
[(
'RQD.A12'
,
1544456704520000000
,
'RQF.A12'
,
1544456704520000000
),
...
...
test/test_gui/pc/fgc_pm_find_button/IpdFgcPmFindButtonBaseModule.py
View file @
277eaa9b
...
...
@@ -4,7 +4,6 @@ from lhcsmapi.gui.pc.fgc_pm_find_button.IpdFgcPmFindButtonBaseModule import quer
class
TestIpdFgcPmFindButtonBaseModule
(
unittest
.
TestCase
):
def
test_query_pm_convert_pc_to_circuit_name_no_data
(
self
):
result
=
query_pm_convert_pc_to_circuit_name
(
1612310400000000000
,
1612396800000000000
,
'RD1.L2'
)
...
...
test/test_gui/pc/fgc_pm_find_button/ItFgcPmFindButtonBaseModule.py
View file @
277eaa9b
...
...
@@ -4,7 +4,6 @@ from lhcsmapi.gui.pc.fgc_pm_find_button.ItFgcPmFindButtonBaseModule import query
class
TestItFgcPmFindButtonBaseModule
(
unittest
.
TestCase
):
def
test_query_pm_convert_pc_to_circuit_name_no_data
(
self
):
result
=
query_pm_convert_pc_to_circuit_name
(
1611615600000000000
,
1612047600000000000
,
'RQX.L1'
)
...
...
test/test_gui/test_600AFgcPmSearchBaseModule.py
View file @
277eaa9b
...
...
@@ -7,7 +7,6 @@ from lhcsmapi.gui.pc.fgc_pm_find_button.R600AFgcPmFindButtonBaseModule import co
class
Test_600ARcbxhvFgcPmSearchBaseModule
(
unittest
.
TestCase
):
def
test_get_circuit_names_600A
(
self
):
# arrange
circuit_type
=
'600A'
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment