Skip to content

Commit

Permalink
feat: recovery features (sportorg#423)
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeikobelev committed Dec 20, 2023
1 parent 698011f commit 8d8435a
Show file tree
Hide file tree
Showing 9 changed files with 432 additions and 4 deletions.
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 @@ -1601,3 +1601,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

0 comments on commit 8d8435a

Please sign in to comment.