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.
Files changed (30) hide show
  1. {aims_convert-2.3 → aims_convert-2.4}/PKG-INFO +14 -3
  2. aims_convert-2.4/aims/csv_logbook_report.py +63 -0
  3. {aims_convert-2.3 → aims_convert-2.4}/aims/gui.py +13 -12
  4. aims_convert-2.4/aims/parse.py +24 -0
  5. {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/PKG-INFO +14 -3
  6. {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/SOURCES.txt +2 -0
  7. {aims_convert-2.3 → aims_convert-2.4}/setup.py +2 -2
  8. aims_convert-2.4/tests/test_csv_logbook.py +55 -0
  9. aims_convert-2.3/aims/parse.py +0 -16
  10. {aims_convert-2.3 → aims_convert-2.4}/LICENSE +0 -0
  11. {aims_convert-2.3 → aims_convert-2.4}/README.md +0 -0
  12. {aims_convert-2.3 → aims_convert-2.4}/aims/__init__.py +0 -0
  13. {aims_convert-2.3 → aims_convert-2.4}/aims/cli.py +0 -0
  14. {aims_convert-2.3 → aims_convert-2.4}/aims/data_structures.py +0 -0
  15. {aims_convert-2.3 → aims_convert-2.4}/aims/logbook_report.py +0 -0
  16. {aims_convert-2.3 → aims_convert-2.4}/aims/output.py +0 -0
  17. {aims_convert-2.3 → aims_convert-2.4}/aims/py.typed +0 -0
  18. {aims_convert-2.3 → aims_convert-2.4}/aims/roster.py +0 -0
  19. {aims_convert-2.3 → aims_convert-2.4}/aims/version.py +0 -0
  20. {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/dependency_links.txt +0 -0
  21. {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/entry_points.txt +0 -0
  22. {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/requires.txt +0 -0
  23. {aims_convert-2.3 → aims_convert-2.4}/aims_convert.egg-info/top_level.txt +0 -0
  24. {aims_convert-2.3 → aims_convert-2.4}/pyproject.toml +0 -0
  25. {aims_convert-2.3 → aims_convert-2.4}/setup.cfg +0 -0
  26. {aims_convert-2.3 → aims_convert-2.4}/tests/test_compound.py +0 -0
  27. {aims_convert-2.3 → aims_convert-2.4}/tests/test_logbook.py +0 -0
  28. {aims_convert-2.3 → aims_convert-2.4}/tests/test_output.py +0 -0
  29. {aims_convert-2.3 → aims_convert-2.4}/tests/test_parse.py +0 -0
  30. {aims_convert-2.3 → aims_convert-2.4}/tests/test_roster.py +0 -0
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: aims-convert
3
- Version: 2.3
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 :: 4 - Beta
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 __roster_html(self):
304
+ def __load_data(self):
305
305
  retval = ""
306
306
  path = self.settings.get('openPath')
307
307
  fn = filedialog.askopenfilename(
308
308
  filetypes=(
309
- ("HTML file", "*.htm"),
310
- ("HTML file", "*.html"),
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
- html = self.__roster_html()
325
- if not html:
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(html)
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
- html = self.__roster_html()
339
- if not html:
339
+ data = self.__load_data()
340
+ if not data:
340
341
  return
341
- duties, ade = aims.parse.parse(html)
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
- html = self.__roster_html()
351
- if not html:
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(html)
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&nbsp;Crew&nbsp;Schedule&nbsp;Report") != -1:
18
+ if in_.find("<nobr>Schedule&nbsp;Details</nobr>") != -1:
19
+ return aims.roster.duties(in_)
20
+ raise InputFileException("Horizontal Schedules not supported")
21
+ elif in_.find("Pilot&nbsp;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
1
+ Metadata-Version: 2.4
2
2
  Name: aims-convert
3
- Version: 2.3
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 :: 4 - Beta
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.3",
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 :: 4 - Beta",
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), ((), ()))
@@ -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&nbsp;Crew&nbsp;Schedule&nbsp;Report") != -1:
12
- return aims.roster.duties(html)
13
- elif html.find("Pilot&nbsp;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