Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: recovery features #423

Merged
merged 12 commits into from
Dec 20, 2023
37 changes: 35 additions & 2 deletions languages/ru_RU/LC_MESSAGES/sportorg.po
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: SportOrg\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-12 11:13+0300\n"
"PO-Revision-Date: 2023-08-27 15:09+0500\n"
"PO-Revision-Date: 2023-12-17 19:16+0500\n"
"Last-Translator: SportOrg\n"
"Language-Team: SportOrg\n"
"Language: ru_RU\n"
Expand All @@ -12,7 +12,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Loco-Source-Locale: ru_RU\n"
"X-Generator: Poedit 3.3.2\n"
"X-Generator: Poedit 3.4.1\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Loco-Parser: loco_parse_po\n"
"X-Poedit-Basepath: ../../../sportorg\n"
Expand Down Expand Up @@ -1568,3 +1568,36 @@ msgstr "Ошибка открытия файла. Файл уже открыт

msgid "Multi day race"
msgstr "Многодневная гонка"

msgid "SportOrg HTML"
msgstr "Файл HTML, SportOrg"

msgid "SportOrg HTML report (*.html)"
msgstr "Файл HTML, SportOrg (*.html)"

msgid "Open SportOrg HTML file"
msgstr "Открыть файл HTML, SportOrg"

msgid "SportOrg SI log"
msgstr "Файл лога чипов, SportOrg"

msgid "SportOrg SI log (*.log)"
msgstr "Файл лога чипов, SportOrg (*.log)"

msgid "Open SportOrg SI log file"
msgstr "Открыть файл лога чипов, SportOrg"

msgid "SPORTident master station CSV"
msgstr "Файл CSV мастер-станции SPORTident"

msgid "CSV file (*.csv)"
msgstr "Файл CSV (*.csv)"

msgid "Open SPORTident master station backup file"
msgstr "Открыть файл CSV мастер-станции SPORTident"

msgid "Orgeo.ru CSV"
msgstr "Файл финиша orgeo.ru, CSV"

msgid "Open orgeo.ru finish CSV file"
msgstr "Открыть файл финиша orgeo.ru, CSV (https://orgeo.ru/event/export?event_id=XXX&sub_id=Y&format=excel_finish)"
4 changes: 2 additions & 2 deletions sportorg/gui/dialogs/file_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from sportorg.modules.configs.configs import Config, ConfigFile


def get_open_file_name(caption='', filter_text=''):
def get_open_file_name(caption='', filter_text='', set_dir=True):
result = QFileDialog.getOpenFileName(None, caption, get_default_dir(), filter_text)[
0
]
if result:
if result and set_dir:
set_default_dir(os.path.dirname(os.path.abspath(result)))
return result

Expand Down
55 changes: 55 additions & 0 deletions sportorg/gui/menu/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import socket
import time
import uuid
from os import remove
from typing import Any, Dict, Type

from PySide2 import QtCore
Expand Down Expand Up @@ -59,6 +60,12 @@
from sportorg.modules.live.live import live_client
from sportorg.modules.ocad import ocad
from sportorg.modules.ocad.ocad import OcadImportException
from sportorg.modules.recovery import (
recovery_orgeo_finish_csv,
recovery_si_master_csv,
recovery_sportorg_html,
recovery_sportorg_si_log,
)
from sportorg.modules.rfid_impinj.rfid_impinj import ImpinjClient
from sportorg.modules.sfr.sfrreader import SFRReaderClient
from sportorg.modules.sportident.sireader import SIReaderClient
Expand Down Expand Up @@ -345,6 +352,54 @@ def execute(self):
)


class RecoverySportorgHtmlAction(Action, metaclass=ActionFactory):
def execute(self):
file_name = get_open_file_name(
translate('Open SportOrg HTML file'),
translate('SportOrg HTML report (*.html)'),
False,
)
tmp_filename = recovery_sportorg_html.recovery(file_name)
with open(tmp_filename) as f:
attr = get_races_from_file(f)
SportOrgImportDialog(*attr).exec_()
remove(tmp_filename)
self.app.refresh()


class RecoverySportorgSiLogAction(Action, metaclass=ActionFactory):
def execute(self):
file_name = get_open_file_name(
translate('Open SportOrg SI log file'),
translate('SportOrg SI log (*.log)'),
False,
)
recovery_sportorg_si_log.recovery(file_name, race())
self.app.refresh()


