personalitygen 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 B.T. Franklin
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.
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.1
2
+ Name: personalitygen
3
+ Version: 0.1.0
4
+ Summary: Generate and manage simulated human-like personalities based on the Big Five model.
5
+ Keywords: personality,big-five,ocean,simulation
6
+ Author-Email: "B.T. Franklin" <brandon.franklin@gmail.com>
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Typing :: Typed
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Utilities
20
+ Project-URL: Homepage, https://github.com/btfranklin/personalitygen
21
+ Project-URL: Issues, https://github.com/btfranklin/personalitygen/issues
22
+ Project-URL: Changelog, https://github.com/btfranklin/personalitygen/releases
23
+ Project-URL: Repository, https://github.com/btfranklin/personalitygen.git
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+
27
+ # personalitygen
28
+
29
+ ![personalitygen social preview](.github/social%20preview/personalitygen_social_preview.jpg)
30
+
31
+ `personalitygen` generates and manages simulated human-like personalities based on the Big Five (OCEAN) model. It is designed for
32
+ simulation, storytelling, and testing scenarios where you want plausible, varied personality profiles without running surveys.
33
+
34
+ ## Intent and scope
35
+
36
+ - Generate full Big Five profiles with sub-trait components and aggregate scores.
37
+ - Bias outputs by life stage using tuned Gaussian distributions (child, young adult, adult).
38
+ - Derive a conflict-resolution style from trait weights, plus mapped concern-for-self/others.
39
+ - Support deterministic generation by accepting a seeded random source.
40
+ - Stay lightweight and dependency-free (pure Python).
41
+
42
+ This package is not a clinical assessment tool and does not implement questionnaires or scoring rubrics.
43
+
44
+ ## Model overview
45
+
46
+ - Big Five traits: openness, conscientiousness, extraversion, agreeableness, neuroticism.
47
+ - Each trait is composed of three sub-traits and a weighted aggregate score.
48
+ - Life stage influences distribution means and standard deviations for sampling.
49
+ - Conflict-resolution style is selected from avoiding, obliging, integrating, dominating, or compromising based on trait scores.
50
+
51
+ ## Usage
52
+
53
+ ```python
54
+ from personalitygen import BigFivePersonality, LifeStage
55
+
56
+ personality = BigFivePersonality.random(LifeStage.ADULT)
57
+ print(personality.trait_configuration)
58
+ print(personality.conflict_resolution_configuration)
59
+ ```
60
+
61
+ If you want deterministic output, pass a seeded random number generator:
62
+
63
+ ```python
64
+ import random
65
+ from personalitygen import BigFiveTraitConfiguration, LifeStage
66
+
67
+ rng = random.Random(42)
68
+ traits = BigFiveTraitConfiguration.random(LifeStage.YOUNG_ADULT, rng=rng)
69
+ print(traits)
70
+ ```
71
+
72
+ ## Development
73
+
74
+ ```bash
75
+ pdm install --group dev
76
+ pdm run test
77
+ ```
@@ -0,0 +1,51 @@
1
+ # personalitygen
2
+
3
+ ![personalitygen social preview](.github/social%20preview/personalitygen_social_preview.jpg)
4
+
5
+ `personalitygen` generates and manages simulated human-like personalities based on the Big Five (OCEAN) model. It is designed for
6
+ simulation, storytelling, and testing scenarios where you want plausible, varied personality profiles without running surveys.
7
+
8
+ ## Intent and scope
9
+
10
+ - Generate full Big Five profiles with sub-trait components and aggregate scores.
11
+ - Bias outputs by life stage using tuned Gaussian distributions (child, young adult, adult).
12
+ - Derive a conflict-resolution style from trait weights, plus mapped concern-for-self/others.
13
+ - Support deterministic generation by accepting a seeded random source.
14
+ - Stay lightweight and dependency-free (pure Python).
15
+
16
+ This package is not a clinical assessment tool and does not implement questionnaires or scoring rubrics.
17
+
18
+ ## Model overview
19
+
20
+ - Big Five traits: openness, conscientiousness, extraversion, agreeableness, neuroticism.
21
+ - Each trait is composed of three sub-traits and a weighted aggregate score.
22
+ - Life stage influences distribution means and standard deviations for sampling.
23
+ - Conflict-resolution style is selected from avoiding, obliging, integrating, dominating, or compromising based on trait scores.
24
+
25
+ ## Usage
26
+
27
+ ```python
28
+ from personalitygen import BigFivePersonality, LifeStage
29
+
30
+ personality = BigFivePersonality.random(LifeStage.ADULT)
31
+ print(personality.trait_configuration)
32
+ print(personality.conflict_resolution_configuration)
33
+ ```
34
+
35
+ If you want deterministic output, pass a seeded random number generator:
36
+
37
+ ```python
38
+ import random
39
+ from personalitygen import BigFiveTraitConfiguration, LifeStage
40
+
41
+ rng = random.Random(42)
42
+ traits = BigFiveTraitConfiguration.random(LifeStage.YOUNG_ADULT, rng=rng)
43
+ print(traits)
44
+ ```
45
+
46
+ ## Development
47
+
48
+ ```bash
49
+ pdm install --group dev
50
+ pdm run test
51
+ ```
@@ -0,0 +1,71 @@
1
+ [project]
2
+ name = "personalitygen"
3
+ version = "0.1.0"
4
+ description = "Generate and manage simulated human-like personalities based on the Big Five model."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ authors = [
8
+ { name = "B.T. Franklin", email = "brandon.franklin@gmail.com" },
9
+ ]
10
+ keywords = [
11
+ "personality",
12
+ "big-five",
13
+ "ocean",
14
+ "simulation",
15
+ ]
16
+ dependencies = []
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Typing :: Typed",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ "Intended Audience :: Developers",
28
+ "Topic :: Software Development :: Libraries :: Python Modules",
29
+ "Topic :: Utilities",
30
+ ]
31
+
32
+ [project.license]
33
+ text = "MIT"
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/btfranklin/personalitygen"
37
+ Issues = "https://github.com/btfranklin/personalitygen/issues"
38
+ Changelog = "https://github.com/btfranklin/personalitygen/releases"
39
+ Repository = "https://github.com/btfranklin/personalitygen.git"
40
+
41
+ [build-system]
42
+ requires = [
43
+ "pdm-backend",
44
+ ]
45
+ build-backend = "pdm.backend"
46
+
47
+ [tool.pdm]
48
+ distribution = true
49
+ packages = [
50
+ { include = "personalitygen", from = "src" },
51
+ ]
52
+
53
+ [tool.pdm.build]
54
+ excludes = [
55
+ "tests/**",
56
+ ]
57
+
58
+ [tool.pdm.scripts]
59
+ test = "pytest"
60
+ lint = "flake8 src tests"
61
+
62
+ [tool.pytest.ini_options]
63
+ testpaths = [
64
+ "tests",
65
+ ]
66
+
67
+ [dependency-groups]
68
+ dev = [
69
+ "pytest>=9.0.2",
70
+ "flake8>=7.3.0",
71
+ ]
@@ -0,0 +1,30 @@
1
+ """Public interface for personalitygen."""
2
+
3
+ from personalitygen.enums import LifeStage, PriorityLevel
4
+ from personalitygen.personality import (
5
+ BigFiveConflictResolutionConfiguration,
6
+ BigFiveConflictResolutionStyle,
7
+ BigFivePersonality,
8
+ BigFiveTraitConfiguration,
9
+ )
10
+ from personalitygen.traits import (
11
+ BigFiveAgreeableness,
12
+ BigFiveConscientiousness,
13
+ BigFiveExtraversion,
14
+ BigFiveNeuroticism,
15
+ BigFiveOpenness,
16
+ )
17
+
18
+ __all__ = [
19
+ "BigFiveAgreeableness",
20
+ "BigFiveConscientiousness",
21
+ "BigFiveConflictResolutionConfiguration",
22
+ "BigFiveConflictResolutionStyle",
23
+ "BigFiveExtraversion",
24
+ "BigFiveNeuroticism",
25
+ "BigFiveOpenness",
26
+ "BigFivePersonality",
27
+ "BigFiveTraitConfiguration",
28
+ "LifeStage",
29
+ "PriorityLevel",
30
+ ]
@@ -0,0 +1,5 @@
1
+ from personalitygen.enums import LifeStage as LifeStage, PriorityLevel as PriorityLevel
2
+ from personalitygen.personality import BigFiveConflictResolutionConfiguration as BigFiveConflictResolutionConfiguration, BigFiveConflictResolutionStyle as BigFiveConflictResolutionStyle, BigFivePersonality as BigFivePersonality, BigFiveTraitConfiguration as BigFiveTraitConfiguration
3
+ from personalitygen.traits import BigFiveAgreeableness as BigFiveAgreeableness, BigFiveConscientiousness as BigFiveConscientiousness, BigFiveExtraversion as BigFiveExtraversion, BigFiveNeuroticism as BigFiveNeuroticism, BigFiveOpenness as BigFiveOpenness
4
+
5
+ __all__ = ['BigFiveAgreeableness', 'BigFiveConscientiousness', 'BigFiveConflictResolutionConfiguration', 'BigFiveConflictResolutionStyle', 'BigFiveExtraversion', 'BigFiveNeuroticism', 'BigFiveOpenness', 'BigFivePersonality', 'BigFiveTraitConfiguration', 'LifeStage', 'PriorityLevel']
@@ -0,0 +1,4 @@
1
+ """Shared constants for personalitygen."""
2
+
3
+ UNIT_RANGE_MIN = 0.0
4
+ UNIT_RANGE_MAX = 1.0
@@ -0,0 +1,2 @@
1
+ UNIT_RANGE_MIN: float
2
+ UNIT_RANGE_MAX: float
@@ -0,0 +1,17 @@
1
+ """Enums for personalitygen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class LifeStage(str, Enum):
9
+ CHILD = "child"
10
+ YOUNG_ADULT = "young_adult"
11
+ ADULT = "adult"
12
+
13
+
14
+ class PriorityLevel(str, Enum):
15
+ LOW = "low"
16
+ MODERATE = "moderate"
17
+ HIGH = "high"
@@ -0,0 +1,11 @@
1
+ from enum import Enum
2
+
3
+ class LifeStage(str, Enum):
4
+ CHILD = 'child'
5
+ YOUNG_ADULT = 'young_adult'
6
+ ADULT = 'adult'
7
+
8
+ class PriorityLevel(str, Enum):
9
+ LOW = 'low'
10
+ MODERATE = 'moderate'
11
+ HIGH = 'high'
@@ -0,0 +1,228 @@
1
+ """Top-level personality configuration models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import random
6
+ from dataclasses import dataclass
7
+ from enum import Enum
8
+ from typing import Self
9
+
10
+ from personalitygen.enums import LifeStage, PriorityLevel
11
+ from personalitygen.randomness import RandomSource
12
+ from personalitygen.traits import (
13
+ BigFiveAgreeableness,
14
+ BigFiveConscientiousness,
15
+ BigFiveExtraversion,
16
+ BigFiveNeuroticism,
17
+ BigFiveOpenness,
18
+ )
19
+
20
+
21
+ def _weighted_choice(
22
+ weights: dict["BigFiveConflictResolutionStyle", float],
23
+ *,
24
+ rng: RandomSource | None = None,
25
+ ) -> "BigFiveConflictResolutionStyle":
26
+ if not weights:
27
+ raise ValueError("weights must be non-empty")
28
+ if any(weight < 0.0 for weight in weights.values()):
29
+ raise ValueError("weights must be non-negative")
30
+ source = rng if rng is not None else random
31
+ total = sum(weights.values())
32
+ if total <= 0.0:
33
+ weights = {style: 1.0 for style in weights}
34
+ total = float(len(weights))
35
+
36
+ threshold = source.uniform(0.0, total)
37
+ for style, weight in weights.items():
38
+ threshold -= weight
39
+ if threshold <= 0.0:
40
+ return style
41
+ return next(iter(weights))
42
+
43
+
44
+ class BigFiveConflictResolutionStyle(str, Enum):
45
+ # Concern for self: low. Concern for others: low. Tries to avoid conflict.
46
+ AVOIDING = "avoiding"
47
+ # Concern for self: low. Concern for others: high. Accommodates others.
48
+ OBLIGING = "obliging"
49
+ # Concern for self: high. Concern for others: high. Collaborates.
50
+ INTEGRATING = "integrating"
51
+ # Concern for self: high. Concern for others: low. Competes to win.
52
+ DOMINATING = "dominating"
53
+ # Concern for self: moderate. Concern for others: moderate. Trades off.
54
+ COMPROMISING = "compromising"
55
+
56
+ @classmethod
57
+ def random(
58
+ cls,
59
+ trait_configuration: BigFiveTraitConfiguration,
60
+ *,
61
+ rng: RandomSource | None = None,
62
+ ) -> Self:
63
+ # These weights are loosely based on:
64
+ # Priyadarshini, S. (2017). Effect of Personality on Conflict
65
+ # Resolution Styles. IRA-International Journal of Management &
66
+ # Social Sciences, 7(2), 196-207.
67
+ style_levels = {
68
+ cls.AVOIDING: trait_configuration.neuroticism.score * 0.7
69
+ + trait_configuration.openness.score * -0.1
70
+ + trait_configuration.agreeableness.score * 0.2
71
+ + trait_configuration.conscientiousness.score * -0.2,
72
+ cls.OBLIGING: trait_configuration.neuroticism.score * 0.2
73
+ + trait_configuration.extraversion.score * -0.2
74
+ + trait_configuration.openness.score * -0.1
75
+ + trait_configuration.agreeableness.score * 0.3,
76
+ cls.INTEGRATING: trait_configuration.openness.score * 0.1
77
+ + trait_configuration.agreeableness.score * 0.2
78
+ + trait_configuration.conscientiousness.score * 0.1,
79
+ cls.DOMINATING: trait_configuration.neuroticism.score * -0.2
80
+ + trait_configuration.extraversion.score * 0.2
81
+ + trait_configuration.openness.score * -0.2
82
+ + trait_configuration.agreeableness.score * -0.4
83
+ + trait_configuration.conscientiousness.score * 0.2,
84
+ cls.COMPROMISING: trait_configuration.neuroticism.score * 0.1
85
+ + trait_configuration.extraversion.score * 0.1
86
+ + trait_configuration.conscientiousness.score * -0.2,
87
+ }
88
+
89
+ # Keep a small chance of selecting counter-indicated styles.
90
+ minimum_weight = 0.1
91
+ weights = {
92
+ style: max(level, minimum_weight)
93
+ for style, level in style_levels.items()
94
+ }
95
+ return _weighted_choice(weights, rng=rng)
96
+
97
+
98
+ @dataclass(frozen=True, slots=True)
99
+ class BigFiveTraitConfiguration:
100
+ # Appreciation for art, emotion, adventure, and curiosity.
101
+ # Opposite: closedness.
102
+ openness: BigFiveOpenness
103
+ # Self-discipline, dutifulness, and achievement orientation.
104
+ # Opposite: undisciplined.
105
+ conscientiousness: BigFiveConscientiousness
106
+ # Energy, sociability, and stimulation-seeking.
107
+ # Opposite: introversion.
108
+ extraversion: BigFiveExtraversion
109
+ # Compassion and cooperation toward others.
110
+ # Opposite: antagonism.
111
+ agreeableness: BigFiveAgreeableness
112
+ # Tendency toward unpleasant emotions and instability.
113
+ # Opposite: emotional stability.
114
+ neuroticism: BigFiveNeuroticism
115
+
116
+ @classmethod
117
+ def random(
118
+ cls, life_stage: LifeStage, *, rng: RandomSource | None = None
119
+ ) -> Self:
120
+ return cls(
121
+ openness=BigFiveOpenness.random(life_stage, rng=rng),
122
+ conscientiousness=BigFiveConscientiousness.random(
123
+ life_stage, rng=rng
124
+ ),
125
+ extraversion=BigFiveExtraversion.random(life_stage, rng=rng),
126
+ agreeableness=BigFiveAgreeableness.random(life_stage, rng=rng),
127
+ neuroticism=BigFiveNeuroticism.random(life_stage, rng=rng),
128
+ )
129
+
130
+ def __str__(self) -> str:
131
+ return (
132
+ "openness: "
133
+ f"{self.openness}\n"
134
+ "conscientiousness: "
135
+ f"{self.conscientiousness}\n"
136
+ "extraversion: "
137
+ f"{self.extraversion}\n"
138
+ "agreeableness: "
139
+ f"{self.agreeableness}\n"
140
+ "neuroticism: "
141
+ f"{self.neuroticism}"
142
+ )
143
+
144
+
145
+ _STYLE_TO_CONCERNS: dict[
146
+ BigFiveConflictResolutionStyle, tuple[PriorityLevel, PriorityLevel]
147
+ ] = {
148
+ BigFiveConflictResolutionStyle.AVOIDING: (
149
+ PriorityLevel.LOW,
150
+ PriorityLevel.LOW,
151
+ ),
152
+ BigFiveConflictResolutionStyle.OBLIGING: (
153
+ PriorityLevel.LOW,
154
+ PriorityLevel.HIGH,
155
+ ),
156
+ BigFiveConflictResolutionStyle.INTEGRATING: (
157
+ PriorityLevel.HIGH,
158
+ PriorityLevel.HIGH,
159
+ ),
160
+ BigFiveConflictResolutionStyle.DOMINATING: (
161
+ PriorityLevel.HIGH,
162
+ PriorityLevel.LOW,
163
+ ),
164
+ BigFiveConflictResolutionStyle.COMPROMISING: (
165
+ PriorityLevel.MODERATE,
166
+ PriorityLevel.MODERATE,
167
+ ),
168
+ }
169
+
170
+
171
+ def _validate_style_concerns() -> None:
172
+ expected = set(BigFiveConflictResolutionStyle)
173
+ actual = set(_STYLE_TO_CONCERNS)
174
+ if expected != actual:
175
+ missing = {style.value for style in expected - actual}
176
+ extra = {style.value for style in actual - expected}
177
+ raise ValueError(
178
+ "Conflict resolution styles and concern mapping are out of sync. "
179
+ f"Missing: {sorted(missing)}. Extra: {sorted(extra)}."
180
+ )
181
+
182
+
183
+ _validate_style_concerns()
184
+
185
+
186
+ @dataclass(frozen=True, slots=True)
187
+ class BigFiveConflictResolutionConfiguration:
188
+ conflict_resolution_style: BigFiveConflictResolutionStyle
189
+ concern_for_self: PriorityLevel
190
+ concern_for_others: PriorityLevel
191
+
192
+ @classmethod
193
+ def random(
194
+ cls,
195
+ trait_configuration: BigFiveTraitConfiguration,
196
+ *,
197
+ rng: RandomSource | None = None,
198
+ ) -> Self:
199
+ style = BigFiveConflictResolutionStyle.random(
200
+ trait_configuration, rng=rng
201
+ )
202
+ concern_for_self, concern_for_others = _STYLE_TO_CONCERNS[style]
203
+ return cls(
204
+ conflict_resolution_style=style,
205
+ concern_for_self=concern_for_self,
206
+ concern_for_others=concern_for_others,
207
+ )
208
+
209
+
210
+ @dataclass(frozen=True, slots=True)
211
+ class BigFivePersonality:
212
+ trait_configuration: BigFiveTraitConfiguration
213
+ conflict_resolution_configuration: BigFiveConflictResolutionConfiguration
214
+
215
+ @classmethod
216
+ def random(
217
+ cls, life_stage: LifeStage, *, rng: RandomSource | None = None
218
+ ) -> Self:
219
+ trait_configuration = BigFiveTraitConfiguration.random(
220
+ life_stage, rng=rng
221
+ )
222
+ conflict_configuration = BigFiveConflictResolutionConfiguration.random(
223
+ trait_configuration, rng=rng
224
+ )
225
+ return cls(
226
+ trait_configuration=trait_configuration,
227
+ conflict_resolution_configuration=conflict_configuration,
228
+ )
@@ -0,0 +1,40 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from personalitygen.enums import LifeStage as LifeStage, PriorityLevel as PriorityLevel
4
+ from personalitygen.randomness import RandomSource as RandomSource
5
+ from personalitygen.traits import BigFiveAgreeableness as BigFiveAgreeableness, BigFiveConscientiousness as BigFiveConscientiousness, BigFiveExtraversion as BigFiveExtraversion, BigFiveNeuroticism as BigFiveNeuroticism, BigFiveOpenness as BigFiveOpenness
6
+ from typing import Self
7
+
8
+ class BigFiveConflictResolutionStyle(str, Enum):
9
+ AVOIDING = 'avoiding'
10
+ OBLIGING = 'obliging'
11
+ INTEGRATING = 'integrating'
12
+ DOMINATING = 'dominating'
13
+ COMPROMISING = 'compromising'
14
+ @classmethod
15
+ def random(cls, trait_configuration: BigFiveTraitConfiguration, *, rng: RandomSource | None = None) -> Self: ...
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class BigFiveTraitConfiguration:
19
+ openness: BigFiveOpenness
20
+ conscientiousness: BigFiveConscientiousness
21
+ extraversion: BigFiveExtraversion
22
+ agreeableness: BigFiveAgreeableness
23
+ neuroticism: BigFiveNeuroticism
24
+ @classmethod
25
+ def random(cls, life_stage: LifeStage, *, rng: RandomSource | None = None) -> Self: ...
26
+
27
+ @dataclass(frozen=True, slots=True)
28
+ class BigFiveConflictResolutionConfiguration:
29
+ conflict_resolution_style: BigFiveConflictResolutionStyle
30
+ concern_for_self: PriorityLevel
31
+ concern_for_others: PriorityLevel
32
+ @classmethod
33
+ def random(cls, trait_configuration: BigFiveTraitConfiguration, *, rng: RandomSource | None = None) -> Self: ...
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class BigFivePersonality:
37
+ trait_configuration: BigFiveTraitConfiguration
38
+ conflict_resolution_configuration: BigFiveConflictResolutionConfiguration
39
+ @classmethod
40
+ def random(cls, life_stage: LifeStage, *, rng: RandomSource | None = None) -> Self: ...
File without changes
@@ -0,0 +1,50 @@
1
+ """Random utilities used by personalitygen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import random
6
+ import statistics
7
+ from typing import Protocol
8
+
9
+
10
+ class RandomSource(Protocol):
11
+ """Minimal interface needed for deterministic sampling."""
12
+
13
+ def gauss(self, mu: float, sigma: float) -> float: ...
14
+
15
+ def uniform(self, a: float, b: float) -> float: ...
16
+
17
+
18
+ def _coerce_rng(rng: RandomSource | None) -> RandomSource:
19
+ return rng if rng is not None else random
20
+
21
+
22
+ def random_gaussian(
23
+ *,
24
+ mean: float,
25
+ stddev: float,
26
+ min_value: float,
27
+ max_value: float,
28
+ rng: RandomSource | None = None,
29
+ ) -> float:
30
+ """Draw a truncated Gaussian sample within the provided bounds."""
31
+ if stddev <= 0:
32
+ raise ValueError("stddev must be positive")
33
+ if min_value > max_value:
34
+ raise ValueError("min_value must be <= max_value")
35
+
36
+ source = _coerce_rng(rng)
37
+ distribution = statistics.NormalDist(mean, stddev)
38
+ lower = distribution.cdf(min_value)
39
+ upper = distribution.cdf(max_value)
40
+ if lower >= upper:
41
+ return max(min_value, min(max_value, mean))
42
+
43
+ cdf_epsilon = 1e-12
44
+ lower = max(lower, cdf_epsilon)
45
+ upper = min(upper, 1.0 - cdf_epsilon)
46
+ if lower >= upper:
47
+ return max(min_value, min(max_value, mean))
48
+
49
+ u = source.uniform(lower, upper)
50
+ return distribution.inv_cdf(u)
@@ -0,0 +1,7 @@
1
+ from typing import Protocol
2
+
3
+ class RandomSource(Protocol):
4
+ def gauss(self, mu: float, sigma: float) -> float: ...
5
+ def uniform(self, a: float, b: float) -> float: ...
6
+
7
+ def random_gaussian(*, mean: float, stddev: float, min_value: float, max_value: float, rng: RandomSource | None = None) -> float: ...
@@ -0,0 +1,345 @@
1
+ """Big Five trait models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Self
7
+
8
+ from personalitygen.constants import UNIT_RANGE_MAX, UNIT_RANGE_MIN
9
+ from personalitygen.enums import LifeStage
10
+ from personalitygen.randomness import RandomSource, random_gaussian
11
+
12
+
13
+ def _validate_unit_range(*values: float) -> None:
14
+ for value in values:
15
+ if not (UNIT_RANGE_MIN <= value <= UNIT_RANGE_MAX):
16
+ raise ValueError(
17
+ "All trait components must be in the range 0.0...1.0"
18
+ )
19
+
20
+
21
+ def _format_score(value: float) -> str:
22
+ return format(value, ".2g")
23
+
24
+
25
+ _TRAIT_SAMPLE_MIN = 0.01
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class _TraitConfig:
30
+ stddev: float
31
+ means_by_stage: dict[LifeStage, tuple[float, float, float]]
32
+
33
+
34
+ def _sample_trait(
35
+ life_stage: LifeStage,
36
+ config: _TraitConfig,
37
+ *,
38
+ rng: RandomSource | None = None,
39
+ ) -> tuple[float, float, float]:
40
+ means = config.means_by_stage.get(life_stage)
41
+ if means is None:
42
+ raise ValueError(f"Unsupported life stage: {life_stage}")
43
+
44
+ mean_a, mean_b, mean_c = means
45
+ return (
46
+ random_gaussian(
47
+ stddev=config.stddev,
48
+ mean=mean_a,
49
+ max_value=UNIT_RANGE_MAX,
50
+ min_value=_TRAIT_SAMPLE_MIN,
51
+ rng=rng,
52
+ ),
53
+ random_gaussian(
54
+ stddev=config.stddev,
55
+ mean=mean_b,
56
+ max_value=UNIT_RANGE_MAX,
57
+ min_value=_TRAIT_SAMPLE_MIN,
58
+ rng=rng,
59
+ ),
60
+ random_gaussian(
61
+ stddev=config.stddev,
62
+ mean=mean_c,
63
+ max_value=UNIT_RANGE_MAX,
64
+ min_value=_TRAIT_SAMPLE_MIN,
65
+ rng=rng,
66
+ ),
67
+ )
68
+
69
+
70
+ _OPENNESS_CONFIG = _TraitConfig(
71
+ stddev=0.16,
72
+ means_by_stage={
73
+ LifeStage.CHILD: (0.80, 0.85, 0.85),
74
+ LifeStage.YOUNG_ADULT: (0.70, 0.75, 0.75),
75
+ LifeStage.ADULT: (0.60, 0.65, 0.65),
76
+ },
77
+ )
78
+
79
+ _CONSCIENTIOUSNESS_CONFIG = _TraitConfig(
80
+ stddev=0.22,
81
+ means_by_stage={
82
+ LifeStage.CHILD: (0.50, 0.55, 0.50),
83
+ LifeStage.YOUNG_ADULT: (0.60, 0.65, 0.60),
84
+ LifeStage.ADULT: (0.70, 0.75, 0.70),
85
+ },
86
+ )
87
+
88
+ _EXTRAVERSION_CONFIG = _TraitConfig(
89
+ stddev=0.27,
90
+ means_by_stage={
91
+ LifeStage.CHILD: (0.72, 0.70, 0.72),
92
+ LifeStage.YOUNG_ADULT: (0.62, 0.60, 0.62),
93
+ LifeStage.ADULT: (0.52, 0.50, 0.52),
94
+ },
95
+ )
96
+
97
+ _AGREEABLENESS_CONFIG = _TraitConfig(
98
+ stddev=0.18,
99
+ means_by_stage={
100
+ LifeStage.CHILD: (0.55, 0.55, 0.40),
101
+ LifeStage.YOUNG_ADULT: (0.65, 0.65, 0.50),
102
+ LifeStage.ADULT: (0.75, 0.75, 0.60),
103
+ },
104
+ )
105
+
106
+ _NEUROTICISM_CONFIG = _TraitConfig(
107
+ stddev=0.32,
108
+ means_by_stage={
109
+ LifeStage.CHILD: (0.70, 0.60, 0.55),
110
+ LifeStage.YOUNG_ADULT: (0.60, 0.50, 0.45),
111
+ LifeStage.ADULT: (0.50, 0.40, 0.35),
112
+ },
113
+ )
114
+
115
+
116
+ @dataclass(frozen=True, slots=True)
117
+ class BigFiveOpenness:
118
+ aesthetic_sensitivity_score: float
119
+ creative_imagination_score: float
120
+ intellectual_curiosity_score: float
121
+ score: float = field(init=False)
122
+
123
+ def __post_init__(self) -> None:
124
+ _validate_unit_range(
125
+ self.aesthetic_sensitivity_score,
126
+ self.creative_imagination_score,
127
+ self.intellectual_curiosity_score,
128
+ )
129
+ object.__setattr__(
130
+ self,
131
+ "score",
132
+ (
133
+ self.aesthetic_sensitivity_score
134
+ + self.creative_imagination_score
135
+ + self.intellectual_curiosity_score
136
+ )
137
+ / 3,
138
+ )
139
+
140
+ @classmethod
141
+ def random(
142
+ cls, life_stage: LifeStage, *, rng: RandomSource | None = None
143
+ ) -> Self:
144
+ (
145
+ aesthetic_sensitivity,
146
+ creative_imagination,
147
+ intellectual_curiosity,
148
+ ) = _sample_trait(life_stage, _OPENNESS_CONFIG, rng=rng)
149
+ return cls(
150
+ aesthetic_sensitivity_score=aesthetic_sensitivity,
151
+ creative_imagination_score=creative_imagination,
152
+ intellectual_curiosity_score=intellectual_curiosity,
153
+ )
154
+
155
+ def __str__(self) -> str:
156
+ return (
157
+ f"{_format_score(self.score)} "
158
+ f"{{A:{_format_score(self.aesthetic_sensitivity_score)} "
159
+ f"C:{_format_score(self.creative_imagination_score)} "
160
+ f"I:{_format_score(self.intellectual_curiosity_score)}}}"
161
+ )
162
+
163
+
164
+ @dataclass(frozen=True, slots=True)
165
+ class BigFiveConscientiousness:
166
+ organization_score: float
167
+ responsibility_score: float
168
+ productivity_score: float
169
+ score: float = field(init=False)
170
+
171
+ def __post_init__(self) -> None:
172
+ _validate_unit_range(
173
+ self.organization_score,
174
+ self.responsibility_score,
175
+ self.productivity_score,
176
+ )
177
+ object.__setattr__(
178
+ self,
179
+ "score",
180
+ (
181
+ self.organization_score
182
+ + self.responsibility_score
183
+ + self.productivity_score
184
+ )
185
+ / 3,
186
+ )
187
+
188
+ @classmethod
189
+ def random(
190
+ cls, life_stage: LifeStage, *, rng: RandomSource | None = None
191
+ ) -> Self:
192
+ organization, responsibility, productivity = _sample_trait(
193
+ life_stage, _CONSCIENTIOUSNESS_CONFIG, rng=rng
194
+ )
195
+ return cls(
196
+ organization_score=organization,
197
+ responsibility_score=responsibility,
198
+ productivity_score=productivity,
199
+ )
200
+
201
+ def __str__(self) -> str:
202
+ return (
203
+ f"{_format_score(self.score)} "
204
+ f"{{O:{_format_score(self.organization_score)} "
205
+ f"R:{_format_score(self.responsibility_score)} "
206
+ f"P:{_format_score(self.productivity_score)}}}"
207
+ )
208
+
209
+
210
+ @dataclass(frozen=True, slots=True)
211
+ class BigFiveExtraversion:
212
+ assertiveness_score: float
213
+ sociability_score: float
214
+ energy_level_score: float
215
+ score: float = field(init=False)
216
+
217
+ def __post_init__(self) -> None:
218
+ _validate_unit_range(
219
+ self.assertiveness_score,
220
+ self.sociability_score,
221
+ self.energy_level_score,
222
+ )
223
+ object.__setattr__(
224
+ self,
225
+ "score",
226
+ (
227
+ self.assertiveness_score
228
+ + self.sociability_score
229
+ + self.energy_level_score
230
+ )
231
+ / 3,
232
+ )
233
+
234
+ @classmethod
235
+ def random(
236
+ cls, life_stage: LifeStage, *, rng: RandomSource | None = None
237
+ ) -> Self:
238
+ assertiveness, sociability, energy_level = _sample_trait(
239
+ life_stage, _EXTRAVERSION_CONFIG, rng=rng
240
+ )
241
+ return cls(
242
+ assertiveness_score=assertiveness,
243
+ sociability_score=sociability,
244
+ energy_level_score=energy_level,
245
+ )
246
+
247
+ def __str__(self) -> str:
248
+ return (
249
+ f"{_format_score(self.score)} "
250
+ f"{{A:{_format_score(self.assertiveness_score)} "
251
+ f"S:{_format_score(self.sociability_score)} "
252
+ f"E:{_format_score(self.energy_level_score)}}}"
253
+ )
254
+
255
+
256
+ @dataclass(frozen=True, slots=True)
257
+ class BigFiveAgreeableness:
258
+ compassion_score: float
259
+ respectfulness_score: float
260
+ trust_score: float
261
+ score: float = field(init=False)
262
+
263
+ def __post_init__(self) -> None:
264
+ _validate_unit_range(
265
+ self.compassion_score,
266
+ self.respectfulness_score,
267
+ self.trust_score,
268
+ )
269
+ object.__setattr__(
270
+ self,
271
+ "score",
272
+ (
273
+ self.compassion_score
274
+ + self.respectfulness_score
275
+ + self.trust_score
276
+ )
277
+ / 3,
278
+ )
279
+
280
+ @classmethod
281
+ def random(
282
+ cls, life_stage: LifeStage, *, rng: RandomSource | None = None
283
+ ) -> Self:
284
+ compassion, respectfulness, trust = _sample_trait(
285
+ life_stage, _AGREEABLENESS_CONFIG, rng=rng
286
+ )
287
+ return cls(
288
+ compassion_score=compassion,
289
+ respectfulness_score=respectfulness,
290
+ trust_score=trust,
291
+ )
292
+
293
+ def __str__(self) -> str:
294
+ return (
295
+ f"{_format_score(self.score)} "
296
+ f"{{C:{_format_score(self.compassion_score)} "
297
+ f"R:{_format_score(self.respectfulness_score)} "
298
+ f"T:{_format_score(self.trust_score)}}}"
299
+ )
300
+
301
+
302
+ @dataclass(frozen=True, slots=True)
303
+ class BigFiveNeuroticism:
304
+ anxiety_score: float
305
+ emotional_volatility_score: float
306
+ depression_score: float
307
+ score: float = field(init=False)
308
+
309
+ def __post_init__(self) -> None:
310
+ _validate_unit_range(
311
+ self.anxiety_score,
312
+ self.emotional_volatility_score,
313
+ self.depression_score,
314
+ )
315
+ object.__setattr__(
316
+ self,
317
+ "score",
318
+ (
319
+ self.anxiety_score
320
+ + self.emotional_volatility_score
321
+ + self.depression_score
322
+ )
323
+ / 3,
324
+ )
325
+
326
+ @classmethod
327
+ def random(
328
+ cls, life_stage: LifeStage, *, rng: RandomSource | None = None
329
+ ) -> Self:
330
+ anxiety, emotional_volatility, depression = _sample_trait(
331
+ life_stage, _NEUROTICISM_CONFIG, rng=rng
332
+ )
333
+ return cls(
334
+ anxiety_score=anxiety,
335
+ emotional_volatility_score=emotional_volatility,
336
+ depression_score=depression,
337
+ )
338
+
339
+ def __str__(self) -> str:
340
+ return (
341
+ f"{_format_score(self.score)} "
342
+ f"{{A:{_format_score(self.anxiety_score)} "
343
+ f"E:{_format_score(self.emotional_volatility_score)} "
344
+ f"D:{_format_score(self.depression_score)}}}"
345
+ )
@@ -0,0 +1,60 @@
1
+ from dataclasses import dataclass, field
2
+ from personalitygen.constants import UNIT_RANGE_MAX as UNIT_RANGE_MAX, UNIT_RANGE_MIN as UNIT_RANGE_MIN
3
+ from personalitygen.enums import LifeStage as LifeStage
4
+ from personalitygen.randomness import RandomSource as RandomSource, random_gaussian as random_gaussian
5
+ from typing import Self
6
+
7
+ @dataclass(frozen=True, slots=True)
8
+ class _TraitConfig:
9
+ stddev: float
10
+ means_by_stage: dict[LifeStage, tuple[float, float, float]]
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class BigFiveOpenness:
14
+ aesthetic_sensitivity_score: float
15
+ creative_imagination_score: float
16
+ intellectual_curiosity_score: float
17
+ score: float = field(init=False)
18
+ def __post_init__(self) -> None: ...
19
+ @classmethod
20
+ def random(cls, life_stage: LifeStage, *, rng: RandomSource | None = None) -> Self: ...
21
+
22
+ @dataclass(frozen=True, slots=True)
23
+ class BigFiveConscientiousness:
24
+ organization_score: float
25
+ responsibility_score: float
26
+ productivity_score: float
27
+ score: float = field(init=False)
28
+ def __post_init__(self) -> None: ...
29
+ @classmethod
30
+ def random(cls, life_stage: LifeStage, *, rng: RandomSource | None = None) -> Self: ...
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class BigFiveExtraversion:
34
+ assertiveness_score: float
35
+ sociability_score: float
36
+ energy_level_score: float
37
+ score: float = field(init=False)
38
+ def __post_init__(self) -> None: ...
39
+ @classmethod
40
+ def random(cls, life_stage: LifeStage, *, rng: RandomSource | None = None) -> Self: ...
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class BigFiveAgreeableness:
44
+ compassion_score: float
45
+ respectfulness_score: float
46
+ trust_score: float
47
+ score: float = field(init=False)
48
+ def __post_init__(self) -> None: ...
49
+ @classmethod
50
+ def random(cls, life_stage: LifeStage, *, rng: RandomSource | None = None) -> Self: ...
51
+
52
+ @dataclass(frozen=True, slots=True)
53
+ class BigFiveNeuroticism:
54
+ anxiety_score: float
55
+ emotional_volatility_score: float
56
+ depression_score: float
57
+ score: float = field(init=False)
58
+ def __post_init__(self) -> None: ...
59
+ @classmethod
60
+ def random(cls, life_stage: LifeStage, *, rng: RandomSource | None = None) -> Self: ...