Created mca_stubs.py which provides record entry details (and slice objects) for the .MCA types.

This commit is contained in:
2026-05-26 11:06:28 +01:00
parent e887cc791e
commit b9ffbdee89
3 changed files with 831 additions and 1 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
from national_rail_timetable.mca_queries import main
from national_rail_timetable.parsing import main
print(main())
+779
View File
@@ -0,0 +1,779 @@
# This file is generated by parsing.generate_mca_stubs.
# Do not modify by hand.
# Imports
from dataclasses import dataclass
# Classes
@dataclass
class BS:
@property
def record_identity(self):
"""With the constant value BS."""
return slice(0, 2)
@property
def transaction_type(self):
"""N = New. D = Delete. R = Revise."""
return slice(2, 3)
@property
def train_uid(self):
"""Unique train Identifier."""
return slice(3, 9)
@property
def date_runs_from(self):
"""yymmdd"""
return slice(9, 15)
@property
def date_runs_to(self):
"""yymmdd"""
return slice(15, 21)
@property
def days_run(self):
"""No description."""
return slice(21, 28)
@property
def bank_holiday_running(self):
"""No description."""
return slice(28, 29)
@property
def train_status(self):
"""No description."""
return slice(29, 30)
@property
def train_category(self):
"""No description."""
return slice(30, 32)
@property
def train_identity(self):
"""No description."""
return slice(32, 36)
@property
def headcode(self):
"""No description."""
return slice(36, 40)
@property
def course_indicator(self):
"""Not used - always set to 1."""
return slice(40, 41)
@property
def profit_centre_code(self):
"""No description."""
return slice(41, 49)
@property
def business_sector(self):
"""Now used to contain the portion suffix for RSID"""
return slice(49, 50)
@property
def power_type(self):
"""No description."""
return slice(50, 53)
@property
def timing_load(self):
"""No description."""
return slice(53, 57)
@property
def speed(self):
"""No description."""
return slice(57, 60)
@property
def operating_chars(self):
"""No description."""
return slice(60, 66)
@property
def train_class(self):
"""No description."""
return slice(66, 67)
@property
def sleepers(self):
"""No description."""
return slice(67, 68)
@property
def reservations(self):
"""Permitted values are: A Seat Reservations Compulsory (R symbol in white box) E Reservations for Bicycles Essential (Inverted black triangle) R Seat Reservations Recommended (R symbol in black box) S Seat Reservations possible from any station (white diamond symbol)"""
return slice(68, 69)
@property
def connect_indicator(self):
"""Not used - always set to blank."""
return slice(69, 70)
@property
def catering_code(self):
"""No description."""
return slice(70, 74)
@property
def service_branding(self):
"""No description."""
return slice(74, 78)
@property
def spare(self):
"""No description."""
return slice(78, 79)
@property
def stp_indicator(self):
"""C = STP cancellation of permanent schedule. N = New STP schedule. O = STP overlay of permanent schedule. P = Permanent. Read in association with the Transaction Type in Field 2"""
return slice(79, 80)
@dataclass
class HD:
@property
def record_identity(self):
"""With the constant value HD."""
return slice(0, 2)
@property
def file_identity(self):
"""No description."""
return slice(2, 22)
@property
def date_of_extract(self):
"""Format ddmmyy defining the date that the BTD extract file was created."""
return slice(22, 28)
@property
def time_of_extract(self):
"""hhmm defining the time that the BTD extract file was created."""
return slice(28, 32)
@property
def current_file_reference(self):
"""Unique file reference."""
return slice(32, 39)
@property
def last_file_reference(self):
"""Unique file reference."""
return slice(39, 46)
@property
def update_indicator(self):
"""U=Update. F=Full extract."""
return slice(46, 47)
@property
def version(self):
"""Version identifier of CIF software."""
return slice(47, 48)
@property
def extract_start_date(self):
"""Same as Field 3 above."""
return slice(48, 54)
@property
def extract_end_date(self):
"""No description."""
return slice(54, 60)
@property
def spare(self):
"""No description."""
return slice(60, 80)
@dataclass
class ZZ:
@property
def record_identity(self):
"""With the constant value ZZ."""
return slice(0, 2)
@property
def spare(self):
"""No description."""
return slice(2, 80)
@dataclass
class TA:
@property
def record_identity(self):
"""With the constant value TA."""
return slice(0, 2)
@property
def tiploc_code(self):
"""A TIPLOC is 4-7 characters. If less than 7 then it will be padded by blanks."""
return slice(2, 9)
@property
def capitals(self):
"""Defines capitalisation of TIPLOC. Can be ignored for retailing/journey planners."""
return slice(9, 11)
@property
def national_location_code(self):
"""No description."""
return slice(11, 17)
@property
def nlc_check_character(self):
"""No description."""
return slice(17, 18)
@property
def tps_description(self):
"""No description."""
return slice(18, 44)
@property
def stanox(self):
"""TOPS location code."""
return slice(44, 49)
@property
def po_mcp_code(self):
"""Post Office Location Code. (Not used but may contain historic data or three blank spaces followed by 0)."""
return slice(49, 53)
@property
def crs_code(self):
"""No description."""
return slice(53, 56)
@property
def description(self):
"""Description used in LENNON."""
return slice(56, 72)
@property
def new_tiploc(self):
"""Only present if TIPLOC change."""
return slice(72, 79)
@property
def spare(self):
"""No description."""
return slice(79, 80)
@dataclass
class CR:
@property
def record_identity(self):
"""With the constant value CR."""
return slice(0, 2)
@property
def location(self):
"""TIPLOC + Suffix. Suffix is always the eighth character."""
return slice(2, 10)
@property
def train_category(self):
"""No description."""
return slice(10, 12)
@property
def train_identity(self):
"""No description."""
return slice(12, 16)
@property
def headcode(self):
"""No description."""
return slice(16, 20)
@property
def course_indicator(self):
"""No description."""
return slice(20, 21)
@property
def profit_centre_code(self):
"""No description."""
return slice(21, 29)
@property
def business_sector(self):
"""No description."""
return slice(29, 30)
@property
def power_type(self):
"""No description."""
return slice(30, 33)
@property
def timing_load(self):
"""No description."""
return slice(33, 37)
@property
def speed(self):
"""No description."""
return slice(37, 40)
@property
def operating_chars(self):
"""No description."""
return slice(40, 46)
@property
def train_class(self):
"""No description."""
return slice(46, 47)
@property
def sleepers(self):
"""No description."""
return slice(47, 48)
@property
def reservations(self):
"""No description."""
return slice(48, 49)
@property
def connect_indicator(self):
"""No description."""
return slice(49, 50)
@property
def catering_code(self):
"""No description."""
return slice(50, 54)
@property
def service_branding(self):
"""No description."""
return slice(54, 58)
@property
def traction_class(self):
"""No description."""
return slice(58, 62)
@property
def uic_code(self):
"""Only populated for trains travelling to/from Europe via the Channel Tunnel, otherwise blank."""
return slice(62, 67)
@property
def retail_service_id(self):
"""No description."""
return slice(67, 75)
@property
def spare(self):
"""No description."""
return slice(75, 80)
@dataclass
class LT:
@property
def record_identity(self):
"""With the constant value LT."""
return slice(0, 2)
@property
def location(self):
"""TIPLOC +Suffix. Suffix is always the eighth character."""
return slice(2, 10)
@property
def scheduled_arrival_time(self):
"""No description."""
return slice(10, 15)
@property
def public_arrival_time(self):
"""If there is no Public Arrival time this field will default to 0000."""
return slice(15, 19)
@property
def platform(self):
"""No description."""
return slice(19, 22)
@property
def path(self):
"""No description."""
return slice(22, 25)
@property
def activity(self):
"""Up to 6 activity codes may be present. The first 2 characters will always be TF (train finishes). If there are no other activity codes, this defaults to being an advertised arrival."""
return slice(25, 37)
@property
def spare(self):
"""No description."""
return slice(37, 80)
@dataclass
class LI:
@property
def record_identity(self):
"""With the constant value LI."""
return slice(0, 2)
@property
def location(self):
"""TIPLOC + Suffix. Suffix is always the eighth character."""
return slice(2, 10)
@property
def scheduled_arrival_time(self):
"""No description."""
return slice(10, 15)
@property
def scheduled_departure_time(self):
"""No description."""
return slice(15, 20)
@property
def scheduled_pass(self):
"""No description."""
return slice(20, 25)
@property
def public_arrival(self):
"""If there is no Public Arrival time this field will default to 0000."""
return slice(25, 29)
@property
def public_departure(self):
"""If there is no Public Departure time this field will default to 0000."""
return slice(29, 33)
@property
def platform(self):
"""No description."""
return slice(33, 36)
@property
def line(self):
"""No description."""
return slice(36, 39)
@property
def path(self):
"""No description."""
return slice(39, 42)
@property
def activity(self):
"""Up to 6 activity codes may be present."""
return slice(42, 54)
@property
def engineering_allowance(self):
"""No description."""
return slice(54, 56)
@property
def pathing_allowance(self):
"""No description."""
return slice(56, 58)
@property
def performance_allowance(self):
"""No description."""
return slice(58, 60)
@property
def spare(self):
"""No description."""
return slice(60, 80)
@dataclass
class TD:
@property
def record_identity(self):
"""With the constant value TD."""
return slice(0, 2)
@property
def tiploc_code(self):
"""No description."""
return slice(2, 9)
@property
def spare(self):
"""No description."""
return slice(9, 80)
@dataclass
class AA:
@property
def record_identity(self):
"""With the constant value AA."""
return slice(0, 2)
@property
def transaction_type(self):
"""N = New. D = Delete. R = Revise."""
return slice(2, 3)
@property
def base_uid(self):
"""One of the trains involved in the association. This will always be the through train, not the splitting/joining portion."""
return slice(3, 9)
@property
def assoc_uid(self):
"""The other train involved."""
return slice(9, 15)
@property
def assoc_start_date(self):
"""Format: yymmdd. May not be the same as the dates of the train schedules."""
return slice(15, 21)
@property
def assoc_end_date(self):
"""Format: yymmdd. May not be the same as the dates of the train schedules."""
return slice(21, 27)
@property
def assoc_days(self):
"""No description."""
return slice(27, 34)
@property
def assoc_cat(self):
"""The ASSOC-CAT for the base UID (first byte), followed by the ASSOC-CAT for the assoc. UID (second byte). Note: Although this field isnt specified as having blanks in the Network Rail CIF specification, if blanks are supplied they will be carried forward. (Blanks are used to override the permanent value in overlays and cancellations). JJ for Joining trains and VV for Dividing trains. NP for Next/Previous Associations may also be displayed but as this is an Operating association it should be ignored by journey planners."""
return slice(34, 36)
@property
def assoc_date_ind(self):
"""S = Standard. N = Over-next-midnight. P = Over-previous-midnight. Note: Although this field isnt specified as having blanks in the Network Rail CIF specification, if blanks are supplied they will be carried forward. (Blanks are used to override the permanent value in overlays and cancellations)."""
return slice(36, 37)
@property
def assoc_location(self):
"""TIPLOC where association occurs."""
return slice(37, 44)
@property
def base_location_suffix(self):
"""Values are space or 2."""
return slice(44, 45)
@property
def assoc_location_suffix(self):
"""Values are space or 2."""
return slice(45, 46)
@property
def diagram_type(self):
"""With the constant value T."""
return slice(46, 47)
@property
def association_type(self):
"""P = Passenger use. O = Operating use. Note: Although this field isnt specified as having blanks in the Network Rail CIF specification, if blanks are supplied they will be carried forward. (If blank then association defaults to Operating and should be ignored by journey planners)."""
return slice(47, 48)
@property
def filler(self):
"""No description."""
return slice(48, 79)
@property
def stp_indicator(self):
"""Read in conjunction with the Transaction Type in Field 2. C = STP cancellation of permanent schedule. N = New STP schedule. O = STP overlay of permanent schedule. P = Permanent."""
return slice(79, 80)
@dataclass
class LO:
@property
def record_identity(self):
"""With the constant value LO."""
return slice(0, 2)
@property
def location(self):
"""TIPLOC + Suffix. Suffix is always the eighth character."""
return slice(2, 10)
@property
def scheduled_departure_time(self):
"""No description."""
return slice(10, 15)
@property
def public_departure_time(self):
"""If there is no Public Departure time this field will default to 0000."""
return slice(15, 19)
@property
def platform(self):
"""No description."""
return slice(19, 22)
@property
def line(self):
"""No description."""
return slice(22, 25)
@property
def engineering_allowance(self):
"""No description."""
return slice(25, 27)
@property
def pathing_allowance(self):
"""No description."""
return slice(27, 29)
@property
def activity(self):
"""Up to 6 activity codes may be present. The first 2 characters will always be TB (train begins). If there are no other activity codes, this defaults to being an advertised departure."""
return slice(29, 41)
@property
def performance_allowance(self):
"""No description."""
return slice(41, 43)
@property
def spare(self):
"""No description."""
return slice(43, 80)
@dataclass
class BX:
@property
def record_identity(self):
"""With the constant value BX."""
return slice(0, 2)
@property
def traction_class(self):
"""Not used always blank."""
return slice(2, 6)
@property
def uic_code(self):
"""Only populated for trains travelling to/from Europe via the Channel Tunnel, otherwise blank."""
return slice(6, 11)
@property
def atoc_code(self):
"""No description."""
return slice(11, 13)
@property
def applicable_timetable_code(self):
"""Always set to Y."""
return slice(13, 14)
@property
def retail_service_id(self):
"""No description."""
return slice(14, 22)
@property
def source(self):
"""Not used always blank."""
return slice(22, 23)
@property
def spare(self):
"""No description."""
return slice(23, 80)
@dataclass
class TI:
@property
def record_identity(self):
"""With the constant value TI."""
return slice(0, 2)
@property
def tiploc_code(self):
"""A TIPLOC is 4-7 characters. If less than 7 then it will be padded by blanks."""
return slice(2, 9)
@property
def capitals(self):
"""Defines capitalisation of TIPLOC. Can be ignored for retailing/journey planners."""
return slice(9, 11)
@property
def national_location_code(self):
"""No description."""
return slice(11, 17)
@property
def nlc_check_character(self):
"""No description."""
return slice(17, 18)
@property
def tps_description(self):
"""No description."""
return slice(18, 44)
@property
def stanox(self):
"""TOPS location code."""
return slice(44, 49)
@property
def po_mcp_code(self):
"""Post Office Location Code. (Not used but may contain historic data or three blank spaces followed by 0)."""
return slice(49, 53)
@property
def crs_code(self):
"""No description."""
return slice(53, 56)
@property
def description(self):
"""Description used in LENNON."""
return slice(56, 72)
@property
def spare(self):
"""No description."""
return slice(72, 80)
+51
View File
@@ -146,3 +146,54 @@ def read_specification_table_raws(
continue
tables[path.name[:-4]] = pd.read_csv(path)
return tables
def generate_mca_stubs(
data_dir: Path | None = None,
):
text = [
"# This file is generated by parsing.generate_mca_stubs.",
"# Do not modify by hand.",
"",
"# Imports",
"from dataclasses import dataclass",
"",
"# Classes",
]
for name, df in read_specification_table_raws(data_dir).items():
if name[:4] != "MCA_":
continue
text += [
"@dataclass",
f"class {name.split('_')[1]}:",
]
for _, row in df.iterrows():
func_name = (
row["Field Description"]
.lower()
.replace(" ", "_")
.replace("-", "_")
.split("/")[0]
.strip()
)
start = int(row["Position"].split("-")[0]) - 1
stop = int(row["Position"].split("-")[-1])
text += [
"",
" @property",
f" def {func_name}(self):",
f' """{t if (t := str(row["Notes"])) != "nan" else "No description."}"""',
f" return slice({start}, {stop})",
]
text += ["", ""]
with open(Path(__file__).parent / "mca_stubs.py", "w") as wf:
return wf.write("\n".join(text))
# Script
def main():
print(generate_mca_stubs())
if __name__ == "__main__":
main()