julee 0.1.5__py3-none-any.whl → 0.1.7__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.
- julee/__init__.py +1 -1
- julee/contrib/polling/apps/worker/pipelines.py +3 -1
- julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +3 -0
- julee/docs/sphinx_hcd/__init__.py +146 -13
- julee/docs/sphinx_hcd/domain/__init__.py +5 -0
- julee/docs/sphinx_hcd/domain/models/__init__.py +32 -0
- julee/docs/sphinx_hcd/domain/models/accelerator.py +152 -0
- julee/docs/sphinx_hcd/domain/models/app.py +151 -0
- julee/docs/sphinx_hcd/domain/models/code_info.py +121 -0
- julee/docs/sphinx_hcd/domain/models/epic.py +79 -0
- julee/docs/sphinx_hcd/domain/models/integration.py +230 -0
- julee/docs/sphinx_hcd/domain/models/journey.py +222 -0
- julee/docs/sphinx_hcd/domain/models/persona.py +106 -0
- julee/docs/sphinx_hcd/domain/models/story.py +128 -0
- julee/docs/sphinx_hcd/domain/repositories/__init__.py +25 -0
- julee/docs/sphinx_hcd/domain/repositories/accelerator.py +98 -0
- julee/docs/sphinx_hcd/domain/repositories/app.py +57 -0
- julee/docs/sphinx_hcd/domain/repositories/base.py +89 -0
- julee/docs/sphinx_hcd/domain/repositories/code_info.py +69 -0
- julee/docs/sphinx_hcd/domain/repositories/epic.py +62 -0
- julee/docs/sphinx_hcd/domain/repositories/integration.py +79 -0
- julee/docs/sphinx_hcd/domain/repositories/journey.py +106 -0
- julee/docs/sphinx_hcd/domain/repositories/story.py +68 -0
- julee/docs/sphinx_hcd/domain/use_cases/__init__.py +64 -0
- julee/docs/sphinx_hcd/domain/use_cases/derive_personas.py +166 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_accelerator_references.py +236 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_app_references.py +144 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_story_references.py +121 -0
- julee/docs/sphinx_hcd/parsers/__init__.py +48 -0
- julee/docs/sphinx_hcd/parsers/ast.py +150 -0
- julee/docs/sphinx_hcd/parsers/gherkin.py +155 -0
- julee/docs/sphinx_hcd/parsers/yaml.py +184 -0
- julee/docs/sphinx_hcd/repositories/__init__.py +4 -0
- julee/docs/sphinx_hcd/repositories/memory/__init__.py +25 -0
- julee/docs/sphinx_hcd/repositories/memory/accelerator.py +86 -0
- julee/docs/sphinx_hcd/repositories/memory/app.py +45 -0
- julee/docs/sphinx_hcd/repositories/memory/base.py +106 -0
- julee/docs/sphinx_hcd/repositories/memory/code_info.py +59 -0
- julee/docs/sphinx_hcd/repositories/memory/epic.py +54 -0
- julee/docs/sphinx_hcd/repositories/memory/integration.py +70 -0
- julee/docs/sphinx_hcd/repositories/memory/journey.py +96 -0
- julee/docs/sphinx_hcd/repositories/memory/story.py +63 -0
- julee/docs/sphinx_hcd/sphinx/__init__.py +28 -0
- julee/docs/sphinx_hcd/sphinx/adapters.py +116 -0
- julee/docs/sphinx_hcd/sphinx/context.py +163 -0
- julee/docs/sphinx_hcd/sphinx/directives/__init__.py +160 -0
- julee/docs/sphinx_hcd/sphinx/directives/accelerator.py +576 -0
- julee/docs/sphinx_hcd/sphinx/directives/app.py +349 -0
- julee/docs/sphinx_hcd/sphinx/directives/base.py +211 -0
- julee/docs/sphinx_hcd/sphinx/directives/epic.py +434 -0
- julee/docs/sphinx_hcd/sphinx/directives/integration.py +220 -0
- julee/docs/sphinx_hcd/sphinx/directives/journey.py +642 -0
- julee/docs/sphinx_hcd/sphinx/directives/persona.py +345 -0
- julee/docs/sphinx_hcd/sphinx/directives/story.py +575 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
- julee/docs/sphinx_hcd/sphinx/initialization.py +139 -0
- julee/docs/sphinx_hcd/tests/__init__.py +9 -0
- julee/docs/sphinx_hcd/tests/conftest.py +6 -0
- julee/docs/sphinx_hcd/tests/domain/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/models/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_accelerator.py +266 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_app.py +258 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_code_info.py +231 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_epic.py +163 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_journey.py +249 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_persona.py +172 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_story.py +216 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_derive_personas.py +314 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_accelerator_references.py +476 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_app_references.py +265 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_story_references.py +229 -0
- julee/docs/sphinx_hcd/tests/integration/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/parsers/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/parsers/test_ast.py +298 -0
- julee/docs/sphinx_hcd/tests/parsers/test_gherkin.py +282 -0
- julee/docs/sphinx_hcd/tests/parsers/test_yaml.py +496 -0
- julee/docs/sphinx_hcd/tests/repositories/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
- julee/docs/sphinx_hcd/tests/repositories/test_app.py +218 -0
- julee/docs/sphinx_hcd/tests/repositories/test_base.py +151 -0
- julee/docs/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
- julee/docs/sphinx_hcd/tests/repositories/test_epic.py +237 -0
- julee/docs/sphinx_hcd/tests/repositories/test_integration.py +268 -0
- julee/docs/sphinx_hcd/tests/repositories/test_journey.py +294 -0
- julee/docs/sphinx_hcd/tests/repositories/test_story.py +236 -0
- julee/docs/sphinx_hcd/tests/sphinx/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
- julee/docs/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
- julee/docs/sphinx_hcd/tests/sphinx/test_context.py +257 -0
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/METADATA +2 -1
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/RECORD +101 -16
- julee/docs/sphinx_hcd/accelerators.py +0 -1175
- julee/docs/sphinx_hcd/apps.py +0 -518
- julee/docs/sphinx_hcd/epics.py +0 -453
- julee/docs/sphinx_hcd/integrations.py +0 -310
- julee/docs/sphinx_hcd/journeys.py +0 -797
- julee/docs/sphinx_hcd/personas.py +0 -457
- julee/docs/sphinx_hcd/stories.py +0 -960
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/WHEEL +0 -0
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""JourneyRepository protocol.
|
|
2
|
+
|
|
3
|
+
Defines the interface for journey data access.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from ..models.journey import Journey
|
|
9
|
+
from .base import BaseRepository
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class JourneyRepository(BaseRepository[Journey], Protocol):
|
|
14
|
+
"""Repository protocol for Journey entities.
|
|
15
|
+
|
|
16
|
+
Extends BaseRepository with journey-specific query methods.
|
|
17
|
+
Journeys are defined in RST documents and support incremental builds
|
|
18
|
+
via docname tracking.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
async def get_by_persona(self, persona: str) -> list[Journey]:
|
|
22
|
+
"""Get all journeys for a persona.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
persona: Persona name (case-insensitive matching)
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of journeys where the persona matches
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
async def get_by_docname(self, docname: str) -> list[Journey]:
|
|
33
|
+
"""Get all journeys defined in a specific document.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
docname: RST document name
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
List of journeys from that document
|
|
40
|
+
"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
async def clear_by_docname(self, docname: str) -> int:
|
|
44
|
+
"""Remove all journeys defined in a specific document.
|
|
45
|
+
|
|
46
|
+
Used during incremental builds when a document is re-read.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
docname: RST document name
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Number of journeys removed
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
async def get_dependents(self, journey_slug: str) -> list[Journey]:
|
|
57
|
+
"""Get journeys that depend on a specific journey.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
journey_slug: Slug of the journey to find dependents of
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of journeys that have this journey in depends_on
|
|
64
|
+
"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
async def get_dependencies(self, journey_slug: str) -> list[Journey]:
|
|
68
|
+
"""Get journeys that a specific journey depends on.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
journey_slug: Slug of the journey to find dependencies of
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List of journeys that this journey depends on
|
|
75
|
+
"""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
async def get_all_personas(self) -> set[str]:
|
|
79
|
+
"""Get all unique personas across all journeys.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Set of persona names (normalized)
|
|
83
|
+
"""
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
async def get_with_story_ref(self, story_title: str) -> list[Journey]:
|
|
87
|
+
"""Get journeys that reference a specific story.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
story_title: Story feature title (case-insensitive)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of journeys containing this story in steps
|
|
94
|
+
"""
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
async def get_with_epic_ref(self, epic_slug: str) -> list[Journey]:
|
|
98
|
+
"""Get journeys that reference a specific epic.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
epic_slug: Epic slug
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List of journeys containing this epic in steps
|
|
105
|
+
"""
|
|
106
|
+
...
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""StoryRepository protocol.
|
|
2
|
+
|
|
3
|
+
Defines the interface for story data access.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from ..models.story import Story
|
|
9
|
+
from .base import BaseRepository
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class StoryRepository(BaseRepository[Story], Protocol):
|
|
14
|
+
"""Repository protocol for Story entities.
|
|
15
|
+
|
|
16
|
+
Extends BaseRepository with story-specific query methods.
|
|
17
|
+
Stories are indexed from .feature files and are read-only during
|
|
18
|
+
a Sphinx build (populated at builder-inited, queried during rendering).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
async def get_by_app(self, app_slug: str) -> list[Story]:
|
|
22
|
+
"""Get all stories for an application.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
app_slug: Application slug (e.g., "staff-portal")
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of stories belonging to the app
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
async def get_by_persona(self, persona: str) -> list[Story]:
|
|
33
|
+
"""Get all stories for a persona.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
persona: Persona name (case-insensitive matching)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
List of stories where the persona matches
|
|
40
|
+
"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
async def get_by_feature_title(self, feature_title: str) -> Story | None:
|
|
44
|
+
"""Get a story by its feature title.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
feature_title: The Feature: line content (case-insensitive)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Story if found, None otherwise
|
|
51
|
+
"""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
async def get_apps_with_stories(self) -> set[str]:
|
|
55
|
+
"""Get the set of app slugs that have stories.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Set of app slugs (normalized)
|
|
59
|
+
"""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
async def get_all_personas(self) -> set[str]:
|
|
63
|
+
"""Get all unique personas across all stories.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Set of persona names (normalized)
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Use cases for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Business logic for cross-referencing and deriving entities.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .derive_personas import (
|
|
7
|
+
derive_personas,
|
|
8
|
+
derive_personas_by_app_type,
|
|
9
|
+
get_apps_for_persona,
|
|
10
|
+
get_epics_for_persona,
|
|
11
|
+
)
|
|
12
|
+
from .resolve_accelerator_references import (
|
|
13
|
+
get_accelerator_cross_references,
|
|
14
|
+
get_apps_for_accelerator,
|
|
15
|
+
get_code_info_for_accelerator,
|
|
16
|
+
get_dependent_accelerators,
|
|
17
|
+
get_fed_by_accelerators,
|
|
18
|
+
get_journeys_for_accelerator,
|
|
19
|
+
get_publish_integrations,
|
|
20
|
+
get_source_integrations,
|
|
21
|
+
get_stories_for_accelerator,
|
|
22
|
+
)
|
|
23
|
+
from .resolve_app_references import (
|
|
24
|
+
get_app_cross_references,
|
|
25
|
+
get_epics_for_app,
|
|
26
|
+
get_journeys_for_app,
|
|
27
|
+
get_personas_for_app,
|
|
28
|
+
get_stories_for_app,
|
|
29
|
+
)
|
|
30
|
+
from .resolve_story_references import (
|
|
31
|
+
get_epics_for_story,
|
|
32
|
+
get_journeys_for_story,
|
|
33
|
+
get_related_stories,
|
|
34
|
+
get_story_cross_references,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# Persona derivation
|
|
39
|
+
"derive_personas",
|
|
40
|
+
"derive_personas_by_app_type",
|
|
41
|
+
"get_apps_for_persona",
|
|
42
|
+
"get_epics_for_persona",
|
|
43
|
+
# Story references
|
|
44
|
+
"get_epics_for_story",
|
|
45
|
+
"get_journeys_for_story",
|
|
46
|
+
"get_related_stories",
|
|
47
|
+
"get_story_cross_references",
|
|
48
|
+
# App references
|
|
49
|
+
"get_app_cross_references",
|
|
50
|
+
"get_epics_for_app",
|
|
51
|
+
"get_journeys_for_app",
|
|
52
|
+
"get_personas_for_app",
|
|
53
|
+
"get_stories_for_app",
|
|
54
|
+
# Accelerator references
|
|
55
|
+
"get_accelerator_cross_references",
|
|
56
|
+
"get_apps_for_accelerator",
|
|
57
|
+
"get_code_info_for_accelerator",
|
|
58
|
+
"get_dependent_accelerators",
|
|
59
|
+
"get_fed_by_accelerators",
|
|
60
|
+
"get_journeys_for_accelerator",
|
|
61
|
+
"get_publish_integrations",
|
|
62
|
+
"get_source_integrations",
|
|
63
|
+
"get_stories_for_accelerator",
|
|
64
|
+
]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Use case for deriving personas from stories and epics.
|
|
2
|
+
|
|
3
|
+
Personas are not defined directly but are extracted from user stories.
|
|
4
|
+
This use case collects persona information from stories and enriches
|
|
5
|
+
it with epic participation data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
|
|
10
|
+
from ...utils import normalize_name
|
|
11
|
+
from ..models.app import App
|
|
12
|
+
from ..models.epic import Epic
|
|
13
|
+
from ..models.persona import Persona
|
|
14
|
+
from ..models.story import Story
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def derive_personas(
|
|
18
|
+
stories: list[Story],
|
|
19
|
+
epics: list[Epic],
|
|
20
|
+
) -> list[Persona]:
|
|
21
|
+
"""Derive personas from stories and epics.
|
|
22
|
+
|
|
23
|
+
Extracts unique personas from stories, then enriches with:
|
|
24
|
+
- List of apps each persona uses (from stories)
|
|
25
|
+
- List of epics each persona participates in (stories in epics)
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
stories: List of Story entities
|
|
29
|
+
epics: List of Epic entities
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of Persona entities sorted by name
|
|
33
|
+
"""
|
|
34
|
+
# Collect persona data from stories
|
|
35
|
+
persona_data: dict[str, dict] = {} # normalized_name -> {name, apps}
|
|
36
|
+
|
|
37
|
+
for story in stories:
|
|
38
|
+
normalized = story.persona_normalized
|
|
39
|
+
if normalized == "unknown":
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
if normalized not in persona_data:
|
|
43
|
+
persona_data[normalized] = {
|
|
44
|
+
"name": story.persona,
|
|
45
|
+
"apps": set(),
|
|
46
|
+
"epics": set(),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
persona_data[normalized]["apps"].add(story.app_slug)
|
|
50
|
+
|
|
51
|
+
# Build lookup of normalized story title -> normalized persona
|
|
52
|
+
story_to_persona: dict[str, str] = {}
|
|
53
|
+
for story in stories:
|
|
54
|
+
story_to_persona[normalize_name(story.feature_title)] = story.persona_normalized
|
|
55
|
+
|
|
56
|
+
# Find epics for each persona
|
|
57
|
+
for epic in epics:
|
|
58
|
+
for story_ref in epic.story_refs:
|
|
59
|
+
story_normalized = normalize_name(story_ref)
|
|
60
|
+
persona_normalized = story_to_persona.get(story_normalized)
|
|
61
|
+
if persona_normalized and persona_normalized in persona_data:
|
|
62
|
+
persona_data[persona_normalized]["epics"].add(epic.slug)
|
|
63
|
+
|
|
64
|
+
# Build Persona entities
|
|
65
|
+
personas = []
|
|
66
|
+
for data in persona_data.values():
|
|
67
|
+
persona = Persona(
|
|
68
|
+
name=data["name"],
|
|
69
|
+
app_slugs=sorted(data["apps"]),
|
|
70
|
+
epic_slugs=sorted(data["epics"]),
|
|
71
|
+
)
|
|
72
|
+
personas.append(persona)
|
|
73
|
+
|
|
74
|
+
return sorted(personas, key=lambda p: p.name)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def derive_personas_by_app_type(
|
|
78
|
+
stories: list[Story],
|
|
79
|
+
epics: list[Epic],
|
|
80
|
+
apps: list[App],
|
|
81
|
+
) -> dict[str, list[Persona]]:
|
|
82
|
+
"""Derive personas grouped by the type of apps they use.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
stories: List of Story entities
|
|
86
|
+
epics: List of Epic entities
|
|
87
|
+
apps: List of App entities
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dict mapping app type strings to lists of Persona entities
|
|
91
|
+
"""
|
|
92
|
+
# First derive all personas
|
|
93
|
+
all_personas = derive_personas(stories, epics)
|
|
94
|
+
|
|
95
|
+
# Build app slug -> app type lookup
|
|
96
|
+
app_types: dict[str, str] = {}
|
|
97
|
+
for app in apps:
|
|
98
|
+
app_types[app.slug] = app.app_type.value if app.app_type else "unknown"
|
|
99
|
+
|
|
100
|
+
# Group personas by app type
|
|
101
|
+
personas_by_type: dict[str, list[Persona]] = defaultdict(list)
|
|
102
|
+
|
|
103
|
+
for persona in all_personas:
|
|
104
|
+
# Find all app types this persona uses
|
|
105
|
+
persona_types: set[str] = set()
|
|
106
|
+
for app_slug in persona.app_slugs:
|
|
107
|
+
app_type = app_types.get(app_slug, "unknown")
|
|
108
|
+
persona_types.add(app_type)
|
|
109
|
+
|
|
110
|
+
# Add persona to each type group
|
|
111
|
+
for app_type in persona_types:
|
|
112
|
+
personas_by_type[app_type].append(persona)
|
|
113
|
+
|
|
114
|
+
# Sort personas within each group
|
|
115
|
+
return {
|
|
116
|
+
app_type: sorted(personas, key=lambda p: p.name)
|
|
117
|
+
for app_type, personas in personas_by_type.items()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_epics_for_persona(
|
|
122
|
+
persona: Persona,
|
|
123
|
+
epics: list[Epic],
|
|
124
|
+
stories: list[Story],
|
|
125
|
+
) -> list[Epic]:
|
|
126
|
+
"""Get Epic entities for a persona.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
persona: Persona to get epics for
|
|
130
|
+
epics: All Epic entities
|
|
131
|
+
stories: All Story entities
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of Epic entities containing stories for this persona
|
|
135
|
+
"""
|
|
136
|
+
# Build lookup of normalized story title -> normalized persona
|
|
137
|
+
story_to_persona: dict[str, str] = {}
|
|
138
|
+
for story in stories:
|
|
139
|
+
story_to_persona[normalize_name(story.feature_title)] = story.persona_normalized
|
|
140
|
+
|
|
141
|
+
matching_epics = []
|
|
142
|
+
for epic in epics:
|
|
143
|
+
for story_ref in epic.story_refs:
|
|
144
|
+
story_normalized = normalize_name(story_ref)
|
|
145
|
+
if story_to_persona.get(story_normalized) == persona.normalized_name:
|
|
146
|
+
matching_epics.append(epic)
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
return sorted(matching_epics, key=lambda e: e.slug)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_apps_for_persona(
|
|
153
|
+
persona: Persona,
|
|
154
|
+
apps: list[App],
|
|
155
|
+
) -> list[App]:
|
|
156
|
+
"""Get App entities for a persona.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
persona: Persona to get apps for
|
|
160
|
+
apps: All App entities
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
List of App entities this persona uses
|
|
164
|
+
"""
|
|
165
|
+
app_lookup = {app.slug: app for app in apps}
|
|
166
|
+
return [app_lookup[slug] for slug in persona.app_slugs if slug in app_lookup]
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Use case for resolving accelerator references.
|
|
2
|
+
|
|
3
|
+
Finds apps, stories, journeys, and integrations related to an accelerator.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from ...utils import normalize_name
|
|
7
|
+
from ..models.accelerator import Accelerator
|
|
8
|
+
from ..models.app import App
|
|
9
|
+
from ..models.code_info import BoundedContextInfo
|
|
10
|
+
from ..models.integration import Integration
|
|
11
|
+
from ..models.journey import Journey
|
|
12
|
+
from ..models.story import Story
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_apps_for_accelerator(
|
|
16
|
+
accelerator: Accelerator,
|
|
17
|
+
apps: list[App],
|
|
18
|
+
) -> list[App]:
|
|
19
|
+
"""Get apps that expose an accelerator.
|
|
20
|
+
|
|
21
|
+
Apps expose accelerators via the 'accelerators' field in their manifest.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
accelerator: Accelerator to find apps for
|
|
25
|
+
apps: All App entities
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of App entities that expose this accelerator, sorted by slug
|
|
29
|
+
"""
|
|
30
|
+
matching = [app for app in apps if accelerator.slug in (app.accelerators or [])]
|
|
31
|
+
return sorted(matching, key=lambda a: a.slug)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_stories_for_accelerator(
|
|
35
|
+
accelerator: Accelerator,
|
|
36
|
+
apps: list[App],
|
|
37
|
+
stories: list[Story],
|
|
38
|
+
) -> list[Story]:
|
|
39
|
+
"""Get stories for apps that expose an accelerator.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
accelerator: Accelerator to find stories for
|
|
43
|
+
apps: All App entities
|
|
44
|
+
stories: All Story entities
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
List of Story entities from apps that expose this accelerator
|
|
48
|
+
"""
|
|
49
|
+
# Get app slugs that expose this accelerator
|
|
50
|
+
app_slugs = {
|
|
51
|
+
app.slug for app in apps if accelerator.slug in (app.accelerators or [])
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if not app_slugs:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
# Find stories for those apps
|
|
58
|
+
matching = [s for s in stories if s.app_slug in app_slugs]
|
|
59
|
+
return sorted(matching, key=lambda s: s.feature_title)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_journeys_for_accelerator(
|
|
63
|
+
accelerator: Accelerator,
|
|
64
|
+
apps: list[App],
|
|
65
|
+
stories: list[Story],
|
|
66
|
+
journeys: list[Journey],
|
|
67
|
+
) -> list[Journey]:
|
|
68
|
+
"""Get journeys that include stories from an accelerator's apps.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
accelerator: Accelerator to find journeys for
|
|
72
|
+
apps: All App entities
|
|
73
|
+
stories: All Story entities
|
|
74
|
+
journeys: All Journey entities
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of Journey entities containing stories from this accelerator's apps
|
|
78
|
+
"""
|
|
79
|
+
# Get stories for this accelerator
|
|
80
|
+
accel_stories = get_stories_for_accelerator(accelerator, apps, stories)
|
|
81
|
+
story_titles = {normalize_name(s.feature_title) for s in accel_stories}
|
|
82
|
+
|
|
83
|
+
if not story_titles:
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
# Find journeys containing these stories
|
|
87
|
+
matching = []
|
|
88
|
+
for journey in journeys:
|
|
89
|
+
story_refs = journey.get_story_refs()
|
|
90
|
+
if any(normalize_name(ref) in story_titles for ref in story_refs):
|
|
91
|
+
matching.append(journey)
|
|
92
|
+
|
|
93
|
+
return sorted(matching, key=lambda j: j.slug)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_source_integrations(
|
|
97
|
+
accelerator: Accelerator,
|
|
98
|
+
integrations: list[Integration],
|
|
99
|
+
) -> list[Integration]:
|
|
100
|
+
"""Get integrations that an accelerator sources from.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
accelerator: Accelerator to find sources for
|
|
104
|
+
integrations: All Integration entities
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
List of Integration entities this accelerator sources from
|
|
108
|
+
"""
|
|
109
|
+
source_slugs = accelerator.get_sources_from_slugs()
|
|
110
|
+
integration_lookup = {i.slug: i for i in integrations}
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
integration_lookup[slug] for slug in source_slugs if slug in integration_lookup
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_publish_integrations(
|
|
118
|
+
accelerator: Accelerator,
|
|
119
|
+
integrations: list[Integration],
|
|
120
|
+
) -> list[Integration]:
|
|
121
|
+
"""Get integrations that an accelerator publishes to.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
accelerator: Accelerator to find publish targets for
|
|
125
|
+
integrations: All Integration entities
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
List of Integration entities this accelerator publishes to
|
|
129
|
+
"""
|
|
130
|
+
publish_slugs = accelerator.get_publishes_to_slugs()
|
|
131
|
+
integration_lookup = {i.slug: i for i in integrations}
|
|
132
|
+
|
|
133
|
+
return [
|
|
134
|
+
integration_lookup[slug] for slug in publish_slugs if slug in integration_lookup
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_dependent_accelerators(
|
|
139
|
+
accelerator: Accelerator,
|
|
140
|
+
accelerators: list[Accelerator],
|
|
141
|
+
) -> list[Accelerator]:
|
|
142
|
+
"""Get accelerators that depend on a specific accelerator.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
accelerator: Accelerator to find dependents of
|
|
146
|
+
accelerators: All Accelerator entities
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of Accelerator entities that depend on this one
|
|
150
|
+
"""
|
|
151
|
+
matching = [a for a in accelerators if accelerator.slug in a.depends_on]
|
|
152
|
+
return sorted(matching, key=lambda a: a.slug)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_fed_by_accelerators(
|
|
156
|
+
accelerator: Accelerator,
|
|
157
|
+
accelerators: list[Accelerator],
|
|
158
|
+
) -> list[Accelerator]:
|
|
159
|
+
"""Get accelerators that feed into a specific accelerator.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
accelerator: Accelerator to find feeders for
|
|
163
|
+
accelerators: All Accelerator entities
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
List of Accelerator entities that feed into this one
|
|
167
|
+
"""
|
|
168
|
+
matching = [a for a in accelerators if accelerator.slug in a.feeds_into]
|
|
169
|
+
return sorted(matching, key=lambda a: a.slug)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_code_info_for_accelerator(
|
|
173
|
+
accelerator: Accelerator,
|
|
174
|
+
code_infos: list[BoundedContextInfo],
|
|
175
|
+
) -> BoundedContextInfo | None:
|
|
176
|
+
"""Get code info for an accelerator's bounded context.
|
|
177
|
+
|
|
178
|
+
Tries to match by slug or snake_case version of slug.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
accelerator: Accelerator to find code for
|
|
182
|
+
code_infos: All BoundedContextInfo entities
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
BoundedContextInfo if found, None otherwise
|
|
186
|
+
"""
|
|
187
|
+
# Try exact match
|
|
188
|
+
for info in code_infos:
|
|
189
|
+
if info.slug == accelerator.slug:
|
|
190
|
+
return info
|
|
191
|
+
|
|
192
|
+
# Try snake_case match
|
|
193
|
+
snake_slug = accelerator.slug.replace("-", "_")
|
|
194
|
+
for info in code_infos:
|
|
195
|
+
if info.slug == snake_slug or info.code_dir == snake_slug:
|
|
196
|
+
return info
|
|
197
|
+
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_accelerator_cross_references(
|
|
202
|
+
accelerator: Accelerator,
|
|
203
|
+
accelerators: list[Accelerator],
|
|
204
|
+
apps: list[App],
|
|
205
|
+
stories: list[Story],
|
|
206
|
+
journeys: list[Journey],
|
|
207
|
+
integrations: list[Integration],
|
|
208
|
+
code_infos: list[BoundedContextInfo],
|
|
209
|
+
) -> dict:
|
|
210
|
+
"""Get all cross-references for an accelerator.
|
|
211
|
+
|
|
212
|
+
Convenience function to get all related entities at once.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
accelerator: Accelerator to find references for
|
|
216
|
+
accelerators: All Accelerator entities
|
|
217
|
+
apps: All App entities
|
|
218
|
+
stories: All Story entities
|
|
219
|
+
journeys: All Journey entities
|
|
220
|
+
integrations: All Integration entities
|
|
221
|
+
code_infos: All BoundedContextInfo entities
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Dict with keys: apps, stories, journeys, source_integrations,
|
|
225
|
+
publish_integrations, dependents, fed_by, code_info
|
|
226
|
+
"""
|
|
227
|
+
return {
|
|
228
|
+
"apps": get_apps_for_accelerator(accelerator, apps),
|
|
229
|
+
"stories": get_stories_for_accelerator(accelerator, apps, stories),
|
|
230
|
+
"journeys": get_journeys_for_accelerator(accelerator, apps, stories, journeys),
|
|
231
|
+
"source_integrations": get_source_integrations(accelerator, integrations),
|
|
232
|
+
"publish_integrations": get_publish_integrations(accelerator, integrations),
|
|
233
|
+
"dependents": get_dependent_accelerators(accelerator, accelerators),
|
|
234
|
+
"fed_by": get_fed_by_accelerators(accelerator, accelerators),
|
|
235
|
+
"code_info": get_code_info_for_accelerator(accelerator, code_infos),
|
|
236
|
+
}
|