flight-alloc 0.0.1__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 (83) hide show
  1. flight_alloc-0.0.1/PKG-INFO +11 -0
  2. flight_alloc-0.0.1/README.md +231 -0
  3. flight_alloc-0.0.1/flight_alloc.egg-info/PKG-INFO +11 -0
  4. flight_alloc-0.0.1/flight_alloc.egg-info/SOURCES.txt +81 -0
  5. flight_alloc-0.0.1/flight_alloc.egg-info/dependency_links.txt +1 -0
  6. flight_alloc-0.0.1/flight_alloc.egg-info/entry_points.txt +2 -0
  7. flight_alloc-0.0.1/flight_alloc.egg-info/requires.txt +7 -0
  8. flight_alloc-0.0.1/flight_alloc.egg-info/top_level.txt +1 -0
  9. flight_alloc-0.0.1/pyproject.toml +28 -0
  10. flight_alloc-0.0.1/setup.cfg +4 -0
  11. flight_alloc-0.0.1/src/__init__.py +0 -0
  12. flight_alloc-0.0.1/src/allocator/__init__.py +6 -0
  13. flight_alloc-0.0.1/src/allocator/caps.py +513 -0
  14. flight_alloc-0.0.1/src/allocator/eligibility.py +302 -0
  15. flight_alloc-0.0.1/src/allocator/greedy_fallback.py +106 -0
  16. flight_alloc-0.0.1/src/allocator/invariants.py +124 -0
  17. flight_alloc-0.0.1/src/allocator/p2f_priority.py +142 -0
  18. flight_alloc-0.0.1/src/allocator/pair_validation.py +108 -0
  19. flight_alloc-0.0.1/src/allocator/pairings.py +554 -0
  20. flight_alloc-0.0.1/src/allocator/postpass_break.py +366 -0
  21. flight_alloc-0.0.1/src/allocator/postpass_intl.py +483 -0
  22. flight_alloc-0.0.1/src/allocator/postpass_p2f.py +723 -0
  23. flight_alloc-0.0.1/src/allocator/postpass_rebalance.py +244 -0
  24. flight_alloc-0.0.1/src/allocator/postsolve.py +549 -0
  25. flight_alloc-0.0.1/src/allocator/recommender.py +348 -0
  26. flight_alloc-0.0.1/src/allocator/windows.py +377 -0
  27. flight_alloc-0.0.1/src/cli.py +41 -0
  28. flight_alloc-0.0.1/src/config.py +168 -0
  29. flight_alloc-0.0.1/src/greedy_fallback.py +102 -0
  30. flight_alloc-0.0.1/src/io/__init__.py +0 -0
  31. flight_alloc-0.0.1/src/io/export.py +270 -0
  32. flight_alloc-0.0.1/src/io/export_xml.py +66 -0
  33. flight_alloc-0.0.1/src/io/readers.py +1048 -0
  34. flight_alloc-0.0.1/src/io/roster_library.py +89 -0
  35. flight_alloc-0.0.1/src/plan.py +192 -0
  36. flight_alloc-0.0.1/src/recommender_staffing.py +329 -0
  37. flight_alloc-0.0.1/src/roster_store.py +159 -0
  38. flight_alloc-0.0.1/src/schemas.py +1244 -0
  39. flight_alloc-0.0.1/src/solver/__init__.py +0 -0
  40. flight_alloc-0.0.1/src/solver/allocator_cpsat.py +1412 -0
  41. flight_alloc-0.0.1/src/staged_overrides.py +468 -0
  42. flight_alloc-0.0.1/src/state.py +494 -0
  43. flight_alloc-0.0.1/src/step1_clean_flights.py +286 -0
  44. flight_alloc-0.0.1/src/step2_extract_roster.py +316 -0
  45. flight_alloc-0.0.1/src/step3_allocate_flights.py +1639 -0
  46. flight_alloc-0.0.1/src/web/__init__.py +47 -0
  47. flight_alloc-0.0.1/src/web/__main__.py +9 -0
  48. flight_alloc-0.0.1/src/web/api/__init__.py +56 -0
  49. flight_alloc-0.0.1/src/web/api/export.py +37 -0
  50. flight_alloc-0.0.1/src/web/api/inputs.py +122 -0
  51. flight_alloc-0.0.1/src/web/api/override_rows.py +138 -0
  52. flight_alloc-0.0.1/src/web/api/pages.py +30 -0
  53. flight_alloc-0.0.1/src/web/api/readbacks.py +72 -0
  54. flight_alloc-0.0.1/src/web/api/recommender.py +72 -0
  55. flight_alloc-0.0.1/src/web/api/runs.py +102 -0
  56. flight_alloc-0.0.1/src/web/api/settings.py +201 -0
  57. flight_alloc-0.0.1/src/web/api/zc.py +117 -0
  58. flight_alloc-0.0.1/src/web/core/__init__.py +5 -0
  59. flight_alloc-0.0.1/src/web/core/responses.py +91 -0
  60. flight_alloc-0.0.1/src/web/core/router.py +167 -0
  61. flight_alloc-0.0.1/src/web/core/static_files.py +85 -0
  62. flight_alloc-0.0.1/src/web/overrides/__init__.py +66 -0
  63. flight_alloc-0.0.1/src/web/overrides/airports.py +261 -0
  64. flight_alloc-0.0.1/src/web/overrides/break_time.py +83 -0
  65. flight_alloc-0.0.1/src/web/overrides/config_yaml.py +21 -0
  66. flight_alloc-0.0.1/src/web/overrides/filters.py +187 -0
  67. flight_alloc-0.0.1/src/web/overrides/rows.py +110 -0
  68. flight_alloc-0.0.1/src/web/readback/__init__.py +67 -0
  69. flight_alloc-0.0.1/src/web/readback/common.py +68 -0
  70. flight_alloc-0.0.1/src/web/readback/dashboard.py +83 -0
  71. flight_alloc-0.0.1/src/web/readback/planning.py +335 -0
  72. flight_alloc-0.0.1/src/web/readback/session.py +158 -0
  73. flight_alloc-0.0.1/src/web/readback/tables.py +163 -0
  74. flight_alloc-0.0.1/src/web/runner.py +168 -0
  75. flight_alloc-0.0.1/src/web/server.py +185 -0
  76. flight_alloc-0.0.1/src/zc_store.py +221 -0
  77. flight_alloc-0.0.1/tests/test_caps_write.py +64 -0
  78. flight_alloc-0.0.1/tests/test_config_files.py +71 -0
  79. flight_alloc-0.0.1/tests/test_invariants.py +110 -0
  80. flight_alloc-0.0.1/tests/test_p2f_buffer.py +61 -0
  81. flight_alloc-0.0.1/tests/test_p2f_handler_no_intl.py +78 -0
  82. flight_alloc-0.0.1/tests/test_p2f_priority.py +146 -0
  83. flight_alloc-0.0.1/tests/test_windows.py +241 -0
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: flight-alloc
3
+ Version: 0.0.1
4
+ Summary: IndiGo CLC flight-allocation system — engine + local web console
5
+ Requires-Python: <3.15,>=3.11
6
+ Requires-Dist: openpyxl~=3.1
7
+ Requires-Dist: pydantic~=2.7
8
+ Requires-Dist: ortools~=9.15
9
+ Requires-Dist: pyyaml~=6.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest~=8.0; extra == "dev"
@@ -0,0 +1,231 @@
1
+ # Flight Allocation
2
+
3
+ Engine + web console for IndiGo CLC flight allocation. Python 3.11–3.14,
4
+ no database, no Docker, no Node — the UI is a zero-dependency stdlib
5
+ `http.server` app on `127.0.0.1:8765` with plain HTML/CSS/JS under
6
+ `src/web/static/`.
7
+
8
+ **Everything goes in and comes out through the browser.** The flight
9
+ schedule is uploaded in the browser and held in the server's memory for
10
+ the session; results are read back as JSON and, if you need a file to
11
+ send on, downloaded as a one-off `.xlsx`. There is no console workbook
12
+ and no per-date file tree.
13
+
14
+ The files are split by how often they change. The flight schedule is a
15
+ fresh export every morning, so it sits on the dashboard. The two rosters
16
+ cover a whole period (about a month — sometimes more, sometimes less)
17
+ and are added once per period in the **Setup** sidebar. **Rosters are
18
+ remembered**: each upload is saved under `data/rosters/` and reloaded
19
+ when the server starts, so a restart does not lose them.
20
+
21
+ ## Quick start (Windows)
22
+
23
+ 1. Double-click `start.bat`.
24
+ 2. First run takes ~1 minute: it creates a `.venv` and installs
25
+ `requirements.txt`. Every run after that opens the browser straight
26
+ to http://127.0.0.1:8765/.
27
+
28
+ ## Manual setup (Mac/Linux, or to run it yourself)
29
+
30
+ ```
31
+ python3.11 -m venv .venv
32
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
33
+ pip install -r requirements.txt
34
+ python -m src.cli # --port 9000 --no-browser also work
35
+ ```
36
+
37
+ ## Daily flow
38
+
39
+ 0. **Once per period** — open **Setup** (top right) and upload the two
40
+ roster files. They are saved and reloaded on every start, so this is
41
+ not part of the daily flow:
42
+
43
+ | Slot | What it is |
44
+ |---|---|
45
+ | Staff roster | regular crew roster |
46
+ | AM + ZC roster | AM and ZC roster |
47
+
48
+ 1. **Upload** today's flight schedule (the SV portal export) on the
49
+ dashboard's *Today's flight schedule* card.
50
+
51
+ Any sheet name works — each reader takes its canonical sheet if
52
+ present and otherwise falls back to the first sheet in the file.
53
+
54
+ 2. Check the allocation date (top right). It is prefilled from the
55
+ **server's** date, not the browser's, so a machine on a stale clock
56
+ can't plan the wrong day. Change it to back-fill another date.
57
+ 3. Click **Plan** — cleans the flights and extracts the roster, ~3s.
58
+ 4. Review the dashboard: per-shift stats, charts, roster by shift,
59
+ staffing recommendation.
60
+ 5. Click **Allocate** — CP-SAT solves, typically 30–90s on a first run.
61
+ 6. Adjust anything that needs it from the **Override** drawer:
62
+ - `p2f` — P2F handler nominations
63
+ - `sick` — pull someone out of the day
64
+ - `change_role` — promote STAFF→ZC, or push someone to AM
65
+ - `max_flights` / `cutoff_time` — per-staff caps
66
+ - add / remove a flight or a staff member
67
+ 7. Click **Allocate** again. Prior assignments are pinned and used as a
68
+ CP-SAT warm start, so only the affected staff's flights move and the
69
+ re-solve takes seconds rather than a minute. Rows whose staff changed
70
+ are flagged in the Allocations tab.
71
+ 8. **Download .xlsx** when you want a file to email or print.
72
+
73
+ ## Rosters: how the right day is found
74
+
75
+ The staff roster is the monthly sheet with a banner row, then
76
+ `S.no | Name | IGA | add info | Contact | Mon,31Aug | Tue,1Sep | ...`.
77
+ The reader finds the header row itself, takes `IGA` as the employee id
78
+ and `add info` (`P2F/Corendon`, `P2F/`, `/Corendon`, `/`) as the
79
+ licence, and works out the year of each `Mon,31Aug` column from the
80
+ weekday (so a roster that crosses New Year just works).
81
+
82
+ The AM + ZC roster is the sheet with `STAFF NAME | 26-May | 27-May | ... | ID`
83
+ (id in the last column, no licence column, statuses like `OFF`, `M/ZC`,
84
+ `A/IGT`). A weekday strip under the header is skipped if present but is
85
+ not required. Its `26-May` headers have no year, so the year comes from
86
+ the file's own last-modified date (nearest year wins, so New Year
87
+ crossings are fine). It is saved, listed and merged exactly like the
88
+ staff roster.
89
+
90
+ Several rosters can be held at once. For the allocation date **D** the
91
+ app reads the stored rosters that have a column for **D** or **D+1** and
92
+ merges them by employee id, a later upload winning where two overlap.
93
+ So on the last day of one roster, D comes from that roster and D+1 from
94
+ the next one you have uploaded. Uploading a new period *adds* it beside
95
+ the old one (re-uploading the same file replaces it); each roster can be
96
+ removed individually in Setup. If the date (or the day after it) has no
97
+ roster, Setup shows a notice and Plan raises W010 / W011.
98
+
99
+ Saved files live in `data/rosters/` (git-ignored). Set
100
+ `FLIGHT_ALLOC_DATA_DIR` to keep them somewhere else.
101
+
102
+ ## Zone Controllers
103
+
104
+ The rosters do **not** carry `/ZC`. Zone Controllers are picked on the
105
+ dashboard's **Zone Controllers** panel: search the AM List and Staff
106
+ together and click a name to make them a ZC; click the × on a badge to
107
+ remove one.
108
+
109
+ * The list is **saved per date** in `data/zc_lists.json` and survives a
110
+ restart.
111
+ * A date with no list of its own **carries over** the most recent earlier
112
+ list, so each morning starts from yesterday's ZCs. Add or remove whoever
113
+ changed and that date gets its own saved list.
114
+ * The list a day is planned with is frozen onto that day, so editing an
115
+ earlier date later never rewrites it.
116
+ * A ZC still marked `/ZC` in a roster file can be removed with the same
117
+ ×. The removal applies to that date only (a roster `/ZC` is a fact about
118
+ one day), and they go back to what they were on the roster — STAFF or AM,
119
+ keeping any P2F duty.
120
+ * Someone on the list who is off (or not rostered) on a given day is
121
+ simply skipped that day, and shown greyed-out as "not on shift".
122
+ * Edits apply immediately (the roster is re-read and the list re-applied);
123
+ re-run **Allocate** to use them. Edits are blocked while a run is in
124
+ progress.
125
+
126
+ ## Session lifetime
127
+
128
+ Overrides, results and the daily flight schedule live in the server
129
+ process; restarting it clears them. The rosters are kept on disk (see
130
+ above). **Reset** is narrower: it clears the results but keeps your
131
+ uploads and override rows, so you can go straight back to Plan.
132
+
133
+ ## Tests
134
+
135
+ ```
136
+ pip install -r requirements-dev.txt
137
+ python -m pytest
138
+ ```
139
+
140
+ * `tests/test_windows.py` pins the shift, spacing and ZC-buffer tables
141
+ (they drive eligibility, the solver and both post-passes).
142
+ * `tests/test_invariants.py` covers `src/allocator/invariants.py`, an
143
+ independent H1 / H10 / H16 checker you can run on any finished
144
+ allocation: `check_allocation(rows, ops_day=D, caps={emp_id: cap})`.
145
+ * `tests/test_config_files.py` validates `config.yml` and `shift_limits.json`.
146
+
147
+ ## Layout
148
+
149
+ ```
150
+ src/
151
+ state.py the in-memory store everything reads and writes
152
+ roster_store.py on-disk copy of the uploaded rosters (data/rosters/)
153
+ zc_store.py per-date Zone Controller list (data/zc_lists.json)
154
+ plan.py Plan stage: step 1 + step 2 + the summary
155
+ step1_clean_flights.py clean the schedule into ops classes
156
+ step2_extract_roster.py rosters -> availability
157
+ step3_allocate_flights.py solve, post-passes, outputs
158
+ staged_overrides.py apply drawer mutations to the working data
159
+ recommender_staffing.py per-shift required headcount
160
+ schemas.py the typed row models the whole app shares
161
+ allocator/ eligibility, caps, pairings, post-passes
162
+ solver/ CP-SAT model
163
+ io/
164
+ readers.py parse the uploaded workbooks + override rows
165
+ roster_library.py pick + merge the stored rosters for a date
166
+ export.py render the state as a downloadable .xlsx
167
+ web/
168
+ server.py the request loop + launcher (no endpoint logic)
169
+ core/ Response, the Router, static file serving
170
+ api/ one module per concern; each registers routes
171
+ readback/ state -> the JSON the UI renders
172
+ overrides/ override + config.yml mutations
173
+ runner.py runs a stage on a background thread
174
+ static/
175
+ index.html a shell; views bring their own markup
176
+ js/core/ dom, api, store, shared constants
177
+ js/ui/ tab registry, layout mounting, modals
178
+ js/views/ one module per tab (views/index.js registers)
179
+ js/charts/ one module per dashboard chart
180
+ js/panels/ dashboard blocks with their own load cycle
181
+ js/drawer/ the Override drawer and its sections
182
+ js/sidebar/ the Setup sidebar (the set-once roster files)
183
+ css/ partials, listed in styles.css
184
+ configs/
185
+ config.yml column maps, status aliases, solver knobs
186
+ shift_limits.json per-(shift, role) preferred / acceptable / cap
187
+ ```
188
+
189
+ ```stdlib http.server web UI.
190
+
191
+ Routes (JSON unless noted):
192
+
193
+ GET / text/html — single-page app
194
+ GET /static/<path> the JS module / CSS partial tree
195
+
196
+ GET /api/inputs which of the 3 files are uploaded,
197
+ grouped daily (schedule) / setup (rosters)
198
+ POST /api/inputs/<kind> upload one (raw .xlsx body)
199
+ DEL /api/inputs/<kind> drop one
200
+ GET /api/server_date the operating date, per the server's clock
201
+ GET /api/export.xlsx download the current results
202
+
203
+ GET /api/zc?date=YYYY-MM-DD the ZC list for a date (saved / carried)
204
+ POST /api/zc/add {date, name}
205
+ POST /api/zc/remove {date, name}
206
+
207
+ GET /api/dashboard headline counts + run status
208
+ GET /api/allocations every allocation row
209
+ GET /api/workload per-staff workload summary
210
+ GET /api/pairs pair map
211
+ GET /api/warnings warnings
212
+ GET /api/unallocated unallocated flights
213
+ GET /api/recommendations Phase-R suggestions per unallocated
214
+ GET /api/plan plan summary payload
215
+ GET /api/handlers P2F handler picture
216
+ GET /api/staffing staffing recommendation
217
+ GET /api/staff_names roster names for the drawer dropdowns
218
+ GET /api/shift_limits current (shift, role) bands
219
+
220
+ GET /api/overrides override rows
221
+ POST /api/overrides append one {values: [...]}
222
+ PUT /api/overrides/<i> overwrite row i
223
+ DEL /api/overrides/<i> delete row i
224
+ POST /api/overrides/apply_staged apply staged rows to the data
225
+
226
+ POST /api/run {date, step} — plan | all | reset | …
227
+ GET /api/run/status current run state
228
+
229
+ Bind: 127.0.0.1:8765. Everything lives in memory for the life of the
230
+ process; there is no workbook and no per-date file.
231
+ ```
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: flight-alloc
3
+ Version: 0.0.1
4
+ Summary: IndiGo CLC flight-allocation system — engine + local web console
5
+ Requires-Python: <3.15,>=3.11
6
+ Requires-Dist: openpyxl~=3.1
7
+ Requires-Dist: pydantic~=2.7
8
+ Requires-Dist: ortools~=9.15
9
+ Requires-Dist: pyyaml~=6.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest~=8.0; extra == "dev"
@@ -0,0 +1,81 @@
1
+ README.md
2
+ pyproject.toml
3
+ flight_alloc.egg-info/PKG-INFO
4
+ flight_alloc.egg-info/SOURCES.txt
5
+ flight_alloc.egg-info/dependency_links.txt
6
+ flight_alloc.egg-info/entry_points.txt
7
+ flight_alloc.egg-info/requires.txt
8
+ flight_alloc.egg-info/top_level.txt
9
+ src/__init__.py
10
+ src/cli.py
11
+ src/config.py
12
+ src/greedy_fallback.py
13
+ src/plan.py
14
+ src/recommender_staffing.py
15
+ src/roster_store.py
16
+ src/schemas.py
17
+ src/staged_overrides.py
18
+ src/state.py
19
+ src/step1_clean_flights.py
20
+ src/step2_extract_roster.py
21
+ src/step3_allocate_flights.py
22
+ src/zc_store.py
23
+ src/allocator/__init__.py
24
+ src/allocator/caps.py
25
+ src/allocator/eligibility.py
26
+ src/allocator/greedy_fallback.py
27
+ src/allocator/invariants.py
28
+ src/allocator/p2f_priority.py
29
+ src/allocator/pair_validation.py
30
+ src/allocator/pairings.py
31
+ src/allocator/postpass_break.py
32
+ src/allocator/postpass_intl.py
33
+ src/allocator/postpass_p2f.py
34
+ src/allocator/postpass_rebalance.py
35
+ src/allocator/postsolve.py
36
+ src/allocator/recommender.py
37
+ src/allocator/windows.py
38
+ src/io/__init__.py
39
+ src/io/export.py
40
+ src/io/export_xml.py
41
+ src/io/readers.py
42
+ src/io/roster_library.py
43
+ src/solver/__init__.py
44
+ src/solver/allocator_cpsat.py
45
+ src/web/__init__.py
46
+ src/web/__main__.py
47
+ src/web/runner.py
48
+ src/web/server.py
49
+ src/web/api/__init__.py
50
+ src/web/api/export.py
51
+ src/web/api/inputs.py
52
+ src/web/api/override_rows.py
53
+ src/web/api/pages.py
54
+ src/web/api/readbacks.py
55
+ src/web/api/recommender.py
56
+ src/web/api/runs.py
57
+ src/web/api/settings.py
58
+ src/web/api/zc.py
59
+ src/web/core/__init__.py
60
+ src/web/core/responses.py
61
+ src/web/core/router.py
62
+ src/web/core/static_files.py
63
+ src/web/overrides/__init__.py
64
+ src/web/overrides/airports.py
65
+ src/web/overrides/break_time.py
66
+ src/web/overrides/config_yaml.py
67
+ src/web/overrides/filters.py
68
+ src/web/overrides/rows.py
69
+ src/web/readback/__init__.py
70
+ src/web/readback/common.py
71
+ src/web/readback/dashboard.py
72
+ src/web/readback/planning.py
73
+ src/web/readback/session.py
74
+ src/web/readback/tables.py
75
+ tests/test_caps_write.py
76
+ tests/test_config_files.py
77
+ tests/test_invariants.py
78
+ tests/test_p2f_buffer.py
79
+ tests/test_p2f_handler_no_intl.py
80
+ tests/test_p2f_priority.py
81
+ tests/test_windows.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ flight-alloc = src.cli:main
@@ -0,0 +1,7 @@
1
+ openpyxl~=3.1
2
+ pydantic~=2.7
3
+ ortools~=9.15
4
+ pyyaml~=6.0
5
+
6
+ [dev]
7
+ pytest~=8.0
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "flight-alloc"
3
+ version = "0.0.1"
4
+ description = "IndiGo CLC flight-allocation system — engine + local web console"
5
+ requires-python = ">=3.11,<3.15"
6
+ dependencies = [
7
+ "openpyxl~=3.1",
8
+ "pydantic~=2.7",
9
+ "ortools~=9.15",
10
+ "pyyaml~=6.0",
11
+ ]
12
+
13
+ [project.scripts]
14
+ flight-alloc = "src.cli:main"
15
+
16
+ [build-system]
17
+ requires = ["setuptools>=68"]
18
+ build-backend = "setuptools.build_meta"
19
+
20
+ [tool.setuptools.packages.find]
21
+ where = ["."]
22
+ include = ["src", "src.*"]
23
+
24
+ [project.optional-dependencies]
25
+ dev = ["pytest~=8.0"]
26
+
27
+ [tool.pytest.ini_options]
28
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,6 @@
1
+ """Step 4 allocator — pre-solve matrix construction, pair generation, and
2
+ post-solve assembly.
3
+
4
+ The CP-SAT model itself lives in ``src.solver.allocator_cpsat``;
5
+ this package holds the deterministic plumbing on either side.
6
+ """