julee 0.1.4__py3-none-any.whl → 0.1.6__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/api/tests/routers/test_assembly_specifications.py +2 -0
- julee/api/tests/routers/test_documents.py +2 -0
- julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
- julee/api/tests/routers/test_system.py +2 -0
- julee/api/tests/routers/test_workflows.py +2 -0
- julee/api/tests/test_app.py +2 -0
- julee/api/tests/test_dependencies.py +2 -0
- julee/api/tests/test_requests.py +2 -0
- julee/contrib/polling/__init__.py +22 -19
- julee/contrib/polling/apps/__init__.py +17 -0
- julee/contrib/polling/apps/worker/__init__.py +17 -0
- julee/contrib/polling/apps/worker/pipelines.py +288 -0
- julee/contrib/polling/domain/__init__.py +7 -9
- julee/contrib/polling/domain/models/__init__.py +6 -7
- julee/contrib/polling/domain/models/polling_config.py +18 -1
- julee/contrib/polling/domain/services/__init__.py +6 -5
- julee/contrib/polling/domain/services/poller.py +1 -1
- julee/contrib/polling/infrastructure/__init__.py +9 -8
- julee/contrib/polling/infrastructure/services/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
- julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
- julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
- julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
- julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
- julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
- julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -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/domain/models/assembly/tests/test_assembly.py +2 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
- julee/domain/models/document/tests/test_document.py +2 -0
- julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
- julee/domain/models/policy/tests/test_policy.py +2 -0
- julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
- julee/domain/use_cases/tests/test_validate_document.py +2 -0
- julee/maintenance/release.py +10 -5
- julee/repositories/memory/tests/test_document.py +2 -0
- julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
- julee/repositories/memory/tests/test_policy.py +2 -0
- julee/repositories/minio/tests/test_assembly.py +2 -0
- julee/repositories/minio/tests/test_assembly_specification.py +2 -0
- julee/repositories/minio/tests/test_client_protocol.py +3 -0
- julee/repositories/minio/tests/test_document.py +2 -0
- julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
- julee/repositories/minio/tests/test_policy.py +2 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/test_factory.py +2 -0
- julee/util/tests/test_decorators.py +2 -0
- julee-0.1.6.dist-info/METADATA +104 -0
- julee-0.1.6.dist-info/RECORD +288 -0
- 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.4.dist-info/METADATA +0 -197
- julee-0.1.4.dist-info/RECORD +0 -196
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/WHEEL +0 -0
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Use case for resolving app references.
|
|
2
|
+
|
|
3
|
+
Finds stories, personas, journeys, and epics related to an app.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from ...utils import normalize_name
|
|
7
|
+
from ..models.app import App
|
|
8
|
+
from ..models.epic import Epic
|
|
9
|
+
from ..models.journey import Journey
|
|
10
|
+
from ..models.persona import Persona
|
|
11
|
+
from ..models.story import Story
|
|
12
|
+
from .derive_personas import derive_personas
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_stories_for_app(
|
|
16
|
+
app: App,
|
|
17
|
+
stories: list[Story],
|
|
18
|
+
) -> list[Story]:
|
|
19
|
+
"""Get stories that belong to an app.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
app: App to find stories for
|
|
23
|
+
stories: All Story entities
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List of Story entities for this app, sorted by feature_title
|
|
27
|
+
"""
|
|
28
|
+
matching = [s for s in stories if s.app_slug == app.slug]
|
|
29
|
+
return sorted(matching, key=lambda s: s.feature_title)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_personas_for_app(
|
|
33
|
+
app: App,
|
|
34
|
+
stories: list[Story],
|
|
35
|
+
epics: list[Epic],
|
|
36
|
+
) -> list[Persona]:
|
|
37
|
+
"""Get personas that use an app.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
app: App to find personas for
|
|
41
|
+
stories: All Story entities
|
|
42
|
+
epics: All Epic entities (for persona derivation)
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List of Persona entities that use this app, sorted by name
|
|
46
|
+
"""
|
|
47
|
+
# Derive all personas
|
|
48
|
+
all_personas = derive_personas(stories, epics)
|
|
49
|
+
|
|
50
|
+
# Filter to those using this app
|
|
51
|
+
matching = [p for p in all_personas if app.slug in p.app_slugs]
|
|
52
|
+
return sorted(matching, key=lambda p: p.name)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_journeys_for_app(
|
|
56
|
+
app: App,
|
|
57
|
+
stories: list[Story],
|
|
58
|
+
journeys: list[Journey],
|
|
59
|
+
) -> list[Journey]:
|
|
60
|
+
"""Get journeys that include stories from an app.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
app: App to find journeys for
|
|
64
|
+
stories: All Story entities
|
|
65
|
+
journeys: All Journey entities
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List of Journey entities containing stories from this app, sorted by slug
|
|
69
|
+
"""
|
|
70
|
+
# Get story titles for this app
|
|
71
|
+
app_story_titles = {
|
|
72
|
+
normalize_name(s.feature_title) for s in stories if s.app_slug == app.slug
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if not app_story_titles:
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
# Find journeys containing these stories
|
|
79
|
+
matching = []
|
|
80
|
+
for journey in journeys:
|
|
81
|
+
story_refs = journey.get_story_refs()
|
|
82
|
+
if any(normalize_name(ref) in app_story_titles for ref in story_refs):
|
|
83
|
+
matching.append(journey)
|
|
84
|
+
|
|
85
|
+
return sorted(matching, key=lambda j: j.slug)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_epics_for_app(
|
|
89
|
+
app: App,
|
|
90
|
+
stories: list[Story],
|
|
91
|
+
epics: list[Epic],
|
|
92
|
+
) -> list[Epic]:
|
|
93
|
+
"""Get epics that contain stories from an app.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
app: App to find epics for
|
|
97
|
+
stories: All Story entities
|
|
98
|
+
epics: All Epic entities
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of Epic entities containing stories from this app, sorted by slug
|
|
102
|
+
"""
|
|
103
|
+
# Get story titles for this app
|
|
104
|
+
app_story_titles = {
|
|
105
|
+
normalize_name(s.feature_title) for s in stories if s.app_slug == app.slug
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if not app_story_titles:
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
# Find epics containing these stories
|
|
112
|
+
matching = []
|
|
113
|
+
for epic in epics:
|
|
114
|
+
if any(normalize_name(ref) in app_story_titles for ref in epic.story_refs):
|
|
115
|
+
matching.append(epic)
|
|
116
|
+
|
|
117
|
+
return sorted(matching, key=lambda e: e.slug)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_app_cross_references(
|
|
121
|
+
app: App,
|
|
122
|
+
stories: list[Story],
|
|
123
|
+
epics: list[Epic],
|
|
124
|
+
journeys: list[Journey],
|
|
125
|
+
) -> dict:
|
|
126
|
+
"""Get all cross-references for an app.
|
|
127
|
+
|
|
128
|
+
Convenience function to get all related entities at once.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
app: App to find references for
|
|
132
|
+
stories: All Story entities
|
|
133
|
+
epics: All Epic entities
|
|
134
|
+
journeys: All Journey entities
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Dict with keys: stories, personas, journeys, epics
|
|
138
|
+
"""
|
|
139
|
+
return {
|
|
140
|
+
"stories": get_stories_for_app(app, stories),
|
|
141
|
+
"personas": get_personas_for_app(app, stories, epics),
|
|
142
|
+
"journeys": get_journeys_for_app(app, stories, journeys),
|
|
143
|
+
"epics": get_epics_for_app(app, stories, epics),
|
|
144
|
+
}
|