csrlite 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
- csrlite/__init__.py +71 -50
- csrlite/ae/__init__.py +1 -1
- csrlite/ae/ae_listing.py +494 -492
- csrlite/ae/ae_specific.py +483 -478
- csrlite/ae/ae_summary.py +401 -399
- csrlite/ae/ae_utils.py +62 -132
- csrlite/common/config.py +34 -0
- csrlite/common/count.py +293 -199
- csrlite/common/parse.py +308 -308
- csrlite/common/plan.py +365 -353
- csrlite/common/rtf.py +137 -0
- csrlite/common/utils.py +33 -33
- csrlite/common/yaml_loader.py +71 -71
- csrlite/disposition/__init__.py +2 -2
- csrlite/disposition/disposition.py +332 -301
- csrlite/ie/ie.py +405 -0
- {csrlite-0.1.0.dist-info → csrlite-0.2.1.dist-info}/METADATA +68 -68
- csrlite-0.2.1.dist-info/RECORD +20 -0
- csrlite-0.1.0.dist-info/RECORD +0 -17
- {csrlite-0.1.0.dist-info → csrlite-0.2.1.dist-info}/WHEEL +0 -0
- {csrlite-0.1.0.dist-info → csrlite-0.2.1.dist-info}/top_level.txt +0 -0
csrlite/ae/ae_utils.py
CHANGED
|
@@ -1,132 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if
|
|
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
|
-
with_label =
|
|
60
|
-
without_label =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
with_label = " " + " ".join(with_label.split())
|
|
64
|
-
without_label = " " + " ".join(without_label.split())
|
|
65
|
-
|
|
66
|
-
return (with_label, without_label)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def create_ae_rtf_table(
|
|
70
|
-
df: pl.DataFrame,
|
|
71
|
-
col_header_1: list[str],
|
|
72
|
-
col_header_2: list[str] | None,
|
|
73
|
-
col_widths: list[float] | None,
|
|
74
|
-
title: list[str] | str,
|
|
75
|
-
footnote: list[str] | str | None,
|
|
76
|
-
source: list[str] | str | None,
|
|
77
|
-
borders_2: bool = True,
|
|
78
|
-
orientation: str = "landscape",
|
|
79
|
-
) -> RTFDocument:
|
|
80
|
-
"""
|
|
81
|
-
Create a standardized RTF table document with 1 or 2 header rows.
|
|
82
|
-
"""
|
|
83
|
-
n_cols = len(df.columns)
|
|
84
|
-
|
|
85
|
-
# Calculate column widths if None - simple default
|
|
86
|
-
if col_widths is None:
|
|
87
|
-
col_widths = [1] * n_cols
|
|
88
|
-
|
|
89
|
-
# Normalize metadata
|
|
90
|
-
title_list = [title] if isinstance(title, str) else title
|
|
91
|
-
footnote_list = [footnote] if isinstance(footnote, str) else (footnote or [])
|
|
92
|
-
source_list = [source] if isinstance(source, str) else (source or [])
|
|
93
|
-
|
|
94
|
-
headers = [
|
|
95
|
-
RTFColumnHeader(
|
|
96
|
-
text=col_header_1,
|
|
97
|
-
col_rel_width=col_widths,
|
|
98
|
-
text_justification=["l"] + ["c"] * (n_cols - 1),
|
|
99
|
-
)
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
if col_header_2:
|
|
103
|
-
h2_kwargs = {
|
|
104
|
-
"text": col_header_2,
|
|
105
|
-
"col_rel_width": col_widths,
|
|
106
|
-
"text_justification": ["l"] + ["c"] * (n_cols - 1),
|
|
107
|
-
}
|
|
108
|
-
if borders_2:
|
|
109
|
-
h2_kwargs["border_left"] = ["single"]
|
|
110
|
-
h2_kwargs["border_top"] = [""]
|
|
111
|
-
|
|
112
|
-
headers.append(RTFColumnHeader(**h2_kwargs))
|
|
113
|
-
|
|
114
|
-
rtf_components: dict[str, Any] = {
|
|
115
|
-
"df": df,
|
|
116
|
-
"rtf_page": RTFPage(orientation=orientation),
|
|
117
|
-
"rtf_title": RTFTitle(text=title_list),
|
|
118
|
-
"rtf_column_header": headers,
|
|
119
|
-
"rtf_body": RTFBody(
|
|
120
|
-
col_rel_width=col_widths,
|
|
121
|
-
text_justification=["l"] + ["c"] * (n_cols - 1),
|
|
122
|
-
border_left=["single"] * n_cols,
|
|
123
|
-
),
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if footnote_list:
|
|
127
|
-
rtf_components["rtf_footnote"] = RTFFootnote(text=footnote_list)
|
|
128
|
-
|
|
129
|
-
if source_list:
|
|
130
|
-
rtf_components["rtf_source"] = RTFSource(text=source_list)
|
|
131
|
-
|
|
132
|
-
return RTFDocument(**rtf_components)
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_ae_parameter_title(param: Any, prefix: str = "Participants With") -> str:
|
|
5
|
+
"""
|
|
6
|
+
Extract title from parameter for ae_* title generation.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
param: Parameter object with terms attribute
|
|
10
|
+
prefix: Prefix for the title (e.g. "Participants With", "Listing of Participants With")
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Title string for the analysis
|
|
14
|
+
"""
|
|
15
|
+
default_suffix = "Adverse Events"
|
|
16
|
+
|
|
17
|
+
if not param:
|
|
18
|
+
return f"{prefix} {default_suffix}"
|
|
19
|
+
|
|
20
|
+
# Check for terms attribute
|
|
21
|
+
if hasattr(param, "terms") and param.terms and isinstance(param.terms, dict):
|
|
22
|
+
terms = param.terms
|
|
23
|
+
|
|
24
|
+
# Preprocess to empty strings (avoiding None)
|
|
25
|
+
before = terms.get("before", "").title()
|
|
26
|
+
after = terms.get("after", "").title()
|
|
27
|
+
|
|
28
|
+
# Build title and clean up extra spaces
|
|
29
|
+
title = f"{prefix} {before} {default_suffix} {after}"
|
|
30
|
+
return " ".join(title.split()) # Remove extra spaces
|
|
31
|
+
|
|
32
|
+
# Fallback to default
|
|
33
|
+
return f"{prefix} {default_suffix}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_ae_parameter_row_labels(param: Any) -> tuple[str, str]:
|
|
37
|
+
"""
|
|
38
|
+
Generate n_with and n_without row labels based on parameter terms.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Tuple of (n_with_label, n_without_label)
|
|
42
|
+
"""
|
|
43
|
+
# Default labels
|
|
44
|
+
default_with = " with one or more adverse events"
|
|
45
|
+
default_without = " with no adverse events"
|
|
46
|
+
|
|
47
|
+
if not param or not hasattr(param, "terms") or not param.terms:
|
|
48
|
+
return (default_with, default_without)
|
|
49
|
+
|
|
50
|
+
terms = param.terms
|
|
51
|
+
before = terms.get("before", "").lower()
|
|
52
|
+
after = terms.get("after", "").lower()
|
|
53
|
+
|
|
54
|
+
# Build dynamic labels with leading indentation
|
|
55
|
+
with_label = f"with one or more {before} adverse events {after}"
|
|
56
|
+
without_label = f"with no {before} adverse events {after}"
|
|
57
|
+
|
|
58
|
+
# Clean up extra spaces and add back the 4-space indentation
|
|
59
|
+
with_label = " " + " ".join(with_label.split())
|
|
60
|
+
without_label = " " + " ".join(without_label.split())
|
|
61
|
+
|
|
62
|
+
return (with_label, without_label)
|
csrlite/common/config.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# pyre-strict
|
|
2
|
+
"""
|
|
3
|
+
Central configuration for csrlite.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Literal, Optional
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CsrLiteConfig(BaseModel):
|
|
12
|
+
"""
|
|
13
|
+
Global configuration for csrlite library.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# Column Name Defaults
|
|
17
|
+
id_col: str = Field(default="USUBJID", description="Subject Identifier Column")
|
|
18
|
+
group_col: Optional[str] = Field(default=None, description="Treatment Group Column")
|
|
19
|
+
|
|
20
|
+
# Missing Value Handling
|
|
21
|
+
missing_str: str = Field(
|
|
22
|
+
default="__missing__", description="String to represent missing string values"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Logging
|
|
26
|
+
logging_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
|
|
27
|
+
default="INFO", description="Default logging level"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Global configuration instance
|
|
34
|
+
config = CsrLiteConfig()
|