-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathduty_parser.py
More file actions
212 lines (165 loc) · 7.74 KB
/
Copy pathduty_parser.py
File metadata and controls
212 lines (165 loc) · 7.74 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
from typing import List, Dict, Tuple, NamedTuple
from datetime import date
class DutyAssignment(NamedTuple):
"""Represents a duty assignment with location context"""
day: int
location: str
row_index: int
class DutyParser:
def __init__(self, year: int, month: int, location_config: dict, csv_format: dict):
self.year = year
self.month = month
self.location_config = location_config
self.csv_format = csv_format
def parse_duty_schedule(
self, csv_rows: List[List[str]]
) -> Dict[str, List[DutyAssignment]]:
"""
Parse CSV data where each row represents duty assignments by position
Format: Each cell position corresponds to a day (column 0 = day 1, column 1 = day 2, etc.)
If a cell contains a letter, that person is on duty for that day
Args:
csv_rows: List of rows from CSV, where each row contains duty assignments by day position
Returns:
Dictionary like {'a': [DutyAssignment(day=1, location='北邊病房', row_index=0), ...], ...}
"""
if not csv_rows:
raise ValueError("CSV data is empty")
if not csv_rows:
raise ValueError("No valid data found in CSV")
# Dictionary to store duty assignments for each person
duty_schedule = {}
# Check orientation from config
orientation = self.csv_format.get("orientation", "columns")
if orientation == "rows":
# Row-based: Each row represents a day (don't filter out empty rows for day calculation)
for row_idx, row in enumerate(csv_rows):
# Day number is row index + 1 (0-based to 1-based)
day = row_idx + 1
# Validate day is within reasonable range for the month
if not (1 <= day <= 31):
continue
# Check each cell in the row (each column represents a location)
for col_idx, cell in enumerate(row):
# Clean and validate cell content
cell_clean = cell.strip() if cell else ""
# Skip empty cells or cells with no alphabetic characters
if not cell_clean or not any(c.isalpha() for c in cell_clean):
continue
# Get location for this column (Column A = 0, Column B = 1, etc.)
location = self.location_config.get("by_column", {}).get(
col_idx, self.location_config.get("default", "一般病房")
)
# Parse multiple people in the same cell (e.g., "a/b", "a,b", "a b")
people = self._parse_multiple_people(cell_clean)
# Create duty assignment for each person
for person in people:
assignment = DutyAssignment(
day=day, location=location, row_index=row_idx
)
# Add this assignment to the person's duty list
if person not in duty_schedule:
duty_schedule[person] = []
duty_schedule[person].append(assignment)
else:
# Column-based: Each column represents a day (original logic)
# Filter out completely empty rows for column-based processing
non_empty_rows = [
row for row in csv_rows if any(cell.strip() for cell in row if cell)
]
if len(non_empty_rows) < 1:
raise ValueError("No valid data found in CSV")
for row_idx, row in enumerate(non_empty_rows):
# Check each cell in the row
for col_idx, cell in enumerate(row):
# Clean and validate cell content
cell_clean = cell.strip() if cell else ""
# Skip empty cells or cells with no alphabetic characters
if not cell_clean or not any(c.isalpha() for c in cell_clean):
continue
# Day number is column index + 1 (0-based to 1-based)
day = col_idx + 1
# Validate day is within reasonable range for the month
if not (1 <= day <= 31):
continue
# Get location for this column (Column A = 0, Column B = 1, etc.)
location = self.location_config.get("by_column", {}).get(
col_idx, self.location_config.get("default", "一般病房")
)
# Parse multiple people in the same cell (e.g., "a/b", "a,b", "a b")
people = self._parse_multiple_people(cell_clean)
# Create duty assignment for each person
for person in people:
assignment = DutyAssignment(
day=day, location=location, row_index=row_idx
)
# Add this assignment to the person's duty list
if person not in duty_schedule:
duty_schedule[person] = []
duty_schedule[person].append(assignment)
return duty_schedule
def _parse_multiple_people(self, cell_content: str) -> List[str]:
"""
Parse a cell that may contain multiple people separated by various delimiters.
Args:
cell_content: Raw cell content (e.g., "a/b", "a,b", "a b", "a")
Returns:
List of normalized person identifiers (lowercase single letters)
"""
import re
# Common separators: /, ,, space, &, +, -
separators = r"[/,\s&+\-]+"
# Split by separators and clean each part
parts = re.split(separators, cell_content.strip())
people = []
for part in parts:
part = part.strip()
if not part:
continue
# Extract first alphabetic character and normalize to lowercase
for char in part:
if char.isalpha():
people.append(char.lower())
break # Only take the first alphabetic char from each part
# Remove duplicates while preserving order
seen = set()
unique_people = []
for person in people:
if person not in seen:
seen.add(person)
unique_people.append(person)
return unique_people
def is_weekend(self, day: int) -> bool:
"""
Check if a given day is a weekend (Saturday or Sunday)
Args:
day: Day of the month
Returns:
True if weekend, False if weekday
"""
try:
date_obj = date(self.year, self.month, day)
# Monday is 0, Sunday is 6
return date_obj.weekday() in [5, 6] # Saturday or Sunday
except ValueError:
return False
def get_duty_dates_with_types(
self, duty_assignments: List[DutyAssignment]
) -> List[Tuple[date, bool, str]]:
"""
Convert duty assignments to date objects with weekend/weekday and location info
Args:
duty_assignments: List of DutyAssignment objects
Returns:
List of tuples (date_object, is_weekend, location)
"""
duty_dates = []
for assignment in duty_assignments:
try:
date_obj = date(self.year, self.month, assignment.day)
is_weekend_duty = self.is_weekend(assignment.day)
duty_dates.append((date_obj, is_weekend_duty, assignment.location))
except ValueError:
# Skip invalid dates
continue
return duty_dates