Simon Strandgaard
commited on
Commit
·
1bfe7f5
1
Parent(s):
22a5c6c
Snapshot of PlanExe commit 773f9ca98123b5751e6b16be192818b572af1aa0
Browse files- requirements.txt +6 -1
- src/assume/shorten_markdown.py +161 -0
- src/assume/test_data/shorten_markdown1/currency_strategy.md +10 -0
- src/assume/test_data/shorten_markdown1/identify_risks.md +113 -0
- src/assume/test_data/shorten_markdown1/physical_locations.md +39 -0
- src/llm_factory.py +55 -9
- src/llm_util/ollama_info.py +14 -7
- src/markdown_util/remove_bold_formatting.py +11 -0
- src/markdown_util/tests/test_remove_bold_formatting.py +13 -0
- src/plan/app_text2plan.py +5 -3
- src/plan/data/simple_plan_prompts.jsonl +2 -0
- src/plan/executive_summary.py +226 -0
- src/plan/filenames.py +11 -3
- src/plan/{create_project_plan.py → project_plan.py} +101 -12
- src/plan/related_resources.py +214 -0
- src/plan/review_plan.py +238 -0
- src/plan/run_plan_pipeline.py +351 -146
- src/plan/test_data/deadfish_assumptions.md +267 -0
- src/plan/test_data/solarfarm_consolidate_assumptions_short.md +306 -0
- src/report/report_generator.py +34 -63
- src/swot/app_swot_analysis.py +1 -1
- src/swot/swot_analysis.py +0 -2
- src/swot/swot_phase1_determine_type.py +3 -0
requirements.txt
CHANGED
@@ -5,6 +5,7 @@ aiosignal==1.3.2
|
|
5 |
annotated-types==0.7.0
|
6 |
anyio==4.8.0
|
7 |
attrs==24.3.0
|
|
|
8 |
beautifulsoup4==4.12.3
|
9 |
certifi==2024.12.14
|
10 |
charset-normalizer==3.4.1
|
@@ -14,6 +15,7 @@ dataclasses-json==0.6.7
|
|
14 |
Deprecated==1.2.15
|
15 |
dirtyjson==1.0.8
|
16 |
distro==1.9.0
|
|
|
17 |
fastapi==0.115.6
|
18 |
ffmpy==0.5.0
|
19 |
filelock==3.16.1
|
@@ -31,6 +33,7 @@ idna==3.10
|
|
31 |
Jinja2==3.1.5
|
32 |
jiter==0.8.2
|
33 |
joblib==1.4.2
|
|
|
34 |
llama-cloud==0.1.8
|
35 |
llama-index==0.12.10
|
36 |
llama-index-agent-openai==0.4.1
|
@@ -40,6 +43,7 @@ llama-index-embeddings-openai==0.3.1
|
|
40 |
llama-index-indices-managed-llama-cloud==0.6.3
|
41 |
llama-index-llms-groq==0.3.1
|
42 |
llama-index-llms-lmstudio==0.3.0
|
|
|
43 |
llama-index-llms-ollama==0.5.0
|
44 |
llama-index-llms-openai==0.3.13
|
45 |
llama-index-llms-openai-like==0.3.3
|
@@ -53,11 +57,12 @@ llama-index-readers-llama-parse==0.4.0
|
|
53 |
llama-parse==0.5.19
|
54 |
lockfile==0.12.2
|
55 |
luigi==3.6.0
|
56 |
-
Markdown==3.7
|
57 |
markdown-it-py==3.0.0
|
58 |
MarkupSafe==2.1.5
|
59 |
marshmallow==3.24.2
|
60 |
mdurl==0.1.2
|
|
|
61 |
multidict==6.1.0
|
62 |
mypy-extensions==1.0.0
|
63 |
nest-asyncio==1.6.0
|
|
|
5 |
annotated-types==0.7.0
|
6 |
anyio==4.8.0
|
7 |
attrs==24.3.0
|
8 |
+
audioop-lts==0.2.1
|
9 |
beautifulsoup4==4.12.3
|
10 |
certifi==2024.12.14
|
11 |
charset-normalizer==3.4.1
|
|
|
15 |
Deprecated==1.2.15
|
16 |
dirtyjson==1.0.8
|
17 |
distro==1.9.0
|
18 |
+
eval_type_backport==0.2.2
|
19 |
fastapi==0.115.6
|
20 |
ffmpy==0.5.0
|
21 |
filelock==3.16.1
|
|
|
33 |
Jinja2==3.1.5
|
34 |
jiter==0.8.2
|
35 |
joblib==1.4.2
|
36 |
+
jsonpath-python==1.0.6
|
37 |
llama-cloud==0.1.8
|
38 |
llama-index==0.12.10
|
39 |
llama-index-agent-openai==0.4.1
|
|
|
43 |
llama-index-indices-managed-llama-cloud==0.6.3
|
44 |
llama-index-llms-groq==0.3.1
|
45 |
llama-index-llms-lmstudio==0.3.0
|
46 |
+
llama-index-llms-mistralai==0.4.0
|
47 |
llama-index-llms-ollama==0.5.0
|
48 |
llama-index-llms-openai==0.3.13
|
49 |
llama-index-llms-openai-like==0.3.3
|
|
|
57 |
llama-parse==0.5.19
|
58 |
lockfile==0.12.2
|
59 |
luigi==3.6.0
|
60 |
+
Markdown==3.7
|
61 |
markdown-it-py==3.0.0
|
62 |
MarkupSafe==2.1.5
|
63 |
marshmallow==3.24.2
|
64 |
mdurl==0.1.2
|
65 |
+
mistralai==1.5.2
|
66 |
multidict==6.1.0
|
67 |
mypy-extensions==1.0.0
|
68 |
nest-asyncio==1.6.0
|
src/assume/shorten_markdown.py
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Shorten the long consolidated assumptions to a shorter markdown document.
|
3 |
+
|
4 |
+
PROMPT> python -m src.assume.shorten_markdown
|
5 |
+
"""
|
6 |
+
import os
|
7 |
+
import json
|
8 |
+
import time
|
9 |
+
import logging
|
10 |
+
from math import ceil
|
11 |
+
from typing import Optional
|
12 |
+
from dataclasses import dataclass
|
13 |
+
from llama_index.core.llms.llm import LLM
|
14 |
+
from llama_index.core.llms import ChatMessage, MessageRole
|
15 |
+
from src.format_json_for_use_in_query import format_json_for_use_in_query
|
16 |
+
from src.markdown_util.fix_bullet_lists import fix_bullet_lists
|
17 |
+
from src.markdown_util.remove_bold_formatting import remove_bold_formatting
|
18 |
+
|
19 |
+
logger = logging.getLogger(__name__)
|
20 |
+
|
21 |
+
SHORTEN_MARKDOWN_SYSTEM_PROMPT = """
|
22 |
+
You are a transformer that shortens project planning Markdown documents. Your only task is to convert the input Markdown into a shorter version while preserving all topics and structure. Do not add any extra text or new information.
|
23 |
+
|
24 |
+
Output must:
|
25 |
+
- Be wrapped exactly in [START_MARKDOWN] and [END_MARKDOWN] (no text before or after).
|
26 |
+
- Use only plain Markdown (no bold formatting).
|
27 |
+
- Retain headings using only '#' and '##'. Convert any deeper levels to these.
|
28 |
+
- Use bullet lists with a hyphen and a space.
|
29 |
+
- Condense paragraphs, remove redundancy, and combine similar sections.
|
30 |
+
- Preserve key details (assumptions, risks, recommendations) without summarizing or providing commentary.
|
31 |
+
"""
|
32 |
+
|
33 |
+
@dataclass
|
34 |
+
class ShortenMarkdown:
|
35 |
+
system_prompt: Optional[str]
|
36 |
+
user_prompt: str
|
37 |
+
response: str
|
38 |
+
markdown: str
|
39 |
+
metadata: dict
|
40 |
+
|
41 |
+
@classmethod
|
42 |
+
def execute(cls, llm: LLM, user_prompt: str) -> 'ShortenMarkdown':
|
43 |
+
"""
|
44 |
+
Invoke LLM with a long markdown document that is to be shortened.
|
45 |
+
"""
|
46 |
+
if not isinstance(llm, LLM):
|
47 |
+
raise ValueError("Invalid LLM instance.")
|
48 |
+
if not isinstance(user_prompt, str):
|
49 |
+
raise ValueError("Invalid user_prompt.")
|
50 |
+
|
51 |
+
user_prompt = user_prompt.strip()
|
52 |
+
user_prompt = remove_bold_formatting(user_prompt)
|
53 |
+
|
54 |
+
system_prompt = SHORTEN_MARKDOWN_SYSTEM_PROMPT.strip()
|
55 |
+
chat_message_list = [
|
56 |
+
ChatMessage(
|
57 |
+
role=MessageRole.SYSTEM,
|
58 |
+
content=system_prompt,
|
59 |
+
),
|
60 |
+
ChatMessage(
|
61 |
+
role=MessageRole.USER,
|
62 |
+
content=user_prompt,
|
63 |
+
)
|
64 |
+
]
|
65 |
+
|
66 |
+
logger.debug(f"User Prompt:\n{user_prompt}")
|
67 |
+
|
68 |
+
logger.debug("Starting LLM chat interaction.")
|
69 |
+
start_time = time.perf_counter()
|
70 |
+
chat_response = llm.chat(chat_message_list)
|
71 |
+
end_time = time.perf_counter()
|
72 |
+
duration = int(ceil(end_time - start_time))
|
73 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
74 |
+
logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
75 |
+
|
76 |
+
metadata = dict(llm.metadata)
|
77 |
+
metadata["llm_classname"] = llm.class_name()
|
78 |
+
metadata["duration"] = duration
|
79 |
+
metadata["response_byte_count"] = response_byte_count
|
80 |
+
|
81 |
+
response_content = chat_response.message.content
|
82 |
+
|
83 |
+
start_delimiter = "[START_MARKDOWN]"
|
84 |
+
end_delimiter = "[END_MARKDOWN]"
|
85 |
+
|
86 |
+
start_index = response_content.find(start_delimiter)
|
87 |
+
end_index = response_content.find(end_delimiter)
|
88 |
+
|
89 |
+
if start_index != -1 and end_index != -1:
|
90 |
+
markdown_content = response_content[start_index + len(start_delimiter):end_index].strip()
|
91 |
+
else:
|
92 |
+
markdown_content = response_content # Use the entire content if delimiters are missing
|
93 |
+
logger.warning("Output delimiters not found in LLM response.")
|
94 |
+
|
95 |
+
markdown_content = fix_bullet_lists(markdown_content)
|
96 |
+
markdown_content = remove_bold_formatting(markdown_content)
|
97 |
+
|
98 |
+
json_response = {}
|
99 |
+
json_response['response_content'] = response_content
|
100 |
+
json_response['markdown'] = markdown_content
|
101 |
+
|
102 |
+
result = ShortenMarkdown(
|
103 |
+
system_prompt=system_prompt,
|
104 |
+
user_prompt=user_prompt,
|
105 |
+
response=json_response,
|
106 |
+
markdown=markdown_content,
|
107 |
+
metadata=metadata,
|
108 |
+
)
|
109 |
+
logger.debug("ShortenMarkdown instance created successfully.")
|
110 |
+
return result
|
111 |
+
|
112 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
|
113 |
+
d = self.response.copy()
|
114 |
+
d['markdown'] = self.markdown
|
115 |
+
if include_metadata:
|
116 |
+
d['metadata'] = self.metadata
|
117 |
+
if include_system_prompt:
|
118 |
+
d['system_prompt'] = self.system_prompt
|
119 |
+
if include_user_prompt:
|
120 |
+
d['user_prompt'] = self.user_prompt
|
121 |
+
return d
|
122 |
+
|
123 |
+
def save_raw(self, file_path: str) -> None:
|
124 |
+
with open(file_path, 'w') as f:
|
125 |
+
f.write(json.dumps(self.to_dict(), indent=2))
|
126 |
+
|
127 |
+
def save_markdown(self, file_path: str) -> None:
|
128 |
+
with open(file_path, "w", encoding="utf-8") as f:
|
129 |
+
f.write(self.markdown)
|
130 |
+
|
131 |
+
if __name__ == "__main__":
|
132 |
+
from src.llm_factory import get_llm
|
133 |
+
|
134 |
+
# path = os.path.join(os.path.dirname(__file__), 'test_data', 'shorten_markdown1', 'currency_strategy.md')
|
135 |
+
# path = os.path.join(os.path.dirname(__file__), 'test_data', 'shorten_markdown1', 'identify_risks.md')
|
136 |
+
path = os.path.join(os.path.dirname(__file__), 'test_data', 'shorten_markdown1', 'physical_locations.md')
|
137 |
+
with open(path, 'r', encoding='utf-8') as f:
|
138 |
+
the_markdown = f.read()
|
139 |
+
|
140 |
+
model_name = "ollama-llama3.1"
|
141 |
+
# model_name = "ollama-qwen2.5-coder"
|
142 |
+
llm = get_llm(model_name)
|
143 |
+
|
144 |
+
query = the_markdown
|
145 |
+
input_bytes_count = len(query.encode('utf-8'))
|
146 |
+
print(f"Query: {query}")
|
147 |
+
result = ShortenMarkdown.execute(llm, query)
|
148 |
+
|
149 |
+
print("\nResponse:")
|
150 |
+
json_response = result.to_dict(include_system_prompt=False, include_user_prompt=False)
|
151 |
+
print(json.dumps(json_response, indent=2))
|
152 |
+
|
153 |
+
print(f"\n\nMarkdown:\n{result.markdown}")
|
154 |
+
|
155 |
+
output_bytes_count = len(result.markdown.encode('utf-8'))
|
156 |
+
print(f"\n\nInput bytes count: {input_bytes_count}")
|
157 |
+
print(f"Output bytes count: {output_bytes_count}")
|
158 |
+
bytes_saved = input_bytes_count - output_bytes_count
|
159 |
+
print(f"Bytes saved: {bytes_saved}")
|
160 |
+
print(f"Percentage saved: {bytes_saved / input_bytes_count * 100:.2f}%")
|
161 |
+
|
src/assume/test_data/shorten_markdown1/currency_strategy.md
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
This plan involves money.
|
2 |
+
|
3 |
+
## Currencies
|
4 |
+
|
5 |
+
- **DKK:** The project is located in Denmark, so Danish Krone will be needed for local expenses, permits, and labor.
|
6 |
+
- **EUR:** Denmark is part of Europe, and some transactions or equipment purchases might be denominated in Euros.
|
7 |
+
|
8 |
+
**Primary currency:** DKK
|
9 |
+
|
10 |
+
**Currency strategy:** Danish Krone will be used for all local transactions. For international transactions, it's advisable to monitor exchange rates between DKK and EUR to minimize currency risks.
|
src/assume/test_data/shorten_markdown1/identify_risks.md
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
## Risk 1 - Regulatory & Permitting
|
3 |
+
Delays in obtaining necessary permits and approvals from Danish authorities (e.g., environmental permits, building permits, grid connection permits). This could be due to complex regulations, public opposition, or administrative bottlenecks.
|
4 |
+
|
5 |
+
**Impact:** A delay of 3-6 months in project commencement, potentially leading to increased costs due to inflation and contract renegotiations. Could also result in fines or legal challenges.
|
6 |
+
|
7 |
+
**Likelihood:** Medium
|
8 |
+
|
9 |
+
**Severity:** High
|
10 |
+
|
11 |
+
**Action:** Engage with relevant authorities early in the project lifecycle to understand requirements and timelines. Conduct thorough environmental impact assessments. Develop a robust permitting strategy with contingency plans.
|
12 |
+
|
13 |
+
## Risk 2 - Technical
|
14 |
+
Unexpected technical challenges during installation or grid connection, such as soil instability, unforeseen geological conditions, or incompatibility with existing grid infrastructure. This includes the risk of panel degradation or underperformance due to local climate conditions.
|
15 |
+
|
16 |
+
**Impact:** Increased construction costs by 10-20%, delays of 2-4 months, and potential reduction in energy output. Could also lead to equipment failures and increased maintenance costs.
|
17 |
+
|
18 |
+
**Likelihood:** Medium
|
19 |
+
|
20 |
+
**Severity:** Medium
|
21 |
+
|
22 |
+
**Action:** Conduct thorough geotechnical surveys and grid compatibility studies before construction. Select high-quality solar panels suitable for the Danish climate. Implement robust quality control procedures during installation. Secure warranties and maintenance agreements with equipment suppliers.
|
23 |
+
|
24 |
+
## Risk 3 - Financial
|
25 |
+
Fluctuations in currency exchange rates (DKK/EUR) could increase the cost of imported equipment or services. Unexpected increases in material costs (e.g., solar panels, steel, aluminum) could also impact the project budget. Changes in government subsidies or tax incentives for renewable energy could affect project profitability.
|
26 |
+
|
27 |
+
**Impact:** A cost overrun of 5-15% of the total project budget. Reduced return on investment. Project delays due to funding shortfalls.
|
28 |
+
|
29 |
+
**Likelihood:** Medium
|
30 |
+
|
31 |
+
**Severity:** Medium
|
32 |
+
|
33 |
+
**Action:** Hedge currency risks through forward contracts or other financial instruments. Negotiate fixed-price contracts with suppliers. Monitor commodity prices and government policies closely. Secure financing with flexible terms to accommodate potential cost increases.
|
34 |
+
|
35 |
+
## Risk 4 - Environmental
|
36 |
+
Negative environmental impacts, such as habitat destruction, soil erosion, or water contamination during construction or operation. Public opposition due to concerns about visual impact or noise pollution.
|
37 |
+
|
38 |
+
**Impact:** Project delays, increased costs for environmental mitigation measures, reputational damage, and potential legal challenges. Loss of biodiversity.
|
39 |
+
|
40 |
+
**Likelihood:** Low
|
41 |
+
|
42 |
+
**Severity:** Medium
|
43 |
+
|
44 |
+
**Action:** Conduct thorough environmental impact assessments. Implement best practices for erosion control, waste management, and noise reduction. Engage with local communities to address concerns and mitigate visual impacts. Consider biodiversity offsets to compensate for habitat loss.
|
45 |
+
|
46 |
+
## Risk 5 - Social
|
47 |
+
Local opposition to the project due to concerns about land use, visual impact, or noise pollution. Lack of community support could lead to protests, legal challenges, and project delays.
|
48 |
+
|
49 |
+
**Impact:** Project delays of 1-3 months, increased costs for community engagement and mitigation measures, reputational damage.
|
50 |
+
|
51 |
+
**Likelihood:** Low
|
52 |
+
|
53 |
+
**Severity:** Medium
|
54 |
+
|
55 |
+
**Action:** Engage with local communities early in the project lifecycle. Address concerns and incorporate feedback into the project design. Offer community benefits, such as local employment opportunities or funding for community projects.
|
56 |
+
|
57 |
+
## Risk 6 - Operational
|
58 |
+
Equipment failures, grid outages, or cyberattacks could disrupt energy production and reduce revenue. Difficulty in securing qualified personnel for operation and maintenance.
|
59 |
+
|
60 |
+
**Impact:** Reduced energy output, revenue losses, increased maintenance costs, and potential safety hazards.
|
61 |
+
|
62 |
+
**Likelihood:** Medium
|
63 |
+
|
64 |
+
**Severity:** Medium
|
65 |
+
|
66 |
+
**Action:** Implement a robust maintenance program. Invest in cybersecurity measures to protect against cyberattacks. Develop a contingency plan for grid outages. Provide training and development opportunities for operational personnel.
|
67 |
+
|
68 |
+
## Risk 7 - Supply Chain
|
69 |
+
Disruptions in the supply chain for solar panels or other critical components due to geopolitical events, natural disasters, or manufacturing delays. Increased lead times for equipment delivery.
|
70 |
+
|
71 |
+
**Impact:** Project delays of 2-4 months, increased costs for expedited shipping or alternative sourcing, potential equipment shortages.
|
72 |
+
|
73 |
+
**Likelihood:** Medium
|
74 |
+
|
75 |
+
**Severity:** Medium
|
76 |
+
|
77 |
+
**Action:** Diversify suppliers and establish relationships with multiple vendors. Maintain a buffer stock of critical components. Monitor global supply chain conditions closely. Consider sourcing equipment from local or regional suppliers.
|
78 |
+
|
79 |
+
## Risk 8 - Security
|
80 |
+
Theft of equipment or vandalism could damage the solar farm and disrupt energy production. Physical security breaches could also pose a safety risk.
|
81 |
+
|
82 |
+
**Impact:** Increased costs for security measures, project delays, and potential safety hazards.
|
83 |
+
|
84 |
+
**Likelihood:** Low
|
85 |
+
|
86 |
+
**Severity:** Low
|
87 |
+
|
88 |
+
**Action:** Implement physical security measures, such as fencing, security cameras, and alarm systems. Conduct background checks on employees and contractors. Coordinate with local law enforcement agencies.
|
89 |
+
|
90 |
+
## Risk 9 - Integration with Existing Infrastructure
|
91 |
+
Challenges in integrating the solar farm with the existing power grid, potentially leading to grid instability or curtailment of energy production. Insufficient grid capacity to accommodate the solar farm's output.
|
92 |
+
|
93 |
+
**Impact:** Reduced energy output, revenue losses, and potential grid instability.
|
94 |
+
|
95 |
+
**Likelihood:** Medium
|
96 |
+
|
97 |
+
**Severity:** Medium
|
98 |
+
|
99 |
+
**Action:** Conduct thorough grid impact studies. Coordinate with the grid operator to ensure sufficient grid capacity and stability. Invest in grid upgrades if necessary.
|
100 |
+
|
101 |
+
## Risk 10 - Long-Term Sustainability
|
102 |
+
Uncertainty regarding the long-term performance and degradation of solar panels. Difficulty in decommissioning the solar farm at the end of its lifespan. Lack of a clear plan for recycling or disposal of solar panels.
|
103 |
+
|
104 |
+
**Impact:** Reduced energy output over time, increased maintenance costs, and environmental liabilities at the end of the project's lifespan.
|
105 |
+
|
106 |
+
**Likelihood:** Medium
|
107 |
+
|
108 |
+
**Severity:** Medium
|
109 |
+
|
110 |
+
**Action:** Select high-quality solar panels with long-term warranties. Develop a decommissioning plan that includes recycling or disposal of solar panels. Explore options for extending the lifespan of the solar farm through repowering or refurbishment.
|
111 |
+
|
112 |
+
## Risk summary
|
113 |
+
The most critical risks for this solar farm project in Denmark are related to Regulatory & Permitting, Technical challenges during installation and grid connection, and Financial risks associated with currency fluctuations and material costs. Delays in permitting can significantly impact the project timeline and budget. Technical issues can lead to increased costs and reduced energy output. Financial risks can erode project profitability. Effective mitigation strategies should focus on proactive engagement with authorities, thorough technical assessments, and robust financial planning.
|
src/assume/test_data/shorten_markdown1/physical_locations.md
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
This plan implies one or more physical locations.
|
2 |
+
|
3 |
+
## Requirements for physical locations
|
4 |
+
|
5 |
+
- High solar irradiance
|
6 |
+
- Flat land
|
7 |
+
- Proximity to grid connection
|
8 |
+
- Minimal environmental impact
|
9 |
+
- Suitable zoning and permits
|
10 |
+
|
11 |
+
## Location 1
|
12 |
+
Denmark
|
13 |
+
|
14 |
+
West Jutland
|
15 |
+
|
16 |
+
Near Varde
|
17 |
+
|
18 |
+
**Rationale**: West Jutland offers large open areas with relatively high solar irradiance compared to other parts of Denmark. The area near Varde has existing grid infrastructure and relatively flat terrain, making it suitable for a solar farm.
|
19 |
+
|
20 |
+
## Location 2
|
21 |
+
Denmark
|
22 |
+
|
23 |
+
South Jutland
|
24 |
+
|
25 |
+
Near Tønder
|
26 |
+
|
27 |
+
**Rationale**: South Jutland also has favorable conditions for solar energy production. The area near Tønder has available land and is close to existing high-voltage power lines, which can reduce connection costs.
|
28 |
+
|
29 |
+
## Location 3
|
30 |
+
Denmark
|
31 |
+
|
32 |
+
Zealand
|
33 |
+
|
34 |
+
Near Holbæk
|
35 |
+
|
36 |
+
**Rationale**: Zealand, particularly the area near Holbæk, offers a balance of available land and proximity to major population centers, potentially reducing transmission losses and increasing the value of the generated electricity. It also benefits from good infrastructure.
|
37 |
+
|
38 |
+
## Location Summary
|
39 |
+
The suggested locations in West Jutland (near Varde), South Jutland (near Tønder), and Zealand (near Holbæk) are all suitable for establishing a solar farm in Denmark due to their high solar irradiance, availability of flat land, proximity to grid connections, and other favorable conditions.
|
src/llm_factory.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1 |
import logging
|
2 |
import os
|
3 |
import json
|
|
|
4 |
from dataclasses import dataclass
|
5 |
from dotenv import dotenv_values
|
6 |
from typing import Optional, Any, Dict
|
7 |
from llama_index.core.llms.llm import LLM
|
|
|
8 |
from llama_index.llms.ollama import Ollama
|
9 |
from llama_index.llms.openai_like import OpenAILike
|
10 |
from llama_index.llms.openai import OpenAI
|
@@ -68,6 +70,13 @@ def substitute_env_vars(config: Dict[str, Any], env_vars: Dict[str, str]) -> Dic
|
|
68 |
|
69 |
return process_item(config)
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
@dataclass
|
72 |
class LLMConfigItem:
|
73 |
id: str
|
@@ -76,7 +85,7 @@ class LLMConfigItem:
|
|
76 |
@dataclass
|
77 |
class LLMInfo:
|
78 |
llm_config_items: list[LLMConfigItem]
|
79 |
-
|
80 |
error_message_list: list[str]
|
81 |
|
82 |
@classmethod
|
@@ -84,16 +93,41 @@ class LLMInfo:
|
|
84 |
"""
|
85 |
Returns a list of available LLM names.
|
86 |
"""
|
|
|
|
|
87 |
error_message_list = []
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
94 |
|
95 |
-
|
|
|
|
|
96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
for config_id, config in _llm_configs.items():
|
98 |
if config.get("class") != "Ollama":
|
99 |
item = LLMConfigItem(id=config_id, label=config_id)
|
@@ -101,6 +135,9 @@ class LLMInfo:
|
|
101 |
continue
|
102 |
arguments = config.get("arguments", {})
|
103 |
model = arguments.get("model", None)
|
|
|
|
|
|
|
104 |
|
105 |
is_model_available = ollama_info.is_model_available(model)
|
106 |
if is_model_available:
|
@@ -115,9 +152,18 @@ class LLMInfo:
|
|
115 |
item = LLMConfigItem(id=config_id, label=label)
|
116 |
llm_config_items.append(item)
|
117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
return LLMInfo(
|
119 |
llm_config_items=llm_config_items,
|
120 |
-
|
121 |
error_message_list=error_message_list,
|
122 |
)
|
123 |
|
|
|
1 |
import logging
|
2 |
import os
|
3 |
import json
|
4 |
+
from enum import Enum
|
5 |
from dataclasses import dataclass
|
6 |
from dotenv import dotenv_values
|
7 |
from typing import Optional, Any, Dict
|
8 |
from llama_index.core.llms.llm import LLM
|
9 |
+
from llama_index.llms.mistralai import MistralAI
|
10 |
from llama_index.llms.ollama import Ollama
|
11 |
from llama_index.llms.openai_like import OpenAILike
|
12 |
from llama_index.llms.openai import OpenAI
|
|
|
70 |
|
71 |
return process_item(config)
|
72 |
|
73 |
+
class OllamaStatus(str, Enum):
|
74 |
+
no_ollama_models = 'no ollama models in the llm_config.json file'
|
75 |
+
ollama_not_running = 'ollama is NOT running'
|
76 |
+
mixed = 'Mixed. Some ollama models are running, but some are NOT running.'
|
77 |
+
ollama_running = 'Ollama is running'
|
78 |
+
|
79 |
+
|
80 |
@dataclass
|
81 |
class LLMConfigItem:
|
82 |
id: str
|
|
|
85 |
@dataclass
|
86 |
class LLMInfo:
|
87 |
llm_config_items: list[LLMConfigItem]
|
88 |
+
ollama_status: OllamaStatus
|
89 |
error_message_list: list[str]
|
90 |
|
91 |
@classmethod
|
|
|
93 |
"""
|
94 |
Returns a list of available LLM names.
|
95 |
"""
|
96 |
+
|
97 |
+
# Probe each Ollama service endpoint just once.
|
98 |
error_message_list = []
|
99 |
+
ollama_info_per_host = {}
|
100 |
+
count_running = 0
|
101 |
+
count_not_running = 0
|
102 |
+
for config_id, config in _llm_configs.items():
|
103 |
+
if config.get("class") != "Ollama":
|
104 |
+
continue
|
105 |
+
arguments = config.get("arguments", {})
|
106 |
+
model = arguments.get("model", None)
|
107 |
+
base_url = arguments.get("base_url", None)
|
108 |
|
109 |
+
if base_url in ollama_info_per_host:
|
110 |
+
# Already got info for this host. No need to get it again.
|
111 |
+
continue
|
112 |
|
113 |
+
ollama_info = OllamaInfo.obtain_info(base_url=base_url)
|
114 |
+
ollama_info_per_host[base_url] = ollama_info
|
115 |
+
|
116 |
+
running_on = "localhost" if base_url is None else base_url
|
117 |
+
|
118 |
+
if ollama_info.is_running:
|
119 |
+
count_running += 1
|
120 |
+
else:
|
121 |
+
count_not_running += 1
|
122 |
+
|
123 |
+
if ollama_info.is_running == False:
|
124 |
+
print(f"Ollama is not running on {running_on}. Please start the Ollama service, in order to use the models via Ollama.")
|
125 |
+
elif ollama_info.error_message:
|
126 |
+
print(f"Error message: {ollama_info.error_message}")
|
127 |
+
error_message_list.append(ollama_info.error_message)
|
128 |
+
|
129 |
+
# Get info about the each LLM config item that is using Ollama.
|
130 |
+
llm_config_items = []
|
131 |
for config_id, config in _llm_configs.items():
|
132 |
if config.get("class") != "Ollama":
|
133 |
item = LLMConfigItem(id=config_id, label=config_id)
|
|
|
135 |
continue
|
136 |
arguments = config.get("arguments", {})
|
137 |
model = arguments.get("model", None)
|
138 |
+
base_url = arguments.get("base_url", None)
|
139 |
+
|
140 |
+
ollama_info = ollama_info_per_host[base_url]
|
141 |
|
142 |
is_model_available = ollama_info.is_model_available(model)
|
143 |
if is_model_available:
|
|
|
152 |
item = LLMConfigItem(id=config_id, label=label)
|
153 |
llm_config_items.append(item)
|
154 |
|
155 |
+
if count_not_running == 0 and count_running > 0:
|
156 |
+
ollama_status = OllamaStatus.ollama_running
|
157 |
+
elif count_not_running > 0 and count_running == 0:
|
158 |
+
ollama_status = OllamaStatus.ollama_not_running
|
159 |
+
elif count_not_running > 0 and count_running > 0:
|
160 |
+
ollama_status = OllamaStatus.mixed
|
161 |
+
else:
|
162 |
+
ollama_status = OllamaStatus.no_ollama_models
|
163 |
+
|
164 |
return LLMInfo(
|
165 |
llm_config_items=llm_config_items,
|
166 |
+
ollama_status=ollama_status,
|
167 |
error_message_list=error_message_list,
|
168 |
)
|
169 |
|
src/llm_util/ollama_info.py
CHANGED
@@ -15,20 +15,21 @@ class OllamaInfo:
|
|
15 |
error_message: Optional[str] = None
|
16 |
|
17 |
@classmethod
|
18 |
-
def obtain_info(cls) -> 'OllamaInfo':
|
19 |
"""Retrieves information about the Ollama service."""
|
20 |
try:
|
21 |
# Only import ollama if it's available
|
22 |
-
from ollama import ListResponse,
|
23 |
-
|
|
|
24 |
except ImportError as e:
|
25 |
-
error_message = f"OllamaInfo. The 'ollama' library was not found: {e}"
|
26 |
return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
|
27 |
except ConnectionError as e:
|
28 |
-
error_message = f"OllamaInfo. Error connecting to Ollama: {e}"
|
29 |
return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
|
30 |
except Exception as e:
|
31 |
-
error_message = f"OllamaInfo. An unexpected error occurred: {e}"
|
32 |
return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
|
33 |
|
34 |
model_names = [model.model for model in list_response.models]
|
@@ -56,7 +57,13 @@ class OllamaInfo:
|
|
56 |
|
57 |
if __name__ == '__main__':
|
58 |
find_model = 'qwen2.5-coder:latest'
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
print(f"Error message: {ollama_info.error_message}")
|
61 |
print(f'Is Ollama running: {ollama_info.is_running}')
|
62 |
found = ollama_info.is_model_available(find_model)
|
|
|
15 |
error_message: Optional[str] = None
|
16 |
|
17 |
@classmethod
|
18 |
+
def obtain_info(cls, base_url: Optional[str] = None) -> 'OllamaInfo':
|
19 |
"""Retrieves information about the Ollama service."""
|
20 |
try:
|
21 |
# Only import ollama if it's available
|
22 |
+
from ollama import ListResponse, Client
|
23 |
+
client = Client(host=base_url, timeout=5)
|
24 |
+
list_response: ListResponse = client.list()
|
25 |
except ImportError as e:
|
26 |
+
error_message = f"OllamaInfo base_url={base_url}. The 'ollama' library was not found: {e}"
|
27 |
return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
|
28 |
except ConnectionError as e:
|
29 |
+
error_message = f"OllamaInfo base_url={base_url}. Error connecting to Ollama: {e}"
|
30 |
return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
|
31 |
except Exception as e:
|
32 |
+
error_message = f"OllamaInfo base_url={base_url}. An unexpected error occurred: {e}"
|
33 |
return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
|
34 |
|
35 |
model_names = [model.model for model in list_response.models]
|
|
|
57 |
|
58 |
if __name__ == '__main__':
|
59 |
find_model = 'qwen2.5-coder:latest'
|
60 |
+
base_url = None
|
61 |
+
# base_url = "localhost:11434"
|
62 |
+
# base_url = "example.com:11434"
|
63 |
+
|
64 |
+
print(f"find_model: {find_model}")
|
65 |
+
print(f"base_url: {base_url}")
|
66 |
+
ollama_info = OllamaInfo.obtain_info(base_url=base_url)
|
67 |
print(f"Error message: {ollama_info.error_message}")
|
68 |
print(f'Is Ollama running: {ollama_info.is_running}')
|
69 |
found = ollama_info.is_model_available(find_model)
|
src/markdown_util/remove_bold_formatting.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
|
3 |
+
def remove_bold_formatting(text: str) -> str:
|
4 |
+
"""
|
5 |
+
Remove bold formatting from the text.
|
6 |
+
|
7 |
+
When processing long texts with LLMs, the token count is a limiting factor.
|
8 |
+
This function removes the bold formatting from the text to reduce the token count.
|
9 |
+
"""
|
10 |
+
text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text)
|
11 |
+
return re.sub(r'__([^_]+?)__', r'\1', text)
|
src/markdown_util/tests/test_remove_bold_formatting.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import unittest
|
2 |
+
from src.markdown_util.remove_bold_formatting import remove_bold_formatting
|
3 |
+
|
4 |
+
class TestRemoveBoldFormatting(unittest.TestCase):
|
5 |
+
def test_remove_bold_formatting1(self):
|
6 |
+
text = "**Hello** __World__"
|
7 |
+
expected = "Hello World"
|
8 |
+
self.assertEqual(remove_bold_formatting(text), expected)
|
9 |
+
|
10 |
+
def test_remove_bold_formatting2(self):
|
11 |
+
text = "**Hello** 123 World**"
|
12 |
+
expected = "Hello 123 World**"
|
13 |
+
self.assertEqual(remove_bold_formatting(text), expected)
|
src/plan/app_text2plan.py
CHANGED
@@ -15,7 +15,7 @@ import logging
|
|
15 |
import json
|
16 |
from dataclasses import dataclass
|
17 |
from math import ceil
|
18 |
-
from src.llm_factory import LLMInfo
|
19 |
from src.plan.generate_run_id import generate_run_id, RUN_ID_PREFIX
|
20 |
from src.plan.create_zip_archive import create_zip_archive
|
21 |
from src.plan.filenames import FilenameEnum
|
@@ -107,7 +107,7 @@ for prompt_item in all_prompts:
|
|
107 |
gradio_examples.append([prompt_item.prompt])
|
108 |
|
109 |
llm_info = LLMInfo.obtain_info()
|
110 |
-
logger.info(f"LLMInfo.
|
111 |
logger.info(f"LLMInfo.error_message_list: {llm_info.error_message_list}")
|
112 |
|
113 |
trimmed_llm_config_items = []
|
@@ -478,8 +478,10 @@ with gr.Blocks(title="PlanExe") as demo_text2plan:
|
|
478 |
|
479 |
with gr.Tab("Settings"):
|
480 |
if CONFIG.visible_llm_info:
|
481 |
-
if llm_info.
|
482 |
gr.Markdown("**Ollama is not running**, so Ollama models are unavailable. Please start Ollama to use them.")
|
|
|
|
|
483 |
|
484 |
if len(llm_info.error_message_list) > 0:
|
485 |
gr.Markdown("**Error messages:**")
|
|
|
15 |
import json
|
16 |
from dataclasses import dataclass
|
17 |
from math import ceil
|
18 |
+
from src.llm_factory import LLMInfo, OllamaStatus
|
19 |
from src.plan.generate_run_id import generate_run_id, RUN_ID_PREFIX
|
20 |
from src.plan.create_zip_archive import create_zip_archive
|
21 |
from src.plan.filenames import FilenameEnum
|
|
|
107 |
gradio_examples.append([prompt_item.prompt])
|
108 |
|
109 |
llm_info = LLMInfo.obtain_info()
|
110 |
+
logger.info(f"LLMInfo.ollama_status: {llm_info.ollama_status.value}")
|
111 |
logger.info(f"LLMInfo.error_message_list: {llm_info.error_message_list}")
|
112 |
|
113 |
trimmed_llm_config_items = []
|
|
|
478 |
|
479 |
with gr.Tab("Settings"):
|
480 |
if CONFIG.visible_llm_info:
|
481 |
+
if llm_info.ollama_status == OllamaStatus.ollama_not_running:
|
482 |
gr.Markdown("**Ollama is not running**, so Ollama models are unavailable. Please start Ollama to use them.")
|
483 |
+
elif llm_info.ollama_status == OllamaStatus.mixed:
|
484 |
+
gr.Markdown("**Mixed. Some Ollama models are running, but some are NOT running.**, You may have to start the ones that aren't running.")
|
485 |
|
486 |
if len(llm_info.error_message_list) > 0:
|
487 |
gr.Markdown("**Error messages:**")
|
src/plan/data/simple_plan_prompts.jsonl
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
{"id": "f847a181-c9b8-419f-8aef-552e1a3b662f", "prompt": "Distill Arxiv papers into an objective, hype-free summary that indicates whether improvements are truly significant or just noise. Compare claims with benchmarks, flag inflated gains, and foster a clear, evidence-based understanding of machine learning progress without marketing language. To make the distilled data available with minimal upkeep and maximum longevity, publish these summaries as an open-access dataset on a well-established repository.", "tags": ["Arxiv", "paper", "dataset", "signal", "noise"]}
|
2 |
{"id": "fdbac6bc-6853-47f3-b7ec-bc0051314952", "prompt": "I'm envisioning a streamlined global language—free of archaic features like gendered terms and excessive suffixes, taking cues from LLM tokenization. Some regions might only choose to adopt certain parts of this modern language. Would humanity ultimately benefit more from preserving many distinct languages, or uniting around a single, optimized one?", "tags": ["language", "tokenization"]}
|
3 |
{"id": "762b64e2-5ac8-4684-807a-efd3e81d6bc1", "prompt": "Create a detailed report examining the current situation of microplastics within the world's oceans.", "tags": ["ocean", "microplastics", "climate change", "sustainability"]}
|
|
|
1 |
+
{"id": "3ca89453-e65b-4828-994f-dff0b679444a", "prompt": "It's 2025 and humanoid robots are entering mainstream society, with China already showcasing robotic athletes in sports events. Plan a 2026 Robot Olympics, outline innovative events, rules, and challenges to test the humanoid robots.", "tags": ["robots", "sport", "olympics"]}
|
2 |
+
{"id": "fc0f0be2-125d-42dd-aac3-2e5039fc7938", "prompt": "Make a 64bit x86 OS in Rust. Linux-like but not POSIX-compliant. Monolithic kernel, memory management, process scheduler, shell, utils (ls, cat, rm, mkdir, mv, rmdir, ps). Basic drivers for console, disk, and virtio-net, and include enough network stack do a ping. This is my hobby project for testing LLM coding skills.", "tags": ["OS", "operating system", "linux", "rust"]}
|
3 |
{"id": "f847a181-c9b8-419f-8aef-552e1a3b662f", "prompt": "Distill Arxiv papers into an objective, hype-free summary that indicates whether improvements are truly significant or just noise. Compare claims with benchmarks, flag inflated gains, and foster a clear, evidence-based understanding of machine learning progress without marketing language. To make the distilled data available with minimal upkeep and maximum longevity, publish these summaries as an open-access dataset on a well-established repository.", "tags": ["Arxiv", "paper", "dataset", "signal", "noise"]}
|
4 |
{"id": "fdbac6bc-6853-47f3-b7ec-bc0051314952", "prompt": "I'm envisioning a streamlined global language—free of archaic features like gendered terms and excessive suffixes, taking cues from LLM tokenization. Some regions might only choose to adopt certain parts of this modern language. Would humanity ultimately benefit more from preserving many distinct languages, or uniting around a single, optimized one?", "tags": ["language", "tokenization"]}
|
5 |
{"id": "762b64e2-5ac8-4684-807a-efd3e81d6bc1", "prompt": "Create a detailed report examining the current situation of microplastics within the world's oceans.", "tags": ["ocean", "microplastics", "climate change", "sustainability"]}
|
src/plan/executive_summary.py
ADDED
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
One-pager that summarizes the plan.
|
3 |
+
|
4 |
+
https://en.wikipedia.org/wiki/Executive_summary
|
5 |
+
|
6 |
+
- The executive summary should always be placed at the beginning of the report.
|
7 |
+
- It's designed to be read first. Its purpose is to provide a high-level overview of the report's contents so that readers can quickly understand the key findings and recommendations.
|
8 |
+
- It helps readers decide how to engage with the rest of the report. Knowing the key takeaways upfront allows readers to prioritize which sections they need to read in detail.
|
9 |
+
- It provides context for the rest of the report. Having a clear understanding of the report's purpose and main findings makes it easier to interpret the details presented in the body of the report.
|
10 |
+
|
11 |
+
The primary target audience for the executive summary is senior management, executives, investors, and other key decision-makers. These individuals typically have limited time and need to quickly understand the most important information in the report.
|
12 |
+
|
13 |
+
I have removed the "high-level approach" section, because the "executive summary" is generated from all the PlanExe documents.
|
14 |
+
It makes no sense to write that it's based on a SWOT analysis and expert interviews,
|
15 |
+
since these documents are already in the PlanExe pipeline. No plan could be created without them.
|
16 |
+
|
17 |
+
PROMPT> python -m src.plan.executive_summary
|
18 |
+
"""
|
19 |
+
import os
|
20 |
+
import json
|
21 |
+
import time
|
22 |
+
import logging
|
23 |
+
from math import ceil
|
24 |
+
from typing import Optional
|
25 |
+
from dataclasses import dataclass
|
26 |
+
from llama_index.core.llms.llm import LLM
|
27 |
+
from pydantic import BaseModel, Field
|
28 |
+
from llama_index.core.llms import ChatMessage, MessageRole
|
29 |
+
from src.markdown_util.fix_bullet_lists import fix_bullet_lists
|
30 |
+
|
31 |
+
logger = logging.getLogger(__name__)
|
32 |
+
|
33 |
+
class DocumentDetails(BaseModel):
|
34 |
+
audience_tailoring: str = Field(
|
35 |
+
description="Adapt the tone and detail based on who will be reading this summary (individual hobbyist, corporate, government, etc.)."
|
36 |
+
)
|
37 |
+
focus_and_context: str = Field(
|
38 |
+
description="A short statement about why this project or plan exists and its overall objectives."
|
39 |
+
)
|
40 |
+
purpose_and_goals: str = Field(
|
41 |
+
description="A crisp statement of the project's main goals and success criteria."
|
42 |
+
)
|
43 |
+
key_deliverables_and_outcomes: str = Field(
|
44 |
+
description="Bulleted summary of the expected end-products or results from the plan."
|
45 |
+
)
|
46 |
+
timeline_and_budget: str = Field(
|
47 |
+
description="A short estimate of time and top-level budget"
|
48 |
+
)
|
49 |
+
risks_and_mitigations: str = Field(
|
50 |
+
description="Identify 1-2 major risks and how you plan to reduce or address them."
|
51 |
+
)
|
52 |
+
action_orientation: str = Field(
|
53 |
+
description="Explain the immediate actions or next steps needed to move forward. Summarize who, what, and when."
|
54 |
+
)
|
55 |
+
overall_takeaway: str = Field(
|
56 |
+
description="A final, concise statement emphasizing the main point or value of the plan (e.g., expected benefits, ROI, key success metric)."
|
57 |
+
)
|
58 |
+
feedback: str = Field(
|
59 |
+
description="Suggestions for how to strengthen the executive summary by adding more evidence or improving persuasiveness."
|
60 |
+
)
|
61 |
+
|
62 |
+
EXECUTIVE_SUMMARY_SYSTEM_PROMPT = """
|
63 |
+
You are a seasoned expert in crafting concise, high-impact executive summaries for any type of plan—from personal projects (such as weight loss or learning a musical instrument) to large-scale business initiatives. Your task is to generate a complete executive summary as a valid JSON object that strictly adheres to the schema below. Do not include any extra text, markdown formatting, or additional keys.
|
64 |
+
|
65 |
+
The JSON object must include exactly the following keys:
|
66 |
+
|
67 |
+
{
|
68 |
+
"audience_tailoring": string,
|
69 |
+
"focus_and_context": string,
|
70 |
+
"purpose_and_goals": string,
|
71 |
+
"key_deliverables_and_outcomes": string,
|
72 |
+
"timeline_and_budget": string,
|
73 |
+
"risks_and_mitigations": string,
|
74 |
+
"action_orientation": string,
|
75 |
+
"overall_takeaway": string,
|
76 |
+
"feedback": string
|
77 |
+
}
|
78 |
+
|
79 |
+
Instructions for each key:
|
80 |
+
- audience_tailoring: Describe how the tone and details are tailored for the intended audience (e.g., an individual with personal goals or senior management for a business plan).
|
81 |
+
- focus_and_context: Provide a succinct overview of why the plan exists and its overall objectives. Begin with a compelling hook—a visionary statement, provocative question, or striking statistic—to immediately capture the decision-maker's attention.
|
82 |
+
- purpose_and_goals: Clearly state the main objectives and success criteria.
|
83 |
+
- key_deliverables_and_outcomes: Summarize the primary deliverables, milestones, or outcomes expected.
|
84 |
+
- timeline_and_budget: Provide a brief estimate of the timeframe and any associated costs or resource needs. For personal plans, note if costs are minimal or not applicable.
|
85 |
+
- risks_and_mitigations: Identify one or two significant risks and outline strategies to mitigate them.
|
86 |
+
- action_orientation: Detail the immediate next steps or actions required, including responsibilities and timelines if relevant.
|
87 |
+
- overall_takeaway: Conclude with a clear, concise statement emphasizing the plan’s overall value or expected benefits.
|
88 |
+
- feedback: Offer multiple constructive suggestions to enhance the summary’s clarity, persuasiveness, or completeness—such as additional data points or more detailed analysis.
|
89 |
+
|
90 |
+
Output Requirements:
|
91 |
+
- Your entire response must be a valid JSON object conforming exactly to the schema above.
|
92 |
+
- Use clear, concise, and professional language appropriate for the context of the plan.
|
93 |
+
- Do not include any extra text or formatting outside the JSON structure.
|
94 |
+
|
95 |
+
Remember: Your output must be valid JSON and nothing else.
|
96 |
+
"""
|
97 |
+
|
98 |
+
@dataclass
|
99 |
+
class ExecutiveSummary:
|
100 |
+
system_prompt: Optional[str]
|
101 |
+
user_prompt: str
|
102 |
+
response: str
|
103 |
+
markdown: str
|
104 |
+
metadata: dict
|
105 |
+
|
106 |
+
@classmethod
|
107 |
+
def execute(cls, llm: LLM, user_prompt: str) -> 'ExecutiveSummary':
|
108 |
+
"""
|
109 |
+
Invoke LLM with a long markdown document that needs an executive summary.
|
110 |
+
"""
|
111 |
+
if not isinstance(llm, LLM):
|
112 |
+
raise ValueError("Invalid LLM instance.")
|
113 |
+
if not isinstance(user_prompt, str):
|
114 |
+
raise ValueError("Invalid user_prompt.")
|
115 |
+
|
116 |
+
system_prompt = EXECUTIVE_SUMMARY_SYSTEM_PROMPT.strip()
|
117 |
+
chat_message_list = [
|
118 |
+
ChatMessage(
|
119 |
+
role=MessageRole.SYSTEM,
|
120 |
+
content=system_prompt,
|
121 |
+
),
|
122 |
+
ChatMessage(
|
123 |
+
role=MessageRole.USER,
|
124 |
+
content=user_prompt,
|
125 |
+
)
|
126 |
+
]
|
127 |
+
|
128 |
+
sllm = llm.as_structured_llm(DocumentDetails)
|
129 |
+
start_time = time.perf_counter()
|
130 |
+
try:
|
131 |
+
chat_response = sllm.chat(chat_message_list)
|
132 |
+
except Exception as e:
|
133 |
+
logger.debug(f"LLM chat interaction failed: {e}")
|
134 |
+
logger.error("LLM chat interaction failed.", exc_info=True)
|
135 |
+
raise ValueError("LLM chat interaction failed.") from e
|
136 |
+
|
137 |
+
end_time = time.perf_counter()
|
138 |
+
duration = int(ceil(end_time - start_time))
|
139 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
140 |
+
logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
141 |
+
|
142 |
+
json_response = chat_response.raw.model_dump()
|
143 |
+
|
144 |
+
metadata = dict(llm.metadata)
|
145 |
+
metadata["llm_classname"] = llm.class_name()
|
146 |
+
metadata["duration"] = duration
|
147 |
+
metadata["response_byte_count"] = response_byte_count
|
148 |
+
|
149 |
+
markdown = cls.convert_to_markdown(chat_response.raw)
|
150 |
+
|
151 |
+
result = ExecutiveSummary(
|
152 |
+
system_prompt=system_prompt,
|
153 |
+
user_prompt=user_prompt,
|
154 |
+
response=json_response,
|
155 |
+
markdown=markdown,
|
156 |
+
metadata=metadata,
|
157 |
+
)
|
158 |
+
logger.debug("ExecutiveSummary instance created successfully.")
|
159 |
+
return result
|
160 |
+
|
161 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
|
162 |
+
d = self.response.copy()
|
163 |
+
d['markdown'] = self.markdown
|
164 |
+
if include_metadata:
|
165 |
+
d['metadata'] = self.metadata
|
166 |
+
if include_system_prompt:
|
167 |
+
d['system_prompt'] = self.system_prompt
|
168 |
+
if include_user_prompt:
|
169 |
+
d['user_prompt'] = self.user_prompt
|
170 |
+
return d
|
171 |
+
|
172 |
+
def save_raw(self, file_path: str) -> None:
|
173 |
+
with open(file_path, 'w') as f:
|
174 |
+
f.write(json.dumps(self.to_dict(), indent=2))
|
175 |
+
|
176 |
+
@staticmethod
|
177 |
+
def convert_to_markdown(document_details: DocumentDetails) -> str:
|
178 |
+
"""
|
179 |
+
Convert the raw document details to markdown.
|
180 |
+
"""
|
181 |
+
rows = []
|
182 |
+
rows.append(f"## Focus and Context\n{document_details.focus_and_context}")
|
183 |
+
rows.append(f"\n## Purpose and Goals\n{document_details.purpose_and_goals}")
|
184 |
+
rows.append(f"\n## Key Deliverables and Outcomes\n{document_details.key_deliverables_and_outcomes}")
|
185 |
+
rows.append(f"\n## Timeline and Budget\n{document_details.timeline_and_budget}")
|
186 |
+
rows.append(f"\n## Risks and Mitigations\n{document_details.risks_and_mitigations}")
|
187 |
+
rows.append(f"\n## Audience Tailoring\n{document_details.audience_tailoring}")
|
188 |
+
rows.append(f"\n## Action Orientation\n{document_details.action_orientation}")
|
189 |
+
rows.append(f"\n## Overall Takeaway\n{document_details.overall_takeaway}")
|
190 |
+
rows.append(f"\n## Feedback\n{document_details.feedback}")
|
191 |
+
markdown = "\n".join(rows)
|
192 |
+
markdown = fix_bullet_lists(markdown)
|
193 |
+
return markdown
|
194 |
+
|
195 |
+
def save_markdown(self, output_file_path: str):
|
196 |
+
with open(output_file_path, 'w', encoding='utf-8') as out_f:
|
197 |
+
out_f.write(self.markdown)
|
198 |
+
|
199 |
+
if __name__ == "__main__":
|
200 |
+
from src.llm_factory import get_llm
|
201 |
+
|
202 |
+
path = os.path.join(os.path.dirname(__file__), 'test_data', 'solarfarm_consolidate_assumptions_short.md')
|
203 |
+
with open(path, 'r', encoding='utf-8') as f:
|
204 |
+
the_markdown = f.read()
|
205 |
+
|
206 |
+
model_name = "ollama-llama3.1"
|
207 |
+
llm = get_llm(model_name)
|
208 |
+
|
209 |
+
query = the_markdown
|
210 |
+
input_bytes_count = len(query.encode('utf-8'))
|
211 |
+
print(f"Query: {query}")
|
212 |
+
result = ExecutiveSummary.execute(llm, query)
|
213 |
+
|
214 |
+
print("\nResponse:")
|
215 |
+
json_response = result.to_dict(include_system_prompt=False, include_user_prompt=False)
|
216 |
+
print(json.dumps(json_response, indent=2))
|
217 |
+
|
218 |
+
print(f"\n\nMarkdown:\n{result.markdown}")
|
219 |
+
|
220 |
+
output_bytes_count = len(result.markdown.encode('utf-8'))
|
221 |
+
print(f"\n\nInput bytes count: {input_bytes_count}")
|
222 |
+
print(f"Output bytes count: {output_bytes_count}")
|
223 |
+
bytes_saved = input_bytes_count - output_bytes_count
|
224 |
+
print(f"Bytes saved: {bytes_saved}")
|
225 |
+
print(f"Percentage saved: {bytes_saved / input_bytes_count * 100:.2f}%")
|
226 |
+
|
src/plan/filenames.py
CHANGED
@@ -17,10 +17,14 @@ class FilenameEnum(str, Enum):
|
|
17 |
DISTILL_ASSUMPTIONS_MARKDOWN = "003-7-distill_assumptions.md"
|
18 |
REVIEW_ASSUMPTIONS_RAW = "003-8-review_assumptions_raw.json"
|
19 |
REVIEW_ASSUMPTIONS_MARKDOWN = "003-9-review_assumptions.md"
|
20 |
-
|
|
|
21 |
PRE_PROJECT_ASSESSMENT_RAW = "004-1-pre_project_assessment_raw.json"
|
22 |
PRE_PROJECT_ASSESSMENT = "004-2-pre_project_assessment.json"
|
23 |
-
|
|
|
|
|
|
|
24 |
FIND_TEAM_MEMBERS_RAW = "006-1-find_team_members_raw.json"
|
25 |
FIND_TEAM_MEMBERS_CLEAN = "006-2-find_team_members.json"
|
26 |
ENRICH_TEAM_MEMBERS_CONTRACT_TYPE_RAW = "007-1-enrich_team_members_contract_type_raw.json"
|
@@ -52,5 +56,9 @@ class FilenameEnum(str, Enum):
|
|
52 |
WBS_LEVEL3 = "021-2-wbs_level3.json"
|
53 |
WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_FULL = "021-3-wbs_project_level1_and_level2_and_level3.json"
|
54 |
WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_CSV = "021-4-wbs_project_level1_and_level2_and_level3.csv"
|
55 |
-
|
|
|
|
|
|
|
|
|
56 |
PIPELINE_COMPLETE = "999-pipeline_complete.txt"
|
|
|
17 |
DISTILL_ASSUMPTIONS_MARKDOWN = "003-7-distill_assumptions.md"
|
18 |
REVIEW_ASSUMPTIONS_RAW = "003-8-review_assumptions_raw.json"
|
19 |
REVIEW_ASSUMPTIONS_MARKDOWN = "003-9-review_assumptions.md"
|
20 |
+
CONSOLIDATE_ASSUMPTIONS_FULL_MARKDOWN = "003-10-consolidate_assumptions_full.md"
|
21 |
+
CONSOLIDATE_ASSUMPTIONS_SHORT_MARKDOWN = "003-11-consolidate_assumptions_short.md"
|
22 |
PRE_PROJECT_ASSESSMENT_RAW = "004-1-pre_project_assessment_raw.json"
|
23 |
PRE_PROJECT_ASSESSMENT = "004-2-pre_project_assessment.json"
|
24 |
+
PROJECT_PLAN_RAW = "005-1-project_plan_raw.json"
|
25 |
+
PROJECT_PLAN_MARKDOWN = "005-2-project_plan.md"
|
26 |
+
RELATED_RESOURCES_RAW = "005-3-related_resources_raw.json"
|
27 |
+
RELATED_RESOURCES_MARKDOWN = "005-4-related_resources.md"
|
28 |
FIND_TEAM_MEMBERS_RAW = "006-1-find_team_members_raw.json"
|
29 |
FIND_TEAM_MEMBERS_CLEAN = "006-2-find_team_members.json"
|
30 |
ENRICH_TEAM_MEMBERS_CONTRACT_TYPE_RAW = "007-1-enrich_team_members_contract_type_raw.json"
|
|
|
56 |
WBS_LEVEL3 = "021-2-wbs_level3.json"
|
57 |
WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_FULL = "021-3-wbs_project_level1_and_level2_and_level3.json"
|
58 |
WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_CSV = "021-4-wbs_project_level1_and_level2_and_level3.csv"
|
59 |
+
REVIEW_PLAN_RAW = "022-1-review_plan_raw.json"
|
60 |
+
REVIEW_PLAN_MARKDOWN = "022-2-review_plan.md"
|
61 |
+
EXECUTIVE_SUMMARY_RAW = "023-1-executive_summary_raw.json"
|
62 |
+
EXECUTIVE_SUMMARY_MARKDOWN = "023-2-executive_summary.md"
|
63 |
+
REPORT = "024-report.html"
|
64 |
PIPELINE_COMPLETE = "999-pipeline_complete.txt"
|
src/plan/{create_project_plan.py → project_plan.py}
RENAMED
@@ -1,11 +1,12 @@
|
|
1 |
"""
|
2 |
-
PROMPT> python -m src.plan.
|
3 |
|
4 |
Based on a vague description, the creates a rough draft for a project plan.
|
5 |
"""
|
6 |
import json
|
7 |
import time
|
8 |
import logging
|
|
|
9 |
from math import ceil
|
10 |
from typing import List, Optional, Any, Type, TypeVar
|
11 |
from dataclasses import dataclass
|
@@ -113,7 +114,7 @@ class GoalDefinition(BaseModel):
|
|
113 |
description="Ensure compliance with all regulatory and legal requirements, including permits, licenses, and industry-specific standards."
|
114 |
)
|
115 |
|
116 |
-
|
117 |
You are an expert project planner tasked with creating comprehensive and detailed project plans based on user-provided descriptions. Your output must be a complete JSON object conforming to the provided GoalDefinition schema. Focus on being specific and actionable, generating a plan that is realistic and useful for guiding project development.
|
118 |
Your plans must include:
|
119 |
- A clear goal statement adhering to the SMART criteria (Specific, Measurable, Achievable, Relevant, Time-bound). Provide specific metrics and timeframes where possible.
|
@@ -126,7 +127,7 @@ Your plans must include:
|
|
126 |
Prioritize feasibility, practicality, and alignment with the user-provided description. Ensure the plan is actionable, with concrete steps where possible and measurable outcomes.
|
127 |
"""
|
128 |
|
129 |
-
|
130 |
You are an expert project planner tasked with creating comprehensive and detailed project plans based on user-provided descriptions. Your output must be a complete JSON object conforming to the provided GoalDefinition schema. Focus on being specific and actionable, generating a plan that is realistic and useful for guiding project development.
|
131 |
|
132 |
Your plans must include:
|
@@ -148,7 +149,7 @@ Your plans must include:
|
|
148 |
Prioritize feasibility, practicality, and alignment with the user-provided description. Ensure the plan is actionable, with concrete steps where possible and measurable outcomes.
|
149 |
"""
|
150 |
|
151 |
-
|
152 |
You are an expert project planner tasked with creating comprehensive and detailed project plans based on user-provided descriptions. Your output must be a complete JSON object conforming to the provided GoalDefinition schema. Focus on being specific and actionable, generating a plan that is realistic and useful for guiding project development.
|
153 |
|
154 |
Your plans must include:
|
@@ -196,12 +197,12 @@ Here's an example of the expected output format for a simple project:
|
|
196 |
}
|
197 |
"""
|
198 |
|
199 |
-
|
200 |
|
201 |
T = TypeVar('T', bound=BaseModel)
|
202 |
|
203 |
@dataclass
|
204 |
-
class
|
205 |
"""
|
206 |
Creating a project plan from a vague description.
|
207 |
"""
|
@@ -209,9 +210,10 @@ class CreateProjectPlan:
|
|
209 |
user_prompt: str
|
210 |
response: dict
|
211 |
metadata: dict
|
|
|
212 |
|
213 |
@classmethod
|
214 |
-
def execute(cls, llm: LLM, user_prompt: str) -> '
|
215 |
"""
|
216 |
Invoke LLM to create project plan from a vague description.
|
217 |
|
@@ -224,7 +226,7 @@ class CreateProjectPlan:
|
|
224 |
if not isinstance(user_prompt, str):
|
225 |
raise ValueError("Invalid user_prompt.")
|
226 |
|
227 |
-
system_prompt =
|
228 |
logger.debug(f"System Prompt:\n{system_prompt}")
|
229 |
logger.debug(f"User Prompt:\n{user_prompt}")
|
230 |
|
@@ -262,11 +264,14 @@ class CreateProjectPlan:
|
|
262 |
metadata["duration"] = duration
|
263 |
metadata["response_byte_count"] = response_byte_count
|
264 |
|
265 |
-
|
|
|
|
|
266 |
system_prompt=system_prompt,
|
267 |
user_prompt=user_prompt,
|
268 |
response=json_response,
|
269 |
-
metadata=metadata
|
|
|
270 |
)
|
271 |
logger.debug("CreateProjectPlan instance created successfully.")
|
272 |
return result
|
@@ -281,11 +286,92 @@ class CreateProjectPlan:
|
|
281 |
d['system_prompt'] = self.system_prompt
|
282 |
return d
|
283 |
|
284 |
-
def
|
285 |
d = self.to_dict()
|
286 |
with open(file_path, 'w') as f:
|
287 |
f.write(json.dumps(d, indent=2))
|
288 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
if __name__ == "__main__":
|
290 |
import logging
|
291 |
from src.llm_factory import get_llm
|
@@ -306,6 +392,9 @@ if __name__ == "__main__":
|
|
306 |
# llm = get_llm("deepseek-chat")
|
307 |
|
308 |
print(f"Query:\n{plan_prompt}\n\n")
|
309 |
-
result =
|
310 |
json_response = result.to_dict(include_system_prompt=False, include_user_prompt=False)
|
|
|
311 |
print(json.dumps(json_response, indent=2))
|
|
|
|
|
|
1 |
"""
|
2 |
+
PROMPT> python -m src.plan.project_plan
|
3 |
|
4 |
Based on a vague description, the creates a rough draft for a project plan.
|
5 |
"""
|
6 |
import json
|
7 |
import time
|
8 |
import logging
|
9 |
+
from dataclasses import dataclass
|
10 |
from math import ceil
|
11 |
from typing import List, Optional, Any, Type, TypeVar
|
12 |
from dataclasses import dataclass
|
|
|
114 |
description="Ensure compliance with all regulatory and legal requirements, including permits, licenses, and industry-specific standards."
|
115 |
)
|
116 |
|
117 |
+
PROJECT_PLAN_SYSTEM_PROMPT_1 = """
|
118 |
You are an expert project planner tasked with creating comprehensive and detailed project plans based on user-provided descriptions. Your output must be a complete JSON object conforming to the provided GoalDefinition schema. Focus on being specific and actionable, generating a plan that is realistic and useful for guiding project development.
|
119 |
Your plans must include:
|
120 |
- A clear goal statement adhering to the SMART criteria (Specific, Measurable, Achievable, Relevant, Time-bound). Provide specific metrics and timeframes where possible.
|
|
|
127 |
Prioritize feasibility, practicality, and alignment with the user-provided description. Ensure the plan is actionable, with concrete steps where possible and measurable outcomes.
|
128 |
"""
|
129 |
|
130 |
+
PROJECT_PLAN_SYSTEM_PROMPT_2 = """
|
131 |
You are an expert project planner tasked with creating comprehensive and detailed project plans based on user-provided descriptions. Your output must be a complete JSON object conforming to the provided GoalDefinition schema. Focus on being specific and actionable, generating a plan that is realistic and useful for guiding project development.
|
132 |
|
133 |
Your plans must include:
|
|
|
149 |
Prioritize feasibility, practicality, and alignment with the user-provided description. Ensure the plan is actionable, with concrete steps where possible and measurable outcomes.
|
150 |
"""
|
151 |
|
152 |
+
PROJECT_PLAN_SYSTEM_PROMPT_3 = """
|
153 |
You are an expert project planner tasked with creating comprehensive and detailed project plans based on user-provided descriptions. Your output must be a complete JSON object conforming to the provided GoalDefinition schema. Focus on being specific and actionable, generating a plan that is realistic and useful for guiding project development.
|
154 |
|
155 |
Your plans must include:
|
|
|
197 |
}
|
198 |
"""
|
199 |
|
200 |
+
PROJECT_PLAN_SYSTEM_PROMPT = PROJECT_PLAN_SYSTEM_PROMPT_3
|
201 |
|
202 |
T = TypeVar('T', bound=BaseModel)
|
203 |
|
204 |
@dataclass
|
205 |
+
class ProjectPlan:
|
206 |
"""
|
207 |
Creating a project plan from a vague description.
|
208 |
"""
|
|
|
210 |
user_prompt: str
|
211 |
response: dict
|
212 |
metadata: dict
|
213 |
+
markdown: str
|
214 |
|
215 |
@classmethod
|
216 |
+
def execute(cls, llm: LLM, user_prompt: str) -> 'ProjectPlan':
|
217 |
"""
|
218 |
Invoke LLM to create project plan from a vague description.
|
219 |
|
|
|
226 |
if not isinstance(user_prompt, str):
|
227 |
raise ValueError("Invalid user_prompt.")
|
228 |
|
229 |
+
system_prompt = PROJECT_PLAN_SYSTEM_PROMPT.strip()
|
230 |
logger.debug(f"System Prompt:\n{system_prompt}")
|
231 |
logger.debug(f"User Prompt:\n{user_prompt}")
|
232 |
|
|
|
264 |
metadata["duration"] = duration
|
265 |
metadata["response_byte_count"] = response_byte_count
|
266 |
|
267 |
+
markdown = cls.convert_to_markdown(chat_response.raw)
|
268 |
+
|
269 |
+
result = ProjectPlan(
|
270 |
system_prompt=system_prompt,
|
271 |
user_prompt=user_prompt,
|
272 |
response=json_response,
|
273 |
+
metadata=metadata,
|
274 |
+
markdown=markdown
|
275 |
)
|
276 |
logger.debug("CreateProjectPlan instance created successfully.")
|
277 |
return result
|
|
|
286 |
d['system_prompt'] = self.system_prompt
|
287 |
return d
|
288 |
|
289 |
+
def save_raw(self, file_path: str) -> None:
|
290 |
d = self.to_dict()
|
291 |
with open(file_path, 'w') as f:
|
292 |
f.write(json.dumps(d, indent=2))
|
293 |
|
294 |
+
@staticmethod
|
295 |
+
def convert_to_markdown(document_details: GoalDefinition) -> str:
|
296 |
+
"""
|
297 |
+
Convert the raw document details to markdown.
|
298 |
+
"""
|
299 |
+
rows = []
|
300 |
+
|
301 |
+
rows.append(f"**Goal Statement:** {document_details.goal_statement}")
|
302 |
+
|
303 |
+
rows.append("\n## SMART Criteria\n")
|
304 |
+
rows.append(f"- **Specific:** {document_details.smart_criteria.specific}")
|
305 |
+
rows.append(f"- **Measurable:** {document_details.smart_criteria.measurable}")
|
306 |
+
rows.append(f"- **Achievable:** {document_details.smart_criteria.achievable}")
|
307 |
+
rows.append(f"- **Relevant:** {document_details.smart_criteria.relevant}")
|
308 |
+
rows.append(f"- **Time-bound:** {document_details.smart_criteria.time_bound}")
|
309 |
+
|
310 |
+
rows.append("\n## Dependencies\n")
|
311 |
+
for dep in document_details.dependencies:
|
312 |
+
rows.append(f"- {dep}")
|
313 |
+
|
314 |
+
rows.append("\n## Resources Required\n")
|
315 |
+
for resource in document_details.resources_required:
|
316 |
+
rows.append(f"- {resource}")
|
317 |
+
|
318 |
+
rows.append("\n## Related Goals\n")
|
319 |
+
for goal in document_details.related_goals:
|
320 |
+
rows.append(f"- {goal}")
|
321 |
+
|
322 |
+
rows.append("\n## Tags\n")
|
323 |
+
for tag in document_details.tags:
|
324 |
+
rows.append(f"- {tag}")
|
325 |
+
|
326 |
+
rows.append("\n## Risk Assessment and Mitigation Strategies\n")
|
327 |
+
rows.append("\n### Key Risks\n")
|
328 |
+
for risk in document_details.risk_assessment_and_mitigation_strategies.key_risks:
|
329 |
+
rows.append(f"- {risk}")
|
330 |
+
|
331 |
+
rows.append("\n### Diverse Risks\n")
|
332 |
+
for risk in document_details.risk_assessment_and_mitigation_strategies.diverse_risks:
|
333 |
+
rows.append(f"- {risk}")
|
334 |
+
|
335 |
+
rows.append("\n### Mitigation Plans\n")
|
336 |
+
for plan in document_details.risk_assessment_and_mitigation_strategies.mitigation_plans:
|
337 |
+
rows.append(f"- {plan}")
|
338 |
+
|
339 |
+
rows.append("\n## Stakeholder Analysis\n")
|
340 |
+
rows.append("\n### Primary Stakeholders\n")
|
341 |
+
for stakeholder in document_details.stakeholder_analysis.primary_stakeholders:
|
342 |
+
rows.append(f"- {stakeholder}")
|
343 |
+
|
344 |
+
rows.append("\n### Secondary Stakeholders\n")
|
345 |
+
for stakeholder in document_details.stakeholder_analysis.secondary_stakeholders:
|
346 |
+
rows.append(f"- {stakeholder}")
|
347 |
+
|
348 |
+
rows.append("\n### Engagement Strategies\n")
|
349 |
+
for strategy in document_details.stakeholder_analysis.engagement_strategies:
|
350 |
+
rows.append(f"- {strategy}")
|
351 |
+
|
352 |
+
rows.append("\n## Regulatory and Compliance Requirements\n")
|
353 |
+
rows.append("\n### Permits and Licenses\n")
|
354 |
+
for permit in document_details.regulatory_and_compliance_requirements.permits_and_licenses:
|
355 |
+
rows.append(f"- {permit}")
|
356 |
+
|
357 |
+
rows.append("\n### Compliance Standards\n")
|
358 |
+
for standard in document_details.regulatory_and_compliance_requirements.compliance_standards:
|
359 |
+
rows.append(f"- {standard}")
|
360 |
+
|
361 |
+
rows.append("\n### Regulatory Bodies\n")
|
362 |
+
for body in document_details.regulatory_and_compliance_requirements.regulatory_bodies:
|
363 |
+
rows.append(f"- {body}")
|
364 |
+
|
365 |
+
rows.append("\n### Compliance Actions\n")
|
366 |
+
for action in document_details.regulatory_and_compliance_requirements.compliance_actions:
|
367 |
+
rows.append(f"- {action}")
|
368 |
+
|
369 |
+
return "\n".join(rows)
|
370 |
+
|
371 |
+
def save_markdown(self, output_file_path: str):
|
372 |
+
with open(output_file_path, 'w', encoding='utf-8') as out_f:
|
373 |
+
out_f.write(self.markdown)
|
374 |
+
|
375 |
if __name__ == "__main__":
|
376 |
import logging
|
377 |
from src.llm_factory import get_llm
|
|
|
392 |
# llm = get_llm("deepseek-chat")
|
393 |
|
394 |
print(f"Query:\n{plan_prompt}\n\n")
|
395 |
+
result = ProjectPlan.execute(llm, plan_prompt)
|
396 |
json_response = result.to_dict(include_system_prompt=False, include_user_prompt=False)
|
397 |
+
print("\n\nResponse:")
|
398 |
print(json.dumps(json_response, indent=2))
|
399 |
+
|
400 |
+
print(f"\n\nMarkdown:\n{result.markdown}")
|
src/plan/related_resources.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Suggest similar past or existing projects that can be used as a reference for the current project.
|
3 |
+
|
4 |
+
PROMPT> python -m src.plan.related_resources
|
5 |
+
"""
|
6 |
+
import os
|
7 |
+
import json
|
8 |
+
import time
|
9 |
+
import logging
|
10 |
+
from math import ceil
|
11 |
+
from dataclasses import dataclass
|
12 |
+
from pydantic import BaseModel, Field
|
13 |
+
from llama_index.core.llms import ChatMessage, MessageRole
|
14 |
+
from llama_index.core.llms.llm import LLM
|
15 |
+
|
16 |
+
logger = logging.getLogger(__name__)
|
17 |
+
|
18 |
+
class SuggestionItem(BaseModel):
|
19 |
+
item_index: int = Field(
|
20 |
+
description="Enumeration, starting from 1."
|
21 |
+
)
|
22 |
+
project_name: str = Field(
|
23 |
+
description="The name of the project."
|
24 |
+
)
|
25 |
+
project_description: str = Field(
|
26 |
+
description="A description of the project."
|
27 |
+
)
|
28 |
+
success_metrics: list[str] = Field(
|
29 |
+
description="Indicators of success, challenges encountered, and project outcomes."
|
30 |
+
)
|
31 |
+
risks_and_challenges_faced: list[str] = Field(
|
32 |
+
description="Explain how each project overcame or mitigated these challenges to provide practical guidance."
|
33 |
+
)
|
34 |
+
where_to_find_more_information: list[str] = Field(
|
35 |
+
description="Links to online resources, articles, official documents, or industry reports where more information can be found."
|
36 |
+
)
|
37 |
+
actionable_steps: list[str] = Field(
|
38 |
+
description="Clear instructions on how the user might directly contact key individuals or organizations from those projects, if they desire deeper insights."
|
39 |
+
)
|
40 |
+
rationale_for_suggestion: str = Field(
|
41 |
+
description="Explain why this particular project is suggested."
|
42 |
+
)
|
43 |
+
|
44 |
+
class DocumentDetails(BaseModel):
|
45 |
+
suggestion_list: list[SuggestionItem] = Field(
|
46 |
+
description="List of suggestions."
|
47 |
+
)
|
48 |
+
summary: str = Field(
|
49 |
+
description="Providing a high level context."
|
50 |
+
)
|
51 |
+
|
52 |
+
RELATED_RESOURCES_SYSTEM_PROMPT = """
|
53 |
+
You are an expert project analyst tasked with recommending highly relevant past or existing projects as references for a user's described project.
|
54 |
+
|
55 |
+
Your goal is to always provide at least **three detailed and insightful recommendations**, strictly adhering to the following guidelines:
|
56 |
+
|
57 |
+
- **Primary Suggestions (at least 2):**
|
58 |
+
- Must be **real and verifiable past or existing projects**—no hypothetical, fictional, or speculative examples.
|
59 |
+
- Include exhaustive detail:
|
60 |
+
- **Project Name:** Clearly state the official name.
|
61 |
+
- **Project Description:** Concise yet comprehensive description of objectives, scale, timeline, industry, location, and outcomes.
|
62 |
+
- **Rationale for Suggestion:** Explicitly highlight similarities in technology, objectives, operational processes, geographical, economic, or cultural aspects.
|
63 |
+
- **Risks and Challenges Faced:** Explicitly list major challenges and clearly explain how each was overcome or mitigated.
|
64 |
+
- **Success Metrics:** Measurable outcomes such as economic impact, production volume, customer satisfaction, timeline adherence, or technology breakthroughs.
|
65 |
+
- **Where to Find More Information:** Direct and authoritative links (official websites, reputable publications, scholarly articles).
|
66 |
+
- **Actionable Steps:** Clearly specify roles, names, and robust communication channels (emails, LinkedIn, organizational contacts).
|
67 |
+
|
68 |
+
- **Secondary Suggestions (optional but encouraged, at least 1):**
|
69 |
+
- Must also be real projects but may contain fewer details.
|
70 |
+
- Mark explicitly as secondary suggestions.
|
71 |
+
|
72 |
+
**Priority for Relevance:**
|
73 |
+
- Emphasize geographical or cultural proximity first, but clearly justify including geographically distant examples if necessary.
|
74 |
+
- If geographically or culturally similar projects are limited, explicitly state this in the rationale.
|
75 |
+
|
76 |
+
**Important:** Avoid any hypothetical, speculative, or fictional suggestions. Only include real, documented projects.
|
77 |
+
|
78 |
+
Your recommendations should collectively provide the user with robust insights, actionable guidance, and practical contacts for successful execution.
|
79 |
+
"""
|
80 |
+
|
81 |
+
@dataclass
|
82 |
+
class RelatedResources:
|
83 |
+
"""
|
84 |
+
Identify similar past or existing projects that can be used as a reference for the current project.
|
85 |
+
"""
|
86 |
+
system_prompt: str
|
87 |
+
user_prompt: str
|
88 |
+
response: dict
|
89 |
+
metadata: dict
|
90 |
+
markdown: str
|
91 |
+
|
92 |
+
@classmethod
|
93 |
+
def execute(cls, llm: LLM, user_prompt: str) -> 'RelatedResources':
|
94 |
+
"""
|
95 |
+
Invoke LLM with the project description.
|
96 |
+
"""
|
97 |
+
if not isinstance(llm, LLM):
|
98 |
+
raise ValueError("Invalid LLM instance.")
|
99 |
+
if not isinstance(user_prompt, str):
|
100 |
+
raise ValueError("Invalid user_prompt.")
|
101 |
+
|
102 |
+
logger.debug(f"User Prompt:\n{user_prompt}")
|
103 |
+
|
104 |
+
system_prompt = RELATED_RESOURCES_SYSTEM_PROMPT.strip()
|
105 |
+
|
106 |
+
chat_message_list = [
|
107 |
+
ChatMessage(
|
108 |
+
role=MessageRole.SYSTEM,
|
109 |
+
content=system_prompt,
|
110 |
+
),
|
111 |
+
ChatMessage(
|
112 |
+
role=MessageRole.USER,
|
113 |
+
content=user_prompt,
|
114 |
+
)
|
115 |
+
]
|
116 |
+
|
117 |
+
sllm = llm.as_structured_llm(DocumentDetails)
|
118 |
+
start_time = time.perf_counter()
|
119 |
+
try:
|
120 |
+
chat_response = sllm.chat(chat_message_list)
|
121 |
+
except Exception as e:
|
122 |
+
logger.debug(f"LLM chat interaction failed: {e}")
|
123 |
+
logger.error("LLM chat interaction failed.", exc_info=True)
|
124 |
+
raise ValueError("LLM chat interaction failed.") from e
|
125 |
+
|
126 |
+
end_time = time.perf_counter()
|
127 |
+
duration = int(ceil(end_time - start_time))
|
128 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
129 |
+
logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
130 |
+
|
131 |
+
json_response = chat_response.raw.model_dump()
|
132 |
+
|
133 |
+
metadata = dict(llm.metadata)
|
134 |
+
metadata["llm_classname"] = llm.class_name()
|
135 |
+
metadata["duration"] = duration
|
136 |
+
metadata["response_byte_count"] = response_byte_count
|
137 |
+
|
138 |
+
markdown = cls.convert_to_markdown(chat_response.raw)
|
139 |
+
|
140 |
+
result = RelatedResources(
|
141 |
+
system_prompt=system_prompt,
|
142 |
+
user_prompt=user_prompt,
|
143 |
+
response=json_response,
|
144 |
+
metadata=metadata,
|
145 |
+
markdown=markdown
|
146 |
+
)
|
147 |
+
return result
|
148 |
+
|
149 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
|
150 |
+
d = self.response.copy()
|
151 |
+
if include_metadata:
|
152 |
+
d['metadata'] = self.metadata
|
153 |
+
if include_system_prompt:
|
154 |
+
d['system_prompt'] = self.system_prompt
|
155 |
+
if include_user_prompt:
|
156 |
+
d['user_prompt'] = self.user_prompt
|
157 |
+
return d
|
158 |
+
|
159 |
+
def save_raw(self, file_path: str) -> None:
|
160 |
+
with open(file_path, 'w') as f:
|
161 |
+
f.write(json.dumps(self.to_dict(), indent=2))
|
162 |
+
|
163 |
+
@staticmethod
|
164 |
+
def convert_to_markdown(document_details: DocumentDetails) -> str:
|
165 |
+
"""
|
166 |
+
Convert the raw document details to markdown.
|
167 |
+
"""
|
168 |
+
rows = []
|
169 |
+
|
170 |
+
for item_index, suggestion in enumerate(document_details.suggestion_list, start=1):
|
171 |
+
rows.append(f"## Suggestion {item_index} - {suggestion.project_name}\n")
|
172 |
+
rows.append(suggestion.project_description)
|
173 |
+
|
174 |
+
success_metrics = "\n".join(suggestion.success_metrics)
|
175 |
+
rows.append(f"\n### Success Metrics\n\n{success_metrics}")
|
176 |
+
|
177 |
+
risks_and_challenges_faced = "\n".join(suggestion.risks_and_challenges_faced)
|
178 |
+
rows.append(f"\n### Risks and Challenges Faced\n\n{risks_and_challenges_faced}")
|
179 |
+
|
180 |
+
where_to_find_more_information = "\n".join(suggestion.where_to_find_more_information)
|
181 |
+
rows.append(f"\n### Where to Find More Information\n\n{where_to_find_more_information}")
|
182 |
+
|
183 |
+
actionable_steps = "\n".join(suggestion.actionable_steps)
|
184 |
+
rows.append(f"\n### Actionable Steps\n\n{actionable_steps}")
|
185 |
+
|
186 |
+
rows.append(f"\n### Rationale for Suggestion\n\n{suggestion.rationale_for_suggestion}")
|
187 |
+
|
188 |
+
rows.append(f"\n## Summary\n\n{document_details.summary}")
|
189 |
+
return "\n".join(rows)
|
190 |
+
|
191 |
+
def save_markdown(self, output_file_path: str):
|
192 |
+
with open(output_file_path, 'w', encoding='utf-8') as out_f:
|
193 |
+
out_f.write(self.markdown)
|
194 |
+
|
195 |
+
if __name__ == "__main__":
|
196 |
+
from src.llm_factory import get_llm
|
197 |
+
from src.plan.find_plan_prompt import find_plan_prompt
|
198 |
+
|
199 |
+
llm = get_llm("ollama-llama3.1")
|
200 |
+
|
201 |
+
plan_prompt = find_plan_prompt("de626417-4871-4acc-899d-2c41fd148807")
|
202 |
+
query = (
|
203 |
+
f"{plan_prompt}\n\n"
|
204 |
+
"Today's date:\n2025-Feb-27\n\n"
|
205 |
+
"Project start ASAP"
|
206 |
+
)
|
207 |
+
print(f"Query: {query}")
|
208 |
+
|
209 |
+
result = RelatedResources.execute(llm, query)
|
210 |
+
json_response = result.to_dict(include_system_prompt=False, include_user_prompt=False)
|
211 |
+
print("\n\nResponse:")
|
212 |
+
print(json.dumps(json_response, indent=2))
|
213 |
+
|
214 |
+
print(f"\n\nMarkdown:\n{result.markdown}")
|
src/plan/review_plan.py
ADDED
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Ask questions about the almost finished plan.
|
3 |
+
|
4 |
+
PROMPT> python -m src.plan.plan_evaluator
|
5 |
+
|
6 |
+
IDEA: Executive Summary: Briefly summarizing critical insights from the review.
|
7 |
+
"""
|
8 |
+
import os
|
9 |
+
import json
|
10 |
+
import time
|
11 |
+
import logging
|
12 |
+
from math import ceil
|
13 |
+
from dataclasses import dataclass
|
14 |
+
from pydantic import BaseModel, Field
|
15 |
+
from llama_index.core.llms import ChatMessage, MessageRole
|
16 |
+
from llama_index.core.llms.llm import LLM
|
17 |
+
|
18 |
+
logger = logging.getLogger(__name__)
|
19 |
+
|
20 |
+
class DocumentDetails(BaseModel):
|
21 |
+
bullet_points: list[str] = Field(
|
22 |
+
description="Answers to the questions in bullet points."
|
23 |
+
)
|
24 |
+
|
25 |
+
REVIEW_PLAN_SYSTEM_PROMPT = """
|
26 |
+
You are an expert in reviewing plans for projects of all scales. Your goal is to identify the most critical issues that could impact the project's success and provide actionable recommendations to address them.
|
27 |
+
|
28 |
+
A good plan is specific, measurable, achievable, relevant, and time-bound (SMART). It addresses potential risks with concrete mitigation strategies, has clear roles and responsibilities, and considers relevant constraints. A strong plan has a detailed financial model, addresses grid connection complexities, and a solid operations and maintenance strategy.
|
29 |
+
|
30 |
+
For each question, you MUST provide exactly three concise and distinct bullet points as your answer. Each bullet point must combine all required details for that question into one sentence or clause. **The *very first phrase or a short sentence of each bullet point should act as a concise summary/title for that bullet point, and it MUST BE BOLDED. This first phrase helps the user understand quickly. Make sure the phrase is informative. Then the rest of the bullet point will contain details, explanation, and actionable recommendations.** Prioritize the *most* critical issues and provide *specific, actionable* recommendations. For each recommendation, explain *why* it's important and what the potential impact of *not* addressing it would be.**
|
31 |
+
|
32 |
+
For example:
|
33 |
+
- If a question asks for key dependencies along with their likelihood (e.g., Medium, High, Low) and control (internal or external), then each bullet point must include the dependency name, its likelihood, and whether it is controlled internally or externally—all combined into one sentence. **Indicate which dependency is the *most* critical and why.**
|
34 |
+
- If a question asks for regulatory requirements, each bullet point must state the requirement and briefly explain how it will be met. Do not include any extra header lines or additional bullet points.
|
35 |
+
|
36 |
+
If additional details are needed, merge or summarize them so that your final answer always consists of exactly three bullet points.
|
37 |
+
|
38 |
+
Your final output must be a JSON object in the following format:
|
39 |
+
{
|
40 |
+
"bullet_points": [
|
41 |
+
"Bullet point 1 (including all required details)",
|
42 |
+
"Bullet point 2 (including all required details)",
|
43 |
+
"Bullet point 3 (including all required details)"
|
44 |
+
]
|
45 |
+
}
|
46 |
+
|
47 |
+
Do not include any extra bullet points, header lines, or any additional text outside of this JSON structure.
|
48 |
+
|
49 |
+
Do not duplicate issues already identified in previous questions of this review.
|
50 |
+
"""
|
51 |
+
|
52 |
+
@dataclass
|
53 |
+
class ReviewPlan:
|
54 |
+
"""
|
55 |
+
Take a look at the proposed plan and provide feedback.
|
56 |
+
"""
|
57 |
+
system_prompt: str
|
58 |
+
question_answers_list: list[dict]
|
59 |
+
metadata: dict
|
60 |
+
markdown: str
|
61 |
+
|
62 |
+
@classmethod
|
63 |
+
def execute(cls, llm: LLM, document: str) -> 'ReviewPlan':
|
64 |
+
"""
|
65 |
+
Invoke LLM with the data to be reviewed.
|
66 |
+
"""
|
67 |
+
if not isinstance(llm, LLM):
|
68 |
+
raise ValueError("Invalid LLM instance.")
|
69 |
+
if not isinstance(document, str):
|
70 |
+
raise ValueError("Invalid document.")
|
71 |
+
|
72 |
+
logger.debug(f"Document:\n{document}")
|
73 |
+
|
74 |
+
system_prompt = REVIEW_PLAN_SYSTEM_PROMPT.strip()
|
75 |
+
system_prompt += "\n\nDocument for review:\n"
|
76 |
+
system_prompt += document
|
77 |
+
|
78 |
+
title_question_list = [
|
79 |
+
("Critical Issues", "Identify exactly three critical or urgent issues highlighted in the report. For each issue, provide a brief explanation of its quantified significance (e.g., impact in terms of cost, risk, or timing) on the immediate priorities, desired outcomes, or overall effectiveness. Also, explain how these issues might interact with or influence each other, along with a brief, actionable recommendation to address it. Please present your answer in exactly three bullet points."),
|
80 |
+
("Implementation Consequences", "Identify exactly three significant consequences—both positive and negative—that may result from implementing the plan. For each consequence, provide a brief explanation of its *quantified* impact (e.g., in terms of cost, time, or ROI) on the plan's overall feasibility, outcomes, or long-term success. *Also, explain how these consequences might interact with or influence each other*, *along with a brief, actionable recommendation to address it*. Please present your answer in exactly three bullet points."),
|
81 |
+
("Recommended Actions", "Identify exactly three specific actions recommended by the report. For each action, briefly quantify its expected impact (e.g., cost savings, risk reduction, time improvements), clearly state its priority level, and provide a brief, actionable recommendation on how it should be implemented. Present your answer in exactly three bullet points. Actions listed here should complement, extend, or provide additional details to recommendations mentioned previously, rather than repeat them directly."),
|
82 |
+
("Showstopper Risks", "Identify exactly three 'showstopper' risks to the project's success that have not yet been addressed.\nFor each risk:\n- Quantify its potential impact (e.g., in terms of budget increase, timeline delays, ROI reduction).\n- State explicitly its likelihood (High, Medium, Low).\n- Clearly explain how these risks might interact or compound each other.\n- Provide a brief, actionable recommendation to address it.\nPresent your answer in exactly three bullet points, avoiding repetition of previously covered issues or actions. Additionally, for each risk, briefly suggest a contingency measure to be activated if the initial mitigation action proves insufficient."),
|
83 |
+
("Critical Assumptions", "List exactly three critical assumptions underlying the plan that must hold true for it to succeed. For each assumption, quantify the impact if proven incorrect (e.g., cost increase, ROI decrease, timeline delays), explain clearly how each assumption interacts or compounds with previously identified risks or consequences, and provide a brief, actionable recommendation for validating or adjusting each assumption. Present your answer in exactly three bullet points without repeating previously mentioned risks or issues."),
|
84 |
+
("Key Performance Indicators", "Identify exactly three Key Performance Indicators (KPIs) essential for measuring the project's long-term success. For each KPI, quantify specific target values or ranges that indicate success or require corrective action. Clearly explain how each KPI interacts with previously identified risks, assumptions, or recommended actions, and provide a brief, actionable recommendation on how to regularly monitor or achieve each KPI. Present your answer in exactly three bullet points without repeating previously mentioned details."),
|
85 |
+
("Report Objectives", "What exactly are the primary objectives and deliverables of this report? Clearly state the intended audience, the key decisions this report aims to inform, and how Version 2 should differ from Version 1. Present your answer concisely in three bullet points."),
|
86 |
+
("Data Quality Concerns", "Identify exactly three key areas in the current draft where data accuracy or completeness may be uncertain or insufficient. For each area, briefly explain why the data is critical, quantify the potential consequences of relying on incorrect or incomplete data, and recommend a clear approach for validating or improving data quality before Version 2. Present your answer in exactly three bullet points."),
|
87 |
+
("Stakeholder Feedback", "List exactly three important pieces of stakeholder feedback or clarification needed before finalizing Version 2 of the report. For each piece of feedback, explain why it's critical, how unresolved stakeholder concerns might impact the project (quantify potential impact), and provide a brief recommendation for obtaining and incorporating this feedback effectively. Present your answer in exactly three bullet points."),
|
88 |
+
("Changed Assumptions", "Identify exactly three assumptions or conditions from the initial planning stage that might have changed or require re-evaluation since Version 1 was drafted. For each assumption, briefly quantify how changes might affect the report's outcomes (cost, ROI, timeline), explain how these revised assumptions could influence previously identified risks or recommendations, and suggest an actionable approach to reviewing or updating each assumption. Present your answer in exactly three bullet points without repeating previously discussed points."),
|
89 |
+
("Budget Clarifications", "Provide exactly three critical budget clarifications necessary before finalizing Version 2. For each clarification, quantify its impact on the project's financial planning (e.g., cost adjustments, budget reserves, ROI impact), clearly explain why it's needed, and recommend actionable steps to resolve the uncertainty."),
|
90 |
+
("Role Definitions", "Identify exactly three key roles or responsibilities that must be explicitly defined or clarified in Version 2. For each role, briefly explain why clarification is essential, quantify potential impacts (e.g., timeline delays, accountability risks) if roles remain unclear, and recommend actionable steps to ensure clear assignment and accountability."),
|
91 |
+
("Timeline Dependencies", "Identify exactly three timeline dependencies or sequencing concerns that must be clarified before finalizing Version 2. For each dependency, quantify its potential impact if incorrectly sequenced (e.g., timeline delays, increased costs), clearly explain its interaction with previously identified risks or actions, and recommend a concrete action to address the sequencing or dependency concern."),
|
92 |
+
("Financial Strategy", "Identify exactly three long-term financial strategy questions that must be clarified in Version 2. For each, quantify the financial or strategic impact of leaving it unanswered, explain clearly how each interacts with previously identified assumptions or risks, and recommend actionable steps to clarify or address the question."),
|
93 |
+
("Motivation Factors", "Identify exactly three factors essential to maintaining motivation and ensuring consistent progress toward the project's goals. For each factor, quantify potential setbacks if motivation falters (e.g., delays, reduced success rates, increased costs), clearly explain how it interacts with previously identified risks or assumptions, and provide a brief, actionable recommendation on maintaining motivation or addressing motivational barriers."),
|
94 |
+
("Automation Opportunities", "Identify exactly three opportunities within the project where tasks or processes can be automated or streamlined for improved efficiency. For each opportunity, quantify the potential savings (time, resources, or cost), explain how this interacts with previously identified timelines or resource constraints, and recommend a clear, actionable approach for implementing each automation or efficiency improvement."),
|
95 |
+
]
|
96 |
+
|
97 |
+
chat_message_list = [
|
98 |
+
ChatMessage(
|
99 |
+
role=MessageRole.SYSTEM,
|
100 |
+
content=system_prompt,
|
101 |
+
)
|
102 |
+
]
|
103 |
+
|
104 |
+
question_answers_list = []
|
105 |
+
|
106 |
+
durations = []
|
107 |
+
response_byte_counts = []
|
108 |
+
|
109 |
+
for index, title_question in enumerate(title_question_list, start=1):
|
110 |
+
title, question = title_question
|
111 |
+
logger.debug(f"Question {index} of {len(title_question_list)}: {question}")
|
112 |
+
chat_message_list.append(ChatMessage(
|
113 |
+
role=MessageRole.USER,
|
114 |
+
content=question,
|
115 |
+
))
|
116 |
+
|
117 |
+
sllm = llm.as_structured_llm(DocumentDetails)
|
118 |
+
start_time = time.perf_counter()
|
119 |
+
try:
|
120 |
+
chat_response = sllm.chat(chat_message_list)
|
121 |
+
except Exception as e:
|
122 |
+
logger.debug(f"Question {index} of {len(title_question_list)}. LLM chat interaction failed: {e}")
|
123 |
+
logger.error(f"Question {index} of {len(title_question_list)}. LLM chat interaction failed.", exc_info=True)
|
124 |
+
raise ValueError("LLM chat interaction failed.") from e
|
125 |
+
|
126 |
+
end_time = time.perf_counter()
|
127 |
+
duration = int(ceil(end_time - start_time))
|
128 |
+
durations.append(duration)
|
129 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
130 |
+
response_byte_counts.append(response_byte_count)
|
131 |
+
logger.info(f"Question {index} of {len(title_question_list)}. LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
132 |
+
|
133 |
+
json_response = chat_response.raw.model_dump()
|
134 |
+
logger.debug(json.dumps(json_response, indent=2))
|
135 |
+
|
136 |
+
question_answers_list.append({
|
137 |
+
"title": title,
|
138 |
+
"question": question,
|
139 |
+
"answers": chat_response.raw.bullet_points,
|
140 |
+
})
|
141 |
+
|
142 |
+
chat_message_list.append(ChatMessage(
|
143 |
+
role=MessageRole.ASSISTANT,
|
144 |
+
content=chat_response.message.content,
|
145 |
+
))
|
146 |
+
|
147 |
+
response_byte_count_total = sum(response_byte_counts)
|
148 |
+
response_byte_count_average = response_byte_count_total / len(title_question_list)
|
149 |
+
response_byte_count_max = max(response_byte_counts)
|
150 |
+
response_byte_count_min = min(response_byte_counts)
|
151 |
+
duration_total = sum(durations)
|
152 |
+
duration_average = duration_total / len(title_question_list)
|
153 |
+
duration_max = max(durations)
|
154 |
+
duration_min = min(durations)
|
155 |
+
|
156 |
+
metadata = dict(llm.metadata)
|
157 |
+
metadata["llm_classname"] = llm.class_name()
|
158 |
+
metadata["duration_total"] = duration_total
|
159 |
+
metadata["duration_average"] = duration_average
|
160 |
+
metadata["duration_max"] = duration_max
|
161 |
+
metadata["duration_min"] = duration_min
|
162 |
+
metadata["response_byte_count_total"] = response_byte_count_total
|
163 |
+
metadata["response_byte_count_average"] = response_byte_count_average
|
164 |
+
metadata["response_byte_count_max"] = response_byte_count_max
|
165 |
+
metadata["response_byte_count_min"] = response_byte_count_min
|
166 |
+
markdown = cls.convert_to_markdown(question_answers_list)
|
167 |
+
|
168 |
+
result = ReviewPlan(
|
169 |
+
system_prompt=system_prompt,
|
170 |
+
question_answers_list=question_answers_list,
|
171 |
+
metadata=metadata,
|
172 |
+
markdown=markdown
|
173 |
+
)
|
174 |
+
return result
|
175 |
+
|
176 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True) -> dict:
|
177 |
+
d = {}
|
178 |
+
d['question_answers_list'] = self.question_answers_list
|
179 |
+
if include_metadata:
|
180 |
+
d['metadata'] = self.metadata
|
181 |
+
if include_system_prompt:
|
182 |
+
d['system_prompt'] = self.system_prompt
|
183 |
+
return d
|
184 |
+
|
185 |
+
def save_raw(self, file_path: str) -> None:
|
186 |
+
with open(file_path, 'w') as f:
|
187 |
+
f.write(json.dumps(self.to_dict(), indent=2))
|
188 |
+
|
189 |
+
@staticmethod
|
190 |
+
def convert_to_markdown(question_answers_list: list[dict]) -> str:
|
191 |
+
"""
|
192 |
+
Convert the question answers list to markdown.
|
193 |
+
"""
|
194 |
+
rows = []
|
195 |
+
|
196 |
+
for question_index, question_answers in enumerate(question_answers_list, start=1):
|
197 |
+
if question_index > 1:
|
198 |
+
rows.append("\n")
|
199 |
+
title = question_answers.get('title', None)
|
200 |
+
if title is None:
|
201 |
+
logger.warning("Title is None.")
|
202 |
+
continue
|
203 |
+
answers = question_answers.get('answers', None)
|
204 |
+
if answers is None:
|
205 |
+
logger.warning("Answers are None.")
|
206 |
+
continue
|
207 |
+
rows.append(f"## Review {question_index}: {title}\n")
|
208 |
+
for answer_index, answer in enumerate(answers, start=1):
|
209 |
+
if answer_index > 1:
|
210 |
+
rows.append("\n")
|
211 |
+
rows.append(f"{answer_index}. {answer}")
|
212 |
+
|
213 |
+
return "\n".join(rows)
|
214 |
+
|
215 |
+
def save_markdown(self, output_file_path: str):
|
216 |
+
with open(output_file_path, 'w', encoding='utf-8') as out_f:
|
217 |
+
out_f.write(self.markdown)
|
218 |
+
|
219 |
+
if __name__ == "__main__":
|
220 |
+
from src.llm_factory import get_llm
|
221 |
+
|
222 |
+
llm = get_llm("ollama-llama3.1")
|
223 |
+
|
224 |
+
path = os.path.join(os.path.dirname(__file__), 'test_data', "deadfish_assumptions.md")
|
225 |
+
with open(path, 'r', encoding='utf-8') as f:
|
226 |
+
assumptions_markdown = f.read()
|
227 |
+
|
228 |
+
query = (
|
229 |
+
f"File 'assumptions.md':\n{assumptions_markdown}"
|
230 |
+
)
|
231 |
+
print(f"Query:\n{query}\n\n")
|
232 |
+
|
233 |
+
result = ReviewPlan.execute(llm, query)
|
234 |
+
json_response = result.to_dict(include_system_prompt=False)
|
235 |
+
print("\n\nResponse:")
|
236 |
+
print(json.dumps(json_response, indent=2))
|
237 |
+
|
238 |
+
print(f"\n\nMarkdown:\n{result.markdown}")
|
src/plan/run_plan_pipeline.py
CHANGED
@@ -23,8 +23,10 @@ from src.assume.identify_risks import IdentifyRisks
|
|
23 |
from src.assume.make_assumptions import MakeAssumptions
|
24 |
from src.assume.distill_assumptions import DistillAssumptions
|
25 |
from src.assume.review_assumptions import ReviewAssumptions
|
|
|
26 |
from src.expert.pre_project_assessment import PreProjectAssessment
|
27 |
-
from src.plan.
|
|
|
28 |
from src.swot.swot_analysis import SWOTAnalysis
|
29 |
from src.expert.expert_finder import ExpertFinder
|
30 |
from src.expert.expert_criticism import ExpertCriticism
|
@@ -36,6 +38,8 @@ from src.pitch.create_pitch import CreatePitch
|
|
36 |
from src.pitch.convert_pitch_to_markdown import ConvertPitchToMarkdown
|
37 |
from src.plan.identify_wbs_task_dependencies import IdentifyWBSTaskDependencies
|
38 |
from src.plan.estimate_wbs_task_durations import EstimateWBSTaskDurations
|
|
|
|
|
39 |
from src.team.find_team_members import FindTeamMembers
|
40 |
from src.team.enrich_team_members_with_contract_type import EnrichTeamMembersWithContractType
|
41 |
from src.team.enrich_team_members_with_background_story import EnrichTeamMembersWithBackgroundStory
|
@@ -499,9 +503,14 @@ class ConsolidateAssumptionsMarkdownTask(PlanTask):
|
|
499 |
}
|
500 |
|
501 |
def output(self):
|
502 |
-
return
|
|
|
|
|
|
|
503 |
|
504 |
def run(self):
|
|
|
|
|
505 |
# Define the list of (title, path) tuples
|
506 |
title_path_list = [
|
507 |
('Plan Type', self.input()['plan_type']['markdown'].path),
|
@@ -514,27 +523,45 @@ class ConsolidateAssumptionsMarkdownTask(PlanTask):
|
|
514 |
]
|
515 |
|
516 |
# Read the files and handle exceptions
|
517 |
-
|
|
|
518 |
for title, path in title_path_list:
|
519 |
try:
|
520 |
with open(path, 'r', encoding='utf-8') as f:
|
521 |
markdown_chunk = f.read()
|
522 |
-
|
523 |
except FileNotFoundError:
|
524 |
logger.warning(f"Markdown file not found: {path} (from {title})")
|
525 |
-
|
|
|
|
|
526 |
except Exception as e:
|
527 |
logger.error(f"Error reading markdown file {path} (from {title}): {e}")
|
528 |
-
|
|
|
|
|
529 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
530 |
# Combine the markdown chunks
|
531 |
-
full_markdown = "\n\n".join(
|
|
|
532 |
|
533 |
# Write the result to disk.
|
534 |
-
|
535 |
-
with open(
|
536 |
f.write(full_markdown)
|
537 |
|
|
|
|
|
|
|
|
|
538 |
|
539 |
class PreProjectAssessmentTask(PlanTask):
|
540 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
@@ -558,7 +585,7 @@ class PreProjectAssessmentTask(PlanTask):
|
|
558 |
with self.input()['setup'].open("r") as f:
|
559 |
plan_prompt = f.read()
|
560 |
|
561 |
-
with self.input()['consolidate_assumptions_markdown'].open("r") as f:
|
562 |
consolidate_assumptions_markdown = f.read()
|
563 |
|
564 |
# Build the query.
|
@@ -599,7 +626,10 @@ class ProjectPlanTask(PlanTask):
|
|
599 |
}
|
600 |
|
601 |
def output(self):
|
602 |
-
return
|
|
|
|
|
|
|
603 |
|
604 |
def run(self):
|
605 |
logger.info("Creating plan...")
|
@@ -610,7 +640,7 @@ class ProjectPlanTask(PlanTask):
|
|
610 |
plan_prompt = f.read()
|
611 |
|
612 |
# Load the consolidated assumptions.
|
613 |
-
with self.input()['consolidate_assumptions_markdown'].open("r") as f:
|
614 |
consolidate_assumptions_markdown = f.read()
|
615 |
|
616 |
# Read the pre-project assessment from its file.
|
@@ -629,12 +659,62 @@ class ProjectPlanTask(PlanTask):
|
|
629 |
llm = get_llm(self.llm_model)
|
630 |
|
631 |
# Execute the plan creation.
|
632 |
-
|
633 |
-
|
634 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
635 |
|
636 |
-
|
|
|
|
|
|
|
|
|
|
|
637 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
638 |
|
639 |
class FindTeamMembersTask(PlanTask):
|
640 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
@@ -644,7 +724,8 @@ class FindTeamMembersTask(PlanTask):
|
|
644 |
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
645 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
646 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
647 |
-
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
|
|
648 |
}
|
649 |
|
650 |
def output(self):
|
@@ -654,32 +735,25 @@ class FindTeamMembersTask(PlanTask):
|
|
654 |
}
|
655 |
|
656 |
def run(self):
|
657 |
-
|
658 |
-
|
659 |
-
# Read the plan prompt from SetupTask.
|
660 |
with self.input()['setup'].open("r") as f:
|
661 |
plan_prompt = f.read()
|
662 |
-
|
663 |
-
# Load the consolidated assumptions.
|
664 |
-
with self.input()['consolidate_assumptions_markdown'].open("r") as f:
|
665 |
consolidate_assumptions_markdown = f.read()
|
666 |
-
|
667 |
-
# Read the pre-project assessment from PreProjectAssessmentTask.
|
668 |
with self.input()['preproject']['clean'].open("r") as f:
|
669 |
pre_project_assessment_dict = json.load(f)
|
670 |
-
|
671 |
-
# Read the project plan from ProjectPlanTask.
|
672 |
-
with self.input()['project_plan'].open("r") as f:
|
673 |
project_plan_dict = json.load(f)
|
674 |
-
|
675 |
-
|
676 |
|
677 |
# Build the query.
|
678 |
query = (
|
679 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
680 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
681 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
682 |
-
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}"
|
|
|
683 |
)
|
684 |
|
685 |
# Create LLM instance.
|
@@ -702,8 +776,6 @@ class FindTeamMembersTask(PlanTask):
|
|
702 |
with self.output()['clean'].open("w") as f:
|
703 |
json.dump(team_member_list, f, indent=2)
|
704 |
|
705 |
-
logger.info("FindTeamMembers complete.")
|
706 |
-
|
707 |
class EnrichTeamMembersWithContractTypeTask(PlanTask):
|
708 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
709 |
|
@@ -713,7 +785,8 @@ class EnrichTeamMembersWithContractTypeTask(PlanTask):
|
|
713 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
714 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
715 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
716 |
-
'find_team_members': FindTeamMembersTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
|
|
717 |
}
|
718 |
|
719 |
def output(self):
|
@@ -723,37 +796,28 @@ class EnrichTeamMembersWithContractTypeTask(PlanTask):
|
|
723 |
}
|
724 |
|
725 |
def run(self):
|
726 |
-
|
727 |
-
|
728 |
-
# Read the plan prompt from SetupTask.
|
729 |
with self.input()['setup'].open("r") as f:
|
730 |
plan_prompt = f.read()
|
731 |
-
|
732 |
-
# Load the consolidated assumptions.
|
733 |
-
with self.input()['consolidate_assumptions_markdown'].open("r") as f:
|
734 |
consolidate_assumptions_markdown = f.read()
|
735 |
-
|
736 |
-
# Read the pre-project assessment from PreProjectAssessmentTask.
|
737 |
with self.input()['preproject']['clean'].open("r") as f:
|
738 |
pre_project_assessment_dict = json.load(f)
|
739 |
-
|
740 |
-
# Read the project plan from ProjectPlanTask.
|
741 |
-
with self.input()['project_plan'].open("r") as f:
|
742 |
project_plan_dict = json.load(f)
|
743 |
-
|
744 |
-
# Read the team_member_list from FindTeamMembersTask.
|
745 |
with self.input()['find_team_members']['clean'].open("r") as f:
|
746 |
team_member_list = json.load(f)
|
747 |
-
|
748 |
-
|
749 |
|
750 |
# Build the query.
|
751 |
query = (
|
752 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
753 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
754 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
755 |
-
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}"
|
756 |
-
f"File 'team-members-that-needs-to-be-enriched.json':\n{format_json_for_use_in_query(team_member_list)}"
|
|
|
757 |
)
|
758 |
|
759 |
# Create LLM instance.
|
@@ -776,8 +840,6 @@ class EnrichTeamMembersWithContractTypeTask(PlanTask):
|
|
776 |
with self.output()['clean'].open("w") as f:
|
777 |
json.dump(team_member_list, f, indent=2)
|
778 |
|
779 |
-
logger.info("EnrichTeamMembersWithContractType complete.")
|
780 |
-
|
781 |
class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
|
782 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
783 |
|
@@ -787,7 +849,8 @@ class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
|
|
787 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
788 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
789 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
790 |
-
'enrich_team_members_with_contract_type': EnrichTeamMembersWithContractTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
|
|
791 |
}
|
792 |
|
793 |
def output(self):
|
@@ -797,37 +860,28 @@ class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
|
|
797 |
}
|
798 |
|
799 |
def run(self):
|
800 |
-
|
801 |
-
|
802 |
-
# Read the plan prompt from SetupTask.
|
803 |
with self.input()['setup'].open("r") as f:
|
804 |
plan_prompt = f.read()
|
805 |
-
|
806 |
-
# Load the consolidated assumptions.
|
807 |
-
with self.input()['consolidate_assumptions_markdown'].open("r") as f:
|
808 |
consolidate_assumptions_markdown = f.read()
|
809 |
-
|
810 |
-
# Read the pre-project assessment from PreProjectAssessmentTask.
|
811 |
with self.input()['preproject']['clean'].open("r") as f:
|
812 |
pre_project_assessment_dict = json.load(f)
|
813 |
-
|
814 |
-
# Read the project plan from ProjectPlanTask.
|
815 |
-
with self.input()['project_plan'].open("r") as f:
|
816 |
project_plan_dict = json.load(f)
|
817 |
-
|
818 |
-
# Read the team_member_list from EnrichTeamMembersWithContractTypeTask.
|
819 |
with self.input()['enrich_team_members_with_contract_type']['clean'].open("r") as f:
|
820 |
team_member_list = json.load(f)
|
821 |
-
|
822 |
-
|
823 |
|
824 |
# Build the query.
|
825 |
query = (
|
826 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
827 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
828 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
829 |
-
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}"
|
830 |
-
f"File 'team-members-that-needs-to-be-enriched.json':\n{format_json_for_use_in_query(team_member_list)}"
|
|
|
831 |
)
|
832 |
|
833 |
# Create LLM instance.
|
@@ -850,8 +904,6 @@ class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
|
|
850 |
with self.output()['clean'].open("w") as f:
|
851 |
json.dump(team_member_list, f, indent=2)
|
852 |
|
853 |
-
logger.info("EnrichTeamMembersWithBackgroundStoryTask complete.")
|
854 |
-
|
855 |
class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
|
856 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
857 |
|
@@ -861,7 +913,8 @@ class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
|
|
861 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
862 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
863 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
864 |
-
'enrich_team_members_with_background_story': EnrichTeamMembersWithBackgroundStoryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
|
|
865 |
}
|
866 |
|
867 |
def output(self):
|
@@ -871,37 +924,28 @@ class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
|
|
871 |
}
|
872 |
|
873 |
def run(self):
|
874 |
-
|
875 |
-
|
876 |
-
# Read the plan prompt from SetupTask.
|
877 |
with self.input()['setup'].open("r") as f:
|
878 |
plan_prompt = f.read()
|
879 |
-
|
880 |
-
# Load the consolidated assumptions.
|
881 |
-
with self.input()['consolidate_assumptions_markdown'].open("r") as f:
|
882 |
consolidate_assumptions_markdown = f.read()
|
883 |
-
|
884 |
-
# Read the pre-project assessment from PreProjectAssessmentTask.
|
885 |
with self.input()['preproject']['clean'].open("r") as f:
|
886 |
pre_project_assessment_dict = json.load(f)
|
887 |
-
|
888 |
-
# Read the project plan from ProjectPlanTask.
|
889 |
-
with self.input()['project_plan'].open("r") as f:
|
890 |
project_plan_dict = json.load(f)
|
891 |
-
|
892 |
-
# Read the team_member_list from EnrichTeamMembersWithBackgroundStoryTask.
|
893 |
with self.input()['enrich_team_members_with_background_story']['clean'].open("r") as f:
|
894 |
team_member_list = json.load(f)
|
895 |
-
|
896 |
-
|
897 |
|
898 |
# Build the query.
|
899 |
query = (
|
900 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
901 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
902 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
903 |
-
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}"
|
904 |
-
f"File 'team-members-that-needs-to-be-enriched.json':\n{format_json_for_use_in_query(team_member_list)}"
|
|
|
905 |
)
|
906 |
|
907 |
# Create LLM instance.
|
@@ -924,8 +968,6 @@ class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
|
|
924 |
with self.output()['clean'].open("w") as f:
|
925 |
json.dump(team_member_list, f, indent=2)
|
926 |
|
927 |
-
logger.info("EnrichTeamMembersWithEnvironmentInfoTask complete.")
|
928 |
-
|
929 |
class ReviewTeamTask(PlanTask):
|
930 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
931 |
|
@@ -935,36 +977,27 @@ class ReviewTeamTask(PlanTask):
|
|
935 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
936 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
937 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
938 |
-
'enrich_team_members_with_environment_info': EnrichTeamMembersWithEnvironmentInfoTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
|
|
939 |
}
|
940 |
|
941 |
def output(self):
|
942 |
return luigi.LocalTarget(str(self.file_path(FilenameEnum.REVIEW_TEAM_RAW)))
|
943 |
|
944 |
def run(self):
|
945 |
-
|
946 |
-
|
947 |
-
# Read the plan prompt from SetupTask.
|
948 |
with self.input()['setup'].open("r") as f:
|
949 |
plan_prompt = f.read()
|
950 |
-
|
951 |
-
# Load the consolidated assumptions.
|
952 |
-
with self.input()['consolidate_assumptions_markdown'].open("r") as f:
|
953 |
consolidate_assumptions_markdown = f.read()
|
954 |
-
|
955 |
-
# Read the pre-project assessment from PreProjectAssessmentTask.
|
956 |
with self.input()['preproject']['clean'].open("r") as f:
|
957 |
pre_project_assessment_dict = json.load(f)
|
958 |
-
|
959 |
-
# Read the project plan from ProjectPlanTask.
|
960 |
-
with self.input()['project_plan'].open("r") as f:
|
961 |
project_plan_dict = json.load(f)
|
962 |
-
|
963 |
-
# Read the team_member_list from EnrichTeamMembersWithEnvironmentInfoTask.
|
964 |
with self.input()['enrich_team_members_with_environment_info']['clean'].open("r") as f:
|
965 |
team_member_list = json.load(f)
|
966 |
-
|
967 |
-
|
968 |
|
969 |
# Convert the team members to a Markdown document.
|
970 |
builder = TeamMarkdownDocumentBuilder()
|
@@ -976,8 +1009,9 @@ class ReviewTeamTask(PlanTask):
|
|
976 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
977 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
978 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
979 |
-
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}"
|
980 |
-
f"File 'team-members.md':\n{team_document_markdown}"
|
|
|
981 |
)
|
982 |
|
983 |
# Create LLM instance.
|
@@ -1039,7 +1073,8 @@ class SWOTAnalysisTask(PlanTask):
|
|
1039 |
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
1040 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1041 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1042 |
-
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
|
|
1043 |
}
|
1044 |
|
1045 |
def output(self):
|
@@ -1049,32 +1084,25 @@ class SWOTAnalysisTask(PlanTask):
|
|
1049 |
}
|
1050 |
|
1051 |
def run(self):
|
1052 |
-
|
1053 |
-
|
1054 |
-
# Read the plan prompt from SetupTask.
|
1055 |
with self.input()['setup'].open("r") as f:
|
1056 |
plan_prompt = f.read()
|
1057 |
-
|
1058 |
-
# Load the consolidated assumptions.
|
1059 |
-
with self.input()['consolidate_assumptions_markdown'].open("r") as f:
|
1060 |
consolidate_assumptions_markdown = f.read()
|
1061 |
-
|
1062 |
-
# Read the pre-project assessment from PreProjectAssessmentTask.
|
1063 |
with self.input()['preproject']['clean'].open("r") as f:
|
1064 |
pre_project_assessment_dict = json.load(f)
|
1065 |
-
|
1066 |
-
# Read the project plan from ProjectPlanTask.
|
1067 |
-
with self.input()['project_plan'].open("r") as f:
|
1068 |
project_plan_dict = json.load(f)
|
1069 |
-
|
1070 |
-
|
1071 |
|
1072 |
# Build the query for SWOT analysis.
|
1073 |
query = (
|
1074 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
1075 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
1076 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
1077 |
-
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}"
|
|
|
1078 |
)
|
1079 |
|
1080 |
# Create LLM instances for SWOT analysis.
|
@@ -1100,8 +1128,6 @@ class SWOTAnalysisTask(PlanTask):
|
|
1100 |
with open(markdown_path, "w", encoding="utf-8") as f:
|
1101 |
f.write(swot_markdown)
|
1102 |
|
1103 |
-
logger.info("SWOT analysis complete.")
|
1104 |
-
|
1105 |
class ExpertReviewTask(PlanTask):
|
1106 |
"""
|
1107 |
Finds experts to review the SWOT analysis and have them provide criticism.
|
@@ -1137,7 +1163,7 @@ class ExpertReviewTask(PlanTask):
|
|
1137 |
plan_prompt = f.read()
|
1138 |
with self.input()['preproject']['clean'].open("r") as f:
|
1139 |
pre_project_assessment_dict = json.load(f)
|
1140 |
-
with self.input()['project_plan'].open("r") as f:
|
1141 |
project_plan_dict = json.load(f)
|
1142 |
swot_markdown_path = self.input()['swot_analysis']['markdown'].path
|
1143 |
with open(swot_markdown_path, "r", encoding="utf-8") as f:
|
@@ -1202,7 +1228,7 @@ class CreateWBSLevel1Task(PlanTask):
|
|
1202 |
logger.info("Creating Work Breakdown Structure (WBS) Level 1...")
|
1203 |
|
1204 |
# Read the project plan JSON from the dependency.
|
1205 |
-
with self.input()['project_plan'].open("r") as f:
|
1206 |
project_plan_dict = json.load(f)
|
1207 |
|
1208 |
# Build the query using the project plan.
|
@@ -1254,7 +1280,7 @@ class CreateWBSLevel2Task(PlanTask):
|
|
1254 |
logger.info("Creating Work Breakdown Structure (WBS) Level 2...")
|
1255 |
|
1256 |
# Read the project plan from the ProjectPlanTask output.
|
1257 |
-
with self.input()['project_plan'].open("r") as f:
|
1258 |
project_plan_dict = json.load(f)
|
1259 |
|
1260 |
# Read the cleaned WBS Level 1 result from the CreateWBSLevel1Task output.
|
@@ -1330,25 +1356,29 @@ class CreatePitchTask(PlanTask):
|
|
1330 |
return {
|
1331 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1332 |
'wbs_project': WBSProjectLevel1AndLevel2Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
|
|
1333 |
}
|
1334 |
|
1335 |
def run(self):
|
1336 |
logger.info("Creating pitch...")
|
1337 |
|
1338 |
# Read the project plan JSON.
|
1339 |
-
with self.input()['project_plan'].open("r") as f:
|
1340 |
project_plan_dict = json.load(f)
|
1341 |
|
1342 |
-
|
1343 |
-
with open(wbs_project_path, "r") as f:
|
1344 |
wbs_project_dict = json.load(f)
|
1345 |
wbs_project = WBSProject.from_dict(wbs_project_dict)
|
1346 |
wbs_project_json = wbs_project.to_dict()
|
|
|
|
|
|
|
1347 |
|
1348 |
# Build the query
|
1349 |
query = (
|
1350 |
f"The project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
1351 |
-
f"Work Breakdown Structure:\n{format_json_for_use_in_query(wbs_project_json)}"
|
|
|
1352 |
)
|
1353 |
|
1354 |
# Get the LLM instance.
|
@@ -1433,7 +1463,7 @@ class IdentifyTaskDependenciesTask(PlanTask):
|
|
1433 |
logger.info("Identifying task dependencies...")
|
1434 |
|
1435 |
# Read the project plan JSON.
|
1436 |
-
with self.input()['project_plan'].open("r") as f:
|
1437 |
project_plan_dict = json.load(f)
|
1438 |
|
1439 |
# Read the major phases with subtasks from WBS Level 2 output.
|
@@ -1487,7 +1517,7 @@ class EstimateTaskDurationsTask(PlanTask):
|
|
1487 |
logger.info("Estimating task durations...")
|
1488 |
|
1489 |
# Load the project plan JSON.
|
1490 |
-
with self.input()['project_plan'].open("r") as f:
|
1491 |
project_plan_dict = json.load(f)
|
1492 |
|
1493 |
with self.input()['wbs_project'].open("r") as f:
|
@@ -1579,7 +1609,7 @@ class CreateWBSLevel3Task(PlanTask):
|
|
1579 |
logger.info("Creating Work Breakdown Structure (WBS) Level 3...")
|
1580 |
|
1581 |
# Load the project plan JSON.
|
1582 |
-
with self.input()['project_plan'].open("r") as f:
|
1583 |
project_plan_dict = json.load(f)
|
1584 |
|
1585 |
with self.input()['wbs_project'].open("r") as f:
|
@@ -1692,6 +1722,167 @@ class WBSProjectLevel1AndLevel2AndLevel3Task(PlanTask):
|
|
1692 |
with self.output()['csv'].open("w") as f:
|
1693 |
f.write(csv_representation)
|
1694 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1695 |
class ReportTask(PlanTask):
|
1696 |
"""
|
1697 |
Generate a report html document.
|
@@ -1701,6 +1892,9 @@ class ReportTask(PlanTask):
|
|
1701 |
- ConvertPitchToMarkdownTask: provides the pitch as Markdown.
|
1702 |
- WBSProjectLevel1AndLevel2AndLevel3Task: provides the table csv file.
|
1703 |
- ExpertReviewTask: provides the expert criticism as Markdown.
|
|
|
|
|
|
|
1704 |
"""
|
1705 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
1706 |
|
@@ -1711,20 +1905,28 @@ class ReportTask(PlanTask):
|
|
1711 |
return {
|
1712 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1713 |
'team_markdown': TeamMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
|
|
1714 |
'swot_analysis': SWOTAnalysisTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1715 |
'pitch_markdown': ConvertPitchToMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1716 |
'wbs_project123': WBSProjectLevel1AndLevel2AndLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1717 |
-
'expert_review': ExpertReviewTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
|
|
|
|
|
|
1718 |
}
|
1719 |
|
1720 |
def run(self):
|
1721 |
rg = ReportGenerator()
|
1722 |
-
rg.
|
1723 |
-
rg.
|
1724 |
-
rg.
|
1725 |
-
rg.
|
1726 |
-
rg.
|
1727 |
-
rg.
|
|
|
|
|
|
|
|
|
1728 |
rg.save_report(self.output().path)
|
1729 |
|
1730 |
class FullPlanPipeline(PlanTask):
|
@@ -1743,6 +1945,7 @@ class FullPlanPipeline(PlanTask):
|
|
1743 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1744 |
'pre_project_assessment': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1745 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
|
|
1746 |
'find_team_members': FindTeamMembersTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1747 |
'enrich_team_members_with_contract_type': EnrichTeamMembersWithContractTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1748 |
'enrich_team_members_with_background_story': EnrichTeamMembersWithBackgroundStoryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
@@ -1760,6 +1963,8 @@ class FullPlanPipeline(PlanTask):
|
|
1760 |
'durations': EstimateTaskDurationsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1761 |
'wbs_level3': CreateWBSLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1762 |
'wbs_project123': WBSProjectLevel1AndLevel2AndLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
|
|
|
|
1763 |
'report': ReportTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1764 |
}
|
1765 |
|
@@ -1811,7 +2016,7 @@ if __name__ == '__main__':
|
|
1811 |
# Capture logs messages to 'run/yyyymmdd_hhmmss/log.txt'
|
1812 |
file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
1813 |
log_file = os.path.join(run_dir, "log.txt")
|
1814 |
-
file_handler = logging.FileHandler(log_file, mode='
|
1815 |
file_handler.setLevel(logging.DEBUG)
|
1816 |
file_handler.setFormatter(file_formatter)
|
1817 |
logger.addHandler(file_handler)
|
|
|
23 |
from src.assume.make_assumptions import MakeAssumptions
|
24 |
from src.assume.distill_assumptions import DistillAssumptions
|
25 |
from src.assume.review_assumptions import ReviewAssumptions
|
26 |
+
from src.assume.shorten_markdown import ShortenMarkdown
|
27 |
from src.expert.pre_project_assessment import PreProjectAssessment
|
28 |
+
from src.plan.project_plan import ProjectPlan
|
29 |
+
from src.plan.related_resources import RelatedResources
|
30 |
from src.swot.swot_analysis import SWOTAnalysis
|
31 |
from src.expert.expert_finder import ExpertFinder
|
32 |
from src.expert.expert_criticism import ExpertCriticism
|
|
|
38 |
from src.pitch.convert_pitch_to_markdown import ConvertPitchToMarkdown
|
39 |
from src.plan.identify_wbs_task_dependencies import IdentifyWBSTaskDependencies
|
40 |
from src.plan.estimate_wbs_task_durations import EstimateWBSTaskDurations
|
41 |
+
from src.plan.review_plan import ReviewPlan
|
42 |
+
from src.plan.executive_summary import ExecutiveSummary
|
43 |
from src.team.find_team_members import FindTeamMembers
|
44 |
from src.team.enrich_team_members_with_contract_type import EnrichTeamMembersWithContractType
|
45 |
from src.team.enrich_team_members_with_background_story import EnrichTeamMembersWithBackgroundStory
|
|
|
503 |
}
|
504 |
|
505 |
def output(self):
|
506 |
+
return {
|
507 |
+
'full': luigi.LocalTarget(str(self.file_path(FilenameEnum.CONSOLIDATE_ASSUMPTIONS_FULL_MARKDOWN))),
|
508 |
+
'short': luigi.LocalTarget(str(self.file_path(FilenameEnum.CONSOLIDATE_ASSUMPTIONS_SHORT_MARKDOWN)))
|
509 |
+
}
|
510 |
|
511 |
def run(self):
|
512 |
+
llm = get_llm(self.llm_model)
|
513 |
+
|
514 |
# Define the list of (title, path) tuples
|
515 |
title_path_list = [
|
516 |
('Plan Type', self.input()['plan_type']['markdown'].path),
|
|
|
523 |
]
|
524 |
|
525 |
# Read the files and handle exceptions
|
526 |
+
full_markdown_chunks = []
|
527 |
+
short_markdown_chunks = []
|
528 |
for title, path in title_path_list:
|
529 |
try:
|
530 |
with open(path, 'r', encoding='utf-8') as f:
|
531 |
markdown_chunk = f.read()
|
532 |
+
full_markdown_chunks.append(f"# {title}\n\n{markdown_chunk}")
|
533 |
except FileNotFoundError:
|
534 |
logger.warning(f"Markdown file not found: {path} (from {title})")
|
535 |
+
full_markdown_chunks.append(f"**Problem with document:** '{title}'\n\nFile not found.")
|
536 |
+
short_markdown_chunks.append(f"**Problem with document:** '{title}'\n\nFile not found.")
|
537 |
+
continue
|
538 |
except Exception as e:
|
539 |
logger.error(f"Error reading markdown file {path} (from {title}): {e}")
|
540 |
+
full_markdown_chunks.append(f"**Problem with document:** '{title}'\n\nError reading markdown file.")
|
541 |
+
short_markdown_chunks.append(f"**Problem with document:** '{title}'\n\nError reading markdown file.")
|
542 |
+
continue
|
543 |
|
544 |
+
try:
|
545 |
+
shorten_markdown = ShortenMarkdown.execute(llm, markdown_chunk)
|
546 |
+
short_markdown_chunks.append(f"# {title}\n{shorten_markdown.markdown}")
|
547 |
+
except Exception as e:
|
548 |
+
logger.error(f"Error shortening markdown file {path} (from {title}): {e}")
|
549 |
+
short_markdown_chunks.append(f"**Problem with document:** '{title}'\n\nError shortening markdown file.")
|
550 |
+
continue
|
551 |
+
|
552 |
# Combine the markdown chunks
|
553 |
+
full_markdown = "\n\n".join(full_markdown_chunks)
|
554 |
+
short_markdown = "\n\n".join(short_markdown_chunks)
|
555 |
|
556 |
# Write the result to disk.
|
557 |
+
output_full_markdown_path = self.output()['full'].path
|
558 |
+
with open(output_full_markdown_path, "w", encoding="utf-8") as f:
|
559 |
f.write(full_markdown)
|
560 |
|
561 |
+
output_short_markdown_path = self.output()['short'].path
|
562 |
+
with open(output_short_markdown_path, "w", encoding="utf-8") as f:
|
563 |
+
f.write(short_markdown)
|
564 |
+
|
565 |
|
566 |
class PreProjectAssessmentTask(PlanTask):
|
567 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
|
|
585 |
with self.input()['setup'].open("r") as f:
|
586 |
plan_prompt = f.read()
|
587 |
|
588 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
589 |
consolidate_assumptions_markdown = f.read()
|
590 |
|
591 |
# Build the query.
|
|
|
626 |
}
|
627 |
|
628 |
def output(self):
|
629 |
+
return {
|
630 |
+
'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.PROJECT_PLAN_RAW))),
|
631 |
+
'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.PROJECT_PLAN_MARKDOWN)))
|
632 |
+
}
|
633 |
|
634 |
def run(self):
|
635 |
logger.info("Creating plan...")
|
|
|
640 |
plan_prompt = f.read()
|
641 |
|
642 |
# Load the consolidated assumptions.
|
643 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
644 |
consolidate_assumptions_markdown = f.read()
|
645 |
|
646 |
# Read the pre-project assessment from its file.
|
|
|
659 |
llm = get_llm(self.llm_model)
|
660 |
|
661 |
# Execute the plan creation.
|
662 |
+
project_plan = ProjectPlan.execute(llm, query)
|
663 |
+
|
664 |
+
# Save raw output
|
665 |
+
project_plan.save_raw(self.output()['raw'].path)
|
666 |
+
|
667 |
+
# Save markdown output
|
668 |
+
project_plan.save_markdown(self.output()['markdown'].path)
|
669 |
+
|
670 |
+
logger.info("Project plan created and saved")
|
671 |
+
|
672 |
+
|
673 |
+
class RelatedResourcesTask(PlanTask):
|
674 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
675 |
+
|
676 |
+
def requires(self):
|
677 |
+
return {
|
678 |
+
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
679 |
+
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
680 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
681 |
+
}
|
682 |
+
|
683 |
+
def output(self):
|
684 |
+
return {
|
685 |
+
'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.RELATED_RESOURCES_RAW))),
|
686 |
+
'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.RELATED_RESOURCES_MARKDOWN)))
|
687 |
+
}
|
688 |
+
|
689 |
+
def run(self):
|
690 |
+
# Read inputs from required tasks.
|
691 |
+
with self.input()['setup'].open("r") as f:
|
692 |
+
plan_prompt = f.read()
|
693 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
694 |
+
consolidate_assumptions_markdown = f.read()
|
695 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
696 |
+
project_plan_dict = json.load(f)
|
697 |
|
698 |
+
# Build the query.
|
699 |
+
query = (
|
700 |
+
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
701 |
+
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
702 |
+
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}"
|
703 |
+
)
|
704 |
|
705 |
+
# Create LLM instance.
|
706 |
+
llm = get_llm(self.llm_model)
|
707 |
+
|
708 |
+
# Execute.
|
709 |
+
try:
|
710 |
+
related_resources = RelatedResources.execute(llm, query)
|
711 |
+
except Exception as e:
|
712 |
+
logger.error("SimilarProjects failed: %s", e)
|
713 |
+
raise
|
714 |
+
|
715 |
+
# Save the results.
|
716 |
+
related_resources.save_raw(self.output()['raw'].path)
|
717 |
+
related_resources.save_markdown(self.output()['markdown'].path)
|
718 |
|
719 |
class FindTeamMembersTask(PlanTask):
|
720 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
|
|
724 |
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
725 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
726 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
727 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
728 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
729 |
}
|
730 |
|
731 |
def output(self):
|
|
|
735 |
}
|
736 |
|
737 |
def run(self):
|
738 |
+
# Read inputs from required tasks.
|
|
|
|
|
739 |
with self.input()['setup'].open("r") as f:
|
740 |
plan_prompt = f.read()
|
741 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
|
|
|
|
742 |
consolidate_assumptions_markdown = f.read()
|
|
|
|
|
743 |
with self.input()['preproject']['clean'].open("r") as f:
|
744 |
pre_project_assessment_dict = json.load(f)
|
745 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
|
|
|
|
746 |
project_plan_dict = json.load(f)
|
747 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
748 |
+
related_resources_dict = json.load(f)
|
749 |
|
750 |
# Build the query.
|
751 |
query = (
|
752 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
753 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
754 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
755 |
+
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
756 |
+
f"File 'related-resources.json':\n{format_json_for_use_in_query(related_resources_dict)}"
|
757 |
)
|
758 |
|
759 |
# Create LLM instance.
|
|
|
776 |
with self.output()['clean'].open("w") as f:
|
777 |
json.dump(team_member_list, f, indent=2)
|
778 |
|
|
|
|
|
779 |
class EnrichTeamMembersWithContractTypeTask(PlanTask):
|
780 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
781 |
|
|
|
785 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
786 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
787 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
788 |
+
'find_team_members': FindTeamMembersTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
789 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
790 |
}
|
791 |
|
792 |
def output(self):
|
|
|
796 |
}
|
797 |
|
798 |
def run(self):
|
799 |
+
# Read inputs from required tasks.
|
|
|
|
|
800 |
with self.input()['setup'].open("r") as f:
|
801 |
plan_prompt = f.read()
|
802 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
|
|
|
|
803 |
consolidate_assumptions_markdown = f.read()
|
|
|
|
|
804 |
with self.input()['preproject']['clean'].open("r") as f:
|
805 |
pre_project_assessment_dict = json.load(f)
|
806 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
|
|
|
|
807 |
project_plan_dict = json.load(f)
|
|
|
|
|
808 |
with self.input()['find_team_members']['clean'].open("r") as f:
|
809 |
team_member_list = json.load(f)
|
810 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
811 |
+
related_resources_dict = json.load(f)
|
812 |
|
813 |
# Build the query.
|
814 |
query = (
|
815 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
816 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
817 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
818 |
+
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
819 |
+
f"File 'team-members-that-needs-to-be-enriched.json':\n{format_json_for_use_in_query(team_member_list)}\n\n"
|
820 |
+
f"File 'related-resources.json':\n{format_json_for_use_in_query(related_resources_dict)}"
|
821 |
)
|
822 |
|
823 |
# Create LLM instance.
|
|
|
840 |
with self.output()['clean'].open("w") as f:
|
841 |
json.dump(team_member_list, f, indent=2)
|
842 |
|
|
|
|
|
843 |
class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
|
844 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
845 |
|
|
|
849 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
850 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
851 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
852 |
+
'enrich_team_members_with_contract_type': EnrichTeamMembersWithContractTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
853 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
854 |
}
|
855 |
|
856 |
def output(self):
|
|
|
860 |
}
|
861 |
|
862 |
def run(self):
|
863 |
+
# Read inputs from required tasks.
|
|
|
|
|
864 |
with self.input()['setup'].open("r") as f:
|
865 |
plan_prompt = f.read()
|
866 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
|
|
|
|
867 |
consolidate_assumptions_markdown = f.read()
|
|
|
|
|
868 |
with self.input()['preproject']['clean'].open("r") as f:
|
869 |
pre_project_assessment_dict = json.load(f)
|
870 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
|
|
|
|
871 |
project_plan_dict = json.load(f)
|
|
|
|
|
872 |
with self.input()['enrich_team_members_with_contract_type']['clean'].open("r") as f:
|
873 |
team_member_list = json.load(f)
|
874 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
875 |
+
related_resources_dict = json.load(f)
|
876 |
|
877 |
# Build the query.
|
878 |
query = (
|
879 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
880 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
881 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
882 |
+
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
883 |
+
f"File 'team-members-that-needs-to-be-enriched.json':\n{format_json_for_use_in_query(team_member_list)}\n\n"
|
884 |
+
f"File 'related-resources.json':\n{format_json_for_use_in_query(related_resources_dict)}"
|
885 |
)
|
886 |
|
887 |
# Create LLM instance.
|
|
|
904 |
with self.output()['clean'].open("w") as f:
|
905 |
json.dump(team_member_list, f, indent=2)
|
906 |
|
|
|
|
|
907 |
class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
|
908 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
909 |
|
|
|
913 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
914 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
915 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
916 |
+
'enrich_team_members_with_background_story': EnrichTeamMembersWithBackgroundStoryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
917 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
918 |
}
|
919 |
|
920 |
def output(self):
|
|
|
924 |
}
|
925 |
|
926 |
def run(self):
|
927 |
+
# Read inputs from required tasks.
|
|
|
|
|
928 |
with self.input()['setup'].open("r") as f:
|
929 |
plan_prompt = f.read()
|
930 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
|
|
|
|
931 |
consolidate_assumptions_markdown = f.read()
|
|
|
|
|
932 |
with self.input()['preproject']['clean'].open("r") as f:
|
933 |
pre_project_assessment_dict = json.load(f)
|
934 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
|
|
|
|
935 |
project_plan_dict = json.load(f)
|
|
|
|
|
936 |
with self.input()['enrich_team_members_with_background_story']['clean'].open("r") as f:
|
937 |
team_member_list = json.load(f)
|
938 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
939 |
+
related_resources_dict = json.load(f)
|
940 |
|
941 |
# Build the query.
|
942 |
query = (
|
943 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
944 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
945 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
946 |
+
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
947 |
+
f"File 'team-members-that-needs-to-be-enriched.json':\n{format_json_for_use_in_query(team_member_list)}\n\n"
|
948 |
+
f"File 'related-resources.json':\n{format_json_for_use_in_query(related_resources_dict)}"
|
949 |
)
|
950 |
|
951 |
# Create LLM instance.
|
|
|
968 |
with self.output()['clean'].open("w") as f:
|
969 |
json.dump(team_member_list, f, indent=2)
|
970 |
|
|
|
|
|
971 |
class ReviewTeamTask(PlanTask):
|
972 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
973 |
|
|
|
977 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
978 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
979 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
980 |
+
'enrich_team_members_with_environment_info': EnrichTeamMembersWithEnvironmentInfoTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
981 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
982 |
}
|
983 |
|
984 |
def output(self):
|
985 |
return luigi.LocalTarget(str(self.file_path(FilenameEnum.REVIEW_TEAM_RAW)))
|
986 |
|
987 |
def run(self):
|
988 |
+
# Read inputs from required tasks.
|
|
|
|
|
989 |
with self.input()['setup'].open("r") as f:
|
990 |
plan_prompt = f.read()
|
991 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
|
|
|
|
992 |
consolidate_assumptions_markdown = f.read()
|
|
|
|
|
993 |
with self.input()['preproject']['clean'].open("r") as f:
|
994 |
pre_project_assessment_dict = json.load(f)
|
995 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
|
|
|
|
996 |
project_plan_dict = json.load(f)
|
|
|
|
|
997 |
with self.input()['enrich_team_members_with_environment_info']['clean'].open("r") as f:
|
998 |
team_member_list = json.load(f)
|
999 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
1000 |
+
related_resources_dict = json.load(f)
|
1001 |
|
1002 |
# Convert the team members to a Markdown document.
|
1003 |
builder = TeamMarkdownDocumentBuilder()
|
|
|
1009 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
1010 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
1011 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
1012 |
+
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
1013 |
+
f"File 'team-members.md':\n{team_document_markdown}\n\n"
|
1014 |
+
f"File 'related-resources.json':\n{format_json_for_use_in_query(related_resources_dict)}"
|
1015 |
)
|
1016 |
|
1017 |
# Create LLM instance.
|
|
|
1073 |
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
1074 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1075 |
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1076 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1077 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
1078 |
}
|
1079 |
|
1080 |
def output(self):
|
|
|
1084 |
}
|
1085 |
|
1086 |
def run(self):
|
1087 |
+
# Read inputs from required tasks.
|
|
|
|
|
1088 |
with self.input()['setup'].open("r") as f:
|
1089 |
plan_prompt = f.read()
|
1090 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
|
|
|
|
1091 |
consolidate_assumptions_markdown = f.read()
|
|
|
|
|
1092 |
with self.input()['preproject']['clean'].open("r") as f:
|
1093 |
pre_project_assessment_dict = json.load(f)
|
1094 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
|
|
|
|
1095 |
project_plan_dict = json.load(f)
|
1096 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
1097 |
+
related_resources_dict = json.load(f)
|
1098 |
|
1099 |
# Build the query for SWOT analysis.
|
1100 |
query = (
|
1101 |
f"File 'initial-plan.txt':\n{plan_prompt}\n\n"
|
1102 |
f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
|
1103 |
f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
1104 |
+
f"File 'project-plan.json':\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
1105 |
+
f"File 'related-resources.json':\n{format_json_for_use_in_query(related_resources_dict)}"
|
1106 |
)
|
1107 |
|
1108 |
# Create LLM instances for SWOT analysis.
|
|
|
1128 |
with open(markdown_path, "w", encoding="utf-8") as f:
|
1129 |
f.write(swot_markdown)
|
1130 |
|
|
|
|
|
1131 |
class ExpertReviewTask(PlanTask):
|
1132 |
"""
|
1133 |
Finds experts to review the SWOT analysis and have them provide criticism.
|
|
|
1163 |
plan_prompt = f.read()
|
1164 |
with self.input()['preproject']['clean'].open("r") as f:
|
1165 |
pre_project_assessment_dict = json.load(f)
|
1166 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
1167 |
project_plan_dict = json.load(f)
|
1168 |
swot_markdown_path = self.input()['swot_analysis']['markdown'].path
|
1169 |
with open(swot_markdown_path, "r", encoding="utf-8") as f:
|
|
|
1228 |
logger.info("Creating Work Breakdown Structure (WBS) Level 1...")
|
1229 |
|
1230 |
# Read the project plan JSON from the dependency.
|
1231 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
1232 |
project_plan_dict = json.load(f)
|
1233 |
|
1234 |
# Build the query using the project plan.
|
|
|
1280 |
logger.info("Creating Work Breakdown Structure (WBS) Level 2...")
|
1281 |
|
1282 |
# Read the project plan from the ProjectPlanTask output.
|
1283 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
1284 |
project_plan_dict = json.load(f)
|
1285 |
|
1286 |
# Read the cleaned WBS Level 1 result from the CreateWBSLevel1Task output.
|
|
|
1356 |
return {
|
1357 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1358 |
'wbs_project': WBSProjectLevel1AndLevel2Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1359 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
1360 |
}
|
1361 |
|
1362 |
def run(self):
|
1363 |
logger.info("Creating pitch...")
|
1364 |
|
1365 |
# Read the project plan JSON.
|
1366 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
1367 |
project_plan_dict = json.load(f)
|
1368 |
|
1369 |
+
with self.input()['wbs_project'].open("r") as f:
|
|
|
1370 |
wbs_project_dict = json.load(f)
|
1371 |
wbs_project = WBSProject.from_dict(wbs_project_dict)
|
1372 |
wbs_project_json = wbs_project.to_dict()
|
1373 |
+
|
1374 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
1375 |
+
related_resources_dict = json.load(f)
|
1376 |
|
1377 |
# Build the query
|
1378 |
query = (
|
1379 |
f"The project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
1380 |
+
f"Work Breakdown Structure:\n{format_json_for_use_in_query(wbs_project_json)}\n\n"
|
1381 |
+
f"Similar projects:\n{format_json_for_use_in_query(related_resources_dict)}"
|
1382 |
)
|
1383 |
|
1384 |
# Get the LLM instance.
|
|
|
1463 |
logger.info("Identifying task dependencies...")
|
1464 |
|
1465 |
# Read the project plan JSON.
|
1466 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
1467 |
project_plan_dict = json.load(f)
|
1468 |
|
1469 |
# Read the major phases with subtasks from WBS Level 2 output.
|
|
|
1517 |
logger.info("Estimating task durations...")
|
1518 |
|
1519 |
# Load the project plan JSON.
|
1520 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
1521 |
project_plan_dict = json.load(f)
|
1522 |
|
1523 |
with self.input()['wbs_project'].open("r") as f:
|
|
|
1609 |
logger.info("Creating Work Breakdown Structure (WBS) Level 3...")
|
1610 |
|
1611 |
# Load the project plan JSON.
|
1612 |
+
with self.input()['project_plan']['raw'].open("r") as f:
|
1613 |
project_plan_dict = json.load(f)
|
1614 |
|
1615 |
with self.input()['wbs_project'].open("r") as f:
|
|
|
1722 |
with self.output()['csv'].open("w") as f:
|
1723 |
f.write(csv_representation)
|
1724 |
|
1725 |
+
class ReviewPlanTask(PlanTask):
|
1726 |
+
"""
|
1727 |
+
Ask questions about the almost finished plan.
|
1728 |
+
|
1729 |
+
It depends on:
|
1730 |
+
- ConsolidateAssumptionsMarkdownTask: provides the assumptions as Markdown.
|
1731 |
+
- ProjectPlanTask: provides the project plan as Markdown.
|
1732 |
+
- SWOTAnalysisTask: provides the SWOT analysis as Markdown.
|
1733 |
+
- TeamMarkdownTask: provides the team as Markdown.
|
1734 |
+
- ConvertPitchToMarkdownTask: provides the pitch as Markdown.
|
1735 |
+
- ExpertReviewTask: provides the expert criticism as Markdown.
|
1736 |
+
- WBSProjectLevel1AndLevel2AndLevel3Task: provides the table csv file.
|
1737 |
+
"""
|
1738 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
1739 |
+
|
1740 |
+
def output(self):
|
1741 |
+
return {
|
1742 |
+
'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.REVIEW_PLAN_RAW))),
|
1743 |
+
'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.REVIEW_PLAN_MARKDOWN)))
|
1744 |
+
}
|
1745 |
+
|
1746 |
+
def requires(self):
|
1747 |
+
return {
|
1748 |
+
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1749 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1750 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1751 |
+
'swot_analysis': SWOTAnalysisTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1752 |
+
'team_markdown': TeamMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1753 |
+
'pitch_markdown': ConvertPitchToMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1754 |
+
'expert_review': ExpertReviewTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1755 |
+
'wbs_project123': WBSProjectLevel1AndLevel2AndLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
1756 |
+
}
|
1757 |
+
|
1758 |
+
def run(self):
|
1759 |
+
# Read inputs from required tasks.
|
1760 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
1761 |
+
assumptions_markdown = f.read()
|
1762 |
+
with self.input()['project_plan']['markdown'].open("r") as f:
|
1763 |
+
project_plan_markdown = f.read()
|
1764 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
1765 |
+
related_resources_dict = json.load(f)
|
1766 |
+
with self.input()['swot_analysis']['markdown'].open("r") as f:
|
1767 |
+
swot_analysis_markdown = f.read()
|
1768 |
+
with self.input()['team_markdown'].open("r") as f:
|
1769 |
+
team_markdown = f.read()
|
1770 |
+
with self.input()['pitch_markdown']['markdown'].open("r") as f:
|
1771 |
+
pitch_markdown = f.read()
|
1772 |
+
with self.input()['expert_review'].open("r") as f:
|
1773 |
+
expert_review = f.read()
|
1774 |
+
with self.input()['wbs_project123']['csv'].open("r") as f:
|
1775 |
+
wbs_project_csv = f.read()
|
1776 |
+
|
1777 |
+
# Build the query.
|
1778 |
+
query = (
|
1779 |
+
f"File 'assumptions.md':\n{assumptions_markdown}\n\n"
|
1780 |
+
f"File 'project-plan.md':\n{project_plan_markdown}\n\n"
|
1781 |
+
f"File 'related-resources.json':\n{format_json_for_use_in_query(related_resources_dict)}\n\n"
|
1782 |
+
f"File 'swot-analysis.md':\n{swot_analysis_markdown}\n\n"
|
1783 |
+
f"File 'team.md':\n{team_markdown}\n\n"
|
1784 |
+
f"File 'pitch.md':\n{pitch_markdown}\n\n"
|
1785 |
+
f"File 'expert-review.md':\n{expert_review}\n\n"
|
1786 |
+
f"File 'work-breakdown-structure.csv':\n{wbs_project_csv}"
|
1787 |
+
)
|
1788 |
+
|
1789 |
+
llm = get_llm(self.llm_model)
|
1790 |
+
|
1791 |
+
# Perform the review.
|
1792 |
+
review_plan = ReviewPlan.execute(llm, query)
|
1793 |
+
|
1794 |
+
# Save the results.
|
1795 |
+
json_path = self.output()['raw'].path
|
1796 |
+
review_plan.save_raw(json_path)
|
1797 |
+
markdown_path = self.output()['markdown'].path
|
1798 |
+
review_plan.save_markdown(markdown_path)
|
1799 |
+
|
1800 |
+
logger.info("Reviewed the plan.")
|
1801 |
+
|
1802 |
+
|
1803 |
+
class ExecutiveSummaryTask(PlanTask):
|
1804 |
+
"""
|
1805 |
+
Create an executive summary of the plan.
|
1806 |
+
|
1807 |
+
It depends on:
|
1808 |
+
- ConsolidateAssumptionsMarkdownTask: provides the assumptions as Markdown.
|
1809 |
+
- ProjectPlanTask: provides the project plan as Markdown.
|
1810 |
+
- SWOTAnalysisTask: provides the SWOT analysis as Markdown.
|
1811 |
+
- TeamMarkdownTask: provides the team as Markdown.
|
1812 |
+
- ConvertPitchToMarkdownTask: provides the pitch as Markdown.
|
1813 |
+
- ExpertReviewTask: provides the expert criticism as Markdown.
|
1814 |
+
- WBSProjectLevel1AndLevel2AndLevel3Task: provides the table csv file.
|
1815 |
+
- ReviewPlanTask: provides the reviewed plan as Markdown.
|
1816 |
+
"""
|
1817 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
1818 |
+
|
1819 |
+
def output(self):
|
1820 |
+
return {
|
1821 |
+
'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.EXECUTIVE_SUMMARY_RAW))),
|
1822 |
+
'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.EXECUTIVE_SUMMARY_MARKDOWN)))
|
1823 |
+
}
|
1824 |
+
|
1825 |
+
def requires(self):
|
1826 |
+
return {
|
1827 |
+
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1828 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1829 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1830 |
+
'swot_analysis': SWOTAnalysisTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1831 |
+
'team_markdown': TeamMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1832 |
+
'pitch_markdown': ConvertPitchToMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1833 |
+
'expert_review': ExpertReviewTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1834 |
+
'wbs_project123': WBSProjectLevel1AndLevel2AndLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1835 |
+
'review_plan': ReviewPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
1836 |
+
}
|
1837 |
+
|
1838 |
+
def run(self):
|
1839 |
+
# Read inputs from required tasks.
|
1840 |
+
with self.input()['consolidate_assumptions_markdown']['short'].open("r") as f:
|
1841 |
+
assumptions_markdown = f.read()
|
1842 |
+
with self.input()['project_plan']['markdown'].open("r") as f:
|
1843 |
+
project_plan_markdown = f.read()
|
1844 |
+
with self.input()['related_resources']['raw'].open("r") as f:
|
1845 |
+
related_resources_dict = json.load(f)
|
1846 |
+
with self.input()['swot_analysis']['markdown'].open("r") as f:
|
1847 |
+
swot_analysis_markdown = f.read()
|
1848 |
+
with self.input()['team_markdown'].open("r") as f:
|
1849 |
+
team_markdown = f.read()
|
1850 |
+
with self.input()['pitch_markdown']['markdown'].open("r") as f:
|
1851 |
+
pitch_markdown = f.read()
|
1852 |
+
with self.input()['expert_review'].open("r") as f:
|
1853 |
+
expert_review = f.read()
|
1854 |
+
with self.input()['wbs_project123']['csv'].open("r") as f:
|
1855 |
+
wbs_project_csv = f.read()
|
1856 |
+
with self.input()['review_plan']['markdown'].open("r") as f:
|
1857 |
+
review_plan_markdown = f.read()
|
1858 |
+
|
1859 |
+
# Build the query.
|
1860 |
+
query = (
|
1861 |
+
f"File 'assumptions.md':\n{assumptions_markdown}\n\n"
|
1862 |
+
f"File 'project-plan.md':\n{project_plan_markdown}\n\n"
|
1863 |
+
f"File 'related-resources.json':\n{format_json_for_use_in_query(related_resources_dict)}\n\n"
|
1864 |
+
f"File 'swot-analysis.md':\n{swot_analysis_markdown}\n\n"
|
1865 |
+
f"File 'team.md':\n{team_markdown}\n\n"
|
1866 |
+
f"File 'pitch.md':\n{pitch_markdown}\n\n"
|
1867 |
+
f"File 'expert-review.md':\n{expert_review}\n\n"
|
1868 |
+
f"File 'work-breakdown-structure.csv':\n{wbs_project_csv}\n\n"
|
1869 |
+
f"File 'review-plan.md':\n{review_plan_markdown}"
|
1870 |
+
)
|
1871 |
+
|
1872 |
+
llm = get_llm(self.llm_model)
|
1873 |
+
|
1874 |
+
# Create the executive summary.
|
1875 |
+
executive_summary = ExecutiveSummary.execute(llm, query)
|
1876 |
+
|
1877 |
+
# Save the results.
|
1878 |
+
json_path = self.output()['raw'].path
|
1879 |
+
executive_summary.save_raw(json_path)
|
1880 |
+
markdown_path = self.output()['markdown'].path
|
1881 |
+
executive_summary.save_markdown(markdown_path)
|
1882 |
+
|
1883 |
+
logger.info("Created executive summary.")
|
1884 |
+
|
1885 |
+
|
1886 |
class ReportTask(PlanTask):
|
1887 |
"""
|
1888 |
Generate a report html document.
|
|
|
1892 |
- ConvertPitchToMarkdownTask: provides the pitch as Markdown.
|
1893 |
- WBSProjectLevel1AndLevel2AndLevel3Task: provides the table csv file.
|
1894 |
- ExpertReviewTask: provides the expert criticism as Markdown.
|
1895 |
+
- ProjectPlanTask: provides the project plan as Markdown.
|
1896 |
+
- ReviewPlanTask: provides the reviewed plan as Markdown.
|
1897 |
+
- ExecutiveSummaryTask: provides the executive summary as Markdown.
|
1898 |
"""
|
1899 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
1900 |
|
|
|
1905 |
return {
|
1906 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1907 |
'team_markdown': TeamMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1908 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1909 |
'swot_analysis': SWOTAnalysisTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1910 |
'pitch_markdown': ConvertPitchToMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1911 |
'wbs_project123': WBSProjectLevel1AndLevel2AndLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1912 |
+
'expert_review': ExpertReviewTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1913 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1914 |
+
'review_plan': ReviewPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1915 |
+
'executive_summary': ExecutiveSummaryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
1916 |
}
|
1917 |
|
1918 |
def run(self):
|
1919 |
rg = ReportGenerator()
|
1920 |
+
rg.append_markdown('Executive Summary', self.input()['executive_summary']['markdown'].path)
|
1921 |
+
rg.append_markdown('Pitch', self.input()['pitch_markdown']['markdown'].path)
|
1922 |
+
rg.append_markdown('Project Plan', self.input()['project_plan']['markdown'].path)
|
1923 |
+
rg.append_markdown('Assumptions', self.input()['consolidate_assumptions_markdown']['full'].path)
|
1924 |
+
rg.append_markdown('Related Resources', self.input()['related_resources']['markdown'].path)
|
1925 |
+
rg.append_markdown('SWOT Analysis', self.input()['swot_analysis']['markdown'].path)
|
1926 |
+
rg.append_markdown('Team', self.input()['team_markdown'].path)
|
1927 |
+
rg.append_markdown('Expert Criticism', self.input()['expert_review'].path)
|
1928 |
+
rg.append_csv('Work Breakdown Structure', self.input()['wbs_project123']['csv'].path)
|
1929 |
+
rg.append_markdown('Review Plan', self.input()['review_plan']['markdown'].path)
|
1930 |
rg.save_report(self.output().path)
|
1931 |
|
1932 |
class FullPlanPipeline(PlanTask):
|
|
|
1945 |
'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1946 |
'pre_project_assessment': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1947 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1948 |
+
'related_resources': RelatedResourcesTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1949 |
'find_team_members': FindTeamMembersTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1950 |
'enrich_team_members_with_contract_type': EnrichTeamMembersWithContractTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1951 |
'enrich_team_members_with_background_story': EnrichTeamMembersWithBackgroundStoryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
|
|
1963 |
'durations': EstimateTaskDurationsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1964 |
'wbs_level3': CreateWBSLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1965 |
'wbs_project123': WBSProjectLevel1AndLevel2AndLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1966 |
+
'plan_evaluator': ReviewPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1967 |
+
'executive_summary': ExecutiveSummaryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1968 |
'report': ReportTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1969 |
}
|
1970 |
|
|
|
2016 |
# Capture logs messages to 'run/yyyymmdd_hhmmss/log.txt'
|
2017 |
file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
2018 |
log_file = os.path.join(run_dir, "log.txt")
|
2019 |
+
file_handler = logging.FileHandler(log_file, mode='a')
|
2020 |
file_handler.setLevel(logging.DEBUG)
|
2021 |
file_handler.setFormatter(file_formatter)
|
2022 |
logger.addHandler(file_handler)
|
src/plan/test_data/deadfish_assumptions.md
ADDED
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Plan Type
|
2 |
+
|
3 |
+
This plan requires one or more physical locations. It cannot be executed digitally.
|
4 |
+
|
5 |
+
**Explanation:** Launching a pollution monitoring program *requires* physical deployment of sensors in Roskilde Fjord, regular physical sample collection, and on-site analysis. This is *unequivocally* a physical endeavor.
|
6 |
+
|
7 |
+
# Physical Locations
|
8 |
+
|
9 |
+
This plan implies one or more physical locations.
|
10 |
+
|
11 |
+
## Requirements for physical locations
|
12 |
+
|
13 |
+
- Accessibility to Roskilde Fjord
|
14 |
+
- Suitable for deploying monitoring sensors
|
15 |
+
- Access to laboratory facilities for sample analysis
|
16 |
+
- Proximity to research institutions or environmental agencies
|
17 |
+
|
18 |
+
## Location 1
|
19 |
+
Denmark
|
20 |
+
|
21 |
+
Roskilde Fjord, Roskilde
|
22 |
+
|
23 |
+
Various locations within Roskilde Fjord
|
24 |
+
|
25 |
+
**Rationale**: The program specifically targets Roskilde Fjord for pollution monitoring.
|
26 |
+
|
27 |
+
## Location 2
|
28 |
+
Denmark
|
29 |
+
|
30 |
+
Roskilde
|
31 |
+
|
32 |
+
Roskilde University
|
33 |
+
|
34 |
+
**Rationale**: Roskilde University has environmental science programs and research facilities that could support the monitoring program.
|
35 |
+
|
36 |
+
## Location 3
|
37 |
+
Denmark
|
38 |
+
|
39 |
+
Copenhagen
|
40 |
+
|
41 |
+
Danish Environmental Protection Agency
|
42 |
+
|
43 |
+
**Rationale**: The Danish EPA could provide expertise, resources, and regulatory oversight for the pollution monitoring program.
|
44 |
+
|
45 |
+
## Location Summary
|
46 |
+
The primary location is Roskilde Fjord, supplemented by Roskilde University for research support and the Danish Environmental Protection Agency in Copenhagen for expertise and oversight.
|
47 |
+
|
48 |
+
# Currency Strategy
|
49 |
+
|
50 |
+
This plan involves money.
|
51 |
+
|
52 |
+
## Currencies
|
53 |
+
|
54 |
+
- **DKK:** Local currency for expenses related to the monitoring program in Denmark, including equipment, personnel, and laboratory services.
|
55 |
+
|
56 |
+
**Primary currency:** DKK
|
57 |
+
|
58 |
+
**Currency strategy:** The Danish Krone (DKK) will be used for all transactions. No additional international risk management is needed.
|
59 |
+
|
60 |
+
# Identify Risks
|
61 |
+
|
62 |
+
|
63 |
+
## Risk 1 - Regulatory & Permitting
|
64 |
+
Deployment of sensors and sample collection within Roskilde Fjord may require permits from local or national environmental agencies. Delays in obtaining these permits could postpone the project start.
|
65 |
+
|
66 |
+
**Impact:** A delay of 1-3 months in project commencement. Potential fines if work begins without proper authorization.
|
67 |
+
|
68 |
+
**Likelihood:** Medium
|
69 |
+
|
70 |
+
**Severity:** Medium
|
71 |
+
|
72 |
+
**Action:** Initiate the permitting process immediately. Engage with relevant authorities (e.g., Roskilde Municipality, Danish EPA) to understand requirements and timelines. Prepare all necessary documentation proactively.
|
73 |
+
|
74 |
+
## Risk 2 - Technical
|
75 |
+
Sensor malfunction or data transmission errors could lead to inaccurate or incomplete data, compromising the program's effectiveness. The real-time aspect adds complexity.
|
76 |
+
|
77 |
+
**Impact:** Compromised data quality, requiring re-calibration or replacement of sensors. Potential delays in identifying pollution sources. Could lead to a 10-20% budget increase for sensor maintenance and replacement.
|
78 |
+
|
79 |
+
**Likelihood:** Medium
|
80 |
+
|
81 |
+
**Severity:** Medium
|
82 |
+
|
83 |
+
**Action:** Select robust and reliable sensors with proven performance in marine environments. Implement a rigorous sensor calibration and maintenance schedule. Establish redundant data transmission pathways. Conduct regular data quality checks.
|
84 |
+
|
85 |
+
## Risk 3 - Environmental
|
86 |
+
The deployment and maintenance of sensors could inadvertently cause localized environmental damage (e.g., disturbance to benthic habitats, introduction of pollutants from sensor materials).
|
87 |
+
|
88 |
+
**Impact:** Localized habitat damage, potentially triggering further environmental concerns and negative public perception. Fines from environmental agencies.
|
89 |
+
|
90 |
+
**Likelihood:** Low
|
91 |
+
|
92 |
+
**Severity:** Medium
|
93 |
+
|
94 |
+
**Action:** Conduct an environmental impact assessment prior to deployment. Select sensor materials that are environmentally benign. Implement best practices for sensor deployment and maintenance to minimize disturbance. Monitor the deployment sites for any signs of environmental damage.
|
95 |
+
|
96 |
+
## Risk 4 - Operational
|
97 |
+
Adverse weather conditions (e.g., storms, ice) could hinder sensor deployment, sample collection, and maintenance activities. This is particularly relevant in a fjord environment.
|
98 |
+
|
99 |
+
**Impact:** Delays in data collection, potentially leading to gaps in the monitoring record. Increased operational costs due to weather-related disruptions. A delay of 2-4 weeks is possible.
|
100 |
+
|
101 |
+
**Likelihood:** Medium
|
102 |
+
|
103 |
+
**Severity:** Medium
|
104 |
+
|
105 |
+
**Action:** Develop a weather contingency plan. Schedule fieldwork during periods of historically favorable weather. Utilize weather forecasting services to anticipate and avoid adverse conditions. Invest in equipment suitable for a range of weather conditions.
|
106 |
+
|
107 |
+
## Risk 5 - Supply Chain
|
108 |
+
Delays in the delivery of sensors, laboratory equipment, or other essential supplies could postpone the project start or disrupt ongoing operations.
|
109 |
+
|
110 |
+
**Impact:** Project delays, increased costs due to expedited shipping or alternative sourcing. A delay of 1-2 months is possible.
|
111 |
+
|
112 |
+
**Likelihood:** Low
|
113 |
+
|
114 |
+
**Severity:** Medium
|
115 |
+
|
116 |
+
**Action:** Establish relationships with multiple suppliers. Maintain a buffer stock of critical supplies. Closely monitor supplier performance and delivery schedules. Consider local suppliers to reduce lead times.
|
117 |
+
|
118 |
+
## Risk 6 - Financial
|
119 |
+
Unexpected cost overruns (e.g., due to sensor failures, permitting delays, or increased labor costs) could strain the project budget.
|
120 |
+
|
121 |
+
**Impact:** Reduced scope of the monitoring program, delays in data analysis, or project termination. An extra cost of 5,000-10,000 DKK is possible.
|
122 |
+
|
123 |
+
**Likelihood:** Medium
|
124 |
+
|
125 |
+
**Severity:** Medium
|
126 |
+
|
127 |
+
**Action:** Develop a detailed budget with contingency funds. Closely monitor project expenditures. Implement cost control measures. Secure additional funding sources if possible.
|
128 |
+
|
129 |
+
## Risk 7 - Social
|
130 |
+
Lack of public awareness or support for the monitoring program could hinder its effectiveness. Concerns about data privacy or potential impacts on fishing activities could lead to opposition.
|
131 |
+
|
132 |
+
**Impact:** Difficulty obtaining access to sampling locations, resistance to sensor deployment, or negative media coverage. Reduced public trust in the program's findings.
|
133 |
+
|
134 |
+
**Likelihood:** Low
|
135 |
+
|
136 |
+
**Severity:** Medium
|
137 |
+
|
138 |
+
**Action:** Develop a public outreach and engagement plan. Communicate the program's goals and benefits clearly and transparently. Address public concerns proactively. Involve local stakeholders in the monitoring process.
|
139 |
+
|
140 |
+
## Risk 8 - Security
|
141 |
+
Vandalism or theft of sensors could disrupt the monitoring program and require costly replacements.
|
142 |
+
|
143 |
+
**Impact:** Data loss, project delays, and increased costs for sensor replacement. A delay of 1-2 weeks is possible.
|
144 |
+
|
145 |
+
**Likelihood:** Low
|
146 |
+
|
147 |
+
**Severity:** Low
|
148 |
+
|
149 |
+
**Action:** Deploy sensors in secure locations. Implement security measures (e.g., GPS tracking, tamper alarms). Establish relationships with local law enforcement.
|
150 |
+
|
151 |
+
## Risk summary
|
152 |
+
The most critical risks are related to regulatory permitting, technical sensor reliability, and potential environmental impacts from sensor deployment. Obtaining permits promptly is crucial to avoid delays. Selecting robust sensors and implementing a rigorous maintenance schedule will minimize data loss. A proactive environmental impact assessment and careful deployment practices will mitigate potential harm to the fjord ecosystem. These three areas require the most attention to ensure the program's success.
|
153 |
+
|
154 |
+
# Make Assumptions
|
155 |
+
|
156 |
+
|
157 |
+
## Question 1 - What is the total budget allocated for the pollution monitoring program, and what are the specific funding sources?
|
158 |
+
|
159 |
+
**Assumptions:** Assumption: The initial budget for the program is 500,000 DKK, sourced from a combination of government grants (70%) and private donations (30%). This is a reasonable starting point for a localized environmental monitoring program, based on similar initiatives in the region.
|
160 |
+
|
161 |
+
**Assessments:** Title: Financial Feasibility Assessment
|
162 |
+
Description: Evaluation of the program's financial viability based on the allocated budget and funding sources.
|
163 |
+
Details: A 500,000 DKK budget may be sufficient for initial setup and operation for one year. Risks include potential cost overruns (identified in 'identify_risks.json'). Mitigation strategies include securing additional funding sources and implementing strict cost control measures. Opportunity: Explore partnerships with local businesses for in-kind contributions or sponsorships.
|
164 |
+
|
165 |
+
## Question 2 - What is the planned duration of the monitoring program, and what are the key milestones for sensor deployment, data collection, and analysis?
|
166 |
+
|
167 |
+
**Assumptions:** Assumption: The monitoring program is planned for a duration of three years, with key milestones including sensor deployment within the first three months, quarterly data collection and analysis reports, and an annual comprehensive report. This timeline allows for sufficient data collection to identify trends and assess the effectiveness of any remediation efforts.
|
168 |
+
|
169 |
+
**Assessments:** Title: Timeline Adherence Assessment
|
170 |
+
Description: Analysis of the project timeline and its feasibility, considering potential delays and dependencies.
|
171 |
+
Details: The three-year duration is adequate for long-term trend analysis. Risk: Delays in permitting (identified in 'identify_risks.json') could impact the sensor deployment milestone. Mitigation: Proactive engagement with regulatory bodies. Opportunity: Phased sensor deployment to accelerate initial data collection.
|
172 |
+
|
173 |
+
## Question 3 - What specific personnel and equipment are required for the program, including expertise in sensor technology, data analysis, and field operations?
|
174 |
+
|
175 |
+
**Assumptions:** Assumption: The program requires a team of three environmental scientists, two field technicians, and access to a fully equipped environmental laboratory. This is based on the scope of the monitoring program and the need for both field data collection and laboratory analysis.
|
176 |
+
|
177 |
+
**Assessments:** Title: Resource Allocation Assessment
|
178 |
+
Description: Evaluation of the adequacy of personnel and equipment resources for the program's objectives.
|
179 |
+
Details: Three scientists and two technicians are likely sufficient. Risk: Technical sensor issues (identified in 'identify_risks.json') may require additional expertise. Mitigation: Contract with external sensor specialists. Opportunity: Collaboration with Roskilde University to leverage student researchers.
|
180 |
+
|
181 |
+
## Question 4 - What specific regulatory approvals and permits are required for sensor deployment and data collection in Roskilde Fjord?
|
182 |
+
|
183 |
+
**Assumptions:** Assumption: Permits are required from Roskilde Municipality and the Danish Environmental Protection Agency for sensor deployment and water sampling. This is based on standard environmental regulations in Denmark.
|
184 |
+
|
185 |
+
**Assessments:** Title: Regulatory Compliance Assessment
|
186 |
+
Description: Evaluation of the program's adherence to relevant environmental regulations and permitting requirements.
|
187 |
+
Details: Permitting delays are a significant risk (identified in 'identify_risks.json'). Mitigation: Early and proactive engagement with regulatory bodies. Opportunity: Establish a strong working relationship with regulators to streamline future monitoring efforts.
|
188 |
+
|
189 |
+
## Question 5 - What safety protocols will be implemented to protect personnel during field operations, particularly considering the fjord environment and potential weather hazards?
|
190 |
+
|
191 |
+
**Assumptions:** Assumption: Standard maritime safety protocols will be followed, including the use of personal flotation devices, weather monitoring, and emergency communication equipment. This is based on standard safety practices for working in marine environments.
|
192 |
+
|
193 |
+
**Assessments:** Title: Safety and Risk Management Assessment
|
194 |
+
Description: Evaluation of safety protocols and risk mitigation strategies for field operations.
|
195 |
+
Details: Adverse weather is a significant risk (identified in 'identify_risks.json'). Mitigation: Weather contingency plan and appropriate equipment. Opportunity: Implement a comprehensive safety training program for all personnel.
|
196 |
+
|
197 |
+
## Question 6 - What measures will be taken to minimize the environmental impact of sensor deployment and maintenance activities in Roskilde Fjord?
|
198 |
+
|
199 |
+
**Assumptions:** Assumption: Environmentally benign sensor materials will be used, and deployment will be conducted to minimize disturbance to benthic habitats. This is based on best practices for environmental monitoring.
|
200 |
+
|
201 |
+
**Assessments:** Title: Environmental Impact Assessment
|
202 |
+
Description: Evaluation of the program's potential environmental impact and mitigation strategies.
|
203 |
+
Details: Sensor deployment could cause localized damage (identified in 'identify_risks.json'). Mitigation: Environmental impact assessment and careful deployment practices. Opportunity: Use of biodegradable sensor components where feasible.
|
204 |
+
|
205 |
+
## Question 7 - How will local stakeholders, including fishermen, residents, and environmental groups, be involved in the monitoring program?
|
206 |
+
|
207 |
+
**Assumptions:** Assumption: A public outreach program will be implemented to inform stakeholders about the program's goals and benefits, and to solicit their feedback. This is based on the importance of public support for environmental monitoring initiatives.
|
208 |
+
|
209 |
+
**Assessments:** Title: Stakeholder Engagement Assessment
|
210 |
+
Description: Evaluation of the program's engagement with local stakeholders and its impact on public perception.
|
211 |
+
Details: Lack of public support is a potential risk (identified in 'identify_risks.json'). Mitigation: Public outreach and engagement plan. Opportunity: Involve stakeholders in data interpretation and dissemination.
|
212 |
+
|
213 |
+
## Question 8 - What specific data management and analysis systems will be used to process and interpret the real-time data collected from the sensors?
|
214 |
+
|
215 |
+
**Assumptions:** Assumption: A cloud-based data management system will be used to store and analyze the data, with automated alerts triggered when pollution levels exceed pre-defined thresholds. This is based on the need for real-time data analysis and efficient data management.
|
216 |
+
|
217 |
+
**Assessments:** Title: Operational Systems Assessment
|
218 |
+
Description: Evaluation of the data management and analysis systems used in the program.
|
219 |
+
Details: Sensor malfunction and data errors are a risk (identified in 'identify_risks.json'). Mitigation: Robust sensor calibration and redundant data transmission. Opportunity: Develop a publicly accessible data dashboard to enhance transparency.
|
220 |
+
|
221 |
+
# Distill Assumptions
|
222 |
+
|
223 |
+
- The initial budget is 500,000 DKK, with 70% from grants and 30% donations.
|
224 |
+
- The monitoring program will last three years, with quarterly data reports.
|
225 |
+
- The program requires three environmental scientists and two field technicians.
|
226 |
+
- Permits are required from Roskilde Municipality and the Danish Environmental Protection Agency.
|
227 |
+
- Standard maritime safety protocols will be followed during field operations.
|
228 |
+
- Environmentally benign sensor materials will be used to minimize habitat disturbance.
|
229 |
+
- A public outreach program will inform stakeholders and solicit their feedback.
|
230 |
+
- A cloud system will store data, with alerts for pollution levels exceeding thresholds.
|
231 |
+
|
232 |
+
# Review Assumptions
|
233 |
+
|
234 |
+
## Domain of the expert reviewer
|
235 |
+
Environmental Project Management and Risk Assessment
|
236 |
+
|
237 |
+
## Domain-specific considerations
|
238 |
+
|
239 |
+
- Environmental regulations and compliance
|
240 |
+
- Stakeholder engagement and community relations
|
241 |
+
- Data quality and reliability
|
242 |
+
- Long-term sustainability of the monitoring program
|
243 |
+
- Specifics of fjord environments
|
244 |
+
|
245 |
+
## Issue 1 - Long-Term Funding Sustainability
|
246 |
+
The assumption of a 500,000 DKK budget with 70% from grants and 30% from donations is a good starting point, but it lacks detail regarding the *long-term sustainability* of funding. Grant funding is often project-specific and may not be renewable. Reliance on donations can be unpredictable. A lack of a long-term funding strategy could lead to premature termination of the monitoring program, undermining its long-term value and ROI.
|
247 |
+
|
248 |
+
**Recommendation:** Develop a comprehensive long-term funding strategy that includes: (1) Identifying potential recurring grant opportunities (e.g., EU environmental funds, national research grants). (2) Diversifying funding sources to include corporate sponsorships, philanthropic foundations, and citizen science initiatives. (3) Creating a detailed financial model that projects costs and revenues over the three-year period and beyond, including sensitivity analyses for different funding scenarios. (4) Exploring opportunities for revenue generation, such as offering data analysis services to local businesses or government agencies.
|
249 |
+
|
250 |
+
**Sensitivity:** If long-term funding is not secured, the project may need to be scaled down or terminated after the initial funding period. A reduction in funding by 20% (100,000 DKK) could lead to a 15-20% reduction in the scope of the monitoring program (e.g., fewer sampling locations, less frequent data collection), reducing the ROI by 10-15%. Complete loss of grant funding after year 1 would result in project termination and a 100% loss of ROI after year 1. The baseline ROI is based on the assumption that the project will run for 3 years as planned.
|
251 |
+
|
252 |
+
## Issue 2 - Data Security and Integrity
|
253 |
+
While the assumption mentions a cloud-based data management system with automated alerts, it lacks detail regarding *data security and integrity*. Environmental data is often sensitive and could be targeted by malicious actors. A breach of data security could compromise the program's credibility, lead to regulatory penalties, and damage stakeholder trust. The real-time aspect adds complexity.
|
254 |
+
|
255 |
+
**Recommendation:** Implement a robust data security plan that includes: (1) Employing encryption for data storage and transmission. (2) Implementing access controls to restrict data access to authorized personnel only. (3) Conducting regular security audits and penetration testing. (4) Establishing a data backup and recovery plan to ensure data availability in the event of a system failure or cyberattack. (5) Ensuring compliance with relevant data privacy regulations (e.g., GDPR).
|
256 |
+
|
257 |
+
**Sensitivity:** A data breach could result in fines ranging from 2-4% of annual turnover under GDPR, potentially costing 10,000-20,000 DKK. Loss of public trust could reduce stakeholder engagement by 30-50%, impacting the program's effectiveness and ROI by 5-10%. The baseline ROI is based on the assumption that the data collected is secure and reliable.
|
258 |
+
|
259 |
+
## Issue 3 - Community and Stakeholder Engagement Depth
|
260 |
+
The assumption of a public outreach program is a good start, but it lacks detail regarding the *depth and breadth* of community and stakeholder engagement. Simply informing stakeholders is not enough; active involvement and co-creation are crucial for building trust and ensuring the program's long-term success. A lack of meaningful engagement could lead to resistance to sensor deployment, skepticism about the program's findings, and ultimately, a failure to achieve its objectives.
|
261 |
+
|
262 |
+
**Recommendation:** Develop a comprehensive stakeholder engagement plan that includes: (1) Conducting stakeholder mapping to identify key stakeholders and their interests. (2) Establishing a stakeholder advisory group to provide ongoing feedback and guidance. (3) Organizing public forums and workshops to discuss the program's goals, methods, and findings. (4) Involving local stakeholders in data collection and analysis (e.g., citizen science initiatives). (5) Communicating the program's findings in a clear and accessible manner to the public.
|
263 |
+
|
264 |
+
**Sensitivity:** If the community opposes the project, the project could be delayed by 2-4 months, and the cost could increase by 5-10% due to the need for additional consultations and mitigation measures. A lack of stakeholder buy-in could reduce the program's effectiveness by 10-20%, impacting the ROI by 5-10%. The baseline ROI is based on the assumption that the community supports the project.
|
265 |
+
|
266 |
+
## Review conclusion
|
267 |
+
The pollution monitoring program in Roskilde Fjord has a solid foundation, but it needs to address the long-term sustainability of funding, data security and integrity, and the depth of community and stakeholder engagement. By developing comprehensive strategies in these areas, the program can increase its chances of success and maximize its impact on the environment and the community.
|
src/plan/test_data/solarfarm_consolidate_assumptions_short.md
ADDED
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Plan Type
|
2 |
+
- Requires physical locations.
|
3 |
+
- Cannot be executed digitally.
|
4 |
+
|
5 |
+
## Explanation
|
6 |
+
|
7 |
+
- Solar farm requires physical location, construction, installation, and maintenance.
|
8 |
+
- Inherently a physical project.
|
9 |
+
|
10 |
+
|
11 |
+
# Physical Locations
|
12 |
+
This plan implies one or more physical locations.
|
13 |
+
|
14 |
+
## Requirements for physical locations
|
15 |
+
|
16 |
+
- High solar irradiance
|
17 |
+
- Available land area
|
18 |
+
- Proximity to grid infrastructure
|
19 |
+
- Favorable zoning regulations
|
20 |
+
|
21 |
+
## Location 1
|
22 |
+
Denmark, West Jutland, Near Varde
|
23 |
+
|
24 |
+
Rationale: Flat terrain, good grid access, available land, favorable conditions.
|
25 |
+
|
26 |
+
## Location 2
|
27 |
+
Denmark, South Jutland, Near Tønder
|
28 |
+
|
29 |
+
Rationale: Ample open space, significant sunlight, proximity to German border for potential energy export.
|
30 |
+
|
31 |
+
## Location 3
|
32 |
+
Denmark, Zealand, Near Holbæk
|
33 |
+
|
34 |
+
Rationale: Balance of available land and proximity to major population centers, reducing transmission losses. Existing infrastructure.
|
35 |
+
|
36 |
+
## Location Summary
|
37 |
+
Locations in West Jutland (near Varde), South Jutland (near Tønder), and Zealand (near Holbæk) offer suitable land area, high solar irradiance, proximity to grid infrastructure, and favorable zoning regulations.
|
38 |
+
|
39 |
+
# Currency Strategy
|
40 |
+
## Currencies
|
41 |
+
|
42 |
+
- DKK: Project in Denmark, needed for local expenses.
|
43 |
+
- EUR: Denmark is part of the EU, simplifies transactions.
|
44 |
+
|
45 |
+
Primary currency: DKK
|
46 |
+
|
47 |
+
Currency strategy: DKK for local transactions. Hedge against exchange rate fluctuations between DKK and EUR/USD for international transactions.
|
48 |
+
|
49 |
+
# Identify Risks
|
50 |
+
# Risk 1 - Regulatory & Permitting
|
51 |
+
Delays in permits/approvals from Danish authorities. Changes in regulations.
|
52 |
+
|
53 |
+
- Impact: 6-12 month delays, 5-10% cost increase, or cancellation.
|
54 |
+
- Likelihood: Medium
|
55 |
+
- Severity: High
|
56 |
+
- Action: Engage regulators early. Conduct impact assessments. Secure legal counsel. Develop contingency plans.
|
57 |
+
|
58 |
+
# Risk 2 - Technical
|
59 |
+
Technical challenges during construction/operation. Suboptimal performance.
|
60 |
+
|
61 |
+
- Impact: 5-15% reduced output, 20-50k DKK increased costs, 1-2 weeks downtime.
|
62 |
+
- Likelihood: Medium
|
63 |
+
- Severity: Medium
|
64 |
+
- Action: Conduct site assessments. Select proven tech. Implement monitoring. Secure warranties. Plan for redundancy.
|
65 |
+
|
66 |
+
# Risk 3 - Financial
|
67 |
+
Cost overruns. Difficulty securing financing. Lower energy prices.
|
68 |
+
|
69 |
+
- Impact: 10-20% budget increase, reduced ROI, or abandonment.
|
70 |
+
- Likelihood: Medium
|
71 |
+
- Severity: High
|
72 |
+
- Action: Develop realistic budget with 10-15% contingency. Secure financing early. Implement cost controls. Hedge currency. Forecast energy prices.
|
73 |
+
|
74 |
+
# Risk 4 - Environmental
|
75 |
+
Negative environmental impacts. Public opposition.
|
76 |
+
|
77 |
+
- Impact: Project delays, 10-30k DKK mitigation costs, reputational damage, or cancellation.
|
78 |
+
- Likelihood: Low
|
79 |
+
- Severity: Medium
|
80 |
+
- Action: Conduct impact assessments. Implement best practices. Engage communities. Develop management plan.
|
81 |
+
|
82 |
+
# Risk 5 - Social
|
83 |
+
Public opposition. Lack of community support.
|
84 |
+
|
85 |
+
- Impact: Project delays, increased costs, or reputational damage.
|
86 |
+
- Likelihood: Low
|
87 |
+
- Severity: Medium
|
88 |
+
- Action: Engage communities early. Address concerns. Offer community benefits.
|
89 |
+
|
90 |
+
# Risk 6 - Operational
|
91 |
+
Difficulties in operating/maintaining the farm.
|
92 |
+
|
93 |
+
- Impact: Reduced output, increased costs, or downtime.
|
94 |
+
- Likelihood: Medium
|
95 |
+
- Severity: Medium
|
96 |
+
- Action: Develop maintenance plan. Train personnel. Implement cybersecurity. Secure service agreements.
|
97 |
+
|
98 |
+
# Risk 7 - Supply Chain
|
99 |
+
Disruptions in supply chain.
|
100 |
+
|
101 |
+
- Impact: 3-6 month delays, 5-10% increased costs, or difficulty completing project.
|
102 |
+
- Likelihood: Medium
|
103 |
+
- Severity: Medium
|
104 |
+
- Action: Diversify suppliers. Maintain buffer stocks. Monitor supply chain. Secure supply agreements.
|
105 |
+
|
106 |
+
# Risk 8 - Security
|
107 |
+
Theft, vandalism, or sabotage.
|
108 |
+
|
109 |
+
- Impact: Project delays, increased security costs, or damage.
|
110 |
+
- Likelihood: Low
|
111 |
+
- Severity: Medium
|
112 |
+
- Action: Implement security measures. Hire personnel. Conduct background checks. Establish relationships with law enforcement.
|
113 |
+
|
114 |
+
# Risk 9 - Grid Integration
|
115 |
+
Challenges in connecting to the grid.
|
116 |
+
|
117 |
+
- Impact: 2-4 week delays, reduced output, or increased grid upgrade costs.
|
118 |
+
- Likelihood: Medium
|
119 |
+
- Severity: Medium
|
120 |
+
- Action: Engage grid operator early. Conduct impact studies. Secure agreements. Invest in upgrades.
|
121 |
+
|
122 |
+
# Risk 10 - Market & Competitive
|
123 |
+
Changes in electricity market dynamics.
|
124 |
+
|
125 |
+
- Impact: Reduced prices, lower profitability, or difficulty competing.
|
126 |
+
- Likelihood: Low
|
127 |
+
- Severity: Medium
|
128 |
+
- Action: Conduct market analysis. Secure PPAs. Advocate for renewable policies.
|
129 |
+
|
130 |
+
# Risk 11 - Long-Term Sustainability
|
131 |
+
Degradation of panel performance. End-of-life disposal.
|
132 |
+
|
133 |
+
- Impact: Reduced output, increased costs, or environmental liabilities.
|
134 |
+
- Likelihood: Medium
|
135 |
+
- Severity: Medium
|
136 |
+
- Action: Select high-quality panels. Develop disposal plan. Explore recycling.
|
137 |
+
|
138 |
+
# Risk summary
|
139 |
+
Critical risks: Regulatory & Permitting, Financial, and Technical. Mitigation: early engagement, securing financing, proven technologies, site assessments, monitoring, and supplier diversification.
|
140 |
+
|
141 |
+
# Make Assumptions
|
142 |
+
# Question 1 - Estimated Total Budget
|
143 |
+
|
144 |
+
- Assumption: 50 million DKK, including 15% contingency.
|
145 |
+
|
146 |
+
## Assessments
|
147 |
+
|
148 |
+
- Title: Financial Feasibility Assessment
|
149 |
+
- Description: Evaluation of financial viability.
|
150 |
+
- Details: Requires significant funding. Risks: cost overruns, lower energy prices. Mitigation: long-term PPAs, cost control. Benefits: strong ROI. Opportunity: government subsidies.
|
151 |
+
|
152 |
+
# Question 2 - Projected Timeline
|
153 |
+
|
154 |
+
- Assumption: 24 months (6 planning/permitting, 12 construction, 6 grid connection/commissioning).
|
155 |
+
|
156 |
+
## Assessments
|
157 |
+
|
158 |
+
- Title: Timeline and Milestone Assessment
|
159 |
+
- Description: Analysis of timeline.
|
160 |
+
- Details: Aggressive timeline. Risks: permitting delays, construction challenges. Mitigation: early engagement, risk management. Benefits: early revenue. Opportunity: streamline processes.
|
161 |
+
|
162 |
+
# Question 3 - Personnel and Resources
|
163 |
+
|
164 |
+
- Assumption: 5 engineers, 2 project managers, 50 construction workers, specialized equipment.
|
165 |
+
|
166 |
+
## Assessments
|
167 |
+
|
168 |
+
- Title: Resource and Personnel Assessment
|
169 |
+
- Description: Evaluation of resources.
|
170 |
+
- Details: Securing personnel/equipment is crucial. Risks: labor shortages, equipment malfunctions. Mitigation: recruitment, maintenance. Benefits: efficient execution. Opportunity: partner with training institutions.
|
171 |
+
|
172 |
+
# Question 4 - Regulations and Governance
|
173 |
+
|
174 |
+
- Assumption: Governed by Danish energy regulations, EIA, grid connection agreements.
|
175 |
+
|
176 |
+
## Assessments
|
177 |
+
|
178 |
+
- Title: Governance and Regulations Assessment
|
179 |
+
- Description: Analysis of regulatory framework.
|
180 |
+
- Details: Compliance is essential. Risks: permitting delays, regulatory changes. Mitigation: early engagement, legal counsel. Benefits: smooth permitting. Opportunity: advocate for policies.
|
181 |
+
|
182 |
+
# Question 5 - Safety Protocols and Risk Management
|
183 |
+
|
184 |
+
- Assumption: Mandatory safety training, inspections, emergency response plans.
|
185 |
+
|
186 |
+
## Assessments
|
187 |
+
|
188 |
+
- Title: Safety and Risk Management Assessment
|
189 |
+
- Description: Evaluation of safety protocols.
|
190 |
+
- Details: Prioritizing safety is paramount. Risks: accidents, injuries. Mitigation: training, inspections. Benefits: safe environment, reduced liability. Opportunity: implement safety technologies.
|
191 |
+
|
192 |
+
# Question 6 - Environmental Impact
|
193 |
+
|
194 |
+
- Assumption: EIA, mitigation measures, habitat restoration, erosion control, waste management.
|
195 |
+
|
196 |
+
## Assessments
|
197 |
+
|
198 |
+
- Title: Environmental Impact Assessment
|
199 |
+
- Description: Analysis of environmental impact.
|
200 |
+
- Details: Minimizing impact is crucial. Risks: habitat disruption, water contamination. Mitigation: EIAs, best practices. Benefits: positive image. Opportunity: biodiversity enhancement.
|
201 |
+
|
202 |
+
# Question 7 - Stakeholder Involvement
|
203 |
+
|
204 |
+
- Assumption: Public consultations, community meetings, feedback mechanisms.
|
205 |
+
|
206 |
+
## Assessments
|
207 |
+
|
208 |
+
- Title: Stakeholder Involvement Assessment
|
209 |
+
- Description: Evaluation of stakeholder engagement.
|
210 |
+
- Details: Community support is essential. Risks: public opposition. Mitigation: proactive engagement. Benefits: positive relationship. Opportunity: community benefits.
|
211 |
+
|
212 |
+
# Question 8 - Operational Systems
|
213 |
+
|
214 |
+
- Assumption: SCADA, CMMS, security systems.
|
215 |
+
|
216 |
+
## Assessments
|
217 |
+
|
218 |
+
- Title: Operational Systems Assessment
|
219 |
+
- Description: Analysis of operational systems.
|
220 |
+
- Details: Efficient operation is crucial. Risks: equipment failures, security breaches. Mitigation: monitoring, maintenance. Benefits: optimized production. Opportunity: predictive maintenance.
|
221 |
+
|
222 |
+
|
223 |
+
# Distill Assumptions
|
224 |
+
# Project Overview
|
225 |
+
|
226 |
+
- Budget: 50 million DKK (15% contingency)
|
227 |
+
- Timeline: 24 months (6 planning, 12 construction, 6 grid)
|
228 |
+
- Resources: 5 engineers, 2 project managers, 50 construction workers, specialized equipment
|
229 |
+
|
230 |
+
## Regulatory Compliance
|
231 |
+
|
232 |
+
- Governed by Danish energy regulations: Electricity Supply Act, Renewable Energy Act, EIA, Energinet
|
233 |
+
|
234 |
+
## Safety and Environment
|
235 |
+
|
236 |
+
- Implement safety training, inspections, emergency plans (Danish standards)
|
237 |
+
- EIA: habitat restoration, erosion control, waste management
|
238 |
+
|
239 |
+
## Stakeholder Engagement
|
240 |
+
|
241 |
+
- Consultations and feedback to address concerns
|
242 |
+
|
243 |
+
## Technology
|
244 |
+
|
245 |
+
- SCADA for energy monitoring
|
246 |
+
- CMMS for maintenance
|
247 |
+
- Security systems
|
248 |
+
|
249 |
+
|
250 |
+
# Review Assumptions
|
251 |
+
# Domain of the expert reviewer
|
252 |
+
Renewable Energy Project Finance and Risk Management
|
253 |
+
|
254 |
+
## Domain-specific considerations
|
255 |
+
|
256 |
+
- Energy market dynamics in Denmark and neighboring countries
|
257 |
+
- Grid connection costs and capacity constraints
|
258 |
+
- Long-term power purchase agreement (PPA) terms
|
259 |
+
- Technological advancements in solar panel efficiency and energy storage
|
260 |
+
- Political and regulatory landscape for renewable energy in Denmark
|
261 |
+
|
262 |
+
## Issue 1 - Incomplete Financial Model and Revenue Projections
|
263 |
+
The budget assumption lacks granularity. A detailed financial model is missing, including revenue projections based on realistic energy prices, PPA terms, and potential for ancillary services. Without this, the project's ROI and financial viability are uncertain. The plan does not address energy price volatility.
|
264 |
+
|
265 |
+
Recommendation:
|
266 |
+
|
267 |
+
- Develop a comprehensive financial model with detailed cost breakdowns and revenue projections based on various energy price scenarios and PPA terms.
|
268 |
+
- Conduct a sensitivity analysis to assess the impact of key variables on the project's ROI.
|
269 |
+
- Secure a PPA with a reputable off-taker to provide revenue certainty.
|
270 |
+
|
271 |
+
Sensitivity:
|
272 |
+
|
273 |
+
- A 10% decrease in energy prices could reduce the project's ROI by 8-12%.
|
274 |
+
- A 2% increase in interest rates could increase the total project cost by 3-5% and delay the ROI by 1-2 years.
|
275 |
+
|
276 |
+
## Issue 2 - Lack of Specificity Regarding Grid Connection and Capacity
|
277 |
+
The plan lacks specific details about the available grid capacity and associated costs for grid connection upgrades. Insufficient grid capacity or high connection costs could significantly impact the project's timeline and budget. The plan does not address the potential for curtailment.
|
278 |
+
|
279 |
+
Recommendation:
|
280 |
+
|
281 |
+
- Conduct a detailed grid impact study for each proposed location.
|
282 |
+
- Obtain firm cost estimates for grid connection.
|
283 |
+
- Negotiate grid connection agreements that address potential curtailment issues.
|
284 |
+
- Explore options for on-site energy storage.
|
285 |
+
|
286 |
+
Sensitivity:
|
287 |
+
|
288 |
+
- Required grid connection upgrades could increase the total project cost by 10-15% and delay the project completion date by 3-6 months.
|
289 |
+
- Curtailment occurring 5% of the time could reduce the project's annual revenue by 3-5%.
|
290 |
+
|
291 |
+
## Issue 3 - Insufficient Consideration of Long-Term Operational Costs and Panel Degradation
|
292 |
+
The plan lacks a detailed assessment of long-term operational costs and does not explicitly address the impact of panel degradation on energy output and revenue.
|
293 |
+
|
294 |
+
Recommendation:
|
295 |
+
|
296 |
+
- Develop a detailed O&M budget with realistic estimates for all operational costs.
|
297 |
+
- Incorporate panel degradation rates into the financial model.
|
298 |
+
- Consider investing in higher-quality panels with lower degradation rates.
|
299 |
+
|
300 |
+
Sensitivity:
|
301 |
+
|
302 |
+
- A 0.2% increase in the annual panel degradation rate could reduce the project's cumulative energy output by 5-7% over 25 years and decrease the ROI by 3-5%.
|
303 |
+
- A 20% increase in annual O&M costs could reduce the project's ROI by 2-3%.
|
304 |
+
|
305 |
+
## Review conclusion
|
306 |
+
The solar farm project has potential but requires a more detailed plan addressing gaps in financial modeling, grid connection, and long-term operational costs. By conducting thorough assessments, securing firm commitments, and implementing robust risk management strategies, the project can maximize its ROI.
|
src/report/report_generator.py
CHANGED
@@ -9,6 +9,7 @@ import re
|
|
9 |
import json
|
10 |
import logging
|
11 |
import pandas as pd
|
|
|
12 |
from pathlib import Path
|
13 |
from datetime import datetime
|
14 |
import markdown
|
@@ -16,9 +17,14 @@ from typing import Dict, Any, Optional
|
|
16 |
|
17 |
logger = logging.getLogger(__name__)
|
18 |
|
|
|
|
|
|
|
|
|
|
|
19 |
class ReportGenerator:
|
20 |
def __init__(self):
|
21 |
-
self.
|
22 |
|
23 |
def read_json_file(self, file_path: Path) -> Optional[Dict[str, Any]]:
|
24 |
"""Read a JSON file and return its contents."""
|
@@ -81,44 +87,26 @@ class ReportGenerator:
|
|
81 |
logging.error(f"Error reading CSV file {file_path}: {str(e)}")
|
82 |
return None
|
83 |
|
84 |
-
def
|
85 |
-
"""Append
|
86 |
-
|
87 |
-
if
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
markdown = self.read_markdown_file(file_path)
|
93 |
-
if markdown:
|
94 |
-
self.report_data['pitch'] = markdown
|
95 |
-
|
96 |
-
def append_swot_analysis_markdown(self, file_path: Path):
|
97 |
-
"""Append the SWOT markdown to the report."""
|
98 |
-
markdown = self.read_markdown_file(file_path)
|
99 |
-
if markdown:
|
100 |
-
self.report_data['swot'] = markdown
|
101 |
-
|
102 |
-
def append_team_markdown(self, file_path: Path):
|
103 |
-
"""Append the team markdown to the report."""
|
104 |
-
markdown = self.read_markdown_file(file_path)
|
105 |
-
if markdown:
|
106 |
-
self.report_data['team'] = markdown
|
107 |
-
|
108 |
-
def append_expert_criticism_markdown(self, file_path: Path):
|
109 |
-
"""Append the expert criticism markdown to the report."""
|
110 |
-
markdown = self.read_markdown_file(file_path)
|
111 |
-
if markdown:
|
112 |
-
self.report_data['expert_criticism'] = markdown
|
113 |
|
114 |
-
def
|
115 |
-
"""Append
|
116 |
-
|
117 |
-
if
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
|
|
|
|
|
|
122 |
|
123 |
def generate_html_report(self) -> str:
|
124 |
"""Generate an HTML report from the gathered data."""
|
@@ -144,25 +132,8 @@ class ReportGenerator:
|
|
144 |
</div>
|
145 |
""")
|
146 |
|
147 |
-
|
148 |
-
add_section(
|
149 |
-
|
150 |
-
if 'assumptions' in self.report_data:
|
151 |
-
add_section('Assumptions', markdown.markdown(self.report_data['assumptions']))
|
152 |
-
|
153 |
-
if 'swot' in self.report_data:
|
154 |
-
add_section('SWOT Analysis', markdown.markdown(self.report_data['swot']))
|
155 |
-
|
156 |
-
if 'team' in self.report_data:
|
157 |
-
add_section('Team', markdown.markdown(self.report_data['team']))
|
158 |
-
|
159 |
-
if 'expert_criticism' in self.report_data:
|
160 |
-
add_section('Expert Criticism', markdown.markdown(self.report_data['expert_criticism']))
|
161 |
-
|
162 |
-
if 'project_plan' in self.report_data:
|
163 |
-
df = self.report_data['project_plan']
|
164 |
-
table_html = df.to_html(classes='dataframe', index=False, na_rep='')
|
165 |
-
add_section('Project Plan', table_html)
|
166 |
|
167 |
html_content = '\n'.join(html_parts)
|
168 |
|
@@ -213,12 +184,12 @@ def main():
|
|
213 |
output_path = input_path / FilenameEnum.REPORT.value
|
214 |
|
215 |
report_generator = ReportGenerator()
|
216 |
-
report_generator.
|
217 |
-
report_generator.
|
218 |
-
report_generator.
|
219 |
-
report_generator.
|
220 |
-
report_generator.
|
221 |
-
report_generator.
|
222 |
report_generator.save_report(output_path)
|
223 |
|
224 |
if not args.no_browser:
|
|
|
9 |
import json
|
10 |
import logging
|
11 |
import pandas as pd
|
12 |
+
from dataclasses import dataclass
|
13 |
from pathlib import Path
|
14 |
from datetime import datetime
|
15 |
import markdown
|
|
|
17 |
|
18 |
logger = logging.getLogger(__name__)
|
19 |
|
20 |
+
@dataclass
|
21 |
+
class ReportDocumentItem:
|
22 |
+
document_title: str
|
23 |
+
document_html_content: str
|
24 |
+
|
25 |
class ReportGenerator:
|
26 |
def __init__(self):
|
27 |
+
self.report_item_list: list[ReportDocumentItem] = []
|
28 |
|
29 |
def read_json_file(self, file_path: Path) -> Optional[Dict[str, Any]]:
|
30 |
"""Read a JSON file and return its contents."""
|
|
|
87 |
logging.error(f"Error reading CSV file {file_path}: {str(e)}")
|
88 |
return None
|
89 |
|
90 |
+
def append_markdown(self, document_title: str, file_path: Path):
|
91 |
+
"""Append a markdown document to the report."""
|
92 |
+
md_data = self.read_markdown_file(file_path)
|
93 |
+
if md_data is None:
|
94 |
+
logging.warning(f"Document: '{document_title}'. Could not read markdown file: {file_path}")
|
95 |
+
return
|
96 |
+
html = markdown.markdown(md_data)
|
97 |
+
self.report_item_list.append(ReportDocumentItem(document_title, html))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
|
99 |
+
def append_csv(self, document_title: str, file_path: Path):
|
100 |
+
"""Append a CSV to the report."""
|
101 |
+
df_data = self.read_csv_file(file_path)
|
102 |
+
if df_data is None:
|
103 |
+
logging.warning(f"Document: '{document_title}'. Could not read CSV file: {file_path}")
|
104 |
+
return
|
105 |
+
# Clean up the dataframe
|
106 |
+
# Remove any completely empty rows or columns
|
107 |
+
df = df_data.dropna(how='all', axis=0).dropna(how='all', axis=1)
|
108 |
+
html = df.to_html(classes='dataframe', index=False, na_rep='')
|
109 |
+
self.report_item_list.append(ReportDocumentItem(document_title, html))
|
110 |
|
111 |
def generate_html_report(self) -> str:
|
112 |
"""Generate an HTML report from the gathered data."""
|
|
|
132 |
</div>
|
133 |
""")
|
134 |
|
135 |
+
for item in self.report_item_list:
|
136 |
+
add_section(item.document_title, item.document_html_content)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
|
138 |
html_content = '\n'.join(html_parts)
|
139 |
|
|
|
184 |
output_path = input_path / FilenameEnum.REPORT.value
|
185 |
|
186 |
report_generator = ReportGenerator()
|
187 |
+
report_generator.append_markdown('Pitch', input_path / FilenameEnum.PITCH_MARKDOWN.value)
|
188 |
+
report_generator.append_markdown('Assumptions', input_path / FilenameEnum.CONSOLIDATE_ASSUMPTIONS_FULL_MARKDOWN.value)
|
189 |
+
report_generator.append_markdown('SWOT Analysis', input_path / FilenameEnum.SWOT_MARKDOWN.value)
|
190 |
+
report_generator.append_markdown('Team', input_path / FilenameEnum.TEAM_MARKDOWN.value)
|
191 |
+
report_generator.append_markdown('Expert Criticism', input_path / FilenameEnum.EXPERT_CRITICISM_MARKDOWN.value)
|
192 |
+
report_generator.append_csv('Work Breakdown Structure', input_path / FilenameEnum.WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_CSV.value)
|
193 |
report_generator.save_report(output_path)
|
194 |
|
195 |
if not args.no_browser:
|
src/swot/app_swot_analysis.py
CHANGED
@@ -31,7 +31,7 @@ for prompt_item in all_prompts:
|
|
31 |
gradio_examples.append([prompt_item.prompt])
|
32 |
|
33 |
llm_info = LLMInfo.obtain_info()
|
34 |
-
logger.info(f"LLMInfo.
|
35 |
logger.info(f"LLMInfo.error_message_list: {llm_info.error_message_list}")
|
36 |
|
37 |
# Create tupples for the Gradio Radio buttons.
|
|
|
31 |
gradio_examples.append([prompt_item.prompt])
|
32 |
|
33 |
llm_info = LLMInfo.obtain_info()
|
34 |
+
logger.info(f"LLMInfo.ollama_status: {llm_info.ollama_status.value}")
|
35 |
logger.info(f"LLMInfo.error_message_list: {llm_info.error_message_list}")
|
36 |
|
37 |
# Create tupples for the Gradio Radio buttons.
|
src/swot/swot_analysis.py
CHANGED
@@ -93,8 +93,6 @@ class SWOTAnalysis:
|
|
93 |
|
94 |
def to_markdown(self, include_metadata=True) -> str:
|
95 |
rows = []
|
96 |
-
rows.append(f"# SWOT Analysis")
|
97 |
-
|
98 |
rows.append(f"\n## Topic")
|
99 |
rows.append(f"{self.topic}")
|
100 |
|
|
|
93 |
|
94 |
def to_markdown(self, include_metadata=True) -> str:
|
95 |
rows = []
|
|
|
|
|
96 |
rows.append(f"\n## Topic")
|
97 |
rows.append(f"{self.topic}")
|
98 |
|
src/swot/swot_phase1_determine_type.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1 |
"""
|
2 |
Determine what kind of SWOT analysis is to be conducted.
|
|
|
|
|
|
|
3 |
|
4 |
PROMPT> python -m src.swot.swot_phase1_determine_type
|
5 |
"""
|
|
|
1 |
"""
|
2 |
Determine what kind of SWOT analysis is to be conducted.
|
3 |
+
- **Business:** Profit-Driven, aimed at generating profit.
|
4 |
+
- **Personal:** Personal stuff, not aimed at generating profit.
|
5 |
+
- **Other:** Doesn't fit into the above categories.
|
6 |
|
7 |
PROMPT> python -m src.swot.swot_phase1_determine_type
|
8 |
"""
|