amarantos 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.
- amarantos/core/loaders.py +13 -0
- amarantos/core/schemas.py +156 -0
- amarantos/rank.py +101 -0
- amarantos-0.1.0.dist-info/METADATA +112 -0
- amarantos-0.1.0.dist-info/RECORD +8 -0
- amarantos-0.1.0.dist-info/WHEEL +4 -0
- amarantos-0.1.0.dist-info/entry_points.txt +2 -0
- amarantos-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Load choice data from YAML files."""
|
|
2
|
+
|
|
3
|
+
from amarantos.core.schemas import CHOICES_DIR, Choice
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_all_choices(domain: str | None = None) -> list[Choice]:
|
|
7
|
+
"""Load all choices, optionally filtered by domain."""
|
|
8
|
+
if domain:
|
|
9
|
+
paths = sorted((CHOICES_DIR / domain).glob("*.yaml"))
|
|
10
|
+
else:
|
|
11
|
+
paths = sorted(CHOICES_DIR.glob("**/*.yaml"))
|
|
12
|
+
|
|
13
|
+
return [Choice.load(path) for path in paths]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Schemas for choice data and user profiles."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import attrs
|
|
8
|
+
import dummio.yaml
|
|
9
|
+
|
|
10
|
+
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
|
11
|
+
CHOICES_DIR = DATA_DIR / "choices"
|
|
12
|
+
|
|
13
|
+
# 95% CI uses 1.96 standard deviations
|
|
14
|
+
Z_95 = 1.96
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _name_to_filename(name: str) -> str:
|
|
18
|
+
"""Convert a choice name to a valid filename."""
|
|
19
|
+
clean = name.lower()
|
|
20
|
+
clean = re.sub(r"\s*\([^)]*\)", "", clean) # Remove parentheses content
|
|
21
|
+
clean = re.sub(r"[^a-z0-9]+", "_", clean) # Replace non-alphanumeric with _
|
|
22
|
+
return clean.strip("_") + ".yaml"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Outcome(StrEnum):
|
|
26
|
+
"""Health outcomes that can be measured -- at least in principle -- on a continuous scale."""
|
|
27
|
+
|
|
28
|
+
RELATIVE_MORTALITY_RISK = "Relative mortality risk"
|
|
29
|
+
DELAYED_AGING = "Years of delayed aging"
|
|
30
|
+
SUBJECTIVE_WELLBEING = "Subjective wellbeing - number of just-noticeable differences"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@attrs.frozen
|
|
34
|
+
class Effect:
|
|
35
|
+
"""A Gaussian-distributed health effect estimate.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
outcome: The health outcome being measured.
|
|
39
|
+
evidence: A summary of the evidence supporting this effect. Open-ended, but ideally includes things such as
|
|
40
|
+
the nature of the studies, sample sizes, and any relevant statistical measures, or first-principles
|
|
41
|
+
reasoning.
|
|
42
|
+
mean: The mean effect estimate.
|
|
43
|
+
std: The standard deviation of the effect estimate.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
outcome: Outcome
|
|
47
|
+
mean: float
|
|
48
|
+
std: float
|
|
49
|
+
evidence: str = ""
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def ci_lower(self) -> float:
|
|
53
|
+
"""Lower bound of 95% confidence interval."""
|
|
54
|
+
return self.mean - Z_95 * self.std
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def ci_upper(self) -> float:
|
|
58
|
+
"""Upper bound of 95% confidence interval."""
|
|
59
|
+
return self.mean + Z_95 * self.std
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_beneficial(self) -> bool:
|
|
63
|
+
"""Effect is beneficial if upper bound < 1.0."""
|
|
64
|
+
return self.ci_upper < 1.0
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def is_harmful(self) -> bool:
|
|
68
|
+
"""Effect is harmful if lower bound > 1.0."""
|
|
69
|
+
return self.ci_lower > 1.0
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def is_uncertain(self) -> bool:
|
|
73
|
+
"""Effect is uncertain if CI crosses 1.0."""
|
|
74
|
+
return self.ci_lower < 1.0 < self.ci_upper
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@attrs.frozen
|
|
78
|
+
class Specification:
|
|
79
|
+
"""Detailed specification of a wellness choice.
|
|
80
|
+
|
|
81
|
+
Attributes:
|
|
82
|
+
duration_h: Average duration of one session of the activity in hours.
|
|
83
|
+
For running, ~0.5. For taking a pill, ~0.001.
|
|
84
|
+
weekly_freq: How many times per week on average. Running might be 4,
|
|
85
|
+
taking a pill might be 7, fasting might be 1.
|
|
86
|
+
annual_cost_h: Total annual time cost including preparation, cleanup,
|
|
87
|
+
and actually doing the activity. Sleep optimization should count
|
|
88
|
+
bedtime ritual plus extra sleep time.
|
|
89
|
+
annual_cost_usd: Total annual dollar cost excluding time. For running,
|
|
90
|
+
maybe $100 for shoes.
|
|
91
|
+
description: Plain english description of the choice noting common
|
|
92
|
+
variations in implementation and citing evidence where available.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
duration_h: float
|
|
96
|
+
weekly_freq: float
|
|
97
|
+
annual_cost_h: float
|
|
98
|
+
annual_cost_usd: float
|
|
99
|
+
description: str = ""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@attrs.frozen
|
|
103
|
+
class Choice:
|
|
104
|
+
"""A wellness choice with effect estimates."""
|
|
105
|
+
|
|
106
|
+
domain: str
|
|
107
|
+
name: str
|
|
108
|
+
effects: tuple[Effect, ...]
|
|
109
|
+
specification: Specification
|
|
110
|
+
summary: str = ""
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def path(self) -> Path:
|
|
114
|
+
"""Default path for this choice."""
|
|
115
|
+
return CHOICES_DIR / self.domain / _name_to_filename(self.name)
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def load(cls, path: Path) -> "Choice":
|
|
119
|
+
"""Load a choice from a YAML file."""
|
|
120
|
+
data = dummio.yaml.load(filepath=path)
|
|
121
|
+
data["effects"] = tuple(Effect(**e) for e in data["effects"])
|
|
122
|
+
data["specification"] = Specification(**data["specification"])
|
|
123
|
+
data.pop("literature", None) # Remove legacy field if present
|
|
124
|
+
data.pop("annual_cost", None) # Remove legacy field if present
|
|
125
|
+
return cls(**data)
|
|
126
|
+
|
|
127
|
+
def save(self, path: Path | None = None) -> None:
|
|
128
|
+
"""Save this choice to a YAML file."""
|
|
129
|
+
if path is None:
|
|
130
|
+
path = self.path
|
|
131
|
+
data = {
|
|
132
|
+
"domain": self.domain,
|
|
133
|
+
"name": self.name,
|
|
134
|
+
"specification": attrs.asdict(self.specification),
|
|
135
|
+
"effects": [attrs.asdict(e) for e in self.effects],
|
|
136
|
+
}
|
|
137
|
+
if self.summary:
|
|
138
|
+
data["summary"] = self.summary
|
|
139
|
+
dummio.yaml.save(data, filepath=path)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@attrs.frozen
|
|
143
|
+
class User:
|
|
144
|
+
"""User attributes for personalized recommendations."""
|
|
145
|
+
|
|
146
|
+
is_male: bool | None = None
|
|
147
|
+
age: int | None = None
|
|
148
|
+
height_cm: float | None = None
|
|
149
|
+
body_fat_pct: float | None = None
|
|
150
|
+
blood_pressure_systolic: int | None = None
|
|
151
|
+
is_vegan: bool | None = None
|
|
152
|
+
is_vegetarian: bool | None = None
|
|
153
|
+
diet_quality_pctl: float | None = None
|
|
154
|
+
exercise_cardio_pctl: float | None = None
|
|
155
|
+
exercise_resistance_pctl: float | None = None
|
|
156
|
+
sleep_hours: float | None = None
|
amarantos/rank.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""CLI to rank wellness choices by conservative lifespan impact estimate."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from amarantos.core.loaders import load_all_choices
|
|
6
|
+
from amarantos.core.schemas import Choice, Effect, Outcome
|
|
7
|
+
|
|
8
|
+
# 30th percentile z-score for normal distribution
|
|
9
|
+
Z_30 = -0.524
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_effect_by_outcome(choice: Choice, outcome: Outcome) -> Effect | None:
|
|
13
|
+
"""Extract a specific effect from a choice by outcome type."""
|
|
14
|
+
for effect in choice.effects:
|
|
15
|
+
if effect.outcome == outcome:
|
|
16
|
+
return effect
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def percentile_30(effect: Effect) -> float:
|
|
21
|
+
"""Calculate 30th percentile of effect estimate."""
|
|
22
|
+
return effect.mean + Z_30 * effect.std
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command()
|
|
26
|
+
@click.option(
|
|
27
|
+
"-n",
|
|
28
|
+
"--num-top-bottom",
|
|
29
|
+
type=int,
|
|
30
|
+
default=None,
|
|
31
|
+
help="Show only top N and bottom N choices",
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"-d",
|
|
35
|
+
"--domain",
|
|
36
|
+
type=str,
|
|
37
|
+
default=None,
|
|
38
|
+
help="Filter by domain (e.g., 'diet', 'exercise')",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--maxd",
|
|
42
|
+
type=int,
|
|
43
|
+
default=None,
|
|
44
|
+
help="Show only top N choices from each domain",
|
|
45
|
+
)
|
|
46
|
+
def main(num_top_bottom: int | None, domain: str | None, maxd: int | None) -> None:
|
|
47
|
+
"""Rank wellness choices by 30th percentile lifespan impact."""
|
|
48
|
+
choices = load_all_choices(domain)
|
|
49
|
+
|
|
50
|
+
results: list[tuple[str, str, float, float, float]] = []
|
|
51
|
+
for choice in choices:
|
|
52
|
+
aging_effect = get_effect_by_outcome(choice, Outcome.DELAYED_AGING)
|
|
53
|
+
if aging_effect:
|
|
54
|
+
p30 = percentile_30(aging_effect)
|
|
55
|
+
results.append(
|
|
56
|
+
(
|
|
57
|
+
choice.name,
|
|
58
|
+
choice.domain,
|
|
59
|
+
p30,
|
|
60
|
+
choice.specification.annual_cost_usd,
|
|
61
|
+
choice.specification.annual_cost_h,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Sort by 30th percentile descending
|
|
66
|
+
results.sort(key=lambda x: x[2], reverse=True)
|
|
67
|
+
|
|
68
|
+
# Apply maxd filter if specified
|
|
69
|
+
if maxd is not None:
|
|
70
|
+
domain_counts: dict[str, int] = {}
|
|
71
|
+
filtered: list[tuple[str, str, float, float, float]] = []
|
|
72
|
+
for item in results:
|
|
73
|
+
d = item[1]
|
|
74
|
+
domain_counts[d] = domain_counts.get(d, 0) + 1
|
|
75
|
+
if domain_counts[d] <= maxd:
|
|
76
|
+
filtered.append(item)
|
|
77
|
+
results = filtered
|
|
78
|
+
|
|
79
|
+
# Header
|
|
80
|
+
click.echo()
|
|
81
|
+
click.echo(f"{'Choice':<40} {'P30 (years)':>12} {'$/year':>10} {'h/year':>10}")
|
|
82
|
+
click.echo("-" * 74)
|
|
83
|
+
|
|
84
|
+
if num_top_bottom is None:
|
|
85
|
+
for name, _, p30, cost_usd, cost_h in results:
|
|
86
|
+
click.echo(f"{name:<40} {p30:>+12.2f} {cost_usd:>10.0f} {cost_h:>10.0f}")
|
|
87
|
+
else:
|
|
88
|
+
click.echo(f"TOP {num_top_bottom}:")
|
|
89
|
+
for name, _, p30, cost_usd, cost_h in results[:num_top_bottom]:
|
|
90
|
+
click.echo(f"{name:<40} {p30:>+12.2f} {cost_usd:>10.0f} {cost_h:>10.0f}")
|
|
91
|
+
|
|
92
|
+
click.echo()
|
|
93
|
+
click.echo(f"BOTTOM {num_top_bottom}:")
|
|
94
|
+
for name, _, p30, cost_usd, cost_h in results[-num_top_bottom:]:
|
|
95
|
+
click.echo(f"{name:<40} {p30:>+12.2f} {cost_usd:>10.0f} {cost_h:>10.0f}")
|
|
96
|
+
|
|
97
|
+
click.echo()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
main()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: amarantos
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tips and tools for personal wellness and longevity
|
|
5
|
+
Project-URL: Repository, https://github.com/zkurtz/amarantos
|
|
6
|
+
Author-email: Zach Kurtz <zkurtz@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Requires-Dist: attrs>=25.4
|
|
11
|
+
Requires-Dist: click>=8.3.1
|
|
12
|
+
Requires-Dist: dummio>=1.10.2
|
|
13
|
+
Requires-Dist: matplotlib>=3.10.8
|
|
14
|
+
Requires-Dist: numpy>=2.2.6
|
|
15
|
+
Requires-Dist: pre-commit>=4.0.1
|
|
16
|
+
Requires-Dist: ruamel-yaml>=0.19.1
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Amarantos
|
|
20
|
+
|
|
21
|
+
Tips and tools for personal wellness and longevity. *Amarantos* (ἀμάραντος) is Greek for "unfading" or "immortal" and is the root of *amaranth*—a flower known for retaining its color even dried.
|
|
22
|
+
|
|
23
|
+
> **Warning: AI-Generated Content**
|
|
24
|
+
>
|
|
25
|
+
> This repository was generated with AI assistance and has not been thoroughly vetted by a human. References, claims, and code may contain errors or inaccuracies. Please double-check all information against primary sources and exercise critical judgment. Treat this as a starting point for careful research, not a definitive resource.
|
|
26
|
+
|
|
27
|
+
## CLI Commands
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Rank all choices by 30th percentile lifespan impact
|
|
31
|
+
amarantos
|
|
32
|
+
|
|
33
|
+
# Filter by domain
|
|
34
|
+
amarantos --domain exercise
|
|
35
|
+
|
|
36
|
+
# Show only top/bottom 5
|
|
37
|
+
amarantos -n 5
|
|
38
|
+
|
|
39
|
+
# Show top 3 from each domain
|
|
40
|
+
amarantos --maxd 3
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Awesome Longevity Resources
|
|
44
|
+
|
|
45
|
+
A curated list of products, tools, and resources for personal wellness and longevity.
|
|
46
|
+
|
|
47
|
+
### Knowledge & Research
|
|
48
|
+
|
|
49
|
+
- [Examine.com](https://examine.com/) - Evidence-based supplement and nutrition information
|
|
50
|
+
- [Peter Attia's The Drive](https://peterattiamd.com/) - Longevity-focused podcast and resources
|
|
51
|
+
- [FoundMyFitness](https://www.foundmyfitness.com/) - Dr. Rhonda Patrick's research on nutrition and aging
|
|
52
|
+
- [Fight Aging!](https://www.fightaging.org/) - Long-running blog covering aging research news
|
|
53
|
+
- [LongevityWiki](https://longevitywiki.org/) - Community-curated longevity information
|
|
54
|
+
- [Longevity Protocols](https://longevity-protocols.com/) - Personal project with AI-analyzed interventions (not peer-reviewed)
|
|
55
|
+
- [Bryan Johnson's Blueprint](https://blueprint.bryanjohnson.com/) - Detailed self-experimentation protocol (note: sells supplements)
|
|
56
|
+
|
|
57
|
+
### Scientific Databases & Journals
|
|
58
|
+
|
|
59
|
+
- [DrugAge Database](https://genomics.senescence.info/drugs/) - Database of aging-related drugs
|
|
60
|
+
- [Human Ageing Genomic Resources](https://genomics.senescence.info/) - Databases on aging-related genes
|
|
61
|
+
- [GeroScience](https://www.springer.com/journal/11357) - Peer-reviewed aging research journal
|
|
62
|
+
|
|
63
|
+
### Communities & DAOs
|
|
64
|
+
|
|
65
|
+
- [r/longevity](https://www.reddit.com/r/longevity/) - Reddit community for longevity research
|
|
66
|
+
- [VitaDAO](https://www.vitadao.com/) - Decentralized collective funding longevity research
|
|
67
|
+
- [LessWrong Longevity Tag](https://www.lesswrong.com/tag/life-extension) - Rationalist perspectives on life extension
|
|
68
|
+
|
|
69
|
+
### GitHub Repos & Awesome Lists
|
|
70
|
+
|
|
71
|
+
- [awesome-longevity (atilatech)](https://github.com/atilatech/awesome-longevity) - Comprehensive longevity resources list
|
|
72
|
+
- [awesome-longevity (Rejuve)](https://github.com/Rejuve/awesome-longevity) - Therapeutics, regenerative medicine, medical AI
|
|
73
|
+
- [awesome-longevity-information](https://github.com/Laurentiu-Andronache/awesome-longevity-information) - Curated healthspan extension resources
|
|
74
|
+
- [longevityTools](https://github.com/tgirke/longevityTools) - R package for longevity research analysis
|
|
75
|
+
- [Longevity Genie](https://github.com/longevity-genie) - Open-source AI tools for longevity research
|
|
76
|
+
|
|
77
|
+
### Tracking & Measurement
|
|
78
|
+
|
|
79
|
+
- [Oura Ring](https://ouraring.com/) - Sleep and activity tracking
|
|
80
|
+
- [Levels](https://www.levels.com/) - Continuous glucose monitoring
|
|
81
|
+
- [InsideTracker](https://www.insidetracker.com/) - Blood biomarker analysis
|
|
82
|
+
- [WHOOP](https://www.whoop.com/) - Strain and recovery monitoring
|
|
83
|
+
|
|
84
|
+
### Academic & Research Institutions
|
|
85
|
+
|
|
86
|
+
- [National Institute on Aging (NIA)](https://www.nia.nih.gov/) - NIH's primary aging research arm
|
|
87
|
+
- [Buck Institute for Research on Aging](https://www.buckinstitute.org/) - Leading independent aging research institute
|
|
88
|
+
- [Lifespan Research Institute (formerly SENS)](https://www.sens.org/) - Nonprofit focused on damage-repair approaches to aging
|
|
89
|
+
- [Altos Labs](https://www.altoslabs.com/) - Well-funded cellular reprogramming research
|
|
90
|
+
- [Calico Labs](https://www.calicolabs.com/) - Google/Alphabet's aging research company
|
|
91
|
+
- [Stanford Center on Longevity](https://longevity.stanford.edu/) - Interdisciplinary longevity research
|
|
92
|
+
- [American Federation for Aging Research (AFAR)](https://www.afar.org/) - Funds aging research and education
|
|
93
|
+
|
|
94
|
+
## Software Tools
|
|
95
|
+
|
|
96
|
+
### Installating amarantos
|
|
97
|
+
|
|
98
|
+
TODO: get on pypi.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv add amarantos
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Usage
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import amarantos
|
|
108
|
+
|
|
109
|
+
amarantos.hello()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
TODO: build something!
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
amarantos/rank.py,sha256=zRPcrmLM0DmWbpyny3cIDiLS1grnN3vDjAzQV5KbKMI,3080
|
|
2
|
+
amarantos/core/loaders.py,sha256=cjP3ZonnEiih_AVRhduxj7M9JUA5aKYIkwEAC6zCD54,413
|
|
3
|
+
amarantos/core/schemas.py,sha256=VB2635pK7v_ImH-CiTHOoDQt8HsgCX2j6iOpBjONLv0,5043
|
|
4
|
+
amarantos-0.1.0.dist-info/METADATA,sha256=SmidSvIaAReZGrjslppQgIE4YXDMZ_pnuGXR3btzctY,4828
|
|
5
|
+
amarantos-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
amarantos-0.1.0.dist-info/entry_points.txt,sha256=q8v2iCpAqD4w2NvptbQCsvdUQhq0yge1WyPaxNOdlwg,50
|
|
7
|
+
amarantos-0.1.0.dist-info/licenses/LICENSE,sha256=yYmJX4hIj1EvCSZdWf20KULynADt84gULi3-Pqkb5l8,1067
|
|
8
|
+
amarantos-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zach Kurtz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|