ltc-code 0.1.91__tar.gz → 0.1.93__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.
- {ltc_code-0.1.91 → ltc_code-0.1.93}/PKG-INFO +1 -1
- {ltc_code-0.1.91 → ltc_code-0.1.93}/pyproject.toml +1 -1
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/june7.py +245 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/README.md +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/__init__.py +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/green_dot.py +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/june2.py +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/june5.py +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/kipp_nj.py +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/kipp_tx.py +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/may27.py +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/polars_dates.py +0 -0
- {ltc_code-0.1.91 → ltc_code-0.1.93}/src/ltc_code/schema_mapping.py +0 -0
|
@@ -403,4 +403,249 @@ final = df.pipe(
|
|
|
403
403
|
"waitlist": "waitlist_school",
|
|
404
404
|
"withdraw": "withdraw_school",
|
|
405
405
|
},
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
import re
|
|
417
|
+
from typing import Mapping, Optional, Sequence, Union
|
|
418
|
+
|
|
419
|
+
import polars as pl
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
PRIORITY_PATTERNS_BY_CMO = {
|
|
423
|
+
"yes_prep": {
|
|
424
|
+
# Patterns used to create broad 1/0 priority flags.
|
|
425
|
+
"concept_patterns": {
|
|
426
|
+
"sibling": [
|
|
427
|
+
r"\bsibling\s+attending\b",
|
|
428
|
+
r"\bdynamic\s+sa\b",
|
|
429
|
+
],
|
|
430
|
+
"staff": [
|
|
431
|
+
r"\bemployee\b",
|
|
432
|
+
],
|
|
433
|
+
"zoned": [
|
|
434
|
+
r"\bisd\b",
|
|
435
|
+
r"\bzoned\s+for\b",
|
|
436
|
+
],
|
|
437
|
+
"transfer": [
|
|
438
|
+
r"\btransfer\b",
|
|
439
|
+
],
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
# Patterns used to flag repeated concepts.
|
|
443
|
+
# Example: "Sibling Attending - Sibling Attending Ben Holt"
|
|
444
|
+
# gives sibling_repeat = 1.
|
|
445
|
+
"repeat_patterns": {
|
|
446
|
+
"sibling_repeat": [
|
|
447
|
+
r"\bsibling\s+attending\b",
|
|
448
|
+
r"\bdynamic\s+sa\b",
|
|
449
|
+
],
|
|
450
|
+
"zoned_repeat": [
|
|
451
|
+
r"\bzoned\s+for\b",
|
|
452
|
+
],
|
|
453
|
+
"transfer_repeat": [
|
|
454
|
+
r"\btransfer\b",
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
# Patterns used to normalize each dash/plus-separated component.
|
|
459
|
+
# Anything unmatched is preserved as lowercased text.
|
|
460
|
+
"component_replacements": {
|
|
461
|
+
"sibling": [
|
|
462
|
+
r"\bsibling\s+attending(?:\s+.+)?\b",
|
|
463
|
+
r"\bdynamic\s+sa\b",
|
|
464
|
+
],
|
|
465
|
+
"staff": [
|
|
466
|
+
r"\bemployee\b",
|
|
467
|
+
],
|
|
468
|
+
"zoned": [
|
|
469
|
+
r"\bisd\b",
|
|
470
|
+
r"\bzoned\s+for\s+.+\b",
|
|
471
|
+
],
|
|
472
|
+
"transfer": [
|
|
473
|
+
r"\btransfer\b",
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
"aspire": {
|
|
479
|
+
"concept_patterns": {
|
|
480
|
+
"sibling": [
|
|
481
|
+
r"\bsibling\s+attending\b",
|
|
482
|
+
r"\bmultiples\b",
|
|
483
|
+
r"\bmutiples\b",
|
|
484
|
+
],
|
|
485
|
+
"staff": [
|
|
486
|
+
r"\bemployee\b",
|
|
487
|
+
],
|
|
488
|
+
"zoned": [
|
|
489
|
+
r"\bin\s+district\b",
|
|
490
|
+
],
|
|
491
|
+
"transfer": [
|
|
492
|
+
r"\btransfer\b",
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
"repeat_patterns": {
|
|
497
|
+
"sibling_repeat": [
|
|
498
|
+
r"\bsibling\s+attending\b",
|
|
499
|
+
r"\bmultiples\b",
|
|
500
|
+
r"\bmutiples\b",
|
|
501
|
+
],
|
|
502
|
+
"transfer_repeat": [
|
|
503
|
+
r"\btransfer\b",
|
|
504
|
+
],
|
|
505
|
+
"zoned_repeat": [
|
|
506
|
+
r"\bin\s+district\b",
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
|
|
510
|
+
"component_replacements": {
|
|
511
|
+
"sibling": [
|
|
512
|
+
r"\bsibling\s+attending(?:\s+.+)?\b",
|
|
513
|
+
r"\bmultiples\b",
|
|
514
|
+
r"\bmutiples\b",
|
|
515
|
+
],
|
|
516
|
+
"staff": [
|
|
517
|
+
r"\bemployee\b",
|
|
518
|
+
],
|
|
519
|
+
"zoned": [
|
|
520
|
+
r"\bin\s+district\b",
|
|
521
|
+
],
|
|
522
|
+
"transfer": [
|
|
523
|
+
r"\b(?:intra\s+)?aspire\s+transfer(?::?\s+.+)?\b",
|
|
524
|
+
r"\btransfer(?::?\s+.+)?\b",
|
|
525
|
+
],
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def add_priority_flags(
|
|
532
|
+
df: Union[pl.DataFrame, pl.LazyFrame],
|
|
533
|
+
priority_col: str,
|
|
534
|
+
cmo: str,
|
|
535
|
+
patterns_by_cmo: Optional[Mapping[str, Mapping[str, Mapping[str, Sequence[str]]]]] = None,
|
|
536
|
+
cleaned_col: str = "priority_group_clean",
|
|
537
|
+
) -> Union[pl.DataFrame, pl.LazyFrame]:
|
|
538
|
+
"""Create broad priority flags and a normalized priority string."""
|
|
539
|
+
if not isinstance(df, (pl.DataFrame, pl.LazyFrame)):
|
|
540
|
+
raise TypeError("df must be a polars DataFrame or LazyFrame.")
|
|
541
|
+
|
|
542
|
+
columns = df.collect_schema().names() if isinstance(df, pl.LazyFrame) else df.columns
|
|
543
|
+
if priority_col not in columns:
|
|
544
|
+
raise ValueError("Missing priority column: %s" % priority_col)
|
|
545
|
+
|
|
546
|
+
patterns_by_cmo = patterns_by_cmo or PRIORITY_PATTERNS_BY_CMO
|
|
547
|
+
if cmo not in patterns_by_cmo:
|
|
548
|
+
raise ValueError(
|
|
549
|
+
"No priority pattern mapping found for cmo=%r. Available CMOs: %s"
|
|
550
|
+
% (cmo, sorted(patterns_by_cmo))
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
config = patterns_by_cmo[cmo]
|
|
554
|
+
concept_patterns = config.get("concept_patterns", {})
|
|
555
|
+
repeat_patterns = config.get("repeat_patterns", {})
|
|
556
|
+
component_replacements = config.get("component_replacements", {})
|
|
557
|
+
|
|
558
|
+
def _contains_any(patterns: Sequence[str]) -> pl.Expr:
|
|
559
|
+
if not patterns:
|
|
560
|
+
return pl.lit(False)
|
|
561
|
+
return pl.any_horizontal(
|
|
562
|
+
[pl.col("_priority_text").str.contains(pattern) for pattern in patterns]
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
def _count_any(patterns: Sequence[str]) -> pl.Expr:
|
|
566
|
+
if not patterns:
|
|
567
|
+
return pl.lit(0)
|
|
568
|
+
return pl.sum_horizontal(
|
|
569
|
+
[pl.col("_priority_text").str.count_matches(pattern) for pattern in patterns]
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
def _clean_component(value: object) -> Optional[str]:
|
|
573
|
+
if value is None:
|
|
574
|
+
return None
|
|
575
|
+
|
|
576
|
+
text = str(value).strip().lower()
|
|
577
|
+
if not text:
|
|
578
|
+
return None
|
|
579
|
+
|
|
580
|
+
text = re.sub(r"\s+", " ", text)
|
|
581
|
+
|
|
582
|
+
for clean_label, patterns in component_replacements.items():
|
|
583
|
+
for pattern in patterns:
|
|
584
|
+
if re.search(pattern, text):
|
|
585
|
+
return clean_label
|
|
586
|
+
|
|
587
|
+
return text
|
|
588
|
+
|
|
589
|
+
def _normalize_priority(value: object) -> Optional[str]:
|
|
590
|
+
if value is None:
|
|
591
|
+
return None
|
|
592
|
+
|
|
593
|
+
text = str(value).lower()
|
|
594
|
+
text = re.sub(r"[-+]", "|", text)
|
|
595
|
+
raw_parts = [part.strip() for part in text.split("|")]
|
|
596
|
+
|
|
597
|
+
cleaned_parts = []
|
|
598
|
+
seen = set()
|
|
599
|
+
for part in raw_parts:
|
|
600
|
+
cleaned = _clean_component(part)
|
|
601
|
+
if cleaned is None or cleaned in seen:
|
|
602
|
+
continue
|
|
603
|
+
cleaned_parts.append(cleaned)
|
|
604
|
+
seen.add(cleaned)
|
|
605
|
+
|
|
606
|
+
if not cleaned_parts:
|
|
607
|
+
return None
|
|
608
|
+
|
|
609
|
+
return " - ".join(sorted(cleaned_parts))
|
|
610
|
+
|
|
611
|
+
result = df.with_columns(
|
|
612
|
+
pl.col(priority_col)
|
|
613
|
+
.cast(pl.Utf8, strict=False)
|
|
614
|
+
.fill_null("")
|
|
615
|
+
.str.to_lowercase()
|
|
616
|
+
.alias("_priority_text")
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
result = result.with_columns(
|
|
620
|
+
[
|
|
621
|
+
_contains_any(patterns).cast(pl.Int8).alias(flag_name)
|
|
622
|
+
for flag_name, patterns in concept_patterns.items()
|
|
623
|
+
]
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
result = result.with_columns(
|
|
627
|
+
[
|
|
628
|
+
(_count_any(patterns) > 1).cast(pl.Int8).alias(flag_name)
|
|
629
|
+
for flag_name, patterns in repeat_patterns.items()
|
|
630
|
+
]
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
result = result.with_columns(
|
|
634
|
+
pl.col(priority_col)
|
|
635
|
+
.map_elements(_normalize_priority, return_dtype=pl.Utf8)
|
|
636
|
+
.alias(cleaned_col)
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
return result.drop("_priority_text")
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
df = df.pipe(
|
|
643
|
+
add_priority_flags,
|
|
644
|
+
priority_col="priority_group_name",
|
|
645
|
+
cmo="yes_prep",
|
|
646
|
+
)
|
|
647
|
+
df = df.pipe(
|
|
648
|
+
add_priority_flags,
|
|
649
|
+
priority_col="priority_group_name",
|
|
650
|
+
cmo="aspire",
|
|
406
651
|
)
|
|
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
|