aims-convert 2.3__tar.gz → 2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aims_convert-2.3 → aims_convert-2.4}/PKG-INFO +14 -3
- aims_convert-2.4/aims/csv_logbook_report.py +63 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/gui.py +13 -12
- aims_convert-2.4/aims/parse.py +24 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/PKG-INFO +14 -3
- {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/SOURCES.txt +2 -0
- {aims_convert-2.3 → aims_convert-2.4}/setup.py +2 -2
- aims_convert-2.4/tests/test_csv_logbook.py +55 -0
- aims_convert-2.3/aims/parse.py +0 -16
- {aims_convert-2.3 → aims_convert-2.4}/LICENSE +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/README.md +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/__init__.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/cli.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/data_structures.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/logbook_report.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/output.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/py.typed +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/roster.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims/version.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/dependency_links.txt +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/entry_points.txt +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/requires.txt +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/top_level.txt +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/pyproject.toml +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/setup.cfg +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/tests/test_compound.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/tests/test_logbook.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/tests/test_output.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/tests/test_parse.py +0 -0
- {aims_convert-2.3 → aims_convert-2.4}/tests/test_roster.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: aims-convert
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4
|
|
4
4
|
Summary: Extract useful information from AIMS
|
|
5
5
|
Home-page: https://github.com/JonHurst/aims-convert
|
|
6
6
|
Author: Jon Hurst
|
|
7
7
|
Author-email: jon.a@hursts.org.uk
|
|
8
|
-
Classifier: Development Status ::
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
9
|
Classifier: Environment :: Console
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -18,6 +18,17 @@ Requires-Dist: bs4
|
|
|
18
18
|
Requires-Dist: html5lib
|
|
19
19
|
Provides-Extra: unit-tests
|
|
20
20
|
Requires-Dist: freezegun; extra == "unit-tests"
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: provides-extra
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
21
32
|
|
|
22
33
|
# AIMS Roster Data Extraction #
|
|
23
34
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
from csv import DictReader
|
|
3
|
+
|
|
4
|
+
from aims.data_structures import Duty, Sector, CrewMember, AllDayEvent
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
HEADER = ("Date,Flight,DepPlace,DepTime,ArrPlace,ArrTime,ACType,Reg,"
|
|
8
|
+
"FltTime,PicName,TKoffsDay,TKoffsNight,LandsDay,LandsNight,"
|
|
9
|
+
"PIC,CoPlt,Instr,SimTime,SimType")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _sector(record: dict[str, str]) -> Sector:
|
|
13
|
+
date = dt.datetime.strptime(record["Date"], "%d/%m/%y").date()
|
|
14
|
+
off = dt.datetime.combine(
|
|
15
|
+
date,
|
|
16
|
+
dt.datetime.strptime(record["DepTime"], "%H:%M").time())
|
|
17
|
+
on = dt.datetime.combine(
|
|
18
|
+
date,
|
|
19
|
+
dt.datetime.strptime(record["ArrTime"], "%H:%M").time())
|
|
20
|
+
if on < off:
|
|
21
|
+
on += dt.timedelta(1)
|
|
22
|
+
return Sector(
|
|
23
|
+
name=record["Flight"],
|
|
24
|
+
reg=record["Reg"],
|
|
25
|
+
type_=record["ACType"],
|
|
26
|
+
from_=record["DepPlace"],
|
|
27
|
+
to=record["ArrPlace"],
|
|
28
|
+
off=off,
|
|
29
|
+
on=on,
|
|
30
|
+
quasi=False,
|
|
31
|
+
position=False,
|
|
32
|
+
crew=(CrewMember(record['PicName'], "CP"),)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _duty(sectors: tuple[Sector, ...]) -> Duty:
|
|
37
|
+
lowest_off = min([X.off for X in sectors])
|
|
38
|
+
highest_on = max([X.on for X in sectors])
|
|
39
|
+
return Duty(lowest_off - dt.timedelta(hours=1),
|
|
40
|
+
highest_on + dt.timedelta(minutes=30),
|
|
41
|
+
sectors)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def duties(csv) -> tuple[tuple[Duty, ...], tuple[AllDayEvent, ...]]:
|
|
45
|
+
if not csv.startswith(HEADER):
|
|
46
|
+
return ((), ())
|
|
47
|
+
sectors: list[Sector] = []
|
|
48
|
+
for record in DictReader(csv.splitlines()):
|
|
49
|
+
if record["SimTime"]:
|
|
50
|
+
pass # a sim sector
|
|
51
|
+
else:
|
|
52
|
+
sectors.append(_sector(record))
|
|
53
|
+
if len(sectors) == 0:
|
|
54
|
+
return ((), ())
|
|
55
|
+
groups = [[sectors[0]]]
|
|
56
|
+
last_on = sectors[0].on
|
|
57
|
+
for sector in sectors[1:]:
|
|
58
|
+
if sector.off - last_on > dt.timedelta(hours=10):
|
|
59
|
+
groups.append([sector])
|
|
60
|
+
else:
|
|
61
|
+
groups[-1].append(sector)
|
|
62
|
+
last_on = sector.on
|
|
63
|
+
return (tuple(_duty(tuple(X)) for X in groups), ())
|
|
@@ -301,13 +301,14 @@ class MainWindow(ttk.Frame):
|
|
|
301
301
|
self.txt.delete('1.0', tk.END)
|
|
302
302
|
messagebox.showerror("Error", str(e))
|
|
303
303
|
|
|
304
|
-
def
|
|
304
|
+
def __load_data(self):
|
|
305
305
|
retval = ""
|
|
306
306
|
path = self.settings.get('openPath')
|
|
307
307
|
fn = filedialog.askopenfilename(
|
|
308
308
|
filetypes=(
|
|
309
|
-
("
|
|
310
|
-
("
|
|
309
|
+
("AIMS file", "*.htm"),
|
|
310
|
+
("AIMS file", "*.html"),
|
|
311
|
+
("AIMS file", "*.csv"),
|
|
311
312
|
("All", "*.*")),
|
|
312
313
|
initialdir=path)
|
|
313
314
|
if fn:
|
|
@@ -321,24 +322,24 @@ class MainWindow(ttk.Frame):
|
|
|
321
322
|
|
|
322
323
|
def __csv(self):
|
|
323
324
|
txt = ""
|
|
324
|
-
|
|
325
|
-
if not
|
|
325
|
+
data = self.__load_data()
|
|
326
|
+
if not data:
|
|
326
327
|
return
|
|
327
328
|
self.txt.delete('1.0', tk.END)
|
|
328
329
|
self.txt.insert(tk.END, "Getting registration and type info...")
|
|
329
330
|
self.txt.update()
|
|
330
331
|
|
|
331
|
-
duties, _ = aims.parse.parse(
|
|
332
|
+
duties, _ = aims.parse.parse(data)
|
|
332
333
|
# note: normalise newlines for Text widget - will restore on output
|
|
333
334
|
txt = csv(duties).replace("\r\n", "\n")
|
|
334
335
|
self.txt.delete('1.0', tk.END)
|
|
335
336
|
self.txt.insert(tk.END, txt, 'csv')
|
|
336
337
|
|
|
337
338
|
def __ical(self):
|
|
338
|
-
|
|
339
|
-
if not
|
|
339
|
+
data = self.__load_data()
|
|
340
|
+
if not data:
|
|
340
341
|
return
|
|
341
|
-
duties, ade = aims.parse.parse(
|
|
342
|
+
duties, ade = aims.parse.parse(data)
|
|
342
343
|
# note: normalise newlines for Text widget - will restore on output
|
|
343
344
|
if not self.ms.with_ade.get():
|
|
344
345
|
ade = ()
|
|
@@ -347,13 +348,13 @@ class MainWindow(ttk.Frame):
|
|
|
347
348
|
self.txt.insert(tk.END, txt, 'ical')
|
|
348
349
|
|
|
349
350
|
def __efj(self):
|
|
350
|
-
|
|
351
|
-
if not
|
|
351
|
+
data = self.__load_data()
|
|
352
|
+
if not data:
|
|
352
353
|
return
|
|
353
354
|
self.txt.delete('1.0', tk.END)
|
|
354
355
|
self.txt.insert(tk.END, "Working…", 'efj')
|
|
355
356
|
self.txt.update()
|
|
356
|
-
duties, _ = aims.parse.parse(
|
|
357
|
+
duties, _ = aims.parse.parse(data)
|
|
357
358
|
txt = efj(duties)
|
|
358
359
|
self.txt.delete('1.0', tk.END)
|
|
359
360
|
self.txt.insert(tk.END, txt, 'efj')
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from aims.data_structures import Duty, AllDayEvent, InputFileException
|
|
2
|
+
import aims.roster
|
|
3
|
+
import aims.logbook_report
|
|
4
|
+
import aims.csv_logbook_report
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse(in_: str) -> tuple[tuple[Duty, ...], tuple[AllDayEvent, ...]]:
|
|
8
|
+
if in_.startswith(
|
|
9
|
+
"Date,Flight,DepPlace,DepTime,ArrPlace,ArrTime,ACType,"
|
|
10
|
+
"Reg,FltTime,PicName,TKoffsDay,TKoffsNight,LandsDay,"
|
|
11
|
+
"LandsNight,PIC,CoPlt,Instr,SimTime,SimType"):
|
|
12
|
+
return aims.csv_logbook_report.duties(in_)
|
|
13
|
+
# check it's an html5 file
|
|
14
|
+
html5_header = "<!DOCTYPE html><html>"
|
|
15
|
+
if in_[:len(html5_header)] != html5_header:
|
|
16
|
+
raise InputFileException("Input format not supported.")
|
|
17
|
+
if in_.find("Personal Crew Schedule Report") != -1:
|
|
18
|
+
if in_.find("<nobr>Schedule Details</nobr>") != -1:
|
|
19
|
+
return aims.roster.duties(in_)
|
|
20
|
+
raise InputFileException("Horizontal Schedules not supported")
|
|
21
|
+
elif in_.find("Pilot Logbook") != -1:
|
|
22
|
+
return aims.logbook_report.duties(in_)
|
|
23
|
+
else:
|
|
24
|
+
raise InputFileException("Report type marker not found")
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: aims-convert
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4
|
|
4
4
|
Summary: Extract useful information from AIMS
|
|
5
5
|
Home-page: https://github.com/JonHurst/aims-convert
|
|
6
6
|
Author: Jon Hurst
|
|
7
7
|
Author-email: jon.a@hursts.org.uk
|
|
8
|
-
Classifier: Development Status ::
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
9
|
Classifier: Environment :: Console
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -18,6 +18,17 @@ Requires-Dist: bs4
|
|
|
18
18
|
Requires-Dist: html5lib
|
|
19
19
|
Provides-Extra: unit-tests
|
|
20
20
|
Requires-Dist: freezegun; extra == "unit-tests"
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: provides-extra
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
21
32
|
|
|
22
33
|
# AIMS Roster Data Extraction #
|
|
23
34
|
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
setup.py
|
|
5
5
|
aims/__init__.py
|
|
6
6
|
aims/cli.py
|
|
7
|
+
aims/csv_logbook_report.py
|
|
7
8
|
aims/data_structures.py
|
|
8
9
|
aims/gui.py
|
|
9
10
|
aims/logbook_report.py
|
|
@@ -19,6 +20,7 @@ aims_convert.egg-info/entry_points.txt
|
|
|
19
20
|
aims_convert.egg-info/requires.txt
|
|
20
21
|
aims_convert.egg-info/top_level.txt
|
|
21
22
|
tests/test_compound.py
|
|
23
|
+
tests/test_csv_logbook.py
|
|
22
24
|
tests/test_logbook.py
|
|
23
25
|
tests/test_output.py
|
|
24
26
|
tests/test_parse.py
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="aims-convert",
|
|
8
|
-
version="2.
|
|
8
|
+
version="2.4",
|
|
9
9
|
author="Jon Hurst",
|
|
10
10
|
author_email="jon.a@hursts.org.uk",
|
|
11
11
|
description="Extract useful information from AIMS",
|
|
@@ -14,7 +14,7 @@ setuptools.setup(
|
|
|
14
14
|
packages=setuptools.find_packages(),
|
|
15
15
|
url="https://github.com/JonHurst/aims-convert",
|
|
16
16
|
classifiers=[
|
|
17
|
-
"Development Status ::
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
18
|
"Environment :: Console",
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
("License :: OSI Approved :: "
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import datetime as dt
|
|
3
|
+
|
|
4
|
+
import aims.csv_logbook_report as report
|
|
5
|
+
from aims.data_structures import Duty, Sector, CrewMember
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestDuties(unittest.TestCase):
|
|
9
|
+
|
|
10
|
+
def test_standard(self):
|
|
11
|
+
src = """\
|
|
12
|
+
Date,Flight,DepPlace,DepTime,ArrPlace,ArrTime,ACType,Reg,FltTime,PicName,TKoffsDay,TKoffsNight,LandsDay,LandsNight,PIC,CoPlt,Instr,SimTime,SimType
|
|
13
|
+
22/08/26,2831,BRS,06:29,VCE,08:33,320,G-EZPX,02:04,HURST,,,,,02:04,,,,
|
|
14
|
+
22/08/26,2832,VCE,09:21,BRS,11:47,320,G-EZPX,02:26,HURST,,,,,02:26,,,,
|
|
15
|
+
23/08/26,2829,BRS,23:00,JSI,01:00,321,G-UZLT,03:16,HURST,,,,,03:16,,,,
|
|
16
|
+
09/11/25,,LGW,06:30,LGW,10:30,319,,,,,,,,,,,04:00,E2D1
|
|
17
|
+
"""
|
|
18
|
+
s1 = Sector("2831", "G-EZPX", "320", "BRS", "VCE",
|
|
19
|
+
dt.datetime(2026, 8, 22, 6, 29),
|
|
20
|
+
dt.datetime(2026, 8, 22, 8, 33),
|
|
21
|
+
False, False,
|
|
22
|
+
(CrewMember("HURST", "CP"),))
|
|
23
|
+
s2 = Sector("2832", "G-EZPX", "320", "VCE", "BRS",
|
|
24
|
+
dt.datetime(2026, 8, 22, 9, 21),
|
|
25
|
+
dt.datetime(2026, 8, 22, 11, 47),
|
|
26
|
+
False, False,
|
|
27
|
+
(CrewMember("HURST", "CP"),))
|
|
28
|
+
s3 = Sector("2829", "G-UZLT", "321", "BRS", "JSI",
|
|
29
|
+
dt.datetime(2026, 8, 23, 23, 0),
|
|
30
|
+
dt.datetime(2026, 8, 24, 1, 0),
|
|
31
|
+
False, False,
|
|
32
|
+
(CrewMember("HURST", "CP"),))
|
|
33
|
+
expected = (
|
|
34
|
+
(Duty(
|
|
35
|
+
dt.datetime(2026, 8, 22, 5, 29),
|
|
36
|
+
dt.datetime(2026, 8, 22, 12, 17),
|
|
37
|
+
(s1, s2)),
|
|
38
|
+
Duty(
|
|
39
|
+
dt.datetime(2026, 8, 23, 22, 0),
|
|
40
|
+
dt.datetime(2026, 8, 24, 1, 30),
|
|
41
|
+
(s3,))),
|
|
42
|
+
())
|
|
43
|
+
self.assertEqual(report.duties(src), expected)
|
|
44
|
+
|
|
45
|
+
def test_empty(self):
|
|
46
|
+
src = """\
|
|
47
|
+
Date,Flight,DepPlace,DepTime,ArrPlace,ArrTime,ACType,Reg,FltTime,PicName,TKoffsDay,TKoffsNight,LandsDay,LandsNight,PIC,CoPlt,Instr,SimTime,SimType
|
|
48
|
+
"""
|
|
49
|
+
self.assertEqual(report.duties(src), ((), ()))
|
|
50
|
+
|
|
51
|
+
def test_bad(self):
|
|
52
|
+
src = "bad"
|
|
53
|
+
self.assertEqual(report.duties(src), ((), ()))
|
|
54
|
+
src = ""
|
|
55
|
+
self.assertEqual(report.duties(src), ((), ()))
|
aims_convert-2.3/aims/parse.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
from aims.data_structures import Duty, AllDayEvent, InputFileException
|
|
2
|
-
import aims.roster
|
|
3
|
-
import aims.logbook_report
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def parse(html: str) -> tuple[tuple[Duty, ...], tuple[AllDayEvent, ...]]:
|
|
7
|
-
# check it's an html5 file
|
|
8
|
-
html5_header = "<!DOCTYPE html><html>"
|
|
9
|
-
if html[:len(html5_header)] != html5_header:
|
|
10
|
-
raise InputFileException("HTML5 header not found.")
|
|
11
|
-
if html.find("Personal Crew Schedule Report") != -1:
|
|
12
|
-
return aims.roster.duties(html)
|
|
13
|
-
elif html.find("Pilot Logbook") != -1:
|
|
14
|
-
return aims.logbook_report.duties(html)
|
|
15
|
-
else:
|
|
16
|
-
raise InputFileException("Report type marker not found")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|