class RecoverySportidentMasterCsvAction(Action, metaclass=ActionFactory):
def execute(self):
file_name = get_open_file_name(
translate('Open SPORTident master station backup file'),
translate('CSV file (*.csv)'),
False,
)
recovery_si_master_csv.recovery(file_name, race())
self.app.refresh()


class RecoveryOrgeoFinishCsvAction(Action, metaclass=ActionFactory):
def execute(self):
file_name = get_open_file_name(
translate('Open orgeo.ru finish CSV file'),
translate('CSV file (*.csv)'),
False,
)
recovery_orgeo_finish_csv.recovery(file_name, race())
self.app.refresh()


class AddObjectAction(Action, metaclass=ActionFactory):
def execute(self):
self.app.add_object()
Expand Down
16 changes: 16 additions & 0 deletions sportorg/gui/menu/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ def menu_list():
'title': translate('IOF xml'),
'action': 'IOFEntryListImportAction',
},
{
'title': translate('SPORTident master station CSV'),
'action': 'RecoverySportidentMasterCsvAction',
},
{
'title': translate('SportOrg SI log'),
'action': 'RecoverySportorgSiLogAction',
},
{
'title': translate('Orgeo.ru CSV'),
'action': 'RecoveryOrgeoFinishCsvAction',
},
{
'title': translate('SportOrg HTML'),
'action': 'RecoverySportorgHtmlAction',
},
],
},
{
Expand Down
112 changes: 112 additions & 0 deletions sportorg/modules/recovery/recovery_orgeo_finish_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""
Parse finish CSV from online-service orgeo.ru (2023)

-- Format:
CSV, separator ";"
SPLITS: [hh:mm:ss|code|]*
П/п;Группа;Фамилия, имя участника;Команда;№;Номер чипа;Место;Результат;Отст.;Время старта;[TV;90cp;]Сплиты;

-- Example:
1;Ж10;Лимонникова Анна;72_СШ №2 Кобелева;39;8510418;1;00:09:10;+00:00;12:33:00;00:01:41|59|00:00:55|60|00:01:14|61|
2;Ж10;Глухарева Светлана;72_СШ №2 Глухарева;35;2102481;2;00:11:06;+01:56;12:29:00;00:01:30|59|00:00:43|60|00:01:28|61|
18;Ж10;Радченко Милана;72_СШ №2 Кобелева;37;9111137;;не старт;;12:37:00;
33;Ж12;Аристова Надежда;55_Омская обл.;162;8517947;;непр.отмет.;;13:09:00;00:03:00|70|
"""
import csv

from sportorg.models.memory import (
Group,
Organization,
Person,
Race,
ResultSportident,
ResultStatus,
Split,
)
from sportorg.utils.time import hhmmss_to_time

POS_GROUP = 1
POS_NAME = 2
POS_TEAM = 3
POS_BIB = 4
POS_CARD = 5
POS_RES = 7
POS_START = 9
POS_SPLITS = -1

DNS_STATUS = ['DNS', 'не старт']
DSQ_STATUS = ['DSQ', 'непр.отмет.']


def recovery(file_name: str, race: Race) -> None:
encoding = 'cp1251'
separator = ';'
spl_separator = '|'

with open(file_name, encoding=encoding) as csv_file:
spam_reader = csv.reader(csv_file, delimiter=separator)
for tokens in spam_reader:
if len(tokens) <= POS_START:
continue

bib = tokens[POS_BIB]
if bib == '' or not bib.isdigit():
continue

name = tokens[POS_NAME]
person = Person()
spl_pos = name.find(' ')
if spl_pos > 0:
person.surname = name[:spl_pos]
person.name = name[spl_pos + 1 :]
else:
person.name = name
person.bib = int(bib)

team_name = tokens[POS_TEAM]
team = race.find_team(team_name)
if not team:
team = Organization()
team.name = team_name
race.organizations.append(team)
person.organization = team

group_name = tokens[POS_GROUP]
group = race.find_group(group_name)
if not group:
group = Group()
group.name = group_name
race.groups.append(group)
person.group = group

if len(tokens[POS_START]) > 0:
person.start_time = hhmmss_to_time(tokens[POS_START])

res = ResultSportident()
res.person = person
if tokens[POS_CARD].isdigit():
res.card_number = int(tokens[POS_CARD])
res.start_time = person.start_time
result = tokens[POS_RES]
if result.find(':') > 0:
result_value = hhmmss_to_time(result)
res.finish_time = res.start_time + result_value
else:
if result in DNS_STATUS:
res.status = ResultStatus.DID_NOT_START
elif result in DSQ_STATUS:
res.status = ResultStatus.DISQUALIFIED

splits = tokens[POS_SPLITS]
if len(splits) > 1:
splits_array = splits.split(spl_separator)
cur_time = person.start_time
for i in range(len(splits_array) // 2):
split = Split()
cur_time += hhmmss_to_time(splits_array[i * 2])
split.time = cur_time
split.code = int(splits_array[i * 2 + 1])
res.splits.append(split)

race.persons.append(person)
race.results.append(res)
62 changes: 62 additions & 0 deletions sportorg/modules/recovery/recovery_si_master_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Parse backup memory CSV file of BSM SPORTident station generated by SI Config Plus

-- Format:
CSV, separator ";"
Needed positions: card number (2), start (15), finish (21), splits (44): count of punches, (code + time) * n

No;Read on;SIID;Start no;Clear CN;Clear DOW;Clear time;Clear_r CN;Clear_r DOW;Clear_r time;Check CN;Check DOW;Check time
;Start CN;Start DOW;Start time;Start_r CN;Start_r DOW;Start_r time;Finish CN;Finish DOW;Finish time;Finish_r CN;Finish_r
DOW;Finish_r time;Class;First name;Last name;Club;Country;Email;Date of birth;Sex;Phone;Street;ZIP;City;Hardware version
;Software version;Battery date;Battery voltage;Clear count;Character set;SEL_FEEDBACK;No. of records;Record 1 CN;Record
1 DOW;Record 1 time;Record 2 CN;Record 2 DOW;Record 2 time;Record 3 CN;

-- Example:
440;2023-12-17 11:59:02;2007313;;2;Su; 12:40:50;;;;2;Su; 12:40:50;;;;;;;1;Su; 14:50:45;;;;;2007313;SPORTident Ru;;;;;;;;
;;;;;;;;;10;34;Su; 12:41:33;49;Su; 12:54:39;43;Su; 13:22:28;39;Su; 13:51:28;42;Su; 14:02:34;46;Su; 14:15:04;40;Su; 14:31
:03;47;Su; 14:39:54;37;Su; 14:47:02;90;Su; 14:49:21;

"""
import csv

from sportorg.common.otime import OTime
from sportorg.models.memory import Race, ResultSportident, Split
from sportorg.modules.sportident.fix_time_sicard_5 import fix_time
from sportorg.utils.time import hhmmss_to_time

POS_CARD = 2
POS_START = 15
POS_FINISH = 21
POS_COUNT = 44


def recovery(file_name: str, race: Race) -> None:
separator = ';'

zero_time_val = race.get_setting('system_zero_time', (8, 0, 0))
zero_time = OTime(
hour=zero_time_val[0], minute=zero_time_val[1], sec=zero_time_val[2]
)

with open(file_name, encoding='cp1251') as csv_file:
spam_reader = csv.reader(csv_file, delimiter=separator)
for tokens in spam_reader:
if tokens[0] == 'No' or len(tokens) < 45:
continue

res = ResultSportident()
res.card_number = int(tokens[POS_CARD])
res.start_time = hhmmss_to_time(tokens[POS_START])
res.finish_time = hhmmss_to_time(tokens[POS_FINISH])

punch_count = int(tokens[POS_COUNT])
existing_punches = (len(tokens) - POS_COUNT - 1) // 3

for i in range(min(punch_count, existing_punches)):
punch = Split()
punch.code = tokens[POS_COUNT + 3 * i + 1]
punch.time = hhmmss_to_time(tokens[POS_COUNT + 3 * i + 3])
res.splits.append(punch)

fix_time(res, zero_time)
race.results.append(res)
27 changes: 27 additions & 0 deletions sportorg/modules/recovery/recovery_sportorg_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Parse SportOrg HTML report (containing full json)

"""
import os.path
import string
from io import open
from random import choices
from tempfile import gettempdir


def recovery(file_name: str) -> str:
with open(file_name, 'r', encoding='utf-8') as f:
for line in f.readlines():
if line.find("var race = {\"courses\":") > -1:
json = line.strip()[11:-1]

# save json to tmp file and op[en with standard import action
tmp_filename = os.path.join(
gettempdir(),
f"sportorg_{''.join(choices(string.ascii_letters, k=10))}.json",
)
with open(tmp_filename, 'w') as temp_file:
temp_file.write(json)

return tmp_filename
return ""
Loading