sensor-modeling 0.2.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.
- sensor_modeling/__init__.py +45 -0
- sensor_modeling/alerts/__init__.py +26 -0
- sensor_modeling/alerts/alert.py +532 -0
- sensor_modeling/analysis/__init__.py +43 -0
- sensor_modeling/analysis/_frame.py +19 -0
- sensor_modeling/analysis/behavioral_analysis.py +57 -0
- sensor_modeling/analysis/behavioral_metrics.py +66 -0
- sensor_modeling/analysis/comparison.py +164 -0
- sensor_modeling/analysis/dependency_network.py +408 -0
- sensor_modeling/analysis/granger_causality.py +314 -0
- sensor_modeling/analysis/pipeline.py +168 -0
- sensor_modeling/analysis/reporting.py +109 -0
- sensor_modeling/baseline/__init__.py +30 -0
- sensor_modeling/baseline/adaptive.py +520 -0
- sensor_modeling/baseline/features.py +224 -0
- sensor_modeling/change_point/__init__.py +13 -0
- sensor_modeling/change_point/_validation.py +31 -0
- sensor_modeling/change_point/adaptive_normalization.py +55 -0
- sensor_modeling/change_point/embedding_cpd.py +60 -0
- sensor_modeling/change_point/energy_efficient.py +57 -0
- sensor_modeling/change_point/genetic_optimization.py +65 -0
- sensor_modeling/cli.py +416 -0
- sensor_modeling/context/__init__.py +33 -0
- sensor_modeling/context/occupancy.py +529 -0
- sensor_modeling/data/__init__.py +5 -0
- sensor_modeling/data/loaders.py +146 -0
- sensor_modeling/data/preprocessing.py +83 -0
- sensor_modeling/data/synthetic.py +121 -0
- sensor_modeling/data/validation.py +81 -0
- sensor_modeling/evaluation/__init__.py +92 -0
- sensor_modeling/evaluation/ablation.py +303 -0
- sensor_modeling/evaluation/attribution.py +474 -0
- sensor_modeling/evaluation/detection.py +297 -0
- sensor_modeling/evaluation/metrics.py +541 -0
- sensor_modeling/evaluation/provenance.py +309 -0
- sensor_modeling/examples/__init__.py +1 -0
- sensor_modeling/examples/demos/__init__.py +1 -0
- sensor_modeling/examples/demos/ambient_pipeline_demo.py +418 -0
- sensor_modeling/examples/demos/bernoulli_ar_demo.py +356 -0
- sensor_modeling/examples/demos/cpd_ar_demo.py +25 -0
- sensor_modeling/examples/demos/cpd_benchmark.py +42 -0
- sensor_modeling/examples/demos/hmm_granger_demo.py +30 -0
- sensor_modeling/examples/demos/nhpp_pelt_demo.py +80 -0
- sensor_modeling/examples/tutorials/__init__.py +1 -0
- sensor_modeling/fusion/__init__.py +46 -0
- sensor_modeling/fusion/defaults.py +296 -0
- sensor_modeling/fusion/emissions.py +339 -0
- sensor_modeling/fusion/estimate.py +375 -0
- sensor_modeling/fusion/filter.py +323 -0
- sensor_modeling/health/__init__.py +31 -0
- sensor_modeling/health/monitor.py +590 -0
- sensor_modeling/health/status.py +74 -0
- sensor_modeling/hmm/__init__.py +15 -0
- sensor_modeling/hmm/adaptive_hmm.py +22 -0
- sensor_modeling/hmm/base.py +134 -0
- sensor_modeling/hmm/circadian_hmm.py +22 -0
- sensor_modeling/hmm/heterogeneous_hmm.py +22 -0
- sensor_modeling/hmm/hierarchical_hmm.py +35 -0
- sensor_modeling/hmm/scaled_dirichlet_hmm.py +23 -0
- sensor_modeling/interop/__init__.py +57 -0
- sensor_modeling/interop/fhir.py +418 -0
- sensor_modeling/interop/privacy.py +308 -0
- sensor_modeling/models/__init__.py +12 -0
- sensor_modeling/models/bernoulli_ar/__init__.py +6 -0
- sensor_modeling/models/bernoulli_ar/base_model.py +569 -0
- sensor_modeling/models/bernoulli_ar/multivariate_model.py +411 -0
- sensor_modeling/models/change_point_detection/__init__.py +10 -0
- sensor_modeling/models/change_point_detection/deep.py +65 -0
- sensor_modeling/models/change_point_detection/pelt.py +159 -0
- sensor_modeling/models/nhpp_pelt/__init__.py +5 -0
- sensor_modeling/models/nhpp_pelt/bspline.py +96 -0
- sensor_modeling/models/nhpp_pelt/cli.py +243 -0
- sensor_modeling/models/nhpp_pelt/diagnostics.py +234 -0
- sensor_modeling/models/nhpp_pelt/io.py +58 -0
- sensor_modeling/models/nhpp_pelt/model.py +408 -0
- sensor_modeling/models/nhpp_pelt/optimizer.py +142 -0
- sensor_modeling/models/nhpp_pelt/plotting.py +218 -0
- sensor_modeling/models/nhpp_pelt/quad.py +72 -0
- sensor_modeling/models/nhpp_pelt/regularization.py +121 -0
- sensor_modeling/models/nhpp_pelt/utils.py +174 -0
- sensor_modeling/observations/__init__.py +59 -0
- sensor_modeling/observations/adapters.py +195 -0
- sensor_modeling/observations/ingest.py +269 -0
- sensor_modeling/observations/observation.py +270 -0
- sensor_modeling/observations/registry.py +262 -0
- sensor_modeling/observations/stream.py +342 -0
- sensor_modeling/observations/types.py +107 -0
- sensor_modeling/observations/units.py +117 -0
- sensor_modeling/online/__init__.py +36 -0
- sensor_modeling/online/benchmarks.py +242 -0
- sensor_modeling/online/pipeline.py +485 -0
- sensor_modeling/simulation/__init__.py +54 -0
- sensor_modeling/simulation/faults.py +191 -0
- sensor_modeling/simulation/household.py +862 -0
- sensor_modeling/states/__init__.py +23 -0
- sensor_modeling/states/markov.py +105 -0
- sensor_modeling/states/ontology.py +238 -0
- sensor_modeling/utils/__init__.py +41 -0
- sensor_modeling/utils/data_io.py +199 -0
- sensor_modeling/utils/logging_config.py +10 -0
- sensor_modeling/utils/missing.py +188 -0
- sensor_modeling/utils/plotting.py +98 -0
- sensor_modeling/utils/validation.py +117 -0
- sensor_modeling/visualization/__init__.py +3 -0
- sensor_modeling/visualization/clinical.py +67 -0
- sensor_modeling/visualization/interactive.py +208 -0
- sensor_modeling/visualization/research.py +60 -0
- sensor_modeling/visualization/web_app.py +137 -0
- sensor_modeling-0.2.0.dist-info/METADATA +683 -0
- sensor_modeling-0.2.0.dist-info/RECORD +114 -0
- sensor_modeling-0.2.0.dist-info/WHEEL +5 -0
- sensor_modeling-0.2.0.dist-info/entry_points.txt +18 -0
- sensor_modeling-0.2.0.dist-info/licenses/LICENSE +21 -0
- sensor_modeling-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Research oriented plotting utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Collection, Sequence
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import plotly.express as px
|
|
10
|
+
import seaborn as sns
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _require_columns(
|
|
14
|
+
data: pd.DataFrame, required: Collection[str], context: str
|
|
15
|
+
) -> None:
|
|
16
|
+
"""Raise a clear error when required plotting columns are missing."""
|
|
17
|
+
missing = sorted(set(required) - set(data.columns))
|
|
18
|
+
if missing:
|
|
19
|
+
names = ", ".join(missing)
|
|
20
|
+
raise ValueError(f"{context} requires columns: {names}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _finite_residuals(residuals: Sequence[float]) -> list[float]:
|
|
24
|
+
"""Return residuals as finite floats for diagnostic plotting."""
|
|
25
|
+
values = np.asarray(list(residuals), dtype=float)
|
|
26
|
+
if values.size == 0:
|
|
27
|
+
raise ValueError("residuals must contain at least one value")
|
|
28
|
+
if not np.isfinite(values).all():
|
|
29
|
+
raise ValueError("residuals must contain only finite values")
|
|
30
|
+
return values.tolist()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def publication_figure(data: pd.DataFrame, x: str, y: str, hue: str | None = None):
|
|
34
|
+
"""Generate a publication quality figure using seaborn."""
|
|
35
|
+
required = {x, y}
|
|
36
|
+
if hue is not None:
|
|
37
|
+
required.add(hue)
|
|
38
|
+
_require_columns(data, required, "publication_figure")
|
|
39
|
+
ax = sns.lineplot(data=data, x=x, y=y, hue=hue)
|
|
40
|
+
ax.set_title("Publication Quality Figure")
|
|
41
|
+
return ax.get_figure()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def model_diagnostics(residuals: Sequence[float]):
|
|
45
|
+
"""Return a Plotly histogram of model residuals."""
|
|
46
|
+
return px.histogram(
|
|
47
|
+
x=_finite_residuals(residuals), nbins=30, title="Model Residuals"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def performance_comparison(scores: pd.DataFrame) -> px.bar:
|
|
52
|
+
"""Bar chart comparing performance metrics across models."""
|
|
53
|
+
_require_columns(scores, {"metric", "model", "score"}, "performance_comparison")
|
|
54
|
+
return px.bar(scores, x="model", y="score", color="metric")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def statistical_tests(results: pd.DataFrame) -> px.scatter:
|
|
58
|
+
"""Scatter plot visualizing statistical test outcomes."""
|
|
59
|
+
_require_columns(results, {"model", "pvalue", "test"}, "statistical_tests")
|
|
60
|
+
return px.scatter(results, x="test", y="pvalue", color="model")
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Lightweight Flask web interface for running analyses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from functools import wraps
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Callable
|
|
11
|
+
|
|
12
|
+
from flask import (
|
|
13
|
+
Flask,
|
|
14
|
+
Response,
|
|
15
|
+
abort,
|
|
16
|
+
current_app,
|
|
17
|
+
render_template_string,
|
|
18
|
+
request,
|
|
19
|
+
send_file,
|
|
20
|
+
)
|
|
21
|
+
from werkzeug.datastructures import FileStorage
|
|
22
|
+
from werkzeug.utils import secure_filename
|
|
23
|
+
|
|
24
|
+
UPLOAD_DIR = Path(tempfile.gettempdir()) / "sensor-modeling-uploads"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _configured_upload_dir() -> Path:
|
|
28
|
+
"""Return the active upload directory, creating it if needed."""
|
|
29
|
+
upload_dir = Path(current_app.config["UPLOAD_DIR"]).expanduser().resolve()
|
|
30
|
+
upload_dir.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
return upload_dir
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _check_auth(username: str, password: str) -> bool:
|
|
35
|
+
"""Validate provided credentials against environment variables."""
|
|
36
|
+
expected_user = os.environ.get("SM_USER")
|
|
37
|
+
expected_pass = os.environ.get("SM_PASS")
|
|
38
|
+
if expected_user is None or expected_pass is None:
|
|
39
|
+
return False
|
|
40
|
+
return username == expected_user and password == expected_pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _auth_failed() -> Response: # pragma: no cover - simple response
|
|
44
|
+
"""Return a 401 response requesting basic authentication."""
|
|
45
|
+
return Response(
|
|
46
|
+
"Authentication required",
|
|
47
|
+
401,
|
|
48
|
+
{"WWW-Authenticate": 'Basic realm="Login"'},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _uploaded_data_file() -> FileStorage:
|
|
53
|
+
"""Return the uploaded data file or abort with a clear client error."""
|
|
54
|
+
if "data" not in request.files:
|
|
55
|
+
abort(400, "missing data file")
|
|
56
|
+
file = request.files["data"]
|
|
57
|
+
if not file.filename:
|
|
58
|
+
abort(400, "invalid filename")
|
|
59
|
+
return file
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def requires_auth(f: Callable) -> Callable:
|
|
63
|
+
"""Enforce basic authentication on endpoints."""
|
|
64
|
+
|
|
65
|
+
@wraps(f)
|
|
66
|
+
def decorated(*args, **kwargs):
|
|
67
|
+
auth = request.authorization
|
|
68
|
+
if not auth or not _check_auth(auth.username, auth.password):
|
|
69
|
+
return _auth_failed()
|
|
70
|
+
return f(*args, **kwargs)
|
|
71
|
+
|
|
72
|
+
return decorated
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
app = Flask(__name__)
|
|
76
|
+
app.config["MAX_CONTENT_LENGTH"] = 2 * 1024 * 1024 # 2MB uploads
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.route("/", methods=["GET"])
|
|
80
|
+
@requires_auth
|
|
81
|
+
def index(): # pragma: no cover - simple template
|
|
82
|
+
"""Render a minimal upload form."""
|
|
83
|
+
return render_template_string(
|
|
84
|
+
"<h1>Sensor Modeling</h1>"
|
|
85
|
+
"<form action='{{ url_for(\"run\") }}' method='post' "
|
|
86
|
+
"enctype='multipart/form-data'>"
|
|
87
|
+
"<input type='file' name='data'>"
|
|
88
|
+
"<input type='text' name='param'>"
|
|
89
|
+
"<input type='submit'>"
|
|
90
|
+
"</form>"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.route("/run", methods=["POST"])
|
|
95
|
+
@requires_auth
|
|
96
|
+
def run():
|
|
97
|
+
"""Persist uploaded data and return a placeholder result."""
|
|
98
|
+
file = _uploaded_data_file()
|
|
99
|
+
param = request.form.get("param", "")
|
|
100
|
+
filename = secure_filename(file.filename)
|
|
101
|
+
if not filename:
|
|
102
|
+
abort(400, "invalid filename")
|
|
103
|
+
upload_dir = _configured_upload_dir()
|
|
104
|
+
path = (upload_dir / filename).resolve()
|
|
105
|
+
if path.parent != upload_dir: # path traversal guard
|
|
106
|
+
abort(400, "invalid path")
|
|
107
|
+
file.save(path)
|
|
108
|
+
# Placeholder analysis step
|
|
109
|
+
result_path = path.with_suffix(".txt")
|
|
110
|
+
with open(result_path, "w", encoding="utf-8") as fh:
|
|
111
|
+
fh.write(f"processed with param={param}\n")
|
|
112
|
+
return send_file(result_path, as_attachment=True)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def create_app(upload_dir: str | os.PathLike[str] | None = None) -> Flask:
|
|
116
|
+
"""Create the Flask application."""
|
|
117
|
+
configured_dir = upload_dir or os.environ.get("SM_UPLOAD_DIR") or UPLOAD_DIR
|
|
118
|
+
app.config["UPLOAD_DIR"] = str(configured_dir)
|
|
119
|
+
return app
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main() -> None:
|
|
123
|
+
"""Run the Flask development server for the web app."""
|
|
124
|
+
parser = argparse.ArgumentParser(description="Run the sensor visualization app")
|
|
125
|
+
parser.add_argument("--host", default="127.0.0.1", help="Host interface to bind")
|
|
126
|
+
parser.add_argument("--port", type=int, default=5000, help="Port to bind")
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--debug",
|
|
129
|
+
action="store_true",
|
|
130
|
+
help="Run the Flask development server in debug mode",
|
|
131
|
+
)
|
|
132
|
+
args = parser.parse_args()
|
|
133
|
+
create_app().run(host=args.host, port=args.port, debug=args.debug)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|