faker-orgchart 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.
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Fake org charts for Faker: job levels, leadership titles, and who reports to whom.
|
|
2
|
+
|
|
3
|
+
Faker's `job` provider returns flat titles ("Bonds trader", "Chief Financial Officer"),
|
|
4
|
+
with no seniority and no reporting line. This provider adds the ladder:
|
|
5
|
+
|
|
6
|
+
>>> from faker import Faker
|
|
7
|
+
>>> from faker_orgchart import OrgChartProvider
|
|
8
|
+
>>> fake = Faker()
|
|
9
|
+
>>> fake.add_provider(OrgChartProvider)
|
|
10
|
+
>>> fake.job_level() # e.g. 'senior_manager'
|
|
11
|
+
>>> fake.leadership_title("vp", "Finance") # 'Vice President, Finance'
|
|
12
|
+
>>> chain = fake.reporting_chain("Finance") # a manager up to the CEO, one person per level
|
|
13
|
+
>>> chart = fake.org_chart() # a whole (small) company, as a tree
|
|
14
|
+
|
|
15
|
+
Every person is a plain dict: `id`, `name`, `level`, `title`, `function`, `manager_id`
|
|
16
|
+
(None for the CEO). Seed Faker and the same org comes back every time.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
|
|
21
|
+
from faker.providers import BaseProvider
|
|
22
|
+
|
|
23
|
+
# The ladder, top down. Keys are stable identifiers; labels are for people.
|
|
24
|
+
LEVELS: tuple[str, ...] = ("c_suite", "vp", "director", "senior_manager", "manager", "individual_contributor")
|
|
25
|
+
LEVEL_LABELS: dict[str, str] = {
|
|
26
|
+
"c_suite": "C-suite",
|
|
27
|
+
"vp": "Vice President",
|
|
28
|
+
"director": "Director",
|
|
29
|
+
"senior_manager": "Senior Manager",
|
|
30
|
+
"manager": "Manager",
|
|
31
|
+
"individual_contributor": "Individual Contributor",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Each function's chief, and a few individual-contributor titles in it.
|
|
35
|
+
FUNCTIONS: dict[str, dict] = {
|
|
36
|
+
"Finance": {"chief": "Chief Financial Officer",
|
|
37
|
+
"ic": ("Financial Analyst", "Accountant", "Senior Accountant", "FP&A Analyst", "Controller")},
|
|
38
|
+
"Engineering": {"chief": "Chief Technology Officer",
|
|
39
|
+
"ic": ("Software Engineer", "Senior Software Engineer", "Site Reliability Engineer",
|
|
40
|
+
"Data Engineer", "QA Engineer")},
|
|
41
|
+
"Operations": {"chief": "Chief Operating Officer",
|
|
42
|
+
"ic": ("Operations Analyst", "Logistics Coordinator", "Process Engineer", "Buyer")},
|
|
43
|
+
"Sales": {"chief": "Chief Revenue Officer",
|
|
44
|
+
"ic": ("Account Executive", "Sales Development Representative", "Solutions Engineer")},
|
|
45
|
+
"Marketing": {"chief": "Chief Marketing Officer",
|
|
46
|
+
"ic": ("Marketing Specialist", "Content Strategist", "Marketing Analyst")},
|
|
47
|
+
"People": {"chief": "Chief People Officer",
|
|
48
|
+
"ic": ("HR Generalist", "Recruiter", "People Operations Specialist")},
|
|
49
|
+
"Legal": {"chief": "Chief Legal Officer",
|
|
50
|
+
"ic": ("Corporate Counsel", "Paralegal", "Compliance Analyst")},
|
|
51
|
+
"Product": {"chief": "Chief Product Officer",
|
|
52
|
+
"ic": ("Product Manager", "Product Designer", "UX Researcher")},
|
|
53
|
+
}
|
|
54
|
+
CEO_TITLE = "Chief Executive Officer"
|
|
55
|
+
EXECUTIVE = "Executive" # the CEO's function
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _check_level(level: str) -> None:
|
|
59
|
+
if level not in LEVELS:
|
|
60
|
+
raise ValueError(f"unknown level {level!r}; choose from {', '.join(LEVELS)}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _check_function(function: str) -> None:
|
|
64
|
+
if function not in FUNCTIONS:
|
|
65
|
+
raise ValueError(f"unknown function {function!r}; choose from {', '.join(FUNCTIONS)}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class OrgChartProvider(BaseProvider):
|
|
69
|
+
"""Job levels, leadership titles, reporting chains and org charts."""
|
|
70
|
+
|
|
71
|
+
def job_level(self) -> str:
|
|
72
|
+
"""A level on the ladder, e.g. 'senior_manager' (see LEVELS)."""
|
|
73
|
+
return self.random_element(LEVELS)
|
|
74
|
+
|
|
75
|
+
def job_level_label(self, level: str | None = None) -> str:
|
|
76
|
+
"""A level as people say it, e.g. 'Senior Manager'."""
|
|
77
|
+
level = level or self.job_level()
|
|
78
|
+
_check_level(level)
|
|
79
|
+
return LEVEL_LABELS[level]
|
|
80
|
+
|
|
81
|
+
def business_function(self) -> str:
|
|
82
|
+
"""A business function, e.g. 'Finance' (see FUNCTIONS)."""
|
|
83
|
+
return self.random_element(tuple(FUNCTIONS))
|
|
84
|
+
|
|
85
|
+
def leadership_title(self, level: str | None = None, function: str | None = None) -> str:
|
|
86
|
+
"""A title for `level` in `function`: 'Chief Financial Officer', 'Director of Finance', ..."""
|
|
87
|
+
level = level or self.job_level()
|
|
88
|
+
function = function or self.business_function()
|
|
89
|
+
_check_level(level)
|
|
90
|
+
_check_function(function)
|
|
91
|
+
if level == "c_suite":
|
|
92
|
+
return FUNCTIONS[function]["chief"]
|
|
93
|
+
if level == "vp":
|
|
94
|
+
return self.random_element((f"Vice President, {function}", f"VP of {function}"))
|
|
95
|
+
if level == "director":
|
|
96
|
+
return self.random_element((f"Director of {function}", f"{function} Director"))
|
|
97
|
+
if level == "senior_manager":
|
|
98
|
+
return f"Senior Manager, {function}"
|
|
99
|
+
if level == "manager":
|
|
100
|
+
return f"{function} Manager"
|
|
101
|
+
return self.random_element(FUNCTIONS[function]["ic"])
|
|
102
|
+
|
|
103
|
+
def _person(self, pid: int, level: str, function: str, manager_id: int | None, title: str | None = None) -> dict:
|
|
104
|
+
return {"id": pid, "name": self.generator.name(), "level": level,
|
|
105
|
+
"title": title or self.leadership_title(level, function), "function": function,
|
|
106
|
+
"manager_id": manager_id}
|
|
107
|
+
|
|
108
|
+
def reporting_chain(self, function: str | None = None, from_level: str = "manager") -> list[dict]:
|
|
109
|
+
"""One person per level, from `from_level` up to the CEO, each reporting to the next.
|
|
110
|
+
|
|
111
|
+
For a demo that needs "a senior manager, their VP, and the C-suite above them".
|
|
112
|
+
"""
|
|
113
|
+
function = function or self.business_function()
|
|
114
|
+
_check_function(function)
|
|
115
|
+
_check_level(from_level)
|
|
116
|
+
levels = LEVELS[: LEVELS.index(from_level) + 1] # top down
|
|
117
|
+
chain = [self._person(0, "c_suite", EXECUTIVE, None, CEO_TITLE)]
|
|
118
|
+
for i, level in enumerate(levels, start=1):
|
|
119
|
+
chain.append(self._person(i, level, function, chain[-1]["id"]))
|
|
120
|
+
return list(reversed(chain)) # bottom up: the first one reports to the second, ...
|
|
121
|
+
|
|
122
|
+
def org_chart(self, functions: Sequence[str] | None = None, span: tuple[int, int] = (1, 3),
|
|
123
|
+
down_to: str = "manager") -> list[dict]:
|
|
124
|
+
"""A company as a tree: the CEO; a chief per function; below each, `span` reports per level.
|
|
125
|
+
|
|
126
|
+
`down_to` is the lowest level included (default 'manager', so the chart stays small;
|
|
127
|
+
'individual_contributor' adds the teams). The CEO comes first; every other person's
|
|
128
|
+
`manager_id` is the id of someone earlier in the list.
|
|
129
|
+
"""
|
|
130
|
+
functions = list(functions) if functions is not None else self.random_elements(
|
|
131
|
+
tuple(FUNCTIONS), length=self.random_int(3, 5), unique=True)
|
|
132
|
+
for f in functions:
|
|
133
|
+
_check_function(f)
|
|
134
|
+
_check_level(down_to)
|
|
135
|
+
low, high = span
|
|
136
|
+
if not 1 <= low <= high:
|
|
137
|
+
raise ValueError(f"span must be (low, high) with 1 <= low <= high, got {span}")
|
|
138
|
+
levels = LEVELS[1: LEVELS.index(down_to) + 1] # below the chiefs
|
|
139
|
+
people = [self._person(0, "c_suite", EXECUTIVE, None, CEO_TITLE)]
|
|
140
|
+
|
|
141
|
+
def add(level: str, function: str, manager_id: int) -> int:
|
|
142
|
+
people.append(self._person(len(people), level, function, manager_id))
|
|
143
|
+
return people[-1]["id"]
|
|
144
|
+
|
|
145
|
+
for function in functions:
|
|
146
|
+
frontier = [add("c_suite", function, 0)]
|
|
147
|
+
for level in levels:
|
|
148
|
+
frontier = [add(level, function, m) for m in frontier for _ in range(self.random_int(low, high))]
|
|
149
|
+
return people
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def reporting_line(chart: Sequence[dict], person_id: int) -> list[dict]:
|
|
153
|
+
"""`person_id` and everyone above them, up to the top."""
|
|
154
|
+
by_id = {p["id"]: p for p in chart}
|
|
155
|
+
line = [by_id[person_id]]
|
|
156
|
+
while line[-1]["manager_id"] is not None:
|
|
157
|
+
line.append(by_id[line[-1]["manager_id"]])
|
|
158
|
+
return line
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def direct_reports(chart: Sequence[dict], person_id: int) -> list[dict]:
|
|
162
|
+
return [p for p in chart if p["manager_id"] == person_id]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
__all__ = ["CEO_TITLE", "FUNCTIONS", "LEVELS", "LEVEL_LABELS", "OrgChartProvider", "direct_reports",
|
|
166
|
+
"reporting_line"]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: faker-orgchart
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Faker provider for org charts: job levels, leadership titles, and who reports to whom.
|
|
5
|
+
Project-URL: Homepage, https://github.com/middleforkgis/faker-orgchart
|
|
6
|
+
Project-URL: Issues, https://github.com/middleforkgis/faker-orgchart/issues
|
|
7
|
+
Author: Steve Walker
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: fake data,faker,org chart,provider,test data
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Testing
|
|
13
|
+
Classifier: Topic :: Utilities
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Requires-Dist: faker>=20
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: pytest; extra == 'test'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# faker-orgchart
|
|
21
|
+
|
|
22
|
+
A [Faker](https://github.com/joke2k/faker) provider for fake org charts: job levels,
|
|
23
|
+
leadership titles, and who reports to whom.
|
|
24
|
+
|
|
25
|
+
Faker's `job` provider returns flat titles ("Bonds trader", "Chief Financial Officer").
|
|
26
|
+
It has no seniority and no reporting line, so it can't give you "a senior manager, their
|
|
27
|
+
VP, and the C-suite above them". This provider does, for demos and tests that need people
|
|
28
|
+
at different levels of a hierarchy, such as approval chains, sign-offs and escalation paths.
|
|
29
|
+
Everything is fictional, and seeding Faker gives you the same org every time.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from faker import Faker
|
|
33
|
+
from faker_orgchart import OrgChartProvider, reporting_line
|
|
34
|
+
|
|
35
|
+
fake = Faker()
|
|
36
|
+
fake.add_provider(OrgChartProvider)
|
|
37
|
+
fake.seed_instance(7)
|
|
38
|
+
|
|
39
|
+
fake.job_level() # 'senior_manager'
|
|
40
|
+
fake.job_level_label("vp") # 'Vice President'
|
|
41
|
+
fake.leadership_title("c_suite", "Finance") # 'Chief Financial Officer'
|
|
42
|
+
fake.leadership_title("director", "Sales") # 'Director of Sales'
|
|
43
|
+
|
|
44
|
+
# One person per level, from a senior manager up to the CEO:
|
|
45
|
+
for p in fake.reporting_chain("Finance", from_level="senior_manager"):
|
|
46
|
+
print(p["level"], p["title"], p["name"], "reports to", p["manager_id"])
|
|
47
|
+
|
|
48
|
+
# A small company as a tree (CEO first; every manager_id points to someone earlier):
|
|
49
|
+
chart = fake.org_chart(functions=["Finance", "Engineering"], span=(1, 3), down_to="manager")
|
|
50
|
+
reporting_line(chart, chart[-1]["id"]) # that person and everyone above them
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## The ladder
|
|
54
|
+
|
|
55
|
+
| Level key | Label | Example title (Finance) |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `c_suite` | C-suite | Chief Financial Officer (the CEO is `c_suite` in function "Executive") |
|
|
58
|
+
| `vp` | Vice President | Vice President, Finance |
|
|
59
|
+
| `director` | Director | Director of Finance |
|
|
60
|
+
| `senior_manager` | Senior Manager | Senior Manager, Finance |
|
|
61
|
+
| `manager` | Manager | Finance Manager |
|
|
62
|
+
| `individual_contributor` | Individual Contributor | Financial Analyst |
|
|
63
|
+
|
|
64
|
+
Functions: Finance, Engineering, Operations, Sales, Marketing, People, Legal and Product,
|
|
65
|
+
each with its chief and a few individual-contributor titles. The titles are English
|
|
66
|
+
(en_US style) for now. Other locales are welcome.
|
|
67
|
+
|
|
68
|
+
Each person is a plain dict: `id`, `name`, `level`, `title`, `function` and `manager_id`.
|
|
69
|
+
|
|
70
|
+
## Develop
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
pip install -e '.[test]'
|
|
74
|
+
pytest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT, like Faker.
|
|
80
|
+
|
|
81
|
+
## Releasing
|
|
82
|
+
|
|
83
|
+
Bump `version` in `pyproject.toml`, then tag and push the tag (`git tag v0.1.1 && git push
|
|
84
|
+
origin v0.1.1`). The `publish` workflow tests, builds and publishes to PyPI through
|
|
85
|
+
Trusted Publishing, so no token is stored.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
faker_orgchart/__init__.py,sha256=PqzSQ1VycQyiL-hJnmg5LPo0URXL1Ju19slEKS1Rc-U,7921
|
|
2
|
+
faker_orgchart-0.1.0.dist-info/METADATA,sha256=paxBgyftGfjrGkenV--DabgjBH_Z_4eC32dasTnYxh4,3284
|
|
3
|
+
faker_orgchart-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
4
|
+
faker_orgchart-0.1.0.dist-info/licenses/LICENSE,sha256=cnX3D4wKcriRFz48dweHVOYe7bwedqiKSfuhHTmEN-Y,1069
|
|
5
|
+
faker_orgchart-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Steve Walker
|
|
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.
|