def get_download_status(task_id: int): from db.tables import TblParticipant as P, TblTaskResult as R, TblTask as T from calcUtils import sec_to_time valid = [] missing = [] with db_session() as db: results = db.query(P.ID, P.name, R.result_type, R.distance_flown, R.SSS_time, R.ESS_time, R.goal_time) \ .join(T, P.comp_id == T.comp_id) \ .outerjoin(R, (R.par_id == P.par_id) & (R.task_id == T.task_id)) \ .filter(T.task_id == task_id).all() pilots = len(results) valid_results = [p for p in results if p.result_type not in ('abs', 'dnf', 'mindist', None)] for pilot in results: data = {'ID': pilot.ID, 'name': pilot.name} if pilot in valid_results: if pilot.ESS_time: time = sec_to_time(pilot.ESS_time - pilot.SSS_time) if pilot.result_type == 'goal': result = f'GOAL {time}' else: result = f"ESS {c_round(pilot.distance_flown / 1000, 2)} Km (~{time}~)" else: result = f"LO {c_round(pilot.distance_flown / 1000, 2)} Km" data['result'] = result valid.append(data) elif not pilot.result_type: missing.append(data) return pilots, valid, missing
def process_igc(task_id: int, par_id: int, tracklog): from pilot.track import create_igc_filename, igc_parsing_config_from_yaml from calcUtils import epoch_to_date from airspace import AirspaceCheck from igc_lib import Flight from task import Task from pilot.flightresult import FlightResult, save_track pilot = FlightResult.read(par_id, task_id) if pilot.name: task = Task.read(task_id) fullname = create_igc_filename(task.file_path, task.date, pilot.name) tracklog.save(fullname) pilot.track_file = Path(fullname).name else: return None, None """import track""" # track = Track(track_file=fullname, par_id=pilot.par_id) FlightParsingConfig = igc_parsing_config_from_yaml(task.igc_config_file) flight = Flight.create_from_file(fullname, config_class=FlightParsingConfig) """check result""" if not flight: error = f"for {pilot.name} - Track is not a valid track file" return None, error elif not epoch_to_date(flight.date_timestamp) == task.date: error = f"for {pilot.name} - Track has a different date from task date" return None, error else: print( f"pilot {pilot.par_id} associated with track {pilot.track_file} \n" ) """checking track against task""" if task.airspace_check: airspace = AirspaceCheck.from_task(task) else: airspace = None pilot.check_flight(flight, task, airspace_obj=airspace) print(f"track verified with task {task.task_id}\n") '''create map file''' pilot.save_tracklog_map_file(task, flight) """adding track to db""" # pilot.to_db() save_track(pilot, task.id) time = '' data = {'par_id': pilot.par_id, 'track_id': pilot.track_id} if pilot.goal_time: time = sec_to_time(pilot.ss_time) if pilot.result_type == 'goal': data['Result'] = f'Goal {time}' elif pilot.result_type == 'lo': data['Result'] = f"LO {round(pilot.distance / 1000, 2)}" if pilot.track_id: # if there is a track, make the result a link to the map # trackid = data['track_id'] parid = data['par_id'] result = data['Result'] data[ 'Result'] = f'<a href="/map/{parid}-{task.task_id}">{result}</a>' return data, None
def update_livetrack_file(result: FlightResult, flight: list, path: str): """ IGC Fix Format: B1132494613837N01248410EA0006900991 """ from calcUtils import sec_to_time, igc_coords file = Path(path, result.track_file) if file.is_file(): '''create fixes lines''' lines = '' for fix in flight: fixtime = sec_to_time(fix.rawtime).strftime("%H%M%S") lat, lon = igc_coords(fix.lat, fix.lon) baro_alt = str(int(fix.press_alt)).zfill(5) gnss_alt = str(int(fix.gnss_alt)).zfill(5) lines += f"B{fixtime}{lat}{lon}A{baro_alt}{gnss_alt}\n" '''append lines''' f = open(file, "a+") f.write(lines) f.close()
def get_pilot_list_for_track_management(taskid: int): from db.tables import TblTaskResult as R, TblParticipant as P, TblTask as T with db_session() as db: results = db.query(R.goal_time, R.track_file, R.track_id, R.result_type, R.distance_flown, R.ESS_time, R.SSS_time, R.par_id).filter(R.task_id == taskid).subquery() pilots = db.query(T.task_id, P.name, P.ID, P.par_id, results.c.track_id, results.c.SSS_time, results.c.ESS_time, results.c.distance_flown, results.c.track_file, results.c.result_type) \ .outerjoin(P, T.comp_id == P.comp_id).filter(T.task_id == taskid) \ .outerjoin(results, results.c.par_id == P.par_id).all() if pilots: pilots = [row._asdict() for row in pilots] all_data = [] for pilot in pilots: data = { 'ID': pilot['ID'], 'name': pilot['name'], 'par_id': pilot['par_id'], 'track_id': pilot['track_id'] } if pilot['track_file']: parid = data['par_id'] if pilot['ESS_time']: time = sec_to_time(pilot['ESS_time'] - pilot['SSS_time']) if pilot['result_type'] == 'goal': result = f'Goal {time}' else: result = f"ESS {round(pilot['distance_flown'] / 1000, 2)} Km (<del>{time}</del>)" else: result = f"LO {round(pilot['distance_flown'] / 1000, 2)} Km" data['Result'] = f'<a href="/map/{parid}-{taskid}">{result}</a>' elif pilot['result_type'] == "mindist": data['Result'] = "Min Dist" else: data['Result'] = "Not Yet Processed" if not pilot[ 'track_id'] else pilot['result_type'].upper() all_data.append(data) return all_data
def to_file(self): """ returns: - filename: STR - fsdb: FSDB xml data, to be used in frontend.""" formula = self.comp.formula pilots = self.comp.participants '''create dicts of attributes for each section''' comp_attr = { 'id': '', # still to do 'name': self.comp.comp_name, 'location': self.comp.comp_site, 'from': self.comp.date_from, 'to': self.comp.date_to, 'utc_offset': self.comp.time_offset / 3600, 'discipline': self.comp.comp_class.lower(), 'ftv_factor': round(1 - formula.validity_param, 2), 'fai_sanctioning': (1 if self.comp.sanction == 'FAI 1' else 2 if self.comp.sanction == 'FAI 2' else 0) } formula_attr = { 'id': formula.formula_name, 'min_dist': km(formula.min_dist, 1), 'nom_dist': km(formula.nominal_dist, 1), 'nom_time': formula.nominal_time / 3600, 'nom_launch': formula.nominal_launch, 'nom_goal': formula.nominal_goal, 'day_quality_override': 0, # still to implement 'bonus_gr': formula.glide_bonus, 'jump_the_gun_factor': (0 if formula.max_JTG == 0 else round( 1 / formula.JTG_penalty_per_sec, 1)), 'jump_the_gun_max': formula.max_JTG, 'normalize_1000_before_day_quality': 0, # still to implement 'time_points_if_not_in_goal': round(1 - formula.no_goal_penalty, 1), 'use_1000_points_for_max_day_quality': 0, # still to implement 'use_arrival_position_points': 1 if formula.formula_arrival == 'position' else 0, 'use_arrival_time_points': 1 if formula.formula_arrival == 'time' else 0, 'use_departure_points': 1 if formula.formula_departure == 'departure' else 0, 'use_difficulty_for_distance_points': 1 if formula.formula_distance == 'difficulty' else 0, 'use_distance_points': 1 if formula.formula_distance != 'off' else 0, 'use_distance_squared_for_LC': 1 if formula.comp_class == 'PG' else 0, # still to implement 'use_leading_points': 1 if formula.formula_departure == 'leadout' else 0, 'use_semi_circle_control_zone_for_goal_line': 1, # still to implement 'use_time_points': 1 if formula.formula_time == 'on' else 0, 'scoring_altitude': 'GPS' if formula.scoring_altitude == 'GPS' else 'QNH', 'final_glide_decelerator': 'none' if formula.arr_alt_bonus == 0 else 'aatb', 'no_final_glide_decelerator_reason': '', 'min_time_span_for_valid_task': 60 if self.comp_class == 'PG' else 0, # still to implement 'score_back_time': formula.score_back_time / 60, 'use_proportional_leading_weight_if_nobody_in_goal': '', # still to implement 'leading_weight_factor': (0 if formula.formula_departure != 'leadout' else round( formula.lead_factor, 3)), 'turnpoint_radius_tolerance': formula.tolerance, 'use_arrival_altitude_points': 0 if formula.arr_alt_bonus == 0 else '' # still to implement } if formula.arr_alt_bonus > 0: formula_attr['aatb_factor'] = round(formula.arr_alt_bonus, 3) '''create the file structure''' root = ET.Element('Fs') root.set('version', '3.5') root.set('comment', 'generated by AirScore') '''FsCompetition''' comp = ET.SubElement(root, 'FsCompetition') for k, v in comp_attr.items(): comp.set(k, str(v)) formula = ET.SubElement(comp, 'FsScoreFormula') for k, v in formula_attr.items(): formula.set(k, str(v)) notes = ET.SubElement(comp, 'FsCompetitionNotes') notes.text = CDATA('Generated by AirScore') # notes.text = '<![CDATA[Generated by AirScore]]>' '''FsParticipants''' participants = ET.SubElement(comp, 'FsParticipants') for p in pilots: pil = ET.SubElement(participants, 'FsParticipant') pilot_attr = { 'id': p.ID or p.par_id, 'name': p.name, 'birthday': p.pilot_birthdate_str, 'glider': p.glider, 'glider_main_colors': '', 'fai_licence': 1 if p.fai_id else 0, 'female': p.female, 'nat_code_3166_a3': p.nat, 'sponsor': p.sponsor, 'CIVLID': p.civl_id, } custom_attr = { 'fai_n': p.fai_id, 'class': p.glider_cert, 'team': p.team, 'LIVE': p.live_id } for k, v in pilot_attr.items(): pil.set(k, str(v)) cus = ET.SubElement(pil, 'FsCustomAttributes') for k, v in custom_attr.items(): sub = ET.SubElement(cus, 'FsCustomAttribute') sub.set('name', k) sub.set('value', str(v)) '''FsTasks''' tasks = ET.SubElement(comp, 'FsTasks') for idx, t in enumerate(self.tasks): task = ET.SubElement(tasks, 'FsTask') task.set('id', str(idx + 1)) task.set('name', t.task_name) task.set('tracklog_folder', '') task_f = ET.SubElement(task, 'FsScoreFormula') task_d = ET.SubElement(task, 'FsTaskDefinition') task_s = ET.SubElement(task, 'FsTaskState') task_p = ET.SubElement(task, 'FsParticipants') task_sp = ET.SubElement(task, 'FsTaskScoreParams') # tf = dict(t.formula.to_dict(), **t.stats) '''FsTaskState''' task_s.set('task_state', ('REGULAR' if not t.stopped_time else 'STOPPED')) # ? task_s.set('score_back_time', str(t.formula.score_back_time / 60)) task_s.set('cancel_reason', t.comment) '''FsScoreFormula''' # we permit just few changes in single tasks from comp formula, so we just update those tf_attr = formula_attr tf_attr.update({ 'jump_the_gun_factor': (0 if not t.formula.JTG_penalty_per_sec else round( 1 / t.formula.JTG_penalty_per_sec, 1)), 'time_points_if_not_in_goal': 1 - t.formula.no_goal_penalty, 'use_arrival_position_points': 1 if t.formula.arrival == 'position' else 0, 'use_arrival_time_points': 1 if t.formula.arrival == 'time' else 0, 'use_departure_points': 1 if t.formula.departure == 'departure' else 0, 'use_difficulty_for_distance_points': 1 if t.formula.distance == 'difficulty' else 0, 'use_distance_points': 0 if t.formula.distance == 'off' else 1, 'use_leading_points': 0 if t.formula.departure == 'off' else 1, 'use_time_points': 0 if t.formula.time == 'off' else 1, 'scoring_altitude': 'GPS' if t.formula.scoring_altitude == 'GPS' else 'QNH', 'final_glide_decelerator': 'none' if t.formula.arr_alt_bonus == 0 else 'aatb', 'use_arrival_altitude_points': 0 if t.formula.arr_alt_bonus == 0 else 1, 'turnpoint_radius_tolerance': t.formula.tolerance, }) for k, v in tf_attr.items(): task_f.set(k, str(v)) '''FsTaskDefinition''' tps = t.turnpoints td_attr = { 'ss': [i + 1 for i, tp in enumerate(tps) if tp.type == 'speed'].pop(0), 'es': [i + 1 for i, tp in enumerate(tps) if tp.type == 'endspeed'].pop(0), 'goal': next(tp.shape for tp in tps if tp.type == 'goal').upper(), 'groundstart': 0, # still to implement 'qnh_setting': 1013.25 # still to implement } for k, v in td_attr.items(): task_d.set(k, str(v)) t_open = get_isotime(t.date, t.window_open_time, t.time_offset) t_close = get_isotime(t.date, t.task_deadline, t.time_offset) ss_open = get_isotime(t.date, t.start_time, t.time_offset) if t.start_close_time: ss_close = get_isotime(t.date, t.start_close_time, t.time_offset) else: ss_close = t_close if t.window_close_time: w_close = get_isotime(t.date, t.window_close_time, t.time_offset) else: w_close = ss_close for i, tp in enumerate(tps): task_tp = ET.SubElement(task_d, 'FsTurnpoint') tp_attr = { 'id': tp.name, 'lat': round(tp.lat, 5), 'lon': round(tp.lon, 5), 'altitude': tp.altitude, 'radius': tp.radius, 'open': t_open if i < (td_attr['ss'] - 1) else ss_open, 'close': w_close if i == 0 else ss_close if i == (td_attr['ss'] - 1) else t_close } for k, v in tp_attr.items(): task_tp.set(k, str(v)) '''we add also FsTaskDistToTp during tp iteration''' sp_dist = ET.SubElement(task_sp, 'FsTaskDistToTp') sp_dist.set('tp_no', str(i + 1)) sp_dist.set('distance', str(t.partial_distance[i])) '''add start gates''' gates = 1 if t.SS_interval > 0: gates += t.start_iteration for i in range(gates): task_sg = ET.SubElement(task_d, 'FsStartGate') intv = 0 if not t.SS_interval else t.SS_interval * i i_time = get_isotime(t.date, (t.start_time + intv), t.time_offset) task_sg.set('open', str(i_time)) '''FsTaskScoreParams''' launch_ess = [ t.partial_distance[i] for i, tp in enumerate(t.turnpoints) if tp.type == 'endspeed' ].pop() sp_attr = { 'ss_distance': km(t.SS_distance), 'task_distance': km(t.opt_dist), 'launch_to_ess_distance': km(launch_ess), 'no_of_pilots_present': t.pilots_present, 'no_of_pilots_flying': t.pilots_launched, 'no_of_pilots_lo': t.pilots_launched - t.pilots_goal, 'no_of_pilots_reaching_nom_dist': len([ x for x in t.valid_results if x.distance_flown > t.formula.nominal_dist ]), 'no_of_pilots_reaching_es': t.pilots_ess, 'no_of_pilots_reaching_goal': t.pilots_goal, 'sum_flown_distance': km(t.tot_distance_flown), 'best_dist': km(t.max_distance or 0), 'best_time': round((t.fastest or 0) / 3600, 14), 'worst_time': round( max((x.ESS_time or 0) - (x.SSS_time or 0) for x in t.valid_results) / 3600, 14), 'no_of_pilots_in_competition': len(self.comp.participants), 'no_of_pilots_landed_before_stop': 0 if not t.stopped_time else t.pilots_landed, 'sum_dist_over_min': km(t.tot_dist_over_min), 'sum_real_dist_over_min': km(t.tot_dist_over_min), # not yet implemented 'best_real_dist': km(t.max_distance), # not yet implemented 'last_start_time': get_isotime( t.date, max([ x.SSS_time for x in t.valid_results if x.SSS_time is not None ]), t.time_offset), 'first_start_time': ('' if not t.min_dept_time else get_isotime( t.date, t.min_dept_time, t.time_offset)), 'first_finish_time': ('' if not t.min_ess_time else get_isotime( t.date, t.min_ess_time, t.time_offset)), 'max_time_to_get_time_points': round(0 / 3600, 14), # not yet implemented 'no_of_pilots_with_time_points': len([x for x in t.valid_results if x.time_score > 0]), 'goal_ratio': (0 if t.pilots_launched == 0 else round( t.pilots_goal / t.pilots_launched, 15)), 'arrival_weight': 0 if t.arrival == 0 else round(t.arr_weight, 3), 'departure_weight': 0 if t.departure != 'on' else round(t.dep_weight, 3), 'leading_weight': 0 if t.departure != 'leadout' else round(t.dep_weight, 3), 'time_weight': 0 if t.arrival == 'off' else round(t.time_weight, 3), 'distance_weight': round(t.dist_weight, 3), # not yet implemented 'smallest_leading_coefficient': round(t.min_lead_coeff, 14), 'available_points_distance': round(t.avail_dist_points, 14), 'available_points_time': round(t.avail_time_points, 14), 'available_points_departure': (0 if not t.formula.departure == 'departure' else round( t.avail_dep_points, 14)), 'available_points_leading': (0 if not t.formula.departure == 'leadout' else round( t.avail_dep_points, 14)), 'available_points_arrival': round(t.avail_arr_points, 14), 'time_validity': round(t.time_validity, 3), 'launch_validity': round(t.launch_validity, 3), 'distance_validity': round(t.dist_validity, 3), 'stop_validity': round(t.stop_validity, 3), 'day_quality': round(t.day_quality, 3), 'ftv_day_validity': t.ftv_validity, 'time_points_stop_correction': 0 # not yet implemented } for k, v in sp_attr.items(): task_sp.set(k, str(v)) '''FsParticipants''' for i, pil in enumerate(t.pilots): '''create pilot result for the task''' pil_p = ET.SubElement(task_p, 'FsParticipant') pil_p.set('id', str(pil.ID or pil.par_id)) if not (pil.result_type in ('abs', 'dnf', 'nyp')): '''only if pilot flew''' pil_fd = ET.SubElement(pil_p, 'FsFlightData') pil_r = ET.SubElement(pil_p, 'FsResult') if not (pil.result_type in ['mindist', 'min_dist']): fd_attr = { 'distance': km(pil.distance_flown), 'bonus_distance': km(pil.distance), # ?? seems 0 for PG and more than dist for HG 'started_ss': '' if not pil.real_start_time else get_isotime( t.date, pil.real_start_time, t.time_offset), 'finished_ss': '' if not pil.ESS_time else get_isotime( t.date, pil.ESS_time, t.time_offset), 'altitude_at_ess': pil.ESS_altitude, 'finished_task': '' if not pil.goal_time else get_isotime( t.date, pil.goal_time, t.time_offset), 'tracklog_filename': pil.track_file, 'lc': pil.lead_coeff, 'iv': pil.fixed_LC or '', 'ts': get_isotime(t.date, pil.first_time, t.time_offset), 'alt': pil.last_altitude, # ?? 'bonus_alt': '', # ?? not implemented 'max_alt': pil.max_altitude, 'last_tracklog_point_distance': '', # not implemented yet 'bonus_last_tracklog_point_distance': '', # ?? not implemented 'last_tracklog_point_time': get_isotime(t.date, pil.landing_time, t.time_offset), 'last_tracklog_point_alt': pil.landing_altitude, 'landed_before_deadline': '1' if pil.landing_time < (t.task_deadline if not t.stopped_time else t.stopped_time) else '0', 'reachedGoal': 1 if pil.goal_time else 0 # only deadline? } for k, v in fd_attr.items(): pil_fd.set(k, str(v)) r_attr = { 'rank': i + 1, # not implemented, they should be ordered tho # Rank IS NOT SAFE (I guess) 'points': round(pil.score), 'distance': km(pil.total_distance if pil.total_distance else pil. distance_flown), 'ss_time': '' if not pil.ss_time else sec_to_time( pil.ss_time).strftime('%H:%M:%S'), 'finished_ss_rank': '' if not pil.ESS_time and pil.ESS_rank else pil.ESS_rank, 'distance_points': 0 if not pil.distance_score else round( pil.distance_score, 1), 'time_points': 0 if not pil.time_score else round(pil.time_score, 1), 'arrival_points': 0 if not pil.arrival_score else round( pil.arrival_score, 1), 'departure_points': 0 if not t.formula.departure == 'departure' else round( pil.departure_score, 1), 'leading_points': 0 if not t.formula.departure == 'leadout' else round( pil.departure_score, 1), 'penalty': 0 if not [ n for n in pil.notifications if n.percentage_penalty > 0 ] else max(n.percentage_penalty for n in pil.notifications), 'penalty_points': 0 if not [ n for n in pil.notifications if n.flat_penalty > 0 ] else max(n.flat_penalty for n in pil.notifications), 'penalty_reason': '; '.join([ n.comment for n in pil.notifications if n.flat_penalty + n.percentage_penalty > 0 and not n.notification_type == 'jtg' ]), 'penalty_points_auto': sum(n.flat_penalty for n in pil.notifications if n.notification_type == 'jtg'), 'penalty_reason_auto': '' if not [ n for n in pil.notifications if n.notification_type == 'jtg' ] else next( n for n in pil.notifications if n.notification_type == 'jtg').flat_penalty, 'penalty_min_dist_points': 0, # ?? 'got_time_but_not_goal_penalty': (pil.ESS_time or 0) > 0 and not pil.goal_time, 'started_ss': '' if not pil.real_start_time else get_isotime( t.date, pil.SSS_time, t.time_offset), 'finished_ss': '' if not pil.ESS_time else get_isotime( t.date, pil.ESS_time, t.time_offset), 'ss_time_dec_hours': 0 if not pil.ESS_time else round( pil.ss_time / 3600, 14), 'ts': get_isotime(t.date, pil.first_time, t.time_offset), # flight origin time 'real_distance': km(pil.distance_flown), 'last_distance': '', # ?? last fix distance? 'last_altitude_above_goal': pil.last_altitude, 'altitude_bonus_seconds': 0, # not implemented 'altitude_bonus_time': sec_to_time(0).strftime('%H:%M:%S'), # not implemented 'altitude_at_ess': pil.ESS_altitude, 'scored_ss_time': ('' if not pil.ss_time else sec_to_time( pil.ss_time).strftime('%H:%M:%S')), 'landed_before_stop': t.stopped_time and res.landing_time < t.stopped_time } for k, v in r_attr.items(): pil_r.set(k, str(v)) '''creates the file to store''' fsdb = ET.tostring(root, pretty_print=True, xml_declaration=True, encoding='UTF-8') return self.filename, fsdb
def process_igc_background(task_id: int, par_id: int, file: Path, user: str): from pilot.track import igc_parsing_config_from_yaml from calcUtils import epoch_to_date from pilot.flightresult import FlightResult, save_track from airspace import AirspaceCheck from igc_lib import Flight from task import Task import json pilot = FlightResult.read(par_id, task_id) task = Task.read(task_id) print = partial(print_to_sse, id=par_id, channel=user) """import track""" # pilot.track = Track(track_file=filename, par_id=pilot.par_id) FlightParsingConfig = igc_parsing_config_from_yaml(task.igc_config_file) flight = Flight.create_from_file(file, config_class=FlightParsingConfig) data = { 'par_id': pilot.par_id, 'track_id': pilot.track_id, 'Result': 'Not Yet Processed' } """check result""" if not flight: print(f"for {pilot.name} - Track is not a valid track file") print(json.dumps(data) + '|result') return None if not flight.valid: print( f'IGC does not meet quality standard set by igc parsing config. Notes:{pilot.flight.notes}' ) print(json.dumps(data) + '|result') return None elif not epoch_to_date(flight.date_timestamp) == task.date: print(f"for {pilot.name} - Track has a different date from task date") print(json.dumps(data) + '|result') return None else: print(f"pilot {pilot.par_id} associated with track {file.name} \n") pilot.track_file = file.name """checking track against task""" if task.airspace_check: airspace = AirspaceCheck.from_task(task) else: airspace = None pilot.check_flight(flight, task, airspace_obj=airspace, print=print) print(f"track verified with task {task.task_id}\n") '''create map file''' pilot.save_tracklog_map_file(task, flight) """adding track to db""" # pilot.to_db() save_track(pilot, task.id) data['track_id'] = pilot.track_id time = '' if pilot.goal_time: time = sec_to_time(pilot.ESS_time - pilot.SSS_time) if pilot.result_type == 'goal': data['Result'] = f'Goal {time}' elif pilot.result_type == 'lo': data['Result'] = f"LO {round(pilot.distance / 1000, 2)}" if pilot.track_id: # if there is a track, make the result a link to the map # trackid = data['track_id'] parid = data['par_id'] result = data['Result'] data[ 'Result'] = f'<a href="/map/{parid}-{task.task_id}">{result}</a>' print(data['Result']) print(json.dumps(data) + '|result') print('***************END****************') return None