forked from ChelseaKR/ca-tariff-parse
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheader.py
More file actions
130 lines (113 loc) · 5.35 KB
/
Copy pathheader.py
File metadata and controls
130 lines (113 loc) · 5.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""Read the schedule's own identity off the page furniture."""
from __future__ import annotations
import re
from ..extract import LayoutDoc, sheet_numbers
from ..model import Cited, ScheduleIdentity
from ..profiles import DEFAULT, DocumentProfile
from .base import Citer, LineKey
FRONT = "front"
SCHEDULE_RE = re.compile(r"\ARate Schedule\s+(?P<code>[A-Za-z0-9][A-Za-z0-9\-]*)\Z")
#: The footer line that dates the schedule. A schedule amended since it was
#: first adopted prints the amending resolution inside brackets, as in
#: "(as amended by Resolution No. 26-04-04 adopted April 16, 2026) Effective:
#: June 1, 2026", so the closing bracket is allowed to fall outside the adopted
#: date rather than being carried into it.
RESOLUTION_RE = re.compile(
r"Resolution\s+No\.?\s*(?P<res>\S+)\s+adopted\s+(?P<adopted>.+?)\)?\s+"
r"Effective:\s*(?P<effective>.+?)\s*\Z",
re.IGNORECASE,
)
SHEET_RE = re.compile(r"Sheet\s*No\.?\s*(?P<sheet>[A-Za-z0-9][A-Za-z0-9\-]*)", re.IGNORECASE)
_MONTH_NAME = (
r"(?:January|February|March|April|May|June|July|August|September|October|November|December)"
)
#: The date a sheet says it takes effect, as its own footer prints it. One
#: publisher files sheet by sheet, so the sheets of a single schedule take
#: effect on different days: on one schedule here, sheet 1 is effective June 1
#: and sheets 2 to 7 are effective March 1. Dating a price to the document
#: rather than to its sheet would file three quarters of that schedule under a
#: day it did not take effect.
#:
#: The match is anchored at the end of the line so that a footer also carrying
#: "Submitted June 1, 2026" cannot be read as the effective date.
SHEET_EFFECTIVE_RE = re.compile(
rf"\bEffective:?\s+(?P<when>{_MONTH_NAME}\s+\d{{1,2}},\s+\d{{4}})\s*\Z",
)
def sheet_effective_dates(doc: LayoutDoc, citer: Citer) -> dict[int, Cited[str]]:
"""The effective date each page's own furniture states, by page number.
A page whose furniture states no date, or states two that disagree, is
absent from the result, and a recognizer that needs a date for that page
emits nothing rather than borrowing a neighbouring sheet's.
"""
found: dict[int, list[Cited[str]]] = {}
for page in doc.pages:
for line in page.lines:
if not line.furniture:
continue
match = SHEET_EFFECTIVE_RE.search(line.text)
if match:
found.setdefault(page.number, []).append(
citer.text(line, FRONT, match.group("when").strip())
)
return {
number: dates[0]
for number, dates in found.items()
if len({date.value for date in dates}) == 1
}
def parse_identity(
doc: LayoutDoc, citer: Citer, profile: DocumentProfile = DEFAULT
) -> tuple[ScheduleIdentity, set[LineKey]]:
"""Extract title, schedule code, resolution and effective date.
Nothing here is inferred. If the document does not print a field, the field
stays ``None`` rather than being guessed from the filename or the date.
"""
consumed: set[LineKey] = set()
schedule_code: Cited[str] | None = None
title: Cited[str] | None = None
resolution: Cited[str] | None = None
adopted: Cited[str] | None = None
effective: Cited[str] | None = None
sheets: list[Cited[str]] = []
for page in doc.pages:
furniture = [line for line in page.lines if line.furniture]
for position, line in enumerate(furniture):
text = line.text
match = SCHEDULE_RE.match(text)
if match:
consumed.add((line.page, line.index))
if schedule_code is None:
schedule_code = citer.text(line, FRONT, match.group("code"))
# The running title is the line directly above the schedule
# line in the header band.
if position > 0 and furniture[position - 1].top < line.top:
above = furniture[position - 1]
title = citer.text(above, FRONT, above.text)
consumed.add((above.page, above.index))
continue
match = RESOLUTION_RE.search(text)
if match:
consumed.add((line.page, line.index))
if resolution is None:
resolution = citer.text(line, FRONT, match.group("res"))
adopted = citer.text(line, FRONT, match.group("adopted").strip())
effective = citer.text(line, FRONT, match.group("effective").strip())
continue
# A supersession header prints the cancelled sheet number as well
# as this page's own. Only the numbers the page asserts as its own
# are recorded, so the schedule is never described by a sheet it
# replaced. The cancelling line is still consumed, because it is
# accounted for even though nothing is read from it.
if SHEET_RE.search(text):
consumed.add((line.page, line.index))
sheets.extend(
citer.text(line, FRONT, number) for number in sheet_numbers(line, profile)
)
identity = ScheduleIdentity(
schedule_code=schedule_code,
title=title,
resolution=resolution,
adopted=adopted,
effective=effective,
sheets=tuple(sheets),
)
return identity, consumed