lss4py 0.1.0__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.
lss4py/__init__.py ADDED
@@ -0,0 +1,53 @@
1
+ from lss4py.domain import (
2
+ EventLog,
3
+ EventLogBindingsIR,
4
+ LeadTimeBindingIR,
5
+ MetricBindingsIR,
6
+ MetricSemanticsIR,
7
+ ProcessEdgeIR,
8
+ ProcessModelIR,
9
+ ProcessStepIR,
10
+ StageBindingsIR,
11
+ validate_event_log,
12
+ )
13
+ from lss4py.errors import AdapterError, LSS4PyError, ValidationError, WorkflowError
14
+ from lss4py.workflows import map_process
15
+ from lss4py.yellow_belt import (
16
+ booked_to_completed_yield,
17
+ created_to_booked_yield,
18
+ lead_time,
19
+ process_cycle_efficiency,
20
+ rolled_yield,
21
+ takt_time,
22
+ wait_time,
23
+ )
24
+
25
+ __all__ = [
26
+ "AdapterError",
27
+ "EventLogBindingsIR",
28
+ "EventLog",
29
+ "LSS4PyError",
30
+ "ValidationError",
31
+ "WorkflowError",
32
+ "booked_to_completed_yield",
33
+ "created_to_booked_yield",
34
+ "lead_time",
35
+ "LeadTimeBindingIR",
36
+ "main",
37
+ "map_process",
38
+ "MetricBindingsIR",
39
+ "MetricSemanticsIR",
40
+ "ProcessEdgeIR",
41
+ "ProcessModelIR",
42
+ "ProcessStepIR",
43
+ "process_cycle_efficiency",
44
+ "rolled_yield",
45
+ "StageBindingsIR",
46
+ "takt_time",
47
+ "validate_event_log",
48
+ "wait_time",
49
+ ]
50
+
51
+
52
+ def main() -> None:
53
+ print("lss4py scaffold ready")
@@ -0,0 +1,4 @@
1
+ from .event_log import load_event_log
2
+ from .tabular import load_tabular_records
3
+
4
+ __all__ = ["load_event_log", "load_tabular_records"]
@@ -0,0 +1,8 @@
1
+ from pathlib import Path
2
+
3
+ from lss4py.domain.models import EventLog
4
+
5
+
6
+ def load_event_log(path: str | Path) -> EventLog:
7
+ _ = path
8
+ raise NotImplementedError("Event-log adapter is not implemented yet.")
@@ -0,0 +1,7 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+
4
+
5
+ def load_tabular_records(path: str | Path) -> list[dict[str, Any]]:
6
+ _ = path
7
+ raise NotImplementedError("Tabular adapter is not implemented yet.")
@@ -0,0 +1,3 @@
1
+ """Experimental analytics modules live here until a stable Yellow Belt API is defined."""
2
+
3
+ __all__: list[str] = []
@@ -0,0 +1,6 @@
1
+ from lss4py.domain.models import AssumptionReport, EventLog
2
+
3
+
4
+ def capability_report(event_log: EventLog) -> AssumptionReport:
5
+ _ = event_log
6
+ raise NotImplementedError("Capability reporting is not implemented yet.")
@@ -0,0 +1,8 @@
1
+ from lss4py.domain.models import EventLog, PercentileProfile
2
+
3
+
4
+ def summarize_cycle_time(
5
+ event_log: EventLog, percentiles: tuple[int, ...] = (50, 90, 95, 99)
6
+ ) -> PercentileProfile:
7
+ _ = (event_log, percentiles)
8
+ raise NotImplementedError("Cycle-time percentile summarization is not implemented yet.")
@@ -0,0 +1,6 @@
1
+ from lss4py.domain.models import AssumptionReport, EventLog
2
+
3
+
4
+ def spc_report(event_log: EventLog) -> AssumptionReport:
5
+ _ = event_log
6
+ raise NotImplementedError("SPC reporting is not implemented yet.")
lss4py/cli.py ADDED
@@ -0,0 +1,16 @@
1
+ import click
2
+ from rich.console import Console
3
+
4
+ from lss4py.observability import configure_logging
5
+
6
+
7
+ @click.group()
8
+ @click.option("--debug", is_flag=True, default=False, help="Enable debug logging.")
9
+ def main(debug: bool) -> None:
10
+ configure_logging(debug=debug)
11
+
12
+
13
+ @main.command("doctor")
14
+ def doctor() -> None:
15
+ console = Console()
16
+ console.print("[green]lss4py[/green] scaffold is ready.")
@@ -0,0 +1,37 @@
1
+ from .models import (
2
+ AssumptionNote,
3
+ AssumptionReport,
4
+ ComparisonSlice,
5
+ EventLog,
6
+ EventLogBindingsIR,
7
+ LeadTimeBindingIR,
8
+ MetricBindingsIR,
9
+ MetricResult,
10
+ MetricSemanticsIR,
11
+ PercentileProfile,
12
+ ProcessEdgeIR,
13
+ ProcessModelIR,
14
+ ProcessStepIR,
15
+ ProcessWindow,
16
+ StageBindingsIR,
17
+ )
18
+ from .validation import validate_event_log
19
+
20
+ __all__ = [
21
+ "AssumptionNote",
22
+ "AssumptionReport",
23
+ "ComparisonSlice",
24
+ "EventLogBindingsIR",
25
+ "EventLog",
26
+ "LeadTimeBindingIR",
27
+ "MetricBindingsIR",
28
+ "MetricSemanticsIR",
29
+ "MetricResult",
30
+ "PercentileProfile",
31
+ "ProcessEdgeIR",
32
+ "ProcessModelIR",
33
+ "ProcessStepIR",
34
+ "ProcessWindow",
35
+ "StageBindingsIR",
36
+ "validate_event_log",
37
+ ]
@@ -0,0 +1,126 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime
3
+ from typing import Any, Literal
4
+
5
+
6
+ @dataclass(slots=True)
7
+ class AssumptionNote:
8
+ code: str
9
+ message: str
10
+ severity: str = "info"
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class AssumptionReport:
15
+ assumptions: list[AssumptionNote] = field(default_factory=list)
16
+ warnings: list[AssumptionNote] = field(default_factory=list)
17
+
18
+
19
+ @dataclass(slots=True)
20
+ class ProcessWindow:
21
+ name: str
22
+ start: datetime | None = None
23
+ end: datetime | None = None
24
+
25
+
26
+ ValueClassification = Literal["value_add", "non_value_add", "required_non_value_add"]
27
+ EdgeKind = Literal["sequence", "parallel_split", "parallel_join", "rework"]
28
+ ParallelTiming = Literal["critical_path", "sum_all_branches"]
29
+ ObservedDataShape = Literal["event_log_rows"]
30
+
31
+
32
+ @dataclass(slots=True)
33
+ class LeadTimeBindingIR:
34
+ start_column: str | None = None
35
+ end_column: str | None = None
36
+ unit: Literal["seconds", "minutes", "hours", "days"] = "hours"
37
+
38
+
39
+ @dataclass(slots=True)
40
+ class StageBindingsIR:
41
+ created: str | None = None
42
+ booked: str | None = None
43
+ completed: str | None = None
44
+
45
+
46
+ @dataclass(slots=True)
47
+ class EventLogBindingsIR:
48
+ case_id_column: str | None = None
49
+ step_id_column: str | None = None
50
+ started_at_column: str | None = None
51
+ completed_at_column: str | None = None
52
+
53
+
54
+ @dataclass(slots=True)
55
+ class MetricBindingsIR:
56
+ lead_time: LeadTimeBindingIR = field(default_factory=LeadTimeBindingIR)
57
+ stages: StageBindingsIR = field(default_factory=StageBindingsIR)
58
+ event_log: EventLogBindingsIR = field(default_factory=EventLogBindingsIR)
59
+
60
+
61
+ @dataclass(slots=True)
62
+ class MetricSemanticsIR:
63
+ parallel_timing: ParallelTiming = "critical_path"
64
+ observed_data_shape: ObservedDataShape = "event_log_rows"
65
+
66
+
67
+ @dataclass(slots=True)
68
+ class ProcessStepIR:
69
+ id: str
70
+ name: str
71
+ cycle_time_column: str | None = None
72
+ value_classification: ValueClassification = "value_add"
73
+ role_label: str | None = None
74
+ metadata: dict[str, Any] = field(default_factory=dict)
75
+
76
+
77
+ @dataclass(slots=True)
78
+ class ProcessEdgeIR:
79
+ source_step_id: str
80
+ target_step_id: str
81
+ kind: EdgeKind = "sequence"
82
+ condition: str | None = None
83
+ metadata: dict[str, Any] = field(default_factory=dict)
84
+
85
+
86
+ @dataclass(slots=True)
87
+ class ProcessModelIR:
88
+ name: str
89
+ schema_version: str = "1.0"
90
+ steps: list[ProcessStepIR] = field(default_factory=list)
91
+ edges: list[ProcessEdgeIR] = field(default_factory=list)
92
+ bindings: MetricBindingsIR = field(default_factory=MetricBindingsIR)
93
+ metric_semantics: MetricSemanticsIR = field(default_factory=MetricSemanticsIR)
94
+ metadata: dict[str, Any] = field(default_factory=dict)
95
+
96
+
97
+ @dataclass(slots=True)
98
+ class EventLog:
99
+ events: list[dict[str, Any]]
100
+ required_fields: tuple[str, ...] = ("case_id", "activity", "timestamp")
101
+ metadata: dict[str, Any] = field(default_factory=dict)
102
+
103
+
104
+ @dataclass(slots=True)
105
+ class ComparisonSlice:
106
+ before: EventLog
107
+ after: EventLog
108
+ label: str = "before_after"
109
+
110
+
111
+ @dataclass(slots=True)
112
+ class MetricResult:
113
+ name: str
114
+ value: float | None
115
+ unit: str | None = None
116
+ notes: list[AssumptionNote] = field(default_factory=list)
117
+ metadata: dict[str, Any] = field(default_factory=dict)
118
+
119
+
120
+ @dataclass(slots=True)
121
+ class PercentileProfile:
122
+ p50: float | None
123
+ p90: float | None
124
+ p95: float | None
125
+ p99: float | None
126
+ count: int
@@ -0,0 +1,30 @@
1
+ from .models import AssumptionNote, AssumptionReport, EventLog
2
+
3
+
4
+ def validate_event_log(event_log: EventLog) -> AssumptionReport:
5
+ if not event_log.events:
6
+ return AssumptionReport(
7
+ warnings=[
8
+ AssumptionNote(
9
+ code="empty_event_log",
10
+ message="Event log contains zero rows.",
11
+ severity="warning",
12
+ )
13
+ ]
14
+ )
15
+
16
+ sample_event = event_log.events[0]
17
+ missing = [field for field in event_log.required_fields if field not in sample_event]
18
+
19
+ if missing:
20
+ return AssumptionReport(
21
+ warnings=[
22
+ AssumptionNote(
23
+ code="missing_required_fields",
24
+ message=f"Missing required fields: {', '.join(missing)}",
25
+ severity="warning",
26
+ )
27
+ ]
28
+ )
29
+
30
+ return AssumptionReport()
lss4py/errors.py ADDED
@@ -0,0 +1,14 @@
1
+ class LSS4PyError(Exception):
2
+ """Base exception for lss4py."""
3
+
4
+
5
+ class ValidationError(LSS4PyError):
6
+ """Raised when input data fails validation checks."""
7
+
8
+
9
+ class AdapterError(LSS4PyError):
10
+ """Raised when adapter-level IO or parsing fails."""
11
+
12
+
13
+ class WorkflowError(LSS4PyError):
14
+ """Raised when workflow orchestration fails."""
@@ -0,0 +1,4 @@
1
+ from .logging import configure_logging, get_logger
2
+ from .telemetry import configure_telemetry, get_tracer
3
+
4
+ __all__ = ["configure_logging", "configure_telemetry", "get_logger", "get_tracer"]
@@ -0,0 +1,35 @@
1
+ import logging
2
+ import sys
3
+ from typing import Any
4
+
5
+ import structlog
6
+
7
+
8
+ def configure_logging(debug: bool = False) -> None:
9
+ log_level = logging.DEBUG if debug else logging.INFO
10
+
11
+ logging.basicConfig(
12
+ format="%(message)s",
13
+ stream=sys.stdout,
14
+ level=log_level,
15
+ )
16
+
17
+ processors: list[Any] = [
18
+ structlog.contextvars.merge_contextvars,
19
+ structlog.processors.add_log_level,
20
+ structlog.processors.TimeStamper(fmt="iso", utc=True),
21
+ structlog.processors.StackInfoRenderer(),
22
+ structlog.processors.format_exc_info,
23
+ structlog.processors.JSONRenderer(),
24
+ ]
25
+
26
+ structlog.configure(
27
+ processors=processors,
28
+ wrapper_class=structlog.make_filtering_bound_logger(log_level),
29
+ logger_factory=structlog.PrintLoggerFactory(file=sys.stdout),
30
+ cache_logger_on_first_use=True,
31
+ )
32
+
33
+
34
+ def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger:
35
+ return structlog.get_logger(name)
@@ -0,0 +1,19 @@
1
+ from typing import Final
2
+
3
+ from opentelemetry import trace
4
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
5
+ from opentelemetry.sdk.trace import TracerProvider
6
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
7
+
8
+ DEFAULT_SERVICE_NAME: Final[str] = "lss4py"
9
+
10
+
11
+ def configure_telemetry(service_name: str = DEFAULT_SERVICE_NAME) -> None:
12
+ resource = Resource.create({SERVICE_NAME: service_name})
13
+ provider = TracerProvider(resource=resource)
14
+ provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
15
+ trace.set_tracer_provider(provider)
16
+
17
+
18
+ def get_tracer(name: str = DEFAULT_SERVICE_NAME):
19
+ return trace.get_tracer(name)
@@ -0,0 +1,3 @@
1
+ from .bundles import AnalysisBundle, to_markdown
2
+
3
+ __all__ = ["AnalysisBundle", "to_markdown"]
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+
5
+ @dataclass(slots=True)
6
+ class AnalysisBundle:
7
+ title: str
8
+ sections: list[dict[str, Any]] = field(default_factory=list)
9
+ metadata: dict[str, Any] = field(default_factory=dict)
10
+
11
+
12
+ def to_markdown(bundle: AnalysisBundle) -> str:
13
+ _ = bundle
14
+ raise NotImplementedError("Markdown bundle rendering is not implemented yet.")
@@ -0,0 +1,3 @@
1
+ from .process_map import map_process
2
+
3
+ __all__ = ["map_process"]
@@ -0,0 +1,6 @@
1
+ from lss4py.domain.models import EventLog
2
+
3
+
4
+ def bottleneck_report(event_log: EventLog) -> dict[str, object]:
5
+ _ = event_log
6
+ raise NotImplementedError("Bottleneck workflow is not implemented yet.")
@@ -0,0 +1,6 @@
1
+ from lss4py.domain.models import ComparisonSlice
2
+
3
+
4
+ def compare_before_after(comparison: ComparisonSlice) -> dict[str, object]:
5
+ _ = comparison
6
+ raise NotImplementedError("Before/after comparison workflow is not implemented yet.")
@@ -0,0 +1,49 @@
1
+ from collections import defaultdict
2
+ from datetime import datetime
3
+ from typing import Any, TypedDict
4
+
5
+ from lss4py.domain.models import EventLog
6
+
7
+
8
+ class ProcessMapResult(TypedDict):
9
+ nodes: list[str]
10
+ edges: dict[str, dict[str, int]]
11
+
12
+
13
+ def _parse_timestamp(value: Any) -> datetime:
14
+ if isinstance(value, datetime):
15
+ return value
16
+
17
+ if isinstance(value, str):
18
+ return datetime.fromisoformat(value)
19
+
20
+ raise TypeError("Event timestamps must be datetime objects or ISO-8601 strings.")
21
+
22
+
23
+ def map_process(event_log: EventLog) -> ProcessMapResult:
24
+ grouped_events: dict[str, list[dict[str, Any]]] = defaultdict(list)
25
+
26
+ for event in event_log.events:
27
+ case_id = str(event["case_id"])
28
+ grouped_events[case_id].append(event)
29
+
30
+ nodes: set[str] = set()
31
+ edges: dict[str, dict[str, int]] = {}
32
+
33
+ for case_events in grouped_events.values():
34
+ ordered_events = sorted(case_events, key=lambda event: _parse_timestamp(event["timestamp"]))
35
+
36
+ for event in ordered_events:
37
+ nodes.add(str(event["activity"]))
38
+
39
+ for current_event, next_event in zip(ordered_events, ordered_events[1:], strict=False):
40
+ current_activity = str(current_event["activity"])
41
+ next_activity = str(next_event["activity"])
42
+ edges.setdefault(current_activity, {})
43
+ transition_count = edges[current_activity].get(next_activity, 0) + 1
44
+ edges[current_activity][next_activity] = transition_count
45
+
46
+ return {
47
+ "nodes": sorted(nodes),
48
+ "edges": edges,
49
+ }
@@ -0,0 +1,6 @@
1
+ from lss4py.domain.models import EventLog
2
+
3
+
4
+ def redesign_simulation(event_log: EventLog) -> dict[str, object]:
5
+ _ = event_log
6
+ raise NotImplementedError("Simulation workflow is not implemented yet.")