Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
I
integrated-scheduling-v3
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张晓彤
integrated-scheduling-v3
Commits
39eaec24
Commit
39eaec24
authored
Oct 14, 2022
by
张晓彤
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
代码优化v1
parent
efc064b5
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
252 additions
and
35 deletions
+252
-35
algorithm.py
alg/algorithm.py
+0
-0
config.json
config.json
+6
-5
dispatcher.py
core/dispatcher.py
+0
-0
group.py
core/group.py
+27
-10
dispatchInfo.py
data/dispatchInfo.py
+4
-1
para_config.py
data/para_config.py
+1
-0
realtime_dispatch_test.py
realtime_dispatch_test.py
+3
-4
settings.py
settings.py
+2
-2
test4.py
test/test4.py
+18
-0
text.ipynb
test/text.ipynb
+111
-0
topo_sql.py
test/topo_sql.py
+0
-0
topo_test.ipynb
test/topo_test.ipynb
+55
-8
core_exception.py
util/core_exception.py
+25
-5
No files found.
alg/algorithm.py
View file @
39eaec24
This diff is collapsed.
Click to expand it.
config.json
View file @
39eaec24
...
...
@@ -8,23 +8,23 @@
},
"mysql"
:
{
"host"
:
"1
72.16.0.103
"
,
"host"
:
"1
92.168.9.152
"
,
"port"
:
"3306"
,
"user"
:
"root"
,
"password"
:
"Huituo@123"
,
"database"
:
"
ht_zhunneng
"
"database"
:
"
waytous
"
},
"postgresql"
:
{
"host"
:
"1
72.16.0.103
"
,
"host"
:
"1
92.168.9.152
"
,
"port"
:
"5432"
,
"user"
:
"postgres"
,
"password"
:
"Huituo@123"
,
"database"
:
"
gis_zhunneng
"
"database"
:
"
shenbao_2021520
"
},
"redis"
:
{
"host"
:
"1
72.16.0.103
"
,
"host"
:
"1
92.168.9.152
"
,
"password"
:
"Huituo@123"
}
}
\ No newline at end of file
core/dispatcher.py
View file @
39eaec24
This diff is collapsed.
Click to expand it.
core/group.py
View file @
39eaec24
...
...
@@ -31,11 +31,12 @@ class CurrentTruck:
"""
def
__init__
(
self
,
truck_id
,
group_id
,
trip
,
task
):
def
__init__
(
self
,
truck_id
,
group_id
,
trip
,
task
,
state
):
self
.
_truck_id
=
truck_id
self
.
_group_id
=
group_id
self
.
_trip
=
trip
self
.
_task
=
task
self
.
_state
=
state
def
get_truck_id
(
self
):
return
self
.
_truck_id
...
...
@@ -49,6 +50,9 @@ class CurrentTruck:
def
get_group_id
(
self
):
return
self
.
_group_id
def
get_sate
(
self
):
return
self
.
_state
class
Group
:
"""
...
...
@@ -237,7 +241,7 @@ class Group:
self
.
update_group_road_network
()
self
.
update_device_material
()
def
group_dispatch
(
self
,
Solver
)
->
Mapping
[
str
,
List
[
str
,
str
]]:
def
group_dispatch
(
self
,
Solver
)
->
Mapping
[
str
,
List
[
str
]]:
"""
Receive a Alg obj. and output dispatch plan for trucks in this group.
:param Solver:
...
...
@@ -257,15 +261,18 @@ class Group:
self
.
logger
.
info
(
f
'调度车辆 {truck_id}'
)
# try:
# get truck index from mapping
try
:
if
truck_id
not
in
self
.
truck
.
truck_uuid_to_index_dict
:
raise
CoreException
(
102
,
f
'truck.truck dict 中不存在 {truck_id}'
)
truck_idx
=
self
.
truck
.
truck_uuid_to_index_dict
[
truck_id
]
except
CoreException
as
es
:
es
.
with_traceback_info
()
self
.
logger
.
error
(
es
)
continue
# get truck trip from truck obj.
try
:
truck_trip_info_list
=
self
.
truck
.
get_truck_current_trip
()
...
...
@@ -274,10 +281,11 @@ class Group:
truck_trip
=
self
.
truck
.
get_truck_current_trip
()[
truck_idx
]
except
CoreException
as
es
:
es
.
with_traceback_info
()
self
.
logger
.
error
(
es
)
continue
# TODO: 怎么知道异常行数
# get truck task from truck obj.
try
:
truck_task_list
=
self
.
truck
.
get_truck_current_task
()
...
...
@@ -286,12 +294,15 @@ class Group:
truck_task
=
truck_task_list
[
truck_id
]
except
CoreException
as
ce
:
self
.
logger
.
error
(
ce
)
except
CoreException
as
es
:
es
.
with_traceback_info
()
self
.
logger
.
error
(
es
)
continue
# Construct a truck obj.
truck_info
=
CurrentTruck
(
truck_id
,
self
.
group_id
,
truck_trip
,
truck_task
)
truck_info
=
CurrentTruck
(
truck_id
,
self
.
group_id
,
truck_trip
,
truck_task
,
self
.
truck
.
get_truck_current_state
()[
truck_id
])
self
.
truck_info_list
[
truck_id
]
=
truck_info
# Construct a test case for redispatch
...
...
@@ -407,12 +418,19 @@ class Group:
self
.
logger
.
error
(
"空载车辆空车智能模式-计算异常"
)
self
.
logger
.
error
(
es
)
def
full_dynamic_mode
(
self
,
i
:
str
,
s
:
AlgorithmBase
,
truck_dispatch
:
Mapping
[
str
,
List
[
str
,
str
]],
truck_info
:
CurrentTruck
,
truck_task
:
int
,
truck_trip
:
List
[
int
,
int
]):
def
full_dynamic_mode
(
self
,
i
:
str
,
s
:
AlgorithmBase
,
truck_dispatch
:
Mapping
[
str
,
List
[
str
]],
truck_info
:
CurrentTruck
,
truck_task
:
int
,
truck_trip
:
List
[
int
]):
# 车辆停止或者车辆位于卸载区内, 调度车辆前往装载区
if
truck_task
in
[
-
2
,
4
,
5
]:
try
:
if
i
in
self
.
truck
.
truck_excavator_bind
:
try
:
if
i
not
in
self
.
truck
.
truck_excavator_bind
:
raise
CoreException
(
102
,
f
'truck.excavator bind 中不存在 {i}'
)
except
CoreException
as
es
:
es
.
with_traceback_info
()
self
.
logger
.
error
(
es
)
return
next_excavator_id
=
self
.
truck
.
truck_excavator_bind
[
i
]
else
:
next_excavator_value
=
s
.
solve
(
truck_info
)
...
...
@@ -527,8 +545,7 @@ class Group:
next_lane_list
=
get_cross_next_lanes
(
truck_locate
)
# 交叉口下一路段可达的装载区
next_lane_load_area_dict
=
get_lane_reach_load_areas
(
load_area_lane_dict
,
next_lane_list
)
next_lane_load_area_dict
=
get_lane_reach_load_areas
(
load_area_lane_dict
,
next_lane_list
)
# 排除下一个路段阻塞的装载区
delete_congestion_load_area
(
congestion_lane_dict
,
load_area_dict
,
...
...
data/dispatchInfo.py
View file @
39eaec24
...
...
@@ -479,12 +479,15 @@ class DispatchInfo:
return
cls
.
unload_area_uuid_to_index_dict
[
group_id
]
@classmethod
def
get_all_group
(
cls
):
def
get_all_group
s_id
(
cls
):
return
set
(
cls
.
group_excavator_dict
.
keys
())
@classmethod
def
get_group_mode
(
cls
,
group_id
):
if
group_id
in
cls
.
group_mode
:
return
cls
.
group_mode
[
group_id
]
else
:
return
None
@classmethod
def
get_excavator
(
cls
,
group_id
):
...
...
data/para_config.py
View file @
39eaec24
...
...
@@ -445,6 +445,7 @@ def global_period_para_update():
unload_area_index_to_uuid_dict
,
)
=
build_work_area_uuid_index_map
()
load_area_num
,
unload_area_num
=
len
(
load_area_uuid_to_index_dict
),
len
(
unload_area_uuid_to_index_dict
)
...
...
realtime_dispatch_test.py
View file @
39eaec24
...
...
@@ -63,8 +63,6 @@ def process(dispatcher):
# 更新周期参数
logger
.
info
(
"#####################################周期更新开始#####################################"
)
global_period_para_update
()
# 清空数据库缓存
session_mysql
.
commit
()
session_mysql
.
flush
()
...
...
@@ -73,6 +71,7 @@ def process(dispatcher):
session_postgre
.
commit
()
session_postgre
.
flush
()
# 更新全局参数信息
global_period_para_update
()
# 更新调度信息
...
...
@@ -86,6 +85,7 @@ def process(dispatcher):
DispatchInfo
.
update_group_name
()
# 打印日志信息
logger
.
info
(
"Dispatchinfo,更新后信息"
)
logger
.
info
(
"group_set"
)
logger
.
info
(
DispatchInfo
.
group_set
)
...
...
@@ -110,13 +110,12 @@ def process(dispatcher):
# logger.info(DispatchInfo.dump_group_dict)
# logger.info(DispatchInfo.truck_group_dict)
# 调度生成
dispatcher
.
period_update
()
dispatcher
.
group_generate
()
dispatcher
.
group_info_update
()
#
dispatcher.group_info_update()
dispatcher
.
group_dispatch
()
...
...
settings.py
View file @
39eaec24
...
...
@@ -67,8 +67,8 @@ def set_log():
# timefilehandler = logging.handlers.TimedRotatingFileHandler(log_path + "/dispatch.log", when='M', interval=1, backupCount=60)
filehandler
=
logging
.
handlers
.
RotatingFileHandler
(
log_path
+
"/dispatch.log"
,
maxBytes
=
30
*
1024
*
1024
,
backupCount
=
10
,
encoding
=
"utf-8"
)
#
filehandler = logging.handlers.RotatingFileHandler("./Logs/dispatch.log", maxBytes=3 * 1024 * 1024, backupCount=10, encoding="utf-8")
#
filehandler = logging.handlers.RotatingFileHandler(log_path + "/dispatch.log", maxBytes=30*1024*1024, backupCount=10, encoding="utf-8")
filehandler
=
logging
.
handlers
.
RotatingFileHandler
(
"./Logs/dispatch.log"
,
maxBytes
=
3
*
1024
*
1024
,
backupCount
=
10
,
encoding
=
"utf-8"
)
# 设置后缀名称,跟strftime的格式一样
filehandler
.
suffix
=
"
%
Y-
%
m-
%
d_
%
H-
%
M.log"
...
...
test/test4.py
0 → 100644
View file @
39eaec24
# import sys
# print(sys.path)
# %%
def
prime
(
p
):
for
i
in
range
(
3
,
p
+
1
):
is_prime
=
True
for
j
in
range
(
2
,
i
):
if
i
%
j
==
0
:
is_prime
=
False
break
if
is_prime
:
print
(
i
)
prime
(
10
)
\ No newline at end of file
test/text.ipynb
0 → 100644
View file @
39eaec24
{
"cells": [
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": true,
"pycharm": {
"is_executing": false
}
},
"outputs": [
{
"name": "stdout",
"text": [
"[-1.33, 122.22, 12.345]\n",
"230\n"
],
"output_type": "stream"
}
],
"source": [
"L = [-1.33, 122.22, 12.345]\n",
"\n",
"for i in L:\n",
" i = round(i, 2)\n",
"\n",
"print(L)\n",
"\n",
"def myfun(a, b, c):\n",
" return a * b + c\n",
"\n",
"x, y, z = 10, 20, 30\n",
"res = myfun(x, y, z)\n",
"print(res)\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"outputs": [
{
"name": "stdout",
"text": [
"3\n",
"5\n",
"7\n"
],
"output_type": "stream"
}
],
"source": [
"def prime(p):\n",
" for i in range(3, p + 1):\n",
" # is_prime = True\n",
" for j in range(2, i):\n",
" if i % j == 0:\n",
" is_prime = False\n",
" break\n",
" # if is_prime:\n",
" print(i)\n",
" \n",
"prime(10)"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n",
"is_executing": false
}
}
}
],
"metadata": {
"kernelspec": {
"name": "pycharm-acbff253",
"language": "python",
"display_name": "PyCharm (integrated-scheduling-v3)"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
},
"pycharm": {
"stem_cell": {
"cell_type": "raw",
"source": [],
"metadata": {
"collapsed": false
}
}
}
},
"nbformat": 4,
"nbformat_minor": 0
}
\ No newline at end of file
topo_sql.py
→
t
est/t
opo_sql.py
View file @
39eaec24
File moved
topo_test.ipynb
→
t
est/t
opo_test.ipynb
View file @
39eaec24
...
...
@@ -13,14 +13,28 @@
},
{
"cell_type": "code",
"execution_count":
55
,
"execution_count":
4
,
"metadata": {
"pycharm": {
"is_executing": false,
"name": "#%%\n"
}
},
"outputs": [],
"outputs": [
{
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mFileNotFoundError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_28476\\3885974955.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mfrom\u001b[0m \u001b[0mgraph\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtopo_graph\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[1;33m*\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mnetworkx\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mnx\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mmatplotlib\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpyplot\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mplt\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0muuid\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0msqlite3\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;32mE:\\Pycharm Projects\\Waytous\\integrated-scheduling-v4.0\\integrated-scheduling-v3.1\\graph\\topo_graph.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# from para_config import *\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32mfrom\u001b[0m \u001b[0msettings\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[1;33m*\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mnumpy\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0msched\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;32mE:\\Pycharm Projects\\Waytous\\integrated-scheduling-v4.0\\integrated-scheduling-v3.1\\settings.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 22\u001b[0m \u001b[0mjson_file\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m\"config.json\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 23\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 24\u001b[1;33m \u001b[1;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mjson_file\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 25\u001b[0m \u001b[0mpara_config\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mjson\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mload\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"para\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 26\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'config.json'"
],
"ename": "FileNotFoundError",
"evalue": "[Errno 2] No such file or directory: 'config.json'",
"output_type": "error"
}
],
"source": [
"from graph.topo_graph import *\n",
"import networkx as nx\n",
...
...
@@ -38,7 +52,7 @@
},
{
"cell_type": "code",
"execution_count":
61
,
"execution_count":
2
,
"metadata": {
"pycharm": {
"is_executing": false,
...
...
@@ -47,11 +61,15 @@
},
"outputs": [
{
"name": "stdout",
"text": [
"数据库打开成功\n"
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_28476\\1331772586.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mconn\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0msqlite3\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mconnect\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'test.db'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[0mc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mconn\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcursor\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mprint\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;34m\"数据库打开成功\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mNameError\u001b[0m: name 'sqlite3' is not defined"
],
"output_type": "stream"
"ename": "NameError",
"evalue": "name 'sqlite3' is not defined",
"output_type": "error"
}
],
"source": [
...
...
@@ -73,7 +91,7 @@
},
{
"cell_type": "code",
"execution_count":
70
,
"execution_count":
null
,
"metadata": {
"pycharm": {
"is_executing": false,
...
...
@@ -862,6 +880,35 @@
"is_executing": false
}
}
},
{
"cell_type": "code",
"execution_count": 7,
"outputs": [
{
"name": "stdout",
"text": [
"positive\n"
],
"output_type": "stream"
}
],
"source": [
"num = 10\n",
"if num > 0:\n",
" print(\"positive\")\n",
"elif num == 0:\n",
" print(\"zero\")\n",
"else:\n",
" print(\"negative\")"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n",
"is_executing": false
}
}
}
],
"metadata": {
...
...
util/core_exception.py
View file @
39eaec24
...
...
@@ -6,19 +6,39 @@
# @File : core_exception.py
# @Software: PyCharm
import
traceback
code_msg
=
{
101
:
"调度失败"
,
102
:
"车辆不存在或信息缺失"
,
103
:
"数组越界异常"
}
103
:
"数组越界异常"
,
104
:
"数组维度不一致"
,
105
:
"数组越界"
,
106
:
"挖机不存在或信息缺失"
}
class
CoreException
(
Exception
):
""" class for my exception."""
def
__init__
(
self
,
Code
=
None
,
ErrorInfo
=
None
):
def
__init__
(
self
,
Code
:
int
=
None
,
ErrorInfo
:
str
=
None
):
super
()
.
__init__
(
self
,
ErrorInfo
)
self
.
error_info
=
ErrorInfo
self
.
__
error_info
=
ErrorInfo
self
.
code
=
Code
self
.
__traceback
=
""
def
__str__
(
self
):
return
'{0:}-{1:}'
.
format
(
code_msg
[
self
.
code
],
self
.
error_info
)
def
__str__
(
self
)
->
str
:
return
'{0:}-{1:}
\n
{2:}'
.
format
(
code_msg
[
self
.
code
],
self
.
__error_info
,
self
.
__traceback
)
# def with_traceback(self, tb) -> BaseException:
# self.__traceback = tb
# return self
def
with_traceback_info
(
self
):
self
.
__traceback
=
traceback
.
format_exc
()
# try:
# raise CoreException(101, "CoreException")
# except CoreException as ce:
# ce.with_traceback_info()
# # print(traceback.format_exc())
# print(ce)
Write
Preview
Markdown
is supported
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