Simon Strandgaard commited on
Commit
1bfe7f5
·
1 Parent(s): 22a5c6c

Snapshot of PlanExe commit 773f9ca98123b5751e6b16be192818b572af1aa0

Browse files
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.0
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
- is_ollama_running: bool
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
- ollama_info = OllamaInfo.obtain_info()
89
- if ollama_info.is_running == False:
90
- print(f"Ollama is not running. Please start the Ollama service, in order to use the models via Ollama.")
91
- elif ollama_info.error_message:
92
- print(f"Error message: {ollama_info.error_message}")
93
- error_message_list.append(ollama_info.error_message)
 
 
 
94
 
95
- llm_config_items = []
 
 
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
- is_ollama_running=ollama_info.is_running,
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, list
23
- list_response: ListResponse = list()
 
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
- ollama_info = OllamaInfo.obtain_info()
 
 
 
 
 
 
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.is_ollama_running: {llm_info.is_ollama_running}")
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.is_ollama_running == False:
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
- CONSOLIDATE_ASSUMPTIONS_MARKDOWN = "003-10-consolidate_assumptions.md"
 
21
  PRE_PROJECT_ASSESSMENT_RAW = "004-1-pre_project_assessment_raw.json"
22
  PRE_PROJECT_ASSESSMENT = "004-2-pre_project_assessment.json"
23
- PROJECT_PLAN = "005-project_plan.json"
 
 
 
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
- REPORT = "022-report.html"
 
 
 
 
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.create_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 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
- CREATE_PROJECT_PLAN_SYSTEM_PROMPT_1 = """
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
- CREATE_PROJECT_PLAN_SYSTEM_PROMPT_2 = """
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
- CREATE_PROJECT_PLAN_SYSTEM_PROMPT_3 = """
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
- CREATE_PROJECT_PLAN_SYSTEM_PROMPT = CREATE_PROJECT_PLAN_SYSTEM_PROMPT_3
200
 
201
  T = TypeVar('T', bound=BaseModel)
202
 
203
  @dataclass
204
- class CreateProjectPlan:
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) -> 'CreateProjectPlan':
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 = CREATE_PROJECT_PLAN_SYSTEM_PROMPT.strip()
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
- result = CreateProjectPlan(
 
 
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 save(self, file_path: str) -> None:
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 = CreateProjectPlan.execute(llm, plan_prompt)
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.create_project_plan import CreateProjectPlan
 
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 luigi.LocalTarget(str(self.file_path(FilenameEnum.CONSOLIDATE_ASSUMPTIONS_MARKDOWN)))
 
 
 
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
- markdown_chunks = []
 
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
- markdown_chunks.append(f"# {title}\n\n{markdown_chunk}")
523
  except FileNotFoundError:
524
  logger.warning(f"Markdown file not found: {path} (from {title})")
525
- markdown_chunks.append(f"**Problem with document:** '{title}'\n\nFile not found.")
 
 
526
  except Exception as e:
527
  logger.error(f"Error reading markdown file {path} (from {title}): {e}")
528
- markdown_chunks.append(f"**Problem with document:** '{title}'\n\nError reading markdown file.")
 
 
529
 
 
 
 
 
 
 
 
 
530
  # Combine the markdown chunks
531
- full_markdown = "\n\n".join(markdown_chunks)
 
532
 
533
  # Write the result to disk.
534
- output_markdown_path = self.output().path
535
- with open(output_markdown_path, "w", encoding="utf-8") as f:
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 luigi.LocalTarget(str(self.file_path(FilenameEnum.PROJECT_PLAN)))
 
 
 
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
- create_project_plan = CreateProjectPlan.execute(llm, query)
633
- output_path = self.output().path
634
- create_project_plan.save(output_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
 
636
- logger.info("Project plan created and saved to %s", output_path)
 
 
 
 
 
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
- logger.info("FindTeamMembers. Loading files...")
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
- logger.info("FindTeamMembers. All files are now ready. Brainstorming a team...")
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
- logger.info("EnrichTeamMembersWithContractType. Loading files...")
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
- logger.info("EnrichTeamMembersWithContractType. All files are now ready. Processing...")
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
- logger.info("EnrichTeamMembersWithBackgroundStoryTask. Loading files...")
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
- logger.info("EnrichTeamMembersWithBackgroundStoryTask. All files are now ready. Processing...")
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
- logger.info("EnrichTeamMembersWithEnvironmentInfoTask. Loading files...")
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
- logger.info("EnrichTeamMembersWithEnvironmentInfoTask. All files are now ready. Processing...")
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
- logger.info("ReviewTeamTask. Loading files...")
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
- logger.info("ReviewTeamTask. All files are now ready. Processing...")
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
- logger.info("SWOTAnalysisTask. Loading files...")
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
- logger.info("SWOTAnalysisTask. All files are now ready. Performing analysis...")
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
- wbs_project_path = self.input()['wbs_project'].path
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.append_assumptions_markdown(self.input()['consolidate_assumptions_markdown'].path)
1723
- rg.append_pitch_markdown(self.input()['pitch_markdown']['markdown'].path)
1724
- rg.append_swot_analysis_markdown(self.input()['swot_analysis']['markdown'].path)
1725
- rg.append_team_markdown(self.input()['team_markdown'].path)
1726
- rg.append_project_plan_csv(self.input()['wbs_project123']['csv'].path)
1727
- rg.append_expert_criticism_markdown(self.input()['expert_review'].path)
 
 
 
 
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='w')
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.report_data = {}
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 append_assumptions_markdown(self, file_path: Path):
85
- """Append the assumptions markdown to the report."""
86
- markdown = self.read_markdown_file(file_path)
87
- if markdown:
88
- self.report_data['assumptions'] = markdown
89
-
90
- def append_pitch_markdown(self, file_path: Path):
91
- """Append the pitch markdown to the report."""
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 append_project_plan_csv(self, file_path: Path):
115
- """Append the project plan CSV to the report."""
116
- plan_df = self.read_csv_file(file_path)
117
- if plan_df is not None:
118
- # Clean up the dataframe
119
- # Remove any completely empty rows or columns
120
- plan_df = plan_df.dropna(how='all', axis=0).dropna(how='all', axis=1)
121
- self.report_data['project_plan'] = plan_df
 
 
 
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
- if 'pitch' in self.report_data:
148
- add_section('Project Pitch', markdown.markdown(self.report_data['pitch']))
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.append_pitch_markdown(input_path / FilenameEnum.PITCH_MARKDOWN.value)
217
- report_generator.append_assumptions_markdown(input_path / FilenameEnum.CONSOLIDATE_ASSUMPTIONS_MARKDOWN.value)
218
- report_generator.append_swot_analysis_markdown(input_path / FilenameEnum.SWOT_MARKDOWN.value)
219
- report_generator.append_team_markdown(input_path / FilenameEnum.TEAM_MARKDOWN.value)
220
- report_generator.append_expert_criticism_markdown(input_path / FilenameEnum.EXPERT_CRITICISM_MARKDOWN.value)
221
- report_generator.append_project_plan_csv(input_path / FilenameEnum.WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_CSV.value)
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.is_ollama_running: {llm_info.is_ollama_running}")
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
  """