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

Snapshot of PlanExe commit d1eee4b3f276c3f079b95539dafb7f1f794abfa4

Browse files
Files changed (39) hide show
  1. src/assume/README.md +33 -1
  2. src/assume/assumption_orchestrator.py +0 -72
  3. src/assume/currency_strategy.py +343 -0
  4. src/assume/distill_assumptions.py +39 -6
  5. src/assume/identify_plan_type.py +290 -0
  6. src/assume/identify_risks.py +231 -0
  7. src/assume/make_assumptions.py +41 -29
  8. src/assume/physical_locations.py +284 -0
  9. src/assume/review_assumptions.py +235 -0
  10. src/assume/test_data/currency_strategy1/001-plan.txt +7 -0
  11. src/assume/test_data/currency_strategy1/002-physical_locations.json +43 -0
  12. src/assume/test_data/currency_strategy2/001-plan.txt +7 -0
  13. src/assume/test_data/currency_strategy2/002-physical_locations.json +7 -0
  14. src/assume/test_data/currency_strategy3/001-plan.txt +7 -0
  15. src/assume/test_data/currency_strategy3/002-physical_locations.json +35 -0
  16. src/assume/test_data/currency_strategy4/001-plan.txt +7 -0
  17. src/assume/test_data/currency_strategy4/002-physical_locations.json +10 -0
  18. src/assume/test_data/currency_strategy5/001-plan.txt +7 -0
  19. src/assume/test_data/currency_strategy5/002-physical_locations.json +35 -0
  20. src/assume/test_data/currency_strategy6/001-plan.txt +7 -0
  21. src/assume/test_data/currency_strategy6/002-physical_locations.json +27 -0
  22. src/assume/test_data/currency_strategy7/001-plan.txt +7 -0
  23. src/assume/test_data/currency_strategy7/002-physical_locations.json +19 -0
  24. src/assume/test_data/review_assumptions1/001-plan.txt +7 -0
  25. src/assume/test_data/review_assumptions1/002-make_assumptions.json +42 -0
  26. src/assume/test_data/review_assumptions1/003-distill_assumptions.json +12 -0
  27. src/assume/test_data/review_assumptions2/001-plan.txt +7 -0
  28. src/assume/test_data/review_assumptions2/002-make_assumptions.json +42 -0
  29. src/assume/test_data/review_assumptions2/003-distill_assumptions.json +12 -0
  30. src/expert/pre_project_assessment.py +2 -0
  31. src/llm_util/ollama_info.py +63 -46
  32. src/pitch/convert_pitch_to_markdown.py +1 -1
  33. src/plan/app_text2plan.py +7 -0
  34. src/plan/data/simple_plan_prompts.jsonl +5 -1
  35. src/plan/filenames.py +16 -3
  36. src/plan/run_plan_pipeline.py +541 -112
  37. src/report/report_generator.py +53 -128
  38. src/report/report_template.html +143 -0
  39. src/utils/concat_files_into_string.py +20 -0
src/assume/README.md CHANGED
@@ -1,3 +1,35 @@
1
  # Assume
2
 
3
- Make assumptions about the project
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Assume
2
 
3
+ Make assumptions about the project. Identify what the project *will* and *will not* include.
4
+
5
+ ## 1. Identify Plan Type
6
+
7
+ - **Digital:** Is this a purely digital project. Can it be executed by agents.
8
+ - **Physical:** Or does this project require physical locations.
9
+
10
+ ## 2. Physical Locations
11
+
12
+ Where does it take place.
13
+
14
+ If it's a bridge between 2 countries, then both country A and country B are the physical locations.
15
+
16
+ ## 3. Currency
17
+
18
+ Decide what currency to use.
19
+
20
+ If the project takes place in one country, then that country's local currency may be relevant.
21
+ In case the local currency is volatile, then pick a more stable currency.
22
+
23
+ If it's a global project, then pick a global currency.
24
+
25
+ ## 4. Identify Risks
26
+
27
+ Understanding potential risks can help you make more informed and realistic assumptions.
28
+
29
+ ## 5. Informed Assumptions
30
+
31
+ Once you have a preliminary list of risks, you can use them to shape your assumptions.
32
+
33
+ ## 6. Reviewing Assumptions
34
+
35
+ Bad assumptions impacts the entire plan. Try identify what is wrong about the assumptions found so far.
src/assume/assumption_orchestrator.py DELETED
@@ -1,72 +0,0 @@
1
- """
2
- PROMPT> python -m src.assume.assumption_orchestrator
3
- """
4
- import logging
5
- from llama_index.core.llms.llm import LLM
6
- from src.assume.make_assumptions import MakeAssumptions
7
- from src.assume.distill_assumptions import DistillAssumptions
8
- from src.format_json_for_use_in_query import format_json_for_use_in_query
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
- class AssumptionOrchestrator:
13
- def __init__(self):
14
- self.phase1_post_callback = None
15
- self.phase2_post_callback = None
16
- self.make_assumptions: MakeAssumptions = None
17
- self.distill_assumptions: DistillAssumptions = None
18
-
19
- def execute(self, llm: LLM, query: str) -> None:
20
- logger.info("Making assumptions...")
21
-
22
- self.make_assumptions = MakeAssumptions.execute(llm, query)
23
- if self.phase1_post_callback:
24
- self.phase1_post_callback(self.make_assumptions)
25
-
26
- logger.info(f"Distilling assumptions...")
27
-
28
- assumptions_json_string = format_json_for_use_in_query(self.make_assumptions.assumptions)
29
-
30
- query2 = (
31
- f"{query}\n\n"
32
- f"assumption.json:\n{assumptions_json_string}"
33
- )
34
- self.distill_assumptions = DistillAssumptions.execute(llm, query2)
35
- if self.phase2_post_callback:
36
- self.phase2_post_callback(self.distill_assumptions)
37
-
38
- if __name__ == "__main__":
39
- import logging
40
- from src.llm_factory import get_llm
41
- from src.plan.find_plan_prompt import find_plan_prompt
42
- import json
43
-
44
- logging.basicConfig(
45
- level=logging.INFO,
46
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
47
- handlers=[
48
- logging.StreamHandler()
49
- ]
50
- )
51
-
52
- plan_prompt = find_plan_prompt("4dc34d55-0d0d-4e9d-92f4-23765f49dd29")
53
-
54
- llm = get_llm("ollama-llama3.1")
55
- # llm = get_llm("openrouter-paid-gemini-2.0-flash-001")
56
- # llm = get_llm("deepseek-chat")
57
-
58
- def phase1_post_callback(make_assumptions: MakeAssumptions) -> None:
59
- count = len(make_assumptions.assumptions)
60
- d = make_assumptions.to_dict(include_system_prompt=False, include_user_prompt=False)
61
- pretty = json.dumps(d, indent=2)
62
- print(f"MakeAssumptions: Made {count} assumptions:\n{pretty}")
63
-
64
- def phase2_post_callback(distill_assumptions: DistillAssumptions) -> None:
65
- d = distill_assumptions.to_dict(include_system_prompt=False, include_user_prompt=False)
66
- pretty = json.dumps(d, indent=2)
67
- print(f"DistillAssumptions:\n{pretty}")
68
-
69
- orchestrator = AssumptionOrchestrator()
70
- orchestrator.phase1_post_callback = phase1_post_callback
71
- orchestrator.phase2_post_callback = phase2_post_callback
72
- orchestrator.execute(llm, plan_prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/assume/currency_strategy.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pick a suitable currency for the project plan. If the description already includes the currency, then there is no need for this step.
3
+ If the currency is not mentioned, then the expert should suggest suitable locations based on the project requirements.
4
+ The project may go across national borders, so picking a currency that is widely accepted is important.
5
+
6
+ Currency codes
7
+ https://en.wikipedia.org/wiki/ISO_4217
8
+
9
+ PROMPT> python -m src.assume.currency_strategy
10
+ """
11
+ import os
12
+ import json
13
+ import time
14
+ import logging
15
+ from math import ceil
16
+ from dataclasses import dataclass
17
+ from typing import Optional
18
+ from pydantic import BaseModel, Field
19
+ from llama_index.core.llms import ChatMessage, MessageRole
20
+ from llama_index.core.llms.llm import LLM
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ class CurrencyItem(BaseModel):
25
+ currency: str = Field(
26
+ description="ISO 4217 alphabetic code."
27
+ )
28
+ consideration: str = Field(
29
+ description="Why use this currency."
30
+ )
31
+
32
+ class DocumentDetails(BaseModel):
33
+ money_involved: bool = Field(
34
+ description="True if the project likely involves any financial transactions (e.g., purchasing equipment, paying for services, travel, lab tests), otherwise False."
35
+ )
36
+ currency_list: list[CurrencyItem] = Field(
37
+ description="List of currencies that are relevant for this project."
38
+ )
39
+ primary_currency: Optional[str] = Field(
40
+ description="The main currency for budgeting and reporting (ISO 4217 alphabetic code).",
41
+ default=None
42
+ )
43
+ currency_strategy: str = Field(
44
+ description="A short summary of how to handle currency exchange and risk during the project.",
45
+ default=""
46
+ )
47
+
48
+ CURRENCY_STRATEGY_SYSTEM_PROMPT_1 = """
49
+ You are a world-class planning expert specializing in picking the best-suited currency for large, international projects. Currency decisions significantly impact project costs, reporting, and financial risk.
50
+
51
+ Here's your decision-making process:
52
+
53
+ 1. **Determine if money is potentially involved:**
54
+
55
+ * Set `money_involved` to `True` if the plan *potentially* requires any financial transactions, *direct or indirect*, such as:
56
+ * Buying goods or services (e.g., lab equipment, scientific instruments, sampling containers, software licenses, data sets).
57
+ * Paying for services (e.g., laboratory analysis, research assistance, data analysis, travel expenses, shipping samples, transcription services, professional editing, publication fees).
58
+ * Paying people (researchers, technicians, consultants, divers, boat crews, etc.) for their time and expertise.
59
+ * Renting equipment or facilities (e.g., lab space, boats, diving gear).
60
+ * Acquiring data (e.g., purchasing existing datasets, paying for access to databases).
61
+ * Travel.
62
+ * Maintaining systems.
63
+
64
+ * Set `money_involved` to `False` only if the plan is purely non-financial and has absolutely no potential impact on financial resources.
65
+
66
+ 2. **Select a primary currency:**
67
+
68
+ * **If a specific currency *can* be determined** based on the project description and location information (e.g., the project is clearly based in the USA):
69
+ * Select that currency (ISO 4217 code).
70
+ * Explain your reasoning (e.g., "USD is appropriate because the project is based in the USA").
71
+
72
+ * **If a specific currency *cannot* be determined** (e.g., the project is global, theoretical, or lacks clear financial details):
73
+ * Suggest USD for all international expenses, such as travel, sample analysis, web hosting, and publication fees.
74
+ * Explain your reasoning (e.g., "USD is a widely accepted currency and suitable for international research expenses.").
75
+
76
+ 3. **Identify additional currencies (if any):**
77
+
78
+ * List any other currencies that might be needed for local expenses or specific transactions.
79
+ * Explain why each currency is necessary (e.g., "EUR for travel expenses in Europe").
80
+
81
+ 4. **Develop a currency management strategy:**
82
+
83
+ * Provide a brief summary of how to manage currency exchange and risk (e.g., "Use forward contracts to hedge against currency fluctuations, especially for travel expenses.").
84
+
85
+ Here are a few examples of the desired output format:
86
+
87
+ **Example 1:**
88
+ Project: Constructing a solar power plant in Nevada, USA
89
+ money_involved: True
90
+ Currency List:
91
+ - USD: For all project-related expenses in the USA.
92
+ Primary Currency: USD
93
+ Currency Strategy: Use USD for all budgeting and accounting.
94
+
95
+ **Example 2:**
96
+ Project: Building a wind farm in the North Sea (offshore UK and Netherlands)
97
+ money_involved: True
98
+ Currency List:
99
+ - EUR: For equipment and services sourced from the Eurozone.
100
+ - GBP: For equipment and services sourced from the Eurozone.
101
+ - DKK: For Danish-based operations and services.
102
+ Primary Currency: EUR
103
+ Currency Strategy: EUR will be the primary currency. Maintain accounts in GBP and DKK for local expenses. Hedge against significant currency fluctuations.
104
+
105
+ **Example 3:**
106
+ Project: Take out the trash
107
+ money_involved: False
108
+ Currency List:
109
+ Primary Currency:
110
+ Currency Strategy:
111
+
112
+ **Example 4:**
113
+ Project: My daily commute is broken, need an alternative in Amsterdam.
114
+ money_involved: True # Potential for public transport, taxis, food, etc.
115
+ Currency List:
116
+ - EUR: For transportation and potential expenses in the Netherlands.
117
+ Primary Currency: EUR
118
+ Currency Strategy: Use EUR for all commute-related expenses.
119
+
120
+ **Example 5:**
121
+ Project: Distill Arxiv papers into an objective, hype-free summary and publish as an open-access dataset.
122
+ money_involved: True # Needs development, hosting, data scraping permission
123
+ Currency List:
124
+ - USD: For potential web hosting and software maintainence
125
+ Primary Currency: USD
126
+ Currency Strategy: Use USD for all web hosting and software maintainence.
127
+
128
+ **Example 6:**
129
+ Project: I'm envisioning a streamlined global language...
130
+ money_involved: True
131
+ Currency List:
132
+ - USD: Best guess for international expenses
133
+ Primary Currency: USD
134
+ Currency Strategy: Use USD for international expenses
135
+
136
+ **Example 7:**
137
+ Project: Create a detailed report examining microplastics within the world's oceans.
138
+ money_involved: True # Travel, lab tests, analysis
139
+ Currency List:
140
+ - USD: Best guess for international expenses
141
+ Primary Currency: USD
142
+ Currency Strategy: Use USD for international expenses
143
+
144
+ Consider the following factors when selecting currencies:
145
+
146
+ * Stability: Choose currencies that are relatively stable to minimize the impact of exchange rate fluctuations on the project budget.
147
+ * Transaction Costs: Minimize the impact of currency conversions to reduce transaction fees.
148
+ * Economic Influence: Consider the economic influence of the countries involved and the currencies used by major suppliers and contractors.
149
+ * Reporting Requirements: Think about the reporting needs of stakeholders and investors.
150
+ * Project Duration: Longer projects are more susceptible to currency risk.
151
+ * Accounting and Tax Implications: Be aware of the accounting and tax rules regarding currency conversions.
152
+ * Important Currency Facts: England uses the British Pound (GBP). Denmark uses the Danish Krone (DKK), NOT the Euro.
153
+
154
+ Given the project description and location information, provide the following:
155
+
156
+ 1. money_involved (True/False)
157
+ 2. currency_list
158
+ 3. primary_currency
159
+ 4. currency_strategy
160
+
161
+ Be precise with your reasoning, and avoid making inaccurate statements about which countries use which currencies.
162
+ """
163
+
164
+ CURRENCY_STRATEGY_SYSTEM_PROMPT_2 = """
165
+ You are an expert planning assistant focused on selecting the best currency for projects of varying scales, from trivial personal tasks to large, international endeavors. Given a project description and any location details, produce a JSON output with the following structure:
166
+
167
+ {
168
+ "money_involved": <Boolean>,
169
+ "currency_list": [
170
+ {
171
+ "currency": "<ISO 4217 Code>",
172
+ "consideration": "<Brief explanation>"
173
+ },
174
+ ...
175
+ ],
176
+ "primary_currency": "<ISO 4217 Code>",
177
+ "currency_strategy": "<Brief explanation of currency management strategy>"
178
+ }
179
+
180
+ Guidelines:
181
+
182
+ 1. money_involved:
183
+ - Set to True if the project likely involves financial transactions such as purchasing equipment, paying for services, travel, repairs, lab tests, or any significant expenses requiring budgeting.
184
+ - Also mark digital, research, or industrial projects as involving money if they require development, data curation, hosting, publication fees, maintenance, or research staff—even if no physical site is needed.
185
+ - Set to False for trivial or personal tasks with minimal or no financial transactions.
186
+ - Note: Even for personal tasks, if the issue implies potential expenses (e.g., a broken bike requiring repairs or alternative transportation costs), mark money_involved as True.
187
+
188
+ 2. currency_list:
189
+ - Provide a list of relevant currencies as objects. Each object should include:
190
+ - currency: the ISO 4217 code.
191
+ - consideration: a brief explanation of why this currency is included.
192
+ - For projects that are clearly local (confined to one country) and not subject to economic instability, list only the local currency.
193
+ - For projects spanning multiple countries, list the local currencies for the countries involved if relevant.
194
+ - For projects in regions with multiple European countries, use EUR as the primary currency.
195
+ - If the project is in a country with known currency instability or hyperinflation, include both the local currency and a stable international currency (e.g., USD) in the list.
196
+
197
+ 3. primary_currency:
198
+ - If the project description explicitly mentions a specific currency, use that only if it does not conflict with the guidelines below.
199
+ - For projects that are clearly local in stable economies, use that country's official currency.
200
+ - For international projects that are not specific to one region, default to "USD".
201
+ - For projects spanning multiple European countries, select "EUR" as the primary currency.
202
+ - For significant projects in countries with notable currency instability (such as Venezuela), **do not use the local currency as primary; instead, set the primary currency to "USD"**. The local currency may still be included in the currency_list for local transactions.
203
+
204
+ 4. currency_strategy:
205
+ - For local projects, simply state that the local currency will be used for all transactions with no additional international risk management needed.
206
+ - For international projects, provide a brief explanation of how to manage currency risks (e.g., hedging against exchange fluctuations or using cards with no foreign transaction fees).
207
+ - For projects spanning multiple European countries with "EUR" as the primary currency, note that EUR will be used for consolidated budgeting while local currencies may still be used for local transactions.
208
+ - For projects in countries with currency instability, explain that a stable international currency (e.g., USD) is recommended for budgeting and reporting to mitigate risks from hyperinflation, and that for significant projects the primary currency must be "USD".
209
+
210
+ Key Instructions:
211
+ - Evaluate the project's scale, geographic scope, and local economic conditions using the provided project description and location details.
212
+ - Ensure that no field is left empty when significant expenses are expected.
213
+ - Apply the appropriate currency guidelines based on the project's geographic scope, local economic conditions, and scale.
214
+ """
215
+
216
+ CURRENCY_STRATEGY_SYSTEM_PROMPT = CURRENCY_STRATEGY_SYSTEM_PROMPT_2
217
+
218
+ @dataclass
219
+ class CurrencyStrategy:
220
+ """
221
+ Take a look at the vague plan description, the physical locations and suggest a currency.
222
+ """
223
+ system_prompt: str
224
+ user_prompt: str
225
+ response: dict
226
+ metadata: dict
227
+ markdown: str
228
+
229
+ @classmethod
230
+ def execute(cls, llm: LLM, user_prompt: str) -> 'CurrencyStrategy':
231
+ """
232
+ Invoke LLM with the project description.
233
+ """
234
+ if not isinstance(llm, LLM):
235
+ raise ValueError("Invalid LLM instance.")
236
+ if not isinstance(user_prompt, str):
237
+ raise ValueError("Invalid user_prompt.")
238
+
239
+ logger.debug(f"User Prompt:\n{user_prompt}")
240
+
241
+ system_prompt = CURRENCY_STRATEGY_SYSTEM_PROMPT.strip()
242
+
243
+ chat_message_list = [
244
+ ChatMessage(
245
+ role=MessageRole.SYSTEM,
246
+ content=system_prompt,
247
+ ),
248
+ ChatMessage(
249
+ role=MessageRole.USER,
250
+ content=user_prompt,
251
+ )
252
+ ]
253
+
254
+ sllm = llm.as_structured_llm(DocumentDetails)
255
+ start_time = time.perf_counter()
256
+ try:
257
+ chat_response = sllm.chat(chat_message_list)
258
+ except Exception as e:
259
+ logger.debug(f"LLM chat interaction failed: {e}")
260
+ logger.error("LLM chat interaction failed.", exc_info=True)
261
+ raise ValueError("LLM chat interaction failed.") from e
262
+
263
+ end_time = time.perf_counter()
264
+ duration = int(ceil(end_time - start_time))
265
+ response_byte_count = len(chat_response.message.content.encode('utf-8'))
266
+ logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
267
+
268
+ json_response = chat_response.raw.model_dump()
269
+
270
+ metadata = dict(llm.metadata)
271
+ metadata["llm_classname"] = llm.class_name()
272
+ metadata["duration"] = duration
273
+ metadata["response_byte_count"] = response_byte_count
274
+
275
+ markdown = cls.convert_to_markdown(chat_response.raw)
276
+
277
+ result = CurrencyStrategy(
278
+ system_prompt=system_prompt,
279
+ user_prompt=user_prompt,
280
+ response=json_response,
281
+ metadata=metadata,
282
+ markdown=markdown
283
+ )
284
+ return result
285
+
286
+ def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
287
+ d = self.response.copy()
288
+ if include_metadata:
289
+ d['metadata'] = self.metadata
290
+ if include_system_prompt:
291
+ d['system_prompt'] = self.system_prompt
292
+ if include_user_prompt:
293
+ d['user_prompt'] = self.user_prompt
294
+ return d
295
+
296
+ def save_raw(self, file_path: str) -> None:
297
+ with open(file_path, 'w') as f:
298
+ f.write(json.dumps(self.to_dict(), indent=2))
299
+
300
+ @staticmethod
301
+ def convert_to_markdown(document_details: DocumentDetails) -> str:
302
+ """
303
+ Convert the raw document details to markdown.
304
+ """
305
+ rows = []
306
+
307
+ if document_details.money_involved:
308
+ rows.append("This plan involves money.")
309
+ else:
310
+ rows.append("This plan **does not** involve money.")
311
+
312
+ if len(document_details.currency_list) > 0:
313
+ rows.append("\n## Currencies\n")
314
+ for currency_item in document_details.currency_list:
315
+ rows.append(f"- **{currency_item.currency}:** {currency_item.consideration}")
316
+ else:
317
+ rows.append("No currencies identified.")
318
+
319
+ rows.append(f"\n**Primary currency:** {document_details.primary_currency}")
320
+ rows.append(f"\n**Currency strategy:** {document_details.currency_strategy}")
321
+ return "\n".join(rows)
322
+
323
+ def save_markdown(self, output_file_path: str):
324
+ with open(output_file_path, 'w', encoding='utf-8') as out_f:
325
+ out_f.write(self.markdown)
326
+
327
+ if __name__ == "__main__":
328
+ from src.llm_factory import get_llm
329
+ from src.utils.concat_files_into_string import concat_files_into_string
330
+
331
+ base_path = os.path.join(os.path.dirname(__file__), 'test_data', 'currency_strategy7')
332
+
333
+ all_documents_string = concat_files_into_string(base_path)
334
+ print(all_documents_string)
335
+
336
+ llm = get_llm("ollama-llama3.1")
337
+
338
+ currency_strategy = CurrencyStrategy.execute(llm, all_documents_string)
339
+ json_response = currency_strategy.to_dict(include_system_prompt=False, include_user_prompt=False)
340
+ print("\n\nResponse:")
341
+ print(json.dumps(json_response, indent=2))
342
+
343
+ print(f"\n\nMarkdown:\n{currency_strategy.markdown}")
src/assume/distill_assumptions.py CHANGED
@@ -8,6 +8,14 @@ The llama3.1 has no problems with it.
8
 
9
  IDEA: Sometimes it recognizes that the project starts ASAP as an assumption. This is already part of the project description, this is not something new.
10
  How do I suppress this kind of information from the output?
 
 
 
 
 
 
 
 
11
  """
12
  import json
13
  import time
@@ -126,6 +134,7 @@ class DistillAssumptions:
126
  user_prompt: str
127
  response: dict
128
  metadata: dict
 
129
 
130
  @classmethod
131
  def execute(cls, llm: LLM, user_prompt: str, **kwargs: Any) -> 'DistillAssumptions':
@@ -155,9 +164,9 @@ class DistillAssumptions:
155
  if system_prompt and not isinstance(system_prompt, str):
156
  raise ValueError("Invalid system prompt.")
157
 
158
- chat_message_list1 = []
159
  if system_prompt:
160
- chat_message_list1.append(
161
  ChatMessage(
162
  role=MessageRole.SYSTEM,
163
  content=system_prompt,
@@ -169,16 +178,16 @@ class DistillAssumptions:
169
  role=MessageRole.USER,
170
  content=user_prompt,
171
  )
172
- chat_message_list1.append(chat_message_user)
173
 
174
  sllm = llm.as_structured_llm(AssumptionDetails)
175
 
176
  logger.debug("Starting LLM chat interaction.")
177
  start_time = time.perf_counter()
178
- chat_response1 = sllm.chat(chat_message_list1)
179
  end_time = time.perf_counter()
180
  duration = int(ceil(end_time - start_time))
181
- response_byte_count = len(chat_response1.message.content.encode('utf-8'))
182
  logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
183
 
184
  metadata = dict(llm.metadata)
@@ -187,16 +196,19 @@ class DistillAssumptions:
187
  metadata["response_byte_count"] = response_byte_count
188
 
189
  try:
190
- json_response = json.loads(chat_response1.message.content)
191
  except json.JSONDecodeError as e:
192
  logger.error("Failed to parse LLM response as JSON.", exc_info=True)
193
  raise ValueError("Invalid JSON response from LLM.") from e
194
 
 
 
195
  result = DistillAssumptions(
196
  system_prompt=system_prompt,
197
  user_prompt=user_prompt,
198
  response=json_response,
199
  metadata=metadata,
 
200
  )
201
  logger.debug("DistillAssumptions instance created successfully.")
202
  return result
@@ -215,6 +227,25 @@ class DistillAssumptions:
215
  with open(file_path, 'w') as f:
216
  f.write(json.dumps(self.to_dict(), indent=2))
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  if __name__ == "__main__":
219
  import os
220
  import logging
@@ -248,3 +279,5 @@ if __name__ == "__main__":
248
 
249
  print("\n\nResponse:")
250
  print(json.dumps(result.to_dict(include_system_prompt=False, include_user_prompt=False), indent=2))
 
 
 
8
 
9
  IDEA: Sometimes it recognizes that the project starts ASAP as an assumption. This is already part of the project description, this is not something new.
10
  How do I suppress this kind of information from the output?
11
+
12
+ IDEA: If there is a mismatch between the number of assumptions in the input and the output.
13
+ Then it's likely that one or more assumptions are getting lost or introduced.
14
+ The number of assumptions should be the same in the input and output.
15
+ Ideally track the assumptions in the input with a uuid, that stays the same in the output.
16
+ If one of the input assumptions gets splitted into 2 assumptions, then the source id should be the same for both.
17
+
18
+ PROMPT> python -m src.assume.distill_assumptions
19
  """
20
  import json
21
  import time
 
134
  user_prompt: str
135
  response: dict
136
  metadata: dict
137
+ markdown: str
138
 
139
  @classmethod
140
  def execute(cls, llm: LLM, user_prompt: str, **kwargs: Any) -> 'DistillAssumptions':
 
164
  if system_prompt and not isinstance(system_prompt, str):
165
  raise ValueError("Invalid system prompt.")
166
 
167
+ chat_message_list = []
168
  if system_prompt:
169
+ chat_message_list.append(
170
  ChatMessage(
171
  role=MessageRole.SYSTEM,
172
  content=system_prompt,
 
178
  role=MessageRole.USER,
179
  content=user_prompt,
180
  )
181
+ chat_message_list.append(chat_message_user)
182
 
183
  sllm = llm.as_structured_llm(AssumptionDetails)
184
 
185
  logger.debug("Starting LLM chat interaction.")
186
  start_time = time.perf_counter()
187
+ chat_response = sllm.chat(chat_message_list)
188
  end_time = time.perf_counter()
189
  duration = int(ceil(end_time - start_time))
190
+ response_byte_count = len(chat_response.message.content.encode('utf-8'))
191
  logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
192
 
193
  metadata = dict(llm.metadata)
 
196
  metadata["response_byte_count"] = response_byte_count
197
 
198
  try:
199
+ json_response = json.loads(chat_response.message.content)
200
  except json.JSONDecodeError as e:
201
  logger.error("Failed to parse LLM response as JSON.", exc_info=True)
202
  raise ValueError("Invalid JSON response from LLM.") from e
203
 
204
+ markdown = cls.convert_to_markdown(chat_response.raw)
205
+
206
  result = DistillAssumptions(
207
  system_prompt=system_prompt,
208
  user_prompt=user_prompt,
209
  response=json_response,
210
  metadata=metadata,
211
+ markdown=markdown
212
  )
213
  logger.debug("DistillAssumptions instance created successfully.")
214
  return result
 
227
  with open(file_path, 'w') as f:
228
  f.write(json.dumps(self.to_dict(), indent=2))
229
 
230
+ @staticmethod
231
+ def convert_to_markdown(assumption_details: AssumptionDetails) -> str:
232
+ """
233
+ Convert the raw document details to markdown.
234
+ """
235
+ rows = []
236
+
237
+ if len(assumption_details.assumption_list) > 0:
238
+ for assumption in assumption_details.assumption_list:
239
+ rows.append(f"- {assumption}")
240
+ else:
241
+ rows.append("**No distilled assumptions:** It's unusual that a plan has no assumptions. Please check if the input data is contains assumptions. Please report to the developer of PlanExe.")
242
+
243
+ return "\n".join(rows)
244
+
245
+ def save_markdown(self, output_file_path: str):
246
+ with open(output_file_path, 'w', encoding='utf-8') as out_f:
247
+ out_f.write(self.markdown)
248
+
249
  if __name__ == "__main__":
250
  import os
251
  import logging
 
279
 
280
  print("\n\nResponse:")
281
  print(json.dumps(result.to_dict(include_system_prompt=False, include_user_prompt=False), indent=2))
282
+
283
+ print(f"\n\nMarkdown:\n{result.markdown}")
src/assume/identify_plan_type.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Determine if the plan can be executed digitally without any physical location. Or if the plan requires a physical location.
3
+
4
+ PROMPT> python -m src.assume.identify_plan_type
5
+ """
6
+ import os
7
+ import json
8
+ import time
9
+ import logging
10
+ from math import ceil
11
+ from enum import Enum
12
+ from dataclasses import dataclass
13
+ from pydantic import BaseModel, Field
14
+ from llama_index.core.llms import ChatMessage, MessageRole
15
+ from llama_index.core.llms.llm import LLM
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class PlanType(str, Enum):
20
+ # A plan that can be executed digitally without any physical location.
21
+ digital = 'digital'
22
+ # A plan that requires a physical location.
23
+ physical = 'physical'
24
+
25
+ class DocumentDetails(BaseModel):
26
+ explanation: str = Field(
27
+ description="Providing a high level context."
28
+ )
29
+ plan_type: PlanType = Field(
30
+ description="Classify the type of plan."
31
+ )
32
+
33
+ PLAN_TYPE_SYSTEM_PROMPT = """
34
+ You are a world-class planning expert specializing in real-world physical locations. Your *default assumption* should be that a plan *requires* a physical element. You are trying to identify plans that lead to actionable, real-world outcomes. Only classify a plan as "digital" if you are *absolutely certain* it can be executed entirely online *without any benefit* from a physical activity or location.
35
+
36
+ Use the following guidelines:
37
+
38
+ ## JSON Model
39
+
40
+ ### DocumentDetails
41
+ - **explanation** (string):
42
+ - A *detailed* explanation of why the plan type was chosen. You must *justify* your choice, especially if you classify a plan as "digital".
43
+ - If `plan_type` is `digital`, you *must* clearly explain why the plan can be fully automated, has no physical requirements *whatsoever*, and *gains no benefit* from a physical presence.
44
+
45
+ - **plan_type** (PlanType):
46
+ - `physical` if the user’s plan *might* involve a physical location, *could benefit* from a physical activity, or *requires* a physical resource. **If there's *any doubt*, classify the plan as `physical`. Examples include: shopping, travel, preparation, setup, construction, repair, in-person meetings, physical testing of products, etc.**
47
+ - `digital` only if the plan can *exclusively* be completed online with absolutely no benefit from a physical presence.
48
+
49
+ ---
50
+
51
+ ## Recognizing Implied Physical Requirements
52
+
53
+ Even if a plan *seems* primarily digital or abstract, carefully consider its *implied physical requirements*. These are common, often overlooked actions needed to make the plan happen:
54
+
55
+ - **Acquiring materials:** Does the plan require buying supplies at a store (e.g., groceries, hardware, art supplies, software)?
56
+ - **Preparation:** Does the plan require physical preparation or setup (e.g., cooking, setting up equipment, cleaning a space, installing software)?
57
+ - **Testing:** Does the plan involve testing a product or service in a real-world environment?
58
+ - **Development:** Does the plan involve physical location for development or meetings?
59
+ - **Transportation:** Does the plan involve traveling to a location, even if the main activity is digital (e.g., working from a coffee shop)?
60
+ - **Location:** Do you want to work in a specific location?
61
+
62
+ If a plan has *any* of these implied physical requirements, it should be classified as `physical`.
63
+
64
+ ---
65
+
66
+ ## Addressing "Software Development" Plans
67
+
68
+ Creating software often *seems* purely digital, but it rarely is. Consider these physical elements:
69
+
70
+ - **Development Environment:** Developers need a physical workspace (home office, co-working space, office).
71
+ - **Physical Hardware:** Developers need a computer, keyboard, monitor, etc.
72
+ - **Collaboration:** Software projects often involve in-person meetings and collaboration.
73
+ - **Testing:** Software often needs to be tested on physical devices (phones, tablets, computers, etc.) in real-world conditions.
74
+
75
+ **Therefore, plans involving software development should generally be classified as `physical` unless they are extremely simple and can be completed entirely in the cloud with no human interaction.**
76
+
77
+ ---
78
+
79
+ Example scenarios:
80
+
81
+ - **Implied Physical Location - Eiffel Tower:**
82
+ Given "Visit the Eiffel Tower."
83
+ The correct output is:
84
+ {
85
+ "explanation": "The plan *unequivocally requires* a physical presence in Paris, France.",
86
+ "plan_type": "physical"
87
+ }
88
+
89
+ - **Purely Digital / No Physical Location**
90
+ Given "Print hello world in Python."
91
+ The correct output is:
92
+ {
93
+ "explanation": "This task is *unquestionably* digital. A LLM can generate the python code; no human or physical task is involved.",
94
+ "plan_type": "digital"
95
+ }
96
+
97
+ - **Implied Physical Requirement - Developing a mobile app**
98
+ Given "The plan involves creating a mobile app."
99
+ The correct output is:
100
+ {
101
+ "explanation": "The plan involves creating a mobile app. This requires developers that requires location for the workspace, as well testing the app in real-world environments.",
102
+ "plan_type": "physical"
103
+ }
104
+
105
+ - **Location - Paris / Requires On-site Research**
106
+ Given "Write a blog post about Paris, my travel journal with real photos."
107
+ The correct output is:
108
+ {
109
+ "explanation": "Taking high-quality photographs of Paris requires on-site research and physical travel to those locations. This has a *clear* physical element.",
110
+ "plan_type": "physical"
111
+ }
112
+
113
+ - **Location - Paris / Requires No Physical Location**
114
+ Given "Write a blog post about Paris, listing the top attractions."
115
+ The correct output is:
116
+ {
117
+ "explanation": "While Paris is the subject, the plan *doesn't* require the writer to be in Paris. The content can be created with a LLM.",
118
+ "plan_type": "digital"
119
+ }
120
+
121
+ - **Implied Physical Requirement - Grocery Shopping:**
122
+ Given "Make spaghetti for dinner."
123
+ The correct output is:
124
+ {
125
+ "explanation": "Making spaghetti *requires* grocery shopping, followed by physical cooking. This *inherently involves* physical components.",
126
+ "plan_type": "physical"
127
+ }
128
+
129
+ - **Implied Physical Requirement - Home Repair:**
130
+ Given "Fix a leaky faucet."
131
+ The correct output is:
132
+ {
133
+ "explanation": "Fixing a leaky faucet *requires* physically inspecting it, acquiring tools, and performing the repair. This is *clearly* a physical task.",
134
+ "plan_type": "physical"
135
+ }
136
+
137
+ - **INCORRECT - Digital (Grocery Shopping Wrongly Ignored):**
138
+ Given "Bake a cake for my friend's birthday."
139
+ The **incorrect** output is:
140
+ {
141
+ "explanation": "Baking is a creative activity that can be planned online.",
142
+ "plan_type": "digital"
143
+ }
144
+
145
+ The **correct** output is:
146
+ {
147
+ "explanation": "Baking a cake *unquestionably requires* shopping for ingredients and physical baking. This is *clearly* a physical task.",
148
+ "plan_type": "physical"
149
+ }
150
+
151
+ - **INCORRECT - Digital (Implied Travel Wrongly Ignored):**
152
+ Given "Work on my presentation at a coffee shop."
153
+ The **incorrect** output is:
154
+ {
155
+ "explanation": "The primary task is working on a digital presentation.",
156
+ "plan_type": "digital"
157
+ }
158
+
159
+ The **correct** output is:
160
+ {
161
+ "explanation": "Working at a coffee shop *requires* traveling to the coffee shop. This *automatically* makes it a physical task.",
162
+ "plan_type": "physical"
163
+ }
164
+ """
165
+
166
+ @dataclass
167
+ class IdentifyPlanType:
168
+ """
169
+ Take a look at the vague plan description and determine:
170
+ - If it's a plan that can be executed digitally, without any physical location.
171
+ - Or if the plan requires a physical location.
172
+ """
173
+ system_prompt: str
174
+ user_prompt: str
175
+ response: dict
176
+ metadata: dict
177
+ markdown: str
178
+
179
+ @classmethod
180
+ def execute(cls, llm: LLM, user_prompt: str) -> 'IdentifyPlanType':
181
+ """
182
+ Invoke LLM with the project description.
183
+ """
184
+ if not isinstance(llm, LLM):
185
+ raise ValueError("Invalid LLM instance.")
186
+ if not isinstance(user_prompt, str):
187
+ raise ValueError("Invalid user_prompt.")
188
+
189
+ logger.debug(f"User Prompt:\n{user_prompt}")
190
+
191
+ system_prompt = PLAN_TYPE_SYSTEM_PROMPT.strip()
192
+
193
+ chat_message_list = [
194
+ ChatMessage(
195
+ role=MessageRole.SYSTEM,
196
+ content=system_prompt,
197
+ ),
198
+ ChatMessage(
199
+ role=MessageRole.USER,
200
+ content=user_prompt,
201
+ )
202
+ ]
203
+
204
+ sllm = llm.as_structured_llm(DocumentDetails)
205
+ start_time = time.perf_counter()
206
+ try:
207
+ chat_response = sllm.chat(chat_message_list)
208
+ except Exception as e:
209
+ logger.debug(f"LLM chat interaction failed: {e}")
210
+ logger.error("LLM chat interaction failed.", exc_info=True)
211
+ raise ValueError("LLM chat interaction failed.") from e
212
+
213
+ end_time = time.perf_counter()
214
+ duration = int(ceil(end_time - start_time))
215
+ response_byte_count = len(chat_response.message.content.encode('utf-8'))
216
+ logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
217
+
218
+ json_response = chat_response.raw.model_dump()
219
+
220
+ metadata = dict(llm.metadata)
221
+ metadata["llm_classname"] = llm.class_name()
222
+ metadata["duration"] = duration
223
+ metadata["response_byte_count"] = response_byte_count
224
+
225
+ markdown = cls.convert_to_markdown(chat_response.raw)
226
+
227
+ result = IdentifyPlanType(
228
+ system_prompt=system_prompt,
229
+ user_prompt=user_prompt,
230
+ response=json_response,
231
+ metadata=metadata,
232
+ markdown=markdown
233
+ )
234
+ return result
235
+
236
+ def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
237
+ d = self.response.copy()
238
+ if include_metadata:
239
+ d['metadata'] = self.metadata
240
+ if include_system_prompt:
241
+ d['system_prompt'] = self.system_prompt
242
+ if include_user_prompt:
243
+ d['user_prompt'] = self.user_prompt
244
+ return d
245
+
246
+ def save_raw(self, file_path: str) -> None:
247
+ with open(file_path, 'w') as f:
248
+ f.write(json.dumps(self.to_dict(), indent=2))
249
+
250
+ @staticmethod
251
+ def convert_to_markdown(document_details: DocumentDetails) -> str:
252
+ """
253
+ Convert the raw document details to markdown.
254
+ """
255
+ rows = []
256
+
257
+ if document_details.plan_type == PlanType.digital:
258
+ rows.append("This plan is purely digital and can be automated. There is no need for any physical locations.")
259
+ elif document_details.plan_type == PlanType.physical:
260
+ rows.append("This plan requires one or more physical locations. It cannot be executed digitally.")
261
+ else:
262
+ rows.append(f"Invalid plan type. {document_details.plan_type}")
263
+
264
+ rows.append(f"\n**Explanation:** {document_details.explanation}")
265
+ return "\n".join(rows)
266
+
267
+ def save_markdown(self, output_file_path: str):
268
+ with open(output_file_path, 'w', encoding='utf-8') as out_f:
269
+ out_f.write(self.markdown)
270
+
271
+ if __name__ == "__main__":
272
+ from src.llm_factory import get_llm
273
+ from src.plan.find_plan_prompt import find_plan_prompt
274
+
275
+ llm = get_llm("ollama-llama3.1")
276
+
277
+ plan_prompt = find_plan_prompt("de626417-4871-4acc-899d-2c41fd148807")
278
+ query = (
279
+ f"{plan_prompt}\n\n"
280
+ "Today's date:\n2025-Feb-27\n\n"
281
+ "Project start ASAP"
282
+ )
283
+ print(f"Query: {query}")
284
+
285
+ identify_plan_type = IdentifyPlanType.execute(llm, query)
286
+ json_response = identify_plan_type.to_dict(include_system_prompt=False, include_user_prompt=False)
287
+ print("\n\nResponse:")
288
+ print(json.dumps(json_response, indent=2))
289
+
290
+ print(f"\n\nMarkdown:\n{identify_plan_type.markdown}")
src/assume/identify_risks.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Identify risks in the project plan.
3
+
4
+ As of 2025-03-03, the result is sensitive to what LLM is being used.
5
+ - Good `openrouter-paid-gemini-2.0-flash-001`.
6
+ - Medium `openrouter-paid-openai-gpt-4o-mini`.
7
+ - Bad `ollama-llama3.1`.
8
+
9
+ IDEA: assign uuid's to each risk. So later stages of the plan can refer to the risks by their uuid's.
10
+
11
+ PROMPT> python -m src.assume.identify_risks
12
+ """
13
+ import os
14
+ import json
15
+ import time
16
+ import logging
17
+ from math import ceil
18
+ from enum import Enum
19
+ from dataclasses import dataclass
20
+ from pydantic import BaseModel, Field
21
+ from llama_index.core.llms import ChatMessage, MessageRole
22
+ from llama_index.core.llms.llm import LLM
23
+ from src.format_json_for_use_in_query import format_json_for_use_in_query
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ class LowMediumHigh(str, Enum):
28
+ low = 'low'
29
+ medium = 'medium'
30
+ high = 'high'
31
+
32
+ class RiskItem(BaseModel):
33
+ risk_area: str = Field(
34
+ description="The category or domain of the risk, e.g., Regulatory, Financial, Technical."
35
+ )
36
+ risk_description: str = Field(
37
+ description="A detailed explanation outlining the specific nature of the risk."
38
+ )
39
+ potential_impact: str = Field(
40
+ description="Possible consequences or adverse effects on the project if the risk materializes."
41
+ )
42
+ likelihood: LowMediumHigh = Field(
43
+ description="A qualitative measure (e.g., low, medium, high) indicating the probability that the risk will occur."
44
+ )
45
+ severity: LowMediumHigh = Field(
46
+ description="A qualitative measure (e.g., low, medium, high) describing the extent of the potential negative impact if the risk occurs."
47
+ )
48
+ action: str = Field(
49
+ description="Recommended mitigation strategies or steps to reduce the likelihood or impact of the risk."
50
+ )
51
+
52
+ class DocumentDetails(BaseModel):
53
+ risks: list[RiskItem] = Field(
54
+ description="A list of identified project risks."
55
+ )
56
+ risk_assessment_summary: str = Field(
57
+ description="Providing a high level context."
58
+ )
59
+
60
+ IDENTIFY_RISKS_SYSTEM_PROMPT = """
61
+ You are a world-class planning expert with extensive experience in risk management for a wide range of projects, from small personal tasks to large-scale business ventures. Your objective is to identify potential risks that could jeopardize the success of a project based on its description. When analyzing the project plan, please consider and include the following aspects:
62
+
63
+ - **Risk Identification & Categorization:**
64
+ Analyze the project description thoroughly and identify risks across various domains such as Regulatory & Permitting, Technical, Financial, Environmental, Social, Operational, Supply Chain, and Security. Also consider integration with existing infrastructure, market or competitive risks (if applicable), and long-term sustainability. Be creative and consider even non-obvious factors.
65
+
66
+ - **Detailed Risk Descriptions:**
67
+ For each risk, provide a detailed explanation of what might go wrong and why it is a concern. Include aspects such as integration challenges with existing systems, maintenance difficulties, or long-term sustainability if relevant.
68
+
69
+ - **Quantification of Potential Impact:**
70
+ Where possible, quantify the potential impact. Include estimates of time delays (e.g., “a delay of 2–4 weeks”), financial overruns (e.g., “an extra cost of 5,000–10,000 in the project’s local currency”), and other measurable consequences. Use the appropriate currency or unit based on the project context.
71
+
72
+ - **Likelihood and Severity Assessments:**
73
+ Assess both the probability of occurrence (low, medium, high) and the potential severity of each risk (low, medium, high). Remember that even low-probability risks can have high severity.
74
+
75
+ - **Actionable Mitigation Strategies:**
76
+ For every identified risk, propose clear, actionable mitigation strategies. Explain how these steps can reduce either the likelihood or the impact of the risk.
77
+
78
+ - **Assumptions and Missing Information:**
79
+ If the project description is vague or key details are missing, explicitly note your assumptions and the potential impact of these uncertainties on the risk assessment.
80
+
81
+ - **Strategic Summary:**
82
+ Finally, provide a concise summary that highlights the 2–3 most critical risks that, if not properly managed, could significantly jeopardize the project’s success. Discuss any trade-offs or overlapping mitigation strategies.
83
+
84
+ Output your findings as a JSON object with the following structure:
85
+
86
+ {
87
+ "risks": [
88
+ {
89
+ "risk_area": "The category or domain of the risk (e.g., Regulatory & Permitting)",
90
+ "risk_description": "A detailed explanation outlining the specific nature of the risk.",
91
+ "potential_impact": "Possible consequences or adverse effects on the project if the risk materializes, with quantifiable details where feasible.",
92
+ "likelihood": "A qualitative measure (low, medium or high) indicating the probability that the risk will occur.",
93
+ "severity": "A qualitative measure (low, medium or high) describing the potential negative impact if the risk occurs.",
94
+ "action": "Recommended mitigation strategies or steps to reduce the likelihood or impact of the risk."
95
+ },
96
+ ...
97
+ ],
98
+ "risk_assessment_summary": "A concise summary of the overall risk landscape and the most critical risks."
99
+ }
100
+ """
101
+
102
+ @dataclass
103
+ class IdentifyRisks:
104
+ """
105
+ Take a look at the vague plan description and identify risks.
106
+ """
107
+ system_prompt: str
108
+ user_prompt: str
109
+ response: dict
110
+ metadata: dict
111
+ markdown: str
112
+
113
+ @classmethod
114
+ def execute(cls, llm: LLM, user_prompt: str) -> 'IdentifyRisks':
115
+ """
116
+ Invoke LLM with the project description.
117
+ """
118
+ if not isinstance(llm, LLM):
119
+ raise ValueError("Invalid LLM instance.")
120
+ if not isinstance(user_prompt, str):
121
+ raise ValueError("Invalid user_prompt.")
122
+
123
+ logger.debug(f"User Prompt:\n{user_prompt}")
124
+
125
+ system_prompt = IDENTIFY_RISKS_SYSTEM_PROMPT.strip()
126
+
127
+ chat_message_list = [
128
+ ChatMessage(
129
+ role=MessageRole.SYSTEM,
130
+ content=system_prompt,
131
+ ),
132
+ ChatMessage(
133
+ role=MessageRole.USER,
134
+ content=user_prompt,
135
+ )
136
+ ]
137
+
138
+ sllm = llm.as_structured_llm(DocumentDetails)
139
+ start_time = time.perf_counter()
140
+ try:
141
+ chat_response = sllm.chat(chat_message_list)
142
+ except Exception as e:
143
+ logger.debug(f"LLM chat interaction failed: {e}")
144
+ logger.error("LLM chat interaction failed.", exc_info=True)
145
+ raise ValueError("LLM chat interaction failed.") from e
146
+
147
+ end_time = time.perf_counter()
148
+ duration = int(ceil(end_time - start_time))
149
+ response_byte_count = len(chat_response.message.content.encode('utf-8'))
150
+ logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
151
+
152
+ json_response = chat_response.raw.model_dump()
153
+
154
+ metadata = dict(llm.metadata)
155
+ metadata["llm_classname"] = llm.class_name()
156
+ metadata["duration"] = duration
157
+ metadata["response_byte_count"] = response_byte_count
158
+
159
+ markdown = cls.convert_to_markdown(chat_response.raw)
160
+
161
+ result = IdentifyRisks(
162
+ system_prompt=system_prompt,
163
+ user_prompt=user_prompt,
164
+ response=json_response,
165
+ metadata=metadata,
166
+ markdown=markdown
167
+ )
168
+ return result
169
+
170
+ def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
171
+ d = self.response.copy()
172
+ if include_metadata:
173
+ d['metadata'] = self.metadata
174
+ if include_system_prompt:
175
+ d['system_prompt'] = self.system_prompt
176
+ if include_user_prompt:
177
+ d['user_prompt'] = self.user_prompt
178
+ return d
179
+
180
+ def save_raw(self, file_path: str) -> None:
181
+ with open(file_path, 'w') as f:
182
+ f.write(json.dumps(self.to_dict(), indent=2))
183
+
184
+ @staticmethod
185
+ def convert_to_markdown(document_details: DocumentDetails) -> str:
186
+ """
187
+ Convert the raw document details to markdown.
188
+ """
189
+ def format_lowmediumhigh(value: LowMediumHigh) -> str:
190
+ return value.capitalize()
191
+
192
+ rows = []
193
+
194
+ if len(document_details.risks) > 0:
195
+ for risk_index, risk_item in enumerate(document_details.risks, start=1):
196
+ rows.append(f"\n## Risk {risk_index} - {risk_item.risk_area}")
197
+ rows.append(risk_item.risk_description)
198
+ rows.append(f"\n**Impact:** {risk_item.potential_impact}")
199
+ rows.append(f"\n**Likelihood:** {format_lowmediumhigh(risk_item.likelihood)}")
200
+ rows.append(f"\n**Severity:** {format_lowmediumhigh(risk_item.severity)}")
201
+ rows.append(f"\n**Action:** {risk_item.action}")
202
+ else:
203
+ rows.append("No risks identified.")
204
+
205
+ rows.append(f"\n## Risk summary\n{document_details.risk_assessment_summary}")
206
+ return "\n".join(rows)
207
+
208
+ def save_markdown(self, output_file_path: str):
209
+ with open(output_file_path, 'w', encoding='utf-8') as out_f:
210
+ out_f.write(self.markdown)
211
+
212
+ if __name__ == "__main__":
213
+ from src.llm_factory import get_llm
214
+ from src.plan.find_plan_prompt import find_plan_prompt
215
+
216
+ llm = get_llm("ollama-llama3.1")
217
+
218
+ plan_prompt = find_plan_prompt("4dc34d55-0d0d-4e9d-92f4-23765f49dd29")
219
+ query = (
220
+ f"{plan_prompt}\n\n"
221
+ "Today's date:\n2025-Feb-27\n\n"
222
+ "Project start ASAP"
223
+ )
224
+ print(f"Query: {query}")
225
+
226
+ identify_risks = IdentifyRisks.execute(llm, query)
227
+ json_response = identify_risks.to_dict(include_system_prompt=False, include_user_prompt=False)
228
+ print("\n\nResponse:")
229
+ print(json.dumps(json_response, indent=2))
230
+
231
+ print(f"\n\nMarkdown:\n{identify_risks.markdown}")
src/assume/make_assumptions.py CHANGED
@@ -212,9 +212,10 @@ class MakeAssumptions:
212
  response: dict
213
  metadata: dict
214
  assumptions: list
 
215
 
216
  @classmethod
217
- def execute(cls, llm: LLM, user_prompt: str, **kwargs: Any) -> 'MakeAssumptions':
218
  """
219
  Invoke LLM and make assumptions based on the user prompt.
220
  """
@@ -231,45 +232,30 @@ class MakeAssumptions:
231
  system_prompt = SYSTEM_PROMPT.strip()
232
  system_prompt = system_prompt.replace("CURRENT_YEAR_PLACEHOLDER", current_year)
233
 
234
- default_args = {
235
- 'system_prompt': system_prompt
236
- }
237
- default_args.update(kwargs)
238
-
239
- system_prompt = default_args.get('system_prompt')
240
- logger.debug(f"System Prompt:\n{system_prompt}")
241
- if system_prompt and not isinstance(system_prompt, str):
242
- raise ValueError("Invalid system prompt.")
243
-
244
- chat_message_list1 = []
245
- if system_prompt:
246
- chat_message_list1.append(
247
- ChatMessage(
248
- role=MessageRole.SYSTEM,
249
- content=system_prompt,
250
- )
251
  )
252
-
253
- logger.debug(f"User Prompt:\n{user_prompt}")
254
- chat_message_user = ChatMessage(
255
- role=MessageRole.USER,
256
- content=user_prompt,
257
- )
258
- chat_message_list1.append(chat_message_user)
259
 
260
  sllm = llm.as_structured_llm(ExpertDetails)
261
 
262
  logger.debug("Starting LLM chat interaction.")
263
  start_time = time.perf_counter()
264
  try:
265
- chat_response1 = sllm.chat(chat_message_list1)
266
  except Exception as e:
267
  logger.debug(f"LLM chat interaction failed: {e}")
268
  logger.error("LLM chat interaction failed.", exc_info=True)
269
  raise ValueError("LLM chat interaction failed.") from e
270
  end_time = time.perf_counter()
271
  duration = int(ceil(end_time - start_time))
272
- response_byte_count = len(chat_response1.message.content.encode('utf-8'))
273
  logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
274
 
275
  metadata = dict(llm.metadata)
@@ -278,7 +264,7 @@ class MakeAssumptions:
278
  metadata["response_byte_count"] = response_byte_count
279
 
280
  try:
281
- json_response = json.loads(chat_response1.message.content)
282
  except json.JSONDecodeError as e:
283
  logger.error("Failed to parse LLM response as JSON.", exc_info=True)
284
  raise ValueError("Invalid JSON response from LLM.") from e
@@ -297,12 +283,15 @@ class MakeAssumptions:
297
  }
298
  assumption_list.append(assumption_item)
299
 
 
 
300
  result = MakeAssumptions(
301
  system_prompt=system_prompt,
302
  user_prompt=user_prompt,
303
  response=json_response,
304
  metadata=metadata,
305
- assumptions=assumption_list
 
306
  )
307
  logger.debug("MakeAssumptions instance created successfully.")
308
  return result
@@ -325,6 +314,27 @@ class MakeAssumptions:
325
  with open(file_path, 'w') as f:
326
  f.write(json.dumps(self.assumptions, indent=2))
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  if __name__ == "__main__":
329
  import logging
330
  from src.llm_factory import get_llm
@@ -356,3 +366,5 @@ if __name__ == "__main__":
356
 
357
  print("\n\nAssumptions:")
358
  print(json.dumps(result.assumptions, indent=2))
 
 
 
212
  response: dict
213
  metadata: dict
214
  assumptions: list
215
+ markdown: str
216
 
217
  @classmethod
218
+ def execute(cls, llm: LLM, user_prompt: str) -> 'MakeAssumptions':
219
  """
220
  Invoke LLM and make assumptions based on the user prompt.
221
  """
 
232
  system_prompt = SYSTEM_PROMPT.strip()
233
  system_prompt = system_prompt.replace("CURRENT_YEAR_PLACEHOLDER", current_year)
234
 
235
+ chat_message_list = [
236
+ ChatMessage(
237
+ role=MessageRole.SYSTEM,
238
+ content=system_prompt,
239
+ ),
240
+ ChatMessage(
241
+ role=MessageRole.USER,
242
+ content=user_prompt,
 
 
 
 
 
 
 
 
 
243
  )
244
+ ]
 
 
 
 
 
 
245
 
246
  sllm = llm.as_structured_llm(ExpertDetails)
247
 
248
  logger.debug("Starting LLM chat interaction.")
249
  start_time = time.perf_counter()
250
  try:
251
+ chat_response = sllm.chat(chat_message_list)
252
  except Exception as e:
253
  logger.debug(f"LLM chat interaction failed: {e}")
254
  logger.error("LLM chat interaction failed.", exc_info=True)
255
  raise ValueError("LLM chat interaction failed.") from e
256
  end_time = time.perf_counter()
257
  duration = int(ceil(end_time - start_time))
258
+ response_byte_count = len(chat_response.message.content.encode('utf-8'))
259
  logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
260
 
261
  metadata = dict(llm.metadata)
 
264
  metadata["response_byte_count"] = response_byte_count
265
 
266
  try:
267
+ json_response = json.loads(chat_response.message.content)
268
  except json.JSONDecodeError as e:
269
  logger.error("Failed to parse LLM response as JSON.", exc_info=True)
270
  raise ValueError("Invalid JSON response from LLM.") from e
 
283
  }
284
  assumption_list.append(assumption_item)
285
 
286
+ markdown = cls.convert_to_markdown(chat_response.raw)
287
+
288
  result = MakeAssumptions(
289
  system_prompt=system_prompt,
290
  user_prompt=user_prompt,
291
  response=json_response,
292
  metadata=metadata,
293
+ assumptions=assumption_list,
294
+ markdown=markdown
295
  )
296
  logger.debug("MakeAssumptions instance created successfully.")
297
  return result
 
314
  with open(file_path, 'w') as f:
315
  f.write(json.dumps(self.assumptions, indent=2))
316
 
317
+ @staticmethod
318
+ def convert_to_markdown(expert_details: ExpertDetails) -> str:
319
+ """
320
+ Convert the raw document details to markdown.
321
+ """
322
+ rows = []
323
+
324
+ if len(expert_details.question_assumption_list) > 0:
325
+ for index, item in enumerate(expert_details.question_assumption_list, start=1):
326
+ rows.append(f"\n## Question {index} - {item.question}")
327
+ rows.append(f"\n**Assumptions:** {item.assumptions}")
328
+ rows.append(f"\n**Assessments:** {item.assessments}")
329
+ else:
330
+ rows.append("The 'question-assumption-list' is empty. Finding zero questions for a plan is unusual, this is likely a bug. Please report this issue to the developer of PlanExe.")
331
+
332
+ return "\n".join(rows)
333
+
334
+ def save_markdown(self, output_file_path: str):
335
+ with open(output_file_path, 'w', encoding='utf-8') as out_f:
336
+ out_f.write(self.markdown)
337
+
338
  if __name__ == "__main__":
339
  import logging
340
  from src.llm_factory import get_llm
 
366
 
367
  print("\n\nAssumptions:")
368
  print(json.dumps(result.assumptions, indent=2))
369
+
370
+ print(f"\n\nMarkdown:\n{result.markdown}")
src/assume/physical_locations.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pick suitable physical locations for the project plan.
3
+ - If the plan is purely digital and can be executed without any physical location, then there is no need to run this step.
4
+ - If the user prompt already includes the physical location, then include that location in the response.
5
+ - If the user prompt does not mention any location, then the expert should suggest suitable locations based on the project requirements.
6
+ - There may be multiple locations, in case a bridge is to be built between two countries.
7
+
8
+ PROMPT> python -m src.assume.physical_locations
9
+ """
10
+ import os
11
+ import json
12
+ import time
13
+ import logging
14
+ from math import ceil
15
+ from dataclasses import dataclass
16
+ from pydantic import BaseModel, Field
17
+ from llama_index.core.llms import ChatMessage, MessageRole
18
+ from llama_index.core.llms.llm import LLM
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ class PhysicalLocationItem(BaseModel):
23
+ item_index: int = Field(
24
+ description="Enumeration of the locations, starting from 1."
25
+ )
26
+ physical_location_broad: str = Field(
27
+ description="A broad location for the project, such as a country or region. Use 'Global' if applicable."
28
+ )
29
+ physical_location_detailed: str = Field(
30
+ description="Narrow down the physical location even more, such as a city name."
31
+ )
32
+ physical_location_specific: str = Field(
33
+ description="Narrow down the physical location even more, such as a city name, region, or type of location (e.g., 'Oceanographic Research Centers')."
34
+ )
35
+ rationale_for_suggestion: str = Field(
36
+ description="Explain why this particular physical location is suggested."
37
+ )
38
+
39
+ class DocumentDetails(BaseModel):
40
+ has_location_in_plan: bool = Field(
41
+ description="Is the location specified in the plan."
42
+ )
43
+ requirements_for_the_physical_locations: list[str] = Field(
44
+ description="List of requirements/constraints for well suited locations."
45
+ )
46
+ physical_locations: list[PhysicalLocationItem] = Field(
47
+ description="List of physical locations."
48
+ )
49
+ location_summary: str = Field(
50
+ description="Providing a high level context."
51
+ )
52
+
53
+ PHYSICAL_LOCATIONS_SYSTEM_PROMPT = """
54
+ You are a world-class planning expert specializing in real-world physical locations. Your goal is to generate a JSON response that follows the `DocumentDetails` and `PhysicalLocationItem` models precisely.
55
+
56
+ Use the following guidelines:
57
+
58
+ ## JSON Models
59
+
60
+ ### DocumentDetails
61
+ - **has_location_in_plan** (bool):
62
+ - `true` if the user’s prompt *explicitly mentions or strongly implies* a physical location. This includes named locations (e.g., "Paris", "my office"), specific landmarks (e.g., "Eiffel Tower," "Grand Canyon"), or clear activities that inherently tie the plan to a location (e.g., "build a house", "open a restaurant"). **If the user's plan can *only* occur in a specific geographic area, consider it to have a location in the plan.**
63
+ - `false` if the user’s prompt does not specify any location.
64
+
65
+ - **requirements_for_the_physical_locations** (list of strings):
66
+ - Key criteria or constraints relevant to location selection (e.g., "cheap labor", "near highways", "near harbor", "space for 10-20 people").
67
+
68
+ - **physical_locations** (list of PhysicalLocationItem):
69
+ - A list of recommended or confirmed physical sites.
70
+ - If the user’s prompt does not require any location, then you **MUST** suggest **three** well-reasoned suggestions.
71
+ - If the user does require a new site (and has no location in mind), you **MUST** provide **three** well-reasoned suggestions.
72
+ - If the user’s prompt already includes a specific location but does not need other suggestions, you may list just that location, or clarify it in one `PhysicalLocationItem` in addition to providing the other **three** well-reasoned suggestions.
73
+ - When suggesting locations, consider a variety of factors, such as accessibility, cost, zoning regulations, and proximity to relevant resources or amenities.
74
+
75
+ - **location_summary** (string):
76
+ - A concise explanation of why the listed sites (if any) are relevant, or—if no location is provided—why no location is necessary (e.g., “All tasks can be done with the user’s current setup; no new site required.”).
77
+
78
+ ### PhysicalLocationItem
79
+ - **item_index** (string):
80
+ - A unique integer (e.g., 1, 2, 3) for each location.
81
+ - **physical_location_broad** (string):
82
+ - A country or wide region (e.g., "USA", "Region of North Denmark").
83
+ - **physical_location_detailed** (string):
84
+ - A more specific subdivision (city, district).
85
+ - **physical_location_specific** (string):
86
+ - A precise address, if relevant.
87
+ - **rationale_for_suggestion** (string):
88
+ - Why this location suits the plan (e.g., "near raw materials", "close to highways", "existing infrastructure").
89
+
90
+ ## Additional Instructions
91
+
92
+ 1. **When the User Already Has a Location**
93
+ - If `has_location_in_plan = true` and the user explicitly provided a place (e.g., "my home", "my shop"), you can either:
94
+ - Use a single `PhysicalLocationItem` to confirm or refine that address in addition to the other **three** well-reasoned suggestions, **or**
95
+ - Provide **three** location items of suggestions if the user is open to alternatives or further detail within the same area.
96
+
97
+ 2. **When the User Needs Suggestions**
98
+ - If `has_location_in_plan = false`, you **MUST** propose **three** distinct sites that satisfy the user’s requirements.
99
+
100
+ 3. **location_summary** Consistency
101
+ - Always provide a summary that matches the `physical_locations` array.
102
+ - If multiple locations are provided, summarize how each meets the user’s needs.
103
+
104
+ ---
105
+
106
+ Example scenarios:
107
+
108
+ - **Implied Physical Location - Eiffel Tower:**
109
+ Given "Visit the Eiffel Tower."
110
+ The correct output is:
111
+ {
112
+ "has_location_in_plan": true,
113
+ "requirements_for_the_physical_locations": [],
114
+ "physical_locations": [
115
+ {
116
+ "item_index": 1,
117
+ "physical_location_broad": "France",
118
+ "physical_location_detailed": "Eiffel Tower, Paris",
119
+ "physical_location_specific": "Champ de Mars, 5 Avenue Anatole France, 75007 Paris, France",
120
+ "rationale_for_suggestion": "The plan is to visit the Eiffel Tower, which is located in Paris, France."
121
+ },
122
+ {
123
+ "item_index": 2,
124
+ "physical_location_broad": "France",
125
+ "physical_location_detailed": "Near Eiffel Tower, Paris",
126
+ "physical_location_specific": "5 Avenue Anatole France, 75007 Paris, France",
127
+ "rationale_for_suggestion": "A location near the Eiffel Tower would provide convenient access for individuals who also plan to visit the landmark."
128
+ },
129
+ {
130
+ "item_index": 3,
131
+ "physical_location_broad": "France",
132
+ "physical_location_detailed": "Central Paris",
133
+ "physical_location_specific": "Various locations in Central Paris",
134
+ "rationale_for_suggestion": "Central Paris offers a vibrant and accessible environment with numerous transportation options."
135
+ }
136
+ ],
137
+ "location_summary": "The plan is to visit the Eiffel Tower, which is located in Paris, France, in addition to a location near the Eiffel Tower and Central Paris."
138
+ }
139
+ """
140
+
141
+ @dataclass
142
+ class PhysicalLocations:
143
+ """
144
+ Take a look at the vague plan description and suggest physical locations.
145
+ """
146
+ system_prompt: str
147
+ user_prompt: str
148
+ response: dict
149
+ metadata: dict
150
+ markdown: str
151
+
152
+ @classmethod
153
+ def execute(cls, llm: LLM, user_prompt: str) -> 'PhysicalLocations':
154
+ """
155
+ Invoke LLM with the project description.
156
+ """
157
+ if not isinstance(llm, LLM):
158
+ raise ValueError("Invalid LLM instance.")
159
+ if not isinstance(user_prompt, str):
160
+ raise ValueError("Invalid user_prompt.")
161
+
162
+ logger.debug(f"User Prompt:\n{user_prompt}")
163
+
164
+ system_prompt = PHYSICAL_LOCATIONS_SYSTEM_PROMPT.strip()
165
+
166
+ chat_message_list = [
167
+ ChatMessage(
168
+ role=MessageRole.SYSTEM,
169
+ content=system_prompt,
170
+ ),
171
+ ChatMessage(
172
+ role=MessageRole.USER,
173
+ content=user_prompt,
174
+ )
175
+ ]
176
+
177
+ sllm = llm.as_structured_llm(DocumentDetails)
178
+ start_time = time.perf_counter()
179
+ try:
180
+ chat_response = sllm.chat(chat_message_list)
181
+ except Exception as e:
182
+ logger.debug(f"LLM chat interaction failed: {e}")
183
+ logger.error("LLM chat interaction failed.", exc_info=True)
184
+ raise ValueError("LLM chat interaction failed.") from e
185
+
186
+ end_time = time.perf_counter()
187
+ duration = int(ceil(end_time - start_time))
188
+ response_byte_count = len(chat_response.message.content.encode('utf-8'))
189
+ logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
190
+
191
+ json_response = chat_response.raw.model_dump()
192
+
193
+ metadata = dict(llm.metadata)
194
+ metadata["llm_classname"] = llm.class_name()
195
+ metadata["duration"] = duration
196
+ metadata["response_byte_count"] = response_byte_count
197
+
198
+ markdown = cls.convert_to_markdown(chat_response.raw)
199
+
200
+ result = PhysicalLocations(
201
+ system_prompt=system_prompt,
202
+ user_prompt=user_prompt,
203
+ response=json_response,
204
+ metadata=metadata,
205
+ markdown=markdown
206
+ )
207
+ return result
208
+
209
+ def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
210
+ d = self.response.copy()
211
+ if include_metadata:
212
+ d['metadata'] = self.metadata
213
+ if include_system_prompt:
214
+ d['system_prompt'] = self.system_prompt
215
+ if include_user_prompt:
216
+ d['user_prompt'] = self.user_prompt
217
+ return d
218
+
219
+ def save_raw(self, file_path: str) -> None:
220
+ with open(file_path, 'w') as f:
221
+ f.write(json.dumps(self.to_dict(), indent=2))
222
+
223
+ @staticmethod
224
+ def convert_to_markdown(document_details: DocumentDetails) -> str:
225
+ """
226
+ Convert the raw document details to markdown.
227
+ """
228
+ rows = []
229
+
230
+ if document_details.has_location_in_plan:
231
+ rows.append("This plan implies one or more physical locations.")
232
+ else:
233
+ rows.append("This plan **does not** imply any physical location.")
234
+
235
+ if len(document_details.requirements_for_the_physical_locations) > 0:
236
+ rows.append("\n## Requirements for physical locations\n")
237
+ for requirement in document_details.requirements_for_the_physical_locations:
238
+ rows.append(f"- {requirement}")
239
+ else:
240
+ rows.append("No requirements for the physical location.")
241
+
242
+ for location_index, location in enumerate(document_details.physical_locations, start=1):
243
+ rows.append(f"\n## Location {location_index}")
244
+ physical_location_broad = location.physical_location_broad.strip()
245
+ physical_location_detailed = location.physical_location_detailed.strip()
246
+ physical_location_specific = location.physical_location_specific.strip()
247
+ missing_location = (len(physical_location_broad) + len(physical_location_detailed) + len(physical_location_specific)) == 0
248
+ if len(physical_location_broad) > 0:
249
+ rows.append(f"{physical_location_broad}\n")
250
+ if len(physical_location_detailed) > 0:
251
+ rows.append(f"{physical_location_detailed}\n")
252
+ if len(physical_location_specific) > 0:
253
+ rows.append(f"{physical_location_specific}\n")
254
+ if missing_location:
255
+ rows.append("Missing location info.\n")
256
+ rows.append(f"**Rationale**: {location.rationale_for_suggestion}")
257
+
258
+ rows.append(f"\n## Location Summary\n{document_details.location_summary}")
259
+ return "\n".join(rows)
260
+
261
+ def save_markdown(self, output_file_path: str):
262
+ with open(output_file_path, 'w', encoding='utf-8') as out_f:
263
+ out_f.write(self.markdown)
264
+
265
+ if __name__ == "__main__":
266
+ from src.llm_factory import get_llm
267
+ from src.plan.find_plan_prompt import find_plan_prompt
268
+
269
+ llm = get_llm("ollama-llama3.1")
270
+
271
+ plan_prompt = find_plan_prompt("de626417-4871-4acc-899d-2c41fd148807")
272
+ query = (
273
+ f"{plan_prompt}\n\n"
274
+ "Today's date:\n2025-Feb-27\n\n"
275
+ "Project start ASAP"
276
+ )
277
+ print(f"Query: {query}")
278
+
279
+ physical_locations = PhysicalLocations.execute(llm, query)
280
+ json_response = physical_locations.to_dict(include_system_prompt=False, include_user_prompt=False)
281
+ print("\n\nResponse:")
282
+ print(json.dumps(json_response, indent=2))
283
+
284
+ print(f"\n\nMarkdown:\n{physical_locations.markdown}")
src/assume/review_assumptions.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Review the assumptions. Are they too low/high? Are they reasonable? Are there any missing assumptions?
3
+
4
+ PROMPT> python -m src.assume.review_assumptions
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
+ from src.format_json_for_use_in_query import format_json_for_use_in_query
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class ReviewItem(BaseModel):
20
+ issue: str = Field(
21
+ description="A brief title or name."
22
+ )
23
+ explanation: str = Field(
24
+ description="A concise description of why this issue is important."
25
+ )
26
+ recommendation: str = Field(
27
+ description="Specific suggestions on how to address the issue."
28
+ )
29
+ sensitivity: str = Field(
30
+ default="",
31
+ description="Optional: Provide any sensitivity analysis insights related to this issue."
32
+ )
33
+
34
+ class DocumentDetails(BaseModel):
35
+ expert_domain: str = Field(
36
+ description="The domain of the expert reviewer."
37
+ )
38
+ domain_specific_considerations: list[str] = Field(
39
+ description="Key factors and areas of focus relevant to the specific project domain, which this review should prioritize."
40
+ )
41
+ issues: list[ReviewItem] = Field(
42
+ description="The most significant issues."
43
+ )
44
+ conclusion: str = Field(
45
+ description="Summary of the most important issues."
46
+ )
47
+
48
+ REVIEW_ASSUMPTIONS_SYSTEM_PROMPT = """
49
+ You are a world-class planning expert specializing in the success of projects. Your task is to critically review the provided assumptions and identify potential weaknesses, omissions, or unrealistic elements that could significantly impact project success. Your analysis should be tailored to the project’s scale and context, while considering standard project management best practices. Be creative and innovative in your analysis, considering risks and opportunities that might be overlooked by others.
50
+
51
+ **Crucial Focus: Missing Assumptions and Impact Assessment**
52
+
53
+ Your primary goal is to identify *critical missing assumptions* that have not been explicitly stated, but are vital for successful project planning and execution. For each missing assumption, estimate its potential impact on the project's key performance indicators (KPIs) such as ROI, timeline, budget, or quality. This impact assessment should be quantitative wherever possible. For instance, if a missing assumption relates to regulatory approval, estimate the potential delay in project completion and the associated cost implications.
54
+
55
+ **Consider the Following Project Aspects:**
56
+
57
+ When reviewing the assumptions, actively consider these areas. Look for explicit *or* implicit assumptions that impact these areas.
58
+
59
+ - **Financial:** Funding sources, cost estimates (initial and operational), revenue projections, pricing strategy, profitability, economic viability, return on investment (ROI), cost of capital, financial risks (e.g., currency fluctuations, interest rate changes), insurance costs.
60
+ - **Timeline:** Project duration, key milestones, task dependencies, resource allocation over time, critical path analysis, potential delays (e.g., permitting, supply chain), seasonality effects, weather-related risks.
61
+ - **Resources:** Human resources (skill availability, labor costs), material resources (supply availability, raw material costs), equipment (availability, maintenance costs), technology (availability, licensing costs), land (acquisition costs, suitability).
62
+ - **Regulations:** Compliance with local, regional, and national laws, environmental regulations, permitting requirements, zoning ordinances, safety standards, data privacy regulations, industry-specific standards, political risks.
63
+ - **Infrastructure:** Availability and capacity of transportation, utilities (electricity, water, gas), communication networks, cybersecurity risks.
64
+ - **Environment:** Potential environmental impacts (e.g., emissions, waste generation, habitat disruption), mitigation strategies, climate change risks, sustainability practices, resource consumption.
65
+ - **Stakeholders:** Community acceptance, government support, customer needs, supplier relationships, investor expectations, media relations, political influence, key partner dependencies.
66
+ - **Technology:** Technology selection, innovation, integration, obsolescence, intellectual property rights, data security, scalability, maintenance, licensing.
67
+ - **Market:** Market demand, competitive landscape, pricing pressure, customer preferences, economic trends, technological disruption, new market entrants, black swan events.
68
+ - **Risk:** Credit risk, operational risk, strategic risk, compliance risk, political risk, insurance needs, cost of capital, inflation. Examples of risks are: the NLP algorithm has a bug and must be rewritten, funding dries up due to a market crash, etc.
69
+
70
+ **Your Analysis MUST:**
71
+
72
+ 1. **Identify Critical Missing Assumptions:** Explicitly state any crucial assumptions that are missing from the provided input. Clearly explain why each missing assumption is critical to the project's success.
73
+ 2. **Highlight Under-Explored Assumptions:** Point out areas where the existing assumptions lack sufficient detail or supporting evidence.
74
+ 3. **Challenge Questionable or Unrealistic Assumptions:** Identify any assumptions that seem unrealistic or based on flawed logic.
75
+ 4. **Discuss Sensitivity Analysis for key variables:** Quantify the potential impact of changes in key variables (e.g., a delay in permitting, a change in energy prices) on the project's overall success. For each issue, consider a plausible range for the key driving variables, and quantify the impact on the project's Return on Investment (ROI) or total project cost. Use percentages or hard numbers! Example of an analysis range of key variables is: The project may experience challenges related to a lack of data privacy considerations. A failure to uphold GDPR principles may result in fines ranging from 5-10% of annual turnover. The cost of a human for the project can be based on a 40/hr for 160 hours and would require a computer, this could be from 6000 to 7000 per month. The variance should not be double the base value.
76
+ 5. **Prioritize Issues:** Focus on the *three most critical* issues, providing detailed and actionable recommendations for addressing them.
77
+
78
+ **Guidance for identifying missing assumptions:**
79
+ Think about all the things that must be true for this project to succeed. Are all of these things in the existing list of assumptions?
80
+ * Resources: Financial, Human, Data, Time, etc.
81
+ * Pre-Existing Work: Benchmarks, Data Sets, Algorithms, Existing papers, etc.
82
+ * Outside Forces: Community Buy-In, Funding, New laws, weather, etc.
83
+ * Metrics: Clear, measurable success conditions.
84
+ * Technical Considerations: Hardware, Software, Algorithms, Scalability, Data security, etc.
85
+
86
+ Please limit your output to no more than 800 words.
87
+
88
+ Return your response as a JSON object with the following structure:
89
+ {
90
+ "expert_domain": "The area of expertise most relevant for this review",
91
+ "domain_specific_considerations": ["List", "of", "relevant", "considerations"],
92
+ "issues": [
93
+ {
94
+ "issue": "Title of the issue",
95
+ "explanation": "Explanation of why this issue is important",
96
+ "recommendation": "Actionable recommendations to address the issue. Be specific. Include specific steps, quantifiable targets, or examples of best practices whenever possible.",
97
+ "sensitivity": "Quantitative sensitivity analysis details. Express the impact as a *range* of values on the project's ROI, total project cost, or project completion date, and include the *baseline* for comparison. Here are examples: * 'A delay in obtaining necessary permits (baseline: 6 months) could increase project costs by \u20ac100,000-200,000, or delay the ROI by 3-6 months.' * 'A 15% increase in the cost of solar panels (baseline: \u20ac1 million) could reduce the project's ROI by 5-7%.' * 'If we underestimate cloud computing costs, the project could be delayed by 3-6 months, or the ROI could be reduced by 10-15%'"
98
+ },
99
+ ...
100
+ ],
101
+ "conclusion": "Summary of main findings and recommendations"
102
+ }
103
+ """
104
+
105
+ @dataclass
106
+ class ReviewAssumptions:
107
+ """
108
+ Take a look at the assumptions and provide feedback on potential omissions and improvements.
109
+ """
110
+ system_prompt: str
111
+ user_prompt: str
112
+ response: dict
113
+ metadata: dict
114
+ markdown: str
115
+
116
+ @classmethod
117
+ def execute(cls, llm: LLM, user_prompt: str) -> 'ReviewAssumptions':
118
+ """
119
+ Invoke LLM with the project description and assumptions to be reviewed.
120
+ """
121
+ if not isinstance(llm, LLM):
122
+ raise ValueError("Invalid LLM instance.")
123
+ if not isinstance(user_prompt, str):
124
+ raise ValueError("Invalid user_prompt.")
125
+
126
+ logger.debug(f"User Prompt:\n{user_prompt}")
127
+
128
+ system_prompt = REVIEW_ASSUMPTIONS_SYSTEM_PROMPT.strip()
129
+
130
+ chat_message_list = [
131
+ ChatMessage(
132
+ role=MessageRole.SYSTEM,
133
+ content=system_prompt,
134
+ ),
135
+ ChatMessage(
136
+ role=MessageRole.USER,
137
+ content=user_prompt,
138
+ )
139
+ ]
140
+
141
+ sllm = llm.as_structured_llm(DocumentDetails)
142
+ start_time = time.perf_counter()
143
+ try:
144
+ chat_response = sllm.chat(chat_message_list)
145
+ except Exception as e:
146
+ logger.debug(f"LLM chat interaction failed: {e}")
147
+ logger.error("LLM chat interaction failed.", exc_info=True)
148
+ raise ValueError("LLM chat interaction failed.") from e
149
+
150
+ end_time = time.perf_counter()
151
+ duration = int(ceil(end_time - start_time))
152
+ response_byte_count = len(chat_response.message.content.encode('utf-8'))
153
+ logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
154
+
155
+ json_response = chat_response.raw.model_dump()
156
+
157
+ metadata = dict(llm.metadata)
158
+ metadata["llm_classname"] = llm.class_name()
159
+ metadata["duration"] = duration
160
+ metadata["response_byte_count"] = response_byte_count
161
+
162
+ markdown = cls.convert_to_markdown(chat_response.raw)
163
+
164
+ result = ReviewAssumptions(
165
+ system_prompt=system_prompt,
166
+ user_prompt=user_prompt,
167
+ response=json_response,
168
+ metadata=metadata,
169
+ markdown=markdown
170
+ )
171
+ return result
172
+
173
+ def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
174
+ d = self.response.copy()
175
+ if include_metadata:
176
+ d['metadata'] = self.metadata
177
+ if include_system_prompt:
178
+ d['system_prompt'] = self.system_prompt
179
+ if include_user_prompt:
180
+ d['user_prompt'] = self.user_prompt
181
+ return d
182
+
183
+ def save_raw(self, file_path: str) -> None:
184
+ with open(file_path, 'w') as f:
185
+ f.write(json.dumps(self.to_dict(), indent=2))
186
+
187
+ @staticmethod
188
+ def convert_to_markdown(document_details: DocumentDetails) -> str:
189
+ """
190
+ Convert the raw document details to markdown.
191
+ """
192
+ rows = []
193
+
194
+ rows.append(f"## Domain of the expert reviewer\n{document_details.expert_domain}")
195
+
196
+ if len(document_details.domain_specific_considerations) > 0:
197
+ rows.append("\n## Domain-specific considerations\n")
198
+ for item in document_details.domain_specific_considerations:
199
+ rows.append(f"- {item}")
200
+ else:
201
+ rows.append("\n## Domain-specific considerations - None\n")
202
+
203
+ if len(document_details.issues) > 0:
204
+ for index, item in enumerate(document_details.issues, start=1):
205
+ rows.append(f"\n## Issue {index} - {item.issue}")
206
+ rows.append(item.explanation)
207
+ rows.append(f"\n**Recommendation:** {item.recommendation}")
208
+ rows.append(f"\n**Sensitivity:** {item.sensitivity}")
209
+ else:
210
+ rows.append("## Issues - None. This is unusual. Please report this to the developer of PlanExe.")
211
+
212
+ rows.append(f"\n## Review conclusion\n{document_details.conclusion}")
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
+ from src.utils.concat_files_into_string import concat_files_into_string
222
+
223
+ llm = get_llm("ollama-llama3.1")
224
+
225
+ base_path = os.path.join(os.path.dirname(__file__), 'test_data', 'review_assumptions1')
226
+
227
+ all_documents_string = concat_files_into_string(base_path)
228
+ print(all_documents_string)
229
+
230
+ result = ReviewAssumptions.execute(llm, all_documents_string)
231
+ json_response = result.to_dict(include_system_prompt=False, include_user_prompt=False)
232
+ print("\n\nResponse:")
233
+ print(json.dumps(json_response, indent=2))
234
+
235
+ print(f"\n\nMarkdown:\n{result.markdown}")
src/assume/test_data/currency_strategy1/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ Construct a train bridge between Denmark and England.
3
+
4
+ Today's date:
5
+ 2025-Feb-28
6
+
7
+ Project start ASAP
src/assume/test_data/currency_strategy1/002-physical_locations.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "physical_location_required": true,
3
+ "has_location_in_plan": false,
4
+ "requirements_for_the_locations": [
5
+ "connects the two countries",
6
+ "crosses waterways"
7
+ ],
8
+ "locations": [
9
+ {
10
+ "item_index": 1,
11
+ "specific_location": "",
12
+ "suggest_location_broad": "Denmark",
13
+ "suggest_location_detail": "Region of North Denmark",
14
+ "suggest_location_address": "between Frederikshavn, Denmark and Esbjerg, Denmark",
15
+ "rationale_for_suggestion": "nearest Danish point to England"
16
+ },
17
+ {
18
+ "item_index": 2,
19
+ "specific_location": "",
20
+ "suggest_location_broad": "England",
21
+ "suggest_location_detail": "East of England region (Norfolk)",
22
+ "suggest_location_address": "between Great Yarmouth, Norfolk and King's Lynn, Norfolk",
23
+ "rationale_for_suggestion": "nearest English point to Denmark"
24
+ },
25
+ {
26
+ "item_index": 3,
27
+ "specific_location": "",
28
+ "suggest_location_broad": "Denmark",
29
+ "suggest_location_detail": "Capital Region of Denmark (Zealand)",
30
+ "suggest_location_address": "between Copenhagen and Frederikssund, Zealand",
31
+ "rationale_for_suggestion": "high population density in the area"
32
+ },
33
+ {
34
+ "item_index": 4,
35
+ "specific_location": "",
36
+ "suggest_location_broad": "England",
37
+ "suggest_location_detail": "East of England region (Suffolk)",
38
+ "suggest_location_address": "between Lowestoft and Southwold, Suffolk",
39
+ "rationale_for_suggestion": "strategic location near sea routes"
40
+ }
41
+ ],
42
+ "location_summary": "Four potential locations have been suggested for the train bridge connecting Denmark and England: 1) Region of North Denmark (near Frederikshavn), 2) East of England region (Norfolk), 3) Capital Region of Denmark (Zealand, near Copenhagen), and 4) East of England region (Suffolk). These locations are chosen for their proximity to the two countries' borders."
43
+ }
src/assume/test_data/currency_strategy2/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ I need to take out the trash. All the bins is in the kitchen, and the dumpsters are outside, such as: metal, plastics, bio. Where I live, citizens must sort the trash into the correct bins.
3
+
4
+ Today's date:
5
+ 2025-Mar-01
6
+
7
+ Project start ASAP
src/assume/test_data/currency_strategy2/002-physical_locations.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "physical_location_required": false,
3
+ "has_location_in_plan": true,
4
+ "requirements_for_the_locations": [],
5
+ "locations": [],
6
+ "location_summary": "The task involves using existing locations (kitchen, outdoor dumpsters) at the user's residence; no new physical site is required."
7
+ }
src/assume/test_data/currency_strategy3/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ My daily commute from home to work takes 1 hour. My bike is broken and need an alternative plan. I live in Amsterdam, Netherlands.
3
+
4
+ Today's date:
5
+ 2025-Mar-01
6
+
7
+ Project start ASAP
src/assume/test_data/currency_strategy3/002-physical_locations.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "physical_location_required": true,
3
+ "has_location_in_plan": true,
4
+ "requirements_for_the_locations": [
5
+ "near work",
6
+ "alternative commute"
7
+ ],
8
+ "locations": [
9
+ {
10
+ "item_index": 1,
11
+ "specific_location": "",
12
+ "suggest_location_broad": "Netherlands",
13
+ "suggest_location_detail": "Amsterdam",
14
+ "suggest_location_address": "",
15
+ "rationale_for_suggestion": "Existing home and work location in Amsterdam"
16
+ },
17
+ {
18
+ "item_index": 2,
19
+ "specific_location": "",
20
+ "suggest_location_broad": "Netherlands",
21
+ "suggest_location_detail": "Utrecht",
22
+ "suggest_location_address": "",
23
+ "rationale_for_suggestion": "Alternative city with good public transportation, relatively close to Amsterdam"
24
+ },
25
+ {
26
+ "item_index": 3,
27
+ "specific_location": "",
28
+ "suggest_location_broad": "Netherlands",
29
+ "suggest_location_detail": "Haarlem",
30
+ "suggest_location_address": "",
31
+ "rationale_for_suggestion": "Neighboring city with good public transportation, close to Amsterdam"
32
+ }
33
+ ],
34
+ "location_summary": "Alternative commute options from home to work in Amsterdam and nearby cities."
35
+ }
src/assume/test_data/currency_strategy4/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ 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.
3
+
4
+ Today's date:
5
+ 2025-Mar-01
6
+
7
+ Project start ASAP
src/assume/test_data/currency_strategy4/002-physical_locations.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "physical_location_required": false,
3
+ "has_location_in_plan": false,
4
+ "requirements_for_the_locations": [
5
+ "research institution",
6
+ "data repository"
7
+ ],
8
+ "locations": [],
9
+ "location_summary": "This project will be conducted in a digital environment, leveraging existing research institutions and data repositories to minimize physical site requirements."
10
+ }
src/assume/test_data/currency_strategy5/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ 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?
3
+
4
+ Today's date:
5
+ 2025-Mar-01
6
+
7
+ Project start ASAP
src/assume/test_data/currency_strategy5/002-physical_locations.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "physical_location_required": true,
3
+ "has_location_in_plan": false,
4
+ "requirements_for_the_locations": [
5
+ "global",
6
+ "language center"
7
+ ],
8
+ "locations": [
9
+ {
10
+ "item_index": 1,
11
+ "specific_location": "",
12
+ "suggest_location_broad": "Global",
13
+ "suggest_location_detail": "",
14
+ "suggest_location_address": "",
15
+ "rationale_for_suggestion": ""
16
+ },
17
+ {
18
+ "item_index": 2,
19
+ "specific_location": "",
20
+ "suggest_location_broad": "Global Headquarters of a major Language Institution",
21
+ "suggest_location_detail": "e.g., UNESCO, British Council",
22
+ "suggest_location_address": "",
23
+ "rationale_for_suggestion": ""
24
+ },
25
+ {
26
+ "item_index": 3,
27
+ "specific_location": "",
28
+ "suggest_location_broad": "A hub city with strong linguistic diversity and cultural significance",
29
+ "suggest_location_detail": "e.g., Paris, Tokyo, New York",
30
+ "suggest_location_address": "",
31
+ "rationale_for_suggestion": ""
32
+ }
33
+ ],
34
+ "location_summary": "This project requires a global approach to develop a unified language. The suggested locations are hubs for linguistic diversity and cultural significance."
35
+ }
src/assume/test_data/currency_strategy6/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ Create a detailed report examining the current situation of microplastics within the world's oceans.
3
+
4
+ Today's date:
5
+ 2025-Mar-01
6
+
7
+ Project start ASAP
src/assume/test_data/currency_strategy6/002-physical_locations.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "physical_location_required": true,
3
+ "has_location_in_plan": false,
4
+ "requirements_for_the_locations": [
5
+ "near research institutions",
6
+ "with access to oceanic sampling equipment"
7
+ ],
8
+ "locations": [
9
+ {
10
+ "item_index": 1,
11
+ "specific_location": "",
12
+ "suggest_location_broad": "Global Oceans",
13
+ "suggest_location_detail": "Various marine ecosystems",
14
+ "suggest_location_address": "",
15
+ "rationale_for_suggestion": "To gather diverse ocean samples and collaborate with local researchers"
16
+ },
17
+ {
18
+ "item_index": 2,
19
+ "specific_location": "",
20
+ "suggest_location_broad": "Major coastal cities",
21
+ "suggest_location_detail": "Port locations",
22
+ "suggest_location_address": "",
23
+ "rationale_for_suggestion": "For easy access to sampling equipment and research institutions"
24
+ }
25
+ ],
26
+ "location_summary": "The study requires accessing various oceanic ecosystems globally for diverse sampling. Suggested sites are near major coastal cities with access to relevant research facilities."
27
+ }
src/assume/test_data/currency_strategy7/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ Establish a humanoid robot factory in Caracas, Venezuela.
3
+
4
+ Today's date:
5
+ 2025-Mar-01
6
+
7
+ Project start ASAP
src/assume/test_data/currency_strategy7/002-physical_locations.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "physical_location_required": true,
3
+ "has_location_in_plan": false,
4
+ "requirements_for_the_locations": [
5
+ "cheap labor",
6
+ "government incentives for robotics industry"
7
+ ],
8
+ "locations": [
9
+ {
10
+ "item_index": 1,
11
+ "specific_location": "",
12
+ "suggest_location_broad": "Venezuela",
13
+ "suggest_location_detail": "Caracas",
14
+ "suggest_location_address": "",
15
+ "rationale_for_suggestion": "Existing infrastructure and access to cheap labor in Caracas make it an ideal location for a humanoid robot factory."
16
+ }
17
+ ],
18
+ "location_summary": "A humanoid robot factory should be set up in Caracas, Venezuela, due to the availability of cheap labor and government incentives for the robotics industry. This will allow us to keep costs low and take advantage of local resources."
19
+ }
src/assume/test_data/review_assumptions1/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ Establish a solar farm in Denmark.
3
+
4
+ Today's date:
5
+ 2025-Feb-26
6
+
7
+ Project start ASAP
src/assume/test_data/review_assumptions1/002-make_assumptions.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "question": "What are the funding and budget requirements for establishing the solar farm?",
4
+ "assumptions": "Assumption: The initial investment includes land acquisition, installation of solar panels, infrastructure (roads, fencing), and permits. Based on industry standards in Denmark, this could range from \u20ac2-4 million, including contingency.",
5
+ "assessments": "Title: Financial Feasibility Assessment\nDescription: Evaluation of the financial viability considering initial costs and potential returns.\nDetails: The estimated cost is \u20ac3 million. Expected return on investment (ROI) is 5-7 years with current technology advances and government incentives. Risks include fluctuating energy prices and regulatory changes. Mitigation strategies involve securing long-term power purchase agreements (PPAs) and diversifying renewable energy portfolio."
6
+ },
7
+ {
8
+ "question": "What is the projected timeline for development, including key milestones?",
9
+ "assumptions": "Assumption: The project follows a phased approach starting with site selection, followed by permits, construction, and finally commissioning. Assuming an average pace of work, this process could take 18-24 months.",
10
+ "assessments": "Title: Timeline & Milestones Assessment\nDescription: Structuring the project timeline for efficient execution.\nDetails: Key milestones include site selection (Month 1), permit acquisition (Months 2-6), start of construction (Month 7), completion and testing (Months 12-18), with full operational capacity by Month 24. Delays in permits could extend timelines; regular updates and stakeholder communication are crucial for maintaining momentum."
11
+ },
12
+ {
13
+ "question": "What resources and personnel are required to execute the project?",
14
+ "assumptions": "Assumption: The team includes engineers, environmental specialists, procurement managers, construction workers, and administrative staff. Given the scale, a core team of around 20-30 people is necessary, supplemented by contractors during peak construction phases.",
15
+ "assessments": "Title: Resource & Personnel Assessment\nDescription: Ensuring adequate human capital and material resources.\nDetails: A skilled team with diverse expertise will be essential for successful execution. Risks include talent shortages; mitigating this involves early recruitment and fostering partnerships with local educational institutions for training programs."
16
+ },
17
+ {
18
+ "question": "What are the governance and regulatory considerations?",
19
+ "assumptions": "Assumption: The project must comply with Danish renewable energy laws, environmental regulations, and local zoning ordinances. Engaging early with relevant authorities is critical.",
20
+ "assessments": "Title: Governance & Regulations Assessment\nDescription: Ensuring adherence to legal requirements.\nDetails: Permits are required from the Danish Energy Agency and local municipalities. Potential risks include regulatory changes; mitigation involves maintaining active dialogue with regulators and being prepared for revisions through flexible planning."
21
+ },
22
+ {
23
+ "question": "What are the safety and risk management protocols?",
24
+ "assumptions": "Assumption: Comprehensive safety measures must be in place, including training for workers on solar panel handling and use of heavy machinery. Risk assessments will identify potential hazards such as electrical risks and working at heights.",
25
+ "assessments": "Title: Safety & Risk Management Assessment\nDescription: Minimizing potential hazards to personnel and the environment.\nDetails: A detailed risk management plan is required, including safety training programs, emergency protocols, and regular audits. Potential risks include worker injury; mitigation strategies involve thorough site inspections, regular safety drills, and adherence to all regulatory safety standards."
26
+ },
27
+ {
28
+ "question": "What are the environmental impacts and mitigations?",
29
+ "assumptions": "Assumption: The project will have minimal impact on local ecosystems; however, potential effects include habitat alteration and water usage. Mitigation strategies involve preserving existing vegetation during construction and implementing efficient irrigation systems.",
30
+ "assessments": "Title: Environmental Impact Assessment\nDescription: Evaluating ecological impacts and implementing mitigation measures.\nDetails: A detailed environmental assessment will be conducted to identify potential impacts. Mitigations include reforestation plans, wildlife corridors, and using rainwater harvesting systems. Monitoring programs post-completion are necessary to ensure long-term sustainability."
31
+ },
32
+ {
33
+ "question": "How can stakeholder involvement be maximized?",
34
+ "assumptions": "Assumption: Engaging local communities, government bodies, and industry stakeholders early in the process is crucial for project success and social acceptance.",
35
+ "assessments": "Title: Stakeholder Involvement Assessment\nDescription: Securing buy-in from all relevant parties.\nDetails: A robust stakeholder engagement plan will be developed, including regular updates, public forums, and feedback mechanisms. Risks include resistance or lack of interest; mitigations involve transparent communication and addressing community concerns proactively."
36
+ },
37
+ {
38
+ "question": "What operational systems and technologies are essential for the solar farm?",
39
+ "assumptions": "Assumption: Advanced monitoring systems, energy storage solutions (like batteries), and smart grid integration will be key to optimizing performance and reliability.",
40
+ "assessments": "Title: Operational Systems Assessment\nDescription: Establishing efficient and sustainable operational frameworks.\nDetails: Investment in cutting-edge technology is crucial for maximizing energy yield. Risks include technological obsolescence; mitigation strategies involve regular system upgrades and fostering partnerships with tech innovators to stay ahead."
41
+ }
42
+ ]
src/assume/test_data/review_assumptions1/003-distill_assumptions.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "assumption_list": [
3
+ "Project takes 18-24 months.",
4
+ "Investment \u20ac3m, ROI 5-7 years.",
5
+ "Team of 20-30 people.",
6
+ "Compliance with Danish renewable laws.",
7
+ "Active regulator dialogue.",
8
+ "Safety protocols and risk assessments.",
9
+ "Minimal environmental impact; mitigation plans.",
10
+ "Stakeholder engagement plan."
11
+ ]
12
+ }
src/assume/test_data/review_assumptions2/001-plan.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Plan:
2
+ 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.
3
+
4
+ Today's date:
5
+ 2025-Feb-10
6
+
7
+ Project start ASAP
src/assume/test_data/review_assumptions2/002-make_assumptions.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "question": "What is the estimated budget allocated for personnel, computational resources, and data storage for this project?",
4
+ "assumptions": "Assumption: A budget of $10,000 is allocated for the initial phase, covering personnel costs (part-time researcher), cloud computing for paper processing, and data storage on a public repository. This is based on typical costs for similar academic projects.",
5
+ "assessments": "Title: Financial Feasibility Assessment\nDescription: Evaluation of the project's financial viability based on the allocated budget.\nDetails: The $10,000 budget is tight but feasible for a small-scale initial phase. Risks include potential cost overruns for cloud computing if the volume of papers is high. Mitigation: Implement strict budget controls, optimize cloud resource usage, and explore free or discounted academic resources. Opportunity: Securing grants or sponsorships could expand the project's scope and impact."
6
+ },
7
+ {
8
+ "question": "What is the target completion date for the initial dataset release, and what are the key milestones leading up to it?",
9
+ "assumptions": "Assumption: The initial dataset release is targeted for 2025-May-10, with milestones including literature review (2 weeks), summary template design (1 week), initial paper distillation (6 weeks), and dataset formatting/publication (2 weeks). This timeline is based on the complexity of the task and available resources.",
10
+ "assessments": "Title: Timeline & Milestones Assessment\nDescription: Evaluation of the project's timeline and the feasibility of meeting the set milestones.\nDetails: The timeline is aggressive but achievable with focused effort. Risks include delays in paper distillation due to unforeseen complexities. Mitigation: Prioritize papers based on impact and relevance, and allocate sufficient time for quality control. Opportunity: Streamlining the distillation process could accelerate the timeline and allow for more papers to be included in the initial release."
11
+ },
12
+ {
13
+ "question": "What specific roles and expertise are required for this project (e.g., machine learning researchers, data scientists, curators)?",
14
+ "assumptions": "Assumption: The project requires one part-time machine learning researcher with experience in natural language processing and benchmark analysis, and access to a data curator for dataset formatting and publication. This is based on the skills needed for paper analysis and data management.",
15
+ "assessments": "Title: Resources & Personnel Assessment\nDescription: Evaluation of the adequacy of the project's human resources and expertise.\nDetails: Having a part-time ML researcher is sufficient for the initial phase. Risks include potential bottlenecks if the researcher is overloaded. Mitigation: Clearly define roles and responsibilities, and consider outsourcing data curation tasks. Opportunity: Collaborating with other researchers or institutions could provide access to additional expertise and resources."
16
+ },
17
+ {
18
+ "question": "What are the guidelines for ensuring objectivity and avoiding bias in the paper summaries, and how will these be enforced?",
19
+ "assumptions": "Assumption: Objectivity will be ensured through a standardized summary template, peer review of summaries, and adherence to a pre-defined set of criteria for evaluating significance. This is based on best practices for scientific reporting.",
20
+ "assessments": "Title: Governance & Regulations Assessment\nDescription: Evaluation of the project's governance structure and adherence to ethical guidelines.\nDetails: The proposed guidelines are a good starting point. Risks include unintentional bias creeping into the summaries. Mitigation: Implement a blind review process and regularly audit summaries for potential biases. Opportunity: Establishing an advisory board of experts could provide oversight and ensure the project's integrity."
21
+ },
22
+ {
23
+ "question": "What measures will be taken to ensure the accuracy and reliability of the distilled information, and to mitigate the risk of misinterpreting research findings?",
24
+ "assumptions": "Assumption: Accuracy will be ensured through cross-validation of summaries by multiple reviewers, verification of claims against original papers and benchmarks, and clear documentation of the distillation process. This is based on standard practices for data validation.",
25
+ "assessments": "Title: Safety & Risk Management Assessment\nDescription: Evaluation of the project's risk management strategies, focusing on data accuracy and reliability.\nDetails: The proposed measures are crucial for maintaining data integrity. Risks include errors in the distillation process leading to inaccurate summaries. Mitigation: Implement a robust quality control process with multiple layers of review and validation. Opportunity: Developing automated tools for claim verification could improve efficiency and accuracy."
26
+ },
27
+ {
28
+ "question": "How will the project minimize its environmental impact, particularly regarding computational resources used for processing and storing the data?",
29
+ "assumptions": "Assumption: The project will minimize its environmental impact by using energy-efficient cloud computing resources, optimizing code for performance, and storing data in a compressed format. This is based on best practices for sustainable computing.",
30
+ "assessments": "Title: Environmental Impact Assessment\nDescription: Evaluation of the project's environmental footprint and strategies for minimizing it.\nDetails: Using cloud computing is generally more efficient than on-premise solutions. Risks include high energy consumption if cloud resources are not optimized. Mitigation: Select cloud providers with renewable energy sources, and optimize code for efficiency. Opportunity: Exploring federated learning or other distributed computing approaches could further reduce the environmental impact."
31
+ },
32
+ {
33
+ "question": "How will the project engage with the machine learning community to gather feedback, promote adoption of the dataset, and ensure its long-term relevance?",
34
+ "assumptions": "Assumption: The project will engage with the community through open-source code, public forums, conference presentations, and collaborations with other researchers. This is based on standard practices for open science.",
35
+ "assessments": "Title: Stakeholder Involvement Assessment\nDescription: Evaluation of the project's engagement with stakeholders and strategies for promoting adoption.\nDetails: Community engagement is crucial for the project's success. Risks include lack of adoption if the dataset is not perceived as valuable. Mitigation: Actively solicit feedback from the community and incorporate it into the dataset. Opportunity: Partnering with influential researchers or organizations could increase visibility and adoption."
36
+ },
37
+ {
38
+ "question": "What platform will be used to host the open-access dataset, and what measures will be taken to ensure its long-term accessibility and maintainability?",
39
+ "assumptions": "Assumption: The dataset will be hosted on a well-established repository like Hugging Face Datasets or Zenodo, with clear documentation, version control, and a stable API. This is based on best practices for data archiving.",
40
+ "assessments": "Title: Operational Systems Assessment\nDescription: Evaluation of the project's operational infrastructure and strategies for ensuring long-term sustainability.\nDetails: Choosing a reputable repository is essential for long-term accessibility. Risks include data loss or corruption if the repository is not properly maintained. Mitigation: Regularly back up the dataset and monitor the repository for any issues. Opportunity: Developing automated tools for dataset updates and maintenance could reduce the long-term operational burden."
41
+ }
42
+ ]
src/assume/test_data/review_assumptions2/003-distill_assumptions.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "assumption_list": [
3
+ "The initial phase budget is $10,000 for personnel, cloud computing, and storage.",
4
+ "Initial dataset release is targeted for 2025-May-10, including key milestones.",
5
+ "The project requires one part-time ML researcher and access to a data curator.",
6
+ "Objectivity is ensured via a template, peer review, and pre-defined criteria.",
7
+ "Accuracy is ensured through cross-validation, verification, and clear documentation.",
8
+ "The project will minimize environmental impact by using efficient cloud resources.",
9
+ "The project will engage the community through open-source code and collaborations.",
10
+ "Dataset hosted on Hugging Face or Zenodo with documentation, version control, and API."
11
+ ]
12
+ }
src/expert/pre_project_assessment.py CHANGED
@@ -1,6 +1,8 @@
1
  """
2
  PROMPT> python -m src.expert.pre_project_assessment
3
 
 
 
4
  Two experts analyze a project plan and provide feedback.
5
 
6
  Analysis: Experts assess the plan.
 
1
  """
2
  PROMPT> python -m src.expert.pre_project_assessment
3
 
4
+ IDEA: markdown document, that goes into the final report.
5
+
6
  Two experts analyze a project plan and provide feedback.
7
 
8
  Analysis: Experts assess the plan.
src/llm_util/ollama_info.py CHANGED
@@ -1,46 +1,63 @@
1
- """
2
- PROMPT> python -m src.llm_util.ollama_info
3
- """
4
- from dataclasses import dataclass
5
-
6
- @dataclass
7
- class OllamaInfo:
8
- """
9
- Details about the Ollama service, including a list of available model names,
10
- a flag indicating whether the service is running, and an optional error message.
11
- """
12
- model_names: list[str]
13
- is_running: bool
14
- error_message: str = None
15
-
16
- @classmethod
17
- def obtain_info(cls) -> 'OllamaInfo':
18
- """Retrieves information about the Ollama service."""
19
- try:
20
- # Only import ollama if it's available
21
- from ollama import ListResponse, list
22
- list_response: ListResponse = list()
23
- except ImportError as e:
24
- error_message = f"OllamaInfo. The 'ollama' library was not found: {e}"
25
- return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
26
- except ConnectionError as e:
27
- error_message = f"OllamaInfo. Error connecting to Ollama: {e}"
28
- return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
29
- except Exception as e:
30
- error_message = f"OllamaInfo. An unexpected error occurred: {e}"
31
- return OllamaInfo(model_names=[], is_running=False, error_message=error_message)
32
-
33
- model_names = [model.model for model in list_response.models]
34
- return OllamaInfo(model_names=model_names, is_running=True, error_message=None)
35
-
36
- def is_model_available(self, find_model: str) -> bool:
37
- """Checks if a specific model is available in the list of model names."""
38
- return find_model in self.model_names
39
-
40
- if __name__ == '__main__':
41
- find_model = 'qwen2.5-coder:latest'
42
- ollama_info = OllamaInfo.obtain_info()
43
- print(f"Error message: {ollama_info.error_message}")
44
- print(f'Is Ollama running: {ollama_info.is_running}')
45
- found = ollama_info.is_model_available(find_model)
46
- print(f'Has model {find_model}: {found}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PROMPT> python -m src.llm_util.ollama_info
3
+ """
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+ @dataclass
8
+ class OllamaInfo:
9
+ """
10
+ Details about the Ollama service, including a list of available model names,
11
+ a flag indicating whether the service is running, and an optional error message.
12
+ """
13
+ model_names: list[str]
14
+ is_running: bool
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]
35
+ return OllamaInfo(model_names=model_names, is_running=True, error_message=None)
36
+
37
+ def is_model_available(self, find_model: str) -> bool:
38
+ """
39
+ Checks if a specific model is available.
40
+
41
+ Args:
42
+ find_model: Name of the model to check. Can be either a local Ollama model
43
+ or a HuggingFace GGUF model (prefixed with 'hf.co/').
44
+
45
+ Returns:
46
+ bool: True if the model is available or is a valid GGUF model path.
47
+ """
48
+ if not find_model:
49
+ return False
50
+
51
+ # Support direct use of GGUF models from HuggingFace
52
+ if find_model.startswith("hf.co/"):
53
+ return True
54
+
55
+ return find_model in self.model_names
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)
63
+ print(f'Has model {find_model}: {found}')
src/pitch/convert_pitch_to_markdown.py CHANGED
@@ -107,7 +107,7 @@ class ConvertPitchToMarkdown:
107
  markdown_content = response_content # Use the entire content if delimiters are missing
108
  logger.warning("Output delimiters not found in LLM response.")
109
 
110
- # The bullet lists are supposed to be preceeded by 2 newlines.
111
  # However often there is just 1 newline.
112
  # This fix makes sure there are 2 newlines before bullet lists.
113
  markdown_content = fix_bullet_lists(markdown_content)
 
107
  markdown_content = response_content # Use the entire content if delimiters are missing
108
  logger.warning("Output delimiters not found in LLM response.")
109
 
110
+ # The bullet lists are supposed to be preceded by 2 newlines.
111
  # However often there is just 1 newline.
112
  # This fix makes sure there are 2 newlines before bullet lists.
113
  markdown_content = fix_bullet_lists(markdown_content)
src/plan/app_text2plan.py CHANGED
@@ -203,6 +203,13 @@ def initialize_browser_settings(browser_state, session_state: SessionState):
203
  openrouter_api_key = settings.get("openrouter_api_key_text", "")
204
  model = settings.get("model_radio", default_model_value)
205
  speedvsdetail = settings.get("speedvsdetail_radio", SpeedVsDetailEnum.ALL_DETAILS_BUT_SLOW)
 
 
 
 
 
 
 
206
  session_state.openrouter_api_key = openrouter_api_key
207
  session_state.llm_model = model
208
  session_state.speedvsdetail = speedvsdetail
 
203
  openrouter_api_key = settings.get("openrouter_api_key_text", "")
204
  model = settings.get("model_radio", default_model_value)
205
  speedvsdetail = settings.get("speedvsdetail_radio", SpeedVsDetailEnum.ALL_DETAILS_BUT_SLOW)
206
+
207
+ # When making changes to the llm_config.json, it may happen that the selected model is no longer among the available_model_names.
208
+ # In that case, set the model to the default_model_value.
209
+ if model not in [item[1] for item in available_model_names]:
210
+ logger.info(f"initialize_browser_settings: model '{model}' is not in available_model_names. Setting to default_model_value: {default_model_value}")
211
+ model = default_model_value
212
+
213
  session_state.openrouter_api_key = openrouter_api_key
214
  session_state.llm_model = model
215
  session_state.speedvsdetail = speedvsdetail
src/plan/data/simple_plan_prompts.jsonl CHANGED
@@ -3,6 +3,7 @@
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"]}
4
  {"id": "930c2abc-faa7-4c21-8ae1-f0323cbcd120", "prompt": "Open the first space elevator terminal in Berlin, Germany, connecting Earths surface to orbit.", "tags": ["space", "exploration", "berlin", "germany"]}
5
  {"id": "45763178-8ba8-4a86-adcd-63ed19d4d47b", "prompt": "Establish a humanoid robot factory in Paris, France.", "tags": ["paris", "france", "robots"]}
 
6
  {"id": "67c461a9-3364-42a4-bf8f-643315abfcf6", "prompt": "When I die, I want to become a skeleton, skull and bones. I love zombies.", "tags": ["death", "bones", "post-mortem", "zoombie"]}
7
  {"id": "d70ced0b-d5c7-4b84-88d7-18a5ada2cfee", "prompt": "Construct a new metro line under the city center of Copenhagen, Denmark.", "tags": ["denmark", "copenhagen", "metro"]}
8
  {"id": "f24a6ba9-20ce-40bb-866a-263b87b5ddcc", "prompt": "I want to make a restaurant for puzzle solving happy people. An important part is that humans are solving puzzles with each other. While having something to drink and eat.", "tags": ["restaurant", "puzzle", "food"]}
@@ -21,8 +22,10 @@
21
  {"id": "d3e10877-446f-4eb0-8027-864e923973b0", "prompt": "Construct a train bridge between Denmark and England.", "tags": ["denmark", "england", "bridge"]}
22
  {"id": "9fbb7ff9-5dc3-44f4-9823-dba3f31d3661", "prompt": "Write a Python script for a bouncing yellow ball within a square. Make sure to handle collision detection. Make the square slightly rotate. Implement it in Python. Make sure the ball stays within the square.", "tags": ["programming", "python", "collision detection"]}
23
  {"id": "676cbca8-5d49-42a0-8826-398318004703", "prompt": "Write a Python script for a snake shape keep bouncing within a pentagon. Make sure to handle collision detection properly. Make the pentagon slowly rotate.", "tags": ["programming", "python", "collision detection"]}
 
24
  {"id": "a9113924-6148-4a0c-b72a-eecdb856e1e2", "prompt": "Investigate outbreak of a deadly new disease in the jungle.", "tags": ["outbreak", "jungle", "emergency"]}
25
- {"id": "d68da41f-9341-40c0-85ee-7fc9181271d1", "prompt": "Eradication of Oak Processionary Caterpillars. It was discovered in Denmark for the first time just under three weeks ago, with around 800 nests found in trees in southeastern Odense. You suffocate from the caterpillar’s toxic hairs. Limit the outbreak as quick as possible.", "tags": ["outbreak", "toxic", "caterpillar", "emergency"]}
 
26
  {"id": "4dc34d55-0d0d-4e9d-92f4-23765f49dd29", "prompt": "Establish a solar farm in Denmark.", "tags": ["denmark", "energy", "sun"]}
27
  {"id": "0bb4a7d3-c16b-4b21-8a9b-20e1cd4002d4", "prompt": "Develop a sustainable solution for extreme poverty in regions where people live on less than 2 USD per day. Focus on improving access to basic necessities like food, shelter, and clean water, and explain how you would allocate investments given that sectors like infrastructure may require higher per capita funding. Indicate whether your approach leverages existing systems or builds new capacity. Assume this initiative will impact 5 million people over 5 years with a total budget of 500 million USD.", "tags": ["poverty", "sustainability", "development"]}
28
  {"id": "307f7e0c-a160-4b7a-9e3c-76577164497e", "prompt": "Create a comprehensive plan to address hunger and malnutrition in impoverished communities by enhancing food security and nutritional education. Provide a detailed cost breakdown and clarify which components might need additional per capita investment. State whether you will leverage existing food distribution networks or develop new infrastructure. Assume the plan targets 3 million individuals across 10 countries with a total budget of 300 million USD over 4 years.", "tags": ["hunger", "malnutrition", "nutrition"]}
@@ -34,3 +37,4 @@
34
  {"id": "a9f410c0-120e-45d6-b042-e88ca47b39bb", "prompt": "Formulate a housing security plan that addresses overcrowding, unsafe living conditions, and homelessness by promoting secure and dignified housing. Clarify whether the initiative involves new construction, rehabilitation of existing structures, or a combination of both, and outline a phased investment strategy given the higher costs typically associated with housing projects. Assume the plan will provide housing for 200,000 individuals over 4 years with a total budget of 250 million USD.", "tags": ["housing", "security", "urban development"]}
35
  {"id": "cdf7f29d-bbcb-478d-8b5a-e82e74ed8626", "prompt": "Propose comprehensive peace initiatives and conflict resolution strategies for areas experiencing high rates of violence and political instability. Detail how your approach will protect vulnerable populations, including mechanisms for community engagement, reconciliation, and rebuilding. Assume the intervention will affect 1 million people in conflict zones over 3 years with a total budget of 150 million USD.", "tags": ["conflict", "peace", "stability"]}
36
  {"id": "79ef9ebf-3173-4b33-81f9-abbd3da7da6d", "prompt": "Design robust adaptation and resilience programs for communities facing environmental degradation and the effects of climate change, especially in disaster-prone regions. Include both short-term relief measures and long-term sustainability strategies, and provide details on how funds will be allocated between immediate response and infrastructure improvements. Assume the initiative will aid 1.2 million people in the 10 most affected countries over 5 years with a total budget of 180 million USD.", "tags": ["environment", "climate change", "resilience"]}
 
 
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"]}
4
  {"id": "930c2abc-faa7-4c21-8ae1-f0323cbcd120", "prompt": "Open the first space elevator terminal in Berlin, Germany, connecting Earths surface to orbit.", "tags": ["space", "exploration", "berlin", "germany"]}
5
  {"id": "45763178-8ba8-4a86-adcd-63ed19d4d47b", "prompt": "Establish a humanoid robot factory in Paris, France.", "tags": ["paris", "france", "robots"]}
6
+ {"id": "de626417-4871-4acc-899d-2c41fd148807", "prompt": "Establish a humanoid robot factory in Caracas, Venezuela.", "tags": ["caracas", "venezuela", "robots"]}
7
  {"id": "67c461a9-3364-42a4-bf8f-643315abfcf6", "prompt": "When I die, I want to become a skeleton, skull and bones. I love zombies.", "tags": ["death", "bones", "post-mortem", "zoombie"]}
8
  {"id": "d70ced0b-d5c7-4b84-88d7-18a5ada2cfee", "prompt": "Construct a new metro line under the city center of Copenhagen, Denmark.", "tags": ["denmark", "copenhagen", "metro"]}
9
  {"id": "f24a6ba9-20ce-40bb-866a-263b87b5ddcc", "prompt": "I want to make a restaurant for puzzle solving happy people. An important part is that humans are solving puzzles with each other. While having something to drink and eat.", "tags": ["restaurant", "puzzle", "food"]}
 
22
  {"id": "d3e10877-446f-4eb0-8027-864e923973b0", "prompt": "Construct a train bridge between Denmark and England.", "tags": ["denmark", "england", "bridge"]}
23
  {"id": "9fbb7ff9-5dc3-44f4-9823-dba3f31d3661", "prompt": "Write a Python script for a bouncing yellow ball within a square. Make sure to handle collision detection. Make the square slightly rotate. Implement it in Python. Make sure the ball stays within the square.", "tags": ["programming", "python", "collision detection"]}
24
  {"id": "676cbca8-5d49-42a0-8826-398318004703", "prompt": "Write a Python script for a snake shape keep bouncing within a pentagon. Make sure to handle collision detection properly. Make the pentagon slowly rotate.", "tags": ["programming", "python", "collision detection"]}
25
+ {"id": "5719d9d2-da23-410e-beac-af8fe65d5482", "prompt": "Write a blog post about Paris, listing the top attractions.", "tags": ["blog", "paris", "tourism"]}
26
  {"id": "a9113924-6148-4a0c-b72a-eecdb856e1e2", "prompt": "Investigate outbreak of a deadly new disease in the jungle.", "tags": ["outbreak", "jungle", "emergency"]}
27
+ {"id": "d68da41f-9341-40c0-85ee-7fc9181271d1", "prompt": "Eradication of Oak Processionary Caterpillars. It was discovered in Denmark for the first time just under three weeks ago, with around 800 nests found in trees in southeastern Odense. You suffocate from the caterpillar’s toxic hairs. Limit the outbreak as quick as possible.", "tags": ["denmark", "outbreak", "toxic", "caterpillar", "emergency"]}
28
+ {"id": "87cbb86d-8ee1-4477-a71d-5e702bf6a887", "prompt": "Launch a pollution monitoring program for Roskilde Fjord in Roskilde, Denmark, in response to alarming fish die-offs. Track oxygen levels, nutrients, microplastics, pH, nitrates, and phosphates in real time.", "tags": ["denmark", "roskilde", "pollution", "fish"]}
29
  {"id": "4dc34d55-0d0d-4e9d-92f4-23765f49dd29", "prompt": "Establish a solar farm in Denmark.", "tags": ["denmark", "energy", "sun"]}
30
  {"id": "0bb4a7d3-c16b-4b21-8a9b-20e1cd4002d4", "prompt": "Develop a sustainable solution for extreme poverty in regions where people live on less than 2 USD per day. Focus on improving access to basic necessities like food, shelter, and clean water, and explain how you would allocate investments given that sectors like infrastructure may require higher per capita funding. Indicate whether your approach leverages existing systems or builds new capacity. Assume this initiative will impact 5 million people over 5 years with a total budget of 500 million USD.", "tags": ["poverty", "sustainability", "development"]}
31
  {"id": "307f7e0c-a160-4b7a-9e3c-76577164497e", "prompt": "Create a comprehensive plan to address hunger and malnutrition in impoverished communities by enhancing food security and nutritional education. Provide a detailed cost breakdown and clarify which components might need additional per capita investment. State whether you will leverage existing food distribution networks or develop new infrastructure. Assume the plan targets 3 million individuals across 10 countries with a total budget of 300 million USD over 4 years.", "tags": ["hunger", "malnutrition", "nutrition"]}
 
37
  {"id": "a9f410c0-120e-45d6-b042-e88ca47b39bb", "prompt": "Formulate a housing security plan that addresses overcrowding, unsafe living conditions, and homelessness by promoting secure and dignified housing. Clarify whether the initiative involves new construction, rehabilitation of existing structures, or a combination of both, and outline a phased investment strategy given the higher costs typically associated with housing projects. Assume the plan will provide housing for 200,000 individuals over 4 years with a total budget of 250 million USD.", "tags": ["housing", "security", "urban development"]}
38
  {"id": "cdf7f29d-bbcb-478d-8b5a-e82e74ed8626", "prompt": "Propose comprehensive peace initiatives and conflict resolution strategies for areas experiencing high rates of violence and political instability. Detail how your approach will protect vulnerable populations, including mechanisms for community engagement, reconciliation, and rebuilding. Assume the intervention will affect 1 million people in conflict zones over 3 years with a total budget of 150 million USD.", "tags": ["conflict", "peace", "stability"]}
39
  {"id": "79ef9ebf-3173-4b33-81f9-abbd3da7da6d", "prompt": "Design robust adaptation and resilience programs for communities facing environmental degradation and the effects of climate change, especially in disaster-prone regions. Include both short-term relief measures and long-term sustainability strategies, and provide details on how funds will be allocated between immediate response and infrastructure improvements. Assume the initiative will aid 1.2 million people in the 10 most affected countries over 5 years with a total budget of 180 million USD.", "tags": ["environment", "climate change", "resilience"]}
40
+ {"id": "fe853807-5bfe-4e5b-8071-d6db3c360279", "prompt": "My daily commute from home to work takes 1 hour. My bike is broken and need an alternative plan. I live in Amsterdam, Netherlands.", "tags": ["bike", "traffic", "amsterdam", "netherlands"]}
src/plan/filenames.py CHANGED
@@ -2,9 +2,22 @@ from enum import Enum
2
 
3
  class FilenameEnum(str, Enum):
4
  INITIAL_PLAN = "001-plan.txt"
5
- MAKE_ASSUMPTIONS_RAW = "002-1-make_assumptions_raw.json"
6
- MAKE_ASSUMPTIONS = "002-2-make_assumptions.json"
7
- DISTILL_ASSUMPTIONS_RAW = "003-distill_assumptions.json"
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  PRE_PROJECT_ASSESSMENT_RAW = "004-1-pre_project_assessment_raw.json"
9
  PRE_PROJECT_ASSESSMENT = "004-2-pre_project_assessment.json"
10
  PROJECT_PLAN = "005-project_plan.json"
 
2
 
3
  class FilenameEnum(str, Enum):
4
  INITIAL_PLAN = "001-plan.txt"
5
+ PLAN_TYPE_RAW = "002-1-plan_type_raw.json"
6
+ PLAN_TYPE_MARKDOWN = "002-2-plan_type.md"
7
+ PHYSICAL_LOCATIONS_RAW = "002-3-physical_locations_raw.json"
8
+ PHYSICAL_LOCATIONS_MARKDOWN = "002-4-physical_locations.md"
9
+ CURRENCY_STRATEGY_RAW = "002-5-currency_strategy_raw.json"
10
+ CURRENCY_STRATEGY_MARKDOWN = "002-6-currency_strategy.md"
11
+ IDENTIFY_RISKS_RAW = "003-1-identify_risks_raw.json"
12
+ IDENTIFY_RISKS_MARKDOWN = "003-2-identify_risks.md"
13
+ MAKE_ASSUMPTIONS_RAW = "003-3-make_assumptions_raw.json"
14
+ MAKE_ASSUMPTIONS_CLEAN = "003-4-make_assumptions.json"
15
+ MAKE_ASSUMPTIONS_MARKDOWN = "003-5-make_assumptions.md"
16
+ DISTILL_ASSUMPTIONS_RAW = "003-6-distill_assumptions_raw.json"
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"
src/plan/run_plan_pipeline.py CHANGED
@@ -16,8 +16,13 @@ from src.plan.filenames import FilenameEnum
16
  from src.plan.speedvsdetail import SpeedVsDetailEnum
17
  from src.plan.plan_file import PlanFile
18
  from src.plan.find_plan_prompt import find_plan_prompt
 
 
 
 
19
  from src.assume.make_assumptions import MakeAssumptions
20
- from src.assume.assumption_orchestrator import AssumptionOrchestrator
 
21
  from src.expert.pre_project_assessment import PreProjectAssessment
22
  from src.plan.create_project_plan import CreateProjectPlan
23
  from src.swot.swot_analysis import SWOTAnalysis
@@ -76,9 +81,9 @@ class SetupTask(PlanTask):
76
  plan_file = PlanFile.create(plan_prompt)
77
  plan_file.save(self.output().path)
78
 
79
- class AssumptionsTask(PlanTask):
80
  """
81
- Make assumptions about the plan.
82
  Depends on:
83
  - SetupTask (for the initial plan)
84
  """
@@ -88,47 +93,457 @@ class AssumptionsTask(PlanTask):
88
  return SetupTask(run_id=self.run_id)
89
 
90
  def output(self):
91
- return luigi.LocalTarget(str(self.file_path(FilenameEnum.DISTILL_ASSUMPTIONS_RAW)))
 
 
 
92
 
93
  def run(self):
94
- logger.info("Making assumptions about the plan...")
95
 
96
  # Read inputs from required tasks.
97
  with self.input().open("r") as f:
98
  plan_prompt = f.read()
99
 
100
- # I'm currently debugging the speedvsdetail parameter. When I'm done I can remove it.
101
- # Verifying that the speedvsdetail parameter is set correctly.
102
- logger.info(f"AssumptionsTask.speedvsdetail: {self.speedvsdetail}")
103
- if self.speedvsdetail == SpeedVsDetailEnum.FAST_BUT_SKIP_DETAILS:
104
- logger.info("AssumptionsTask: We are in FAST_BUT_SKIP_DETAILS mode.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  else:
106
- logger.info("AssumptionsTask: We are in another mode")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  llm = get_llm(self.llm_model)
109
 
110
- # Define callback functions.
111
- def phase1_post_callback(make_assumptions: MakeAssumptions) -> None:
112
- raw_path = self.run_dir / FilenameEnum.MAKE_ASSUMPTIONS_RAW.value
113
- cleaned_path = self.run_dir / FilenameEnum.MAKE_ASSUMPTIONS.value
114
- make_assumptions.save_raw(str(raw_path))
115
- make_assumptions.save_assumptions(str(cleaned_path))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
- # Execute
118
- orchestrator = AssumptionOrchestrator()
119
- orchestrator.phase1_post_callback = phase1_post_callback
120
- orchestrator.execute(llm, plan_prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- # Write the assumptions to the output file.
123
- file_path = self.run_dir / FilenameEnum.DISTILL_ASSUMPTIONS_RAW.value
124
- orchestrator.distill_assumptions.save_raw(str(file_path))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
 
127
  class PreProjectAssessmentTask(PlanTask):
128
  llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
129
 
130
  def requires(self):
131
- return SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail)
 
 
 
132
 
133
  def output(self):
134
  return {
@@ -140,11 +555,17 @@ class PreProjectAssessmentTask(PlanTask):
140
  logger.info("Conducting pre-project assessment...")
141
 
142
  # Read the plan prompt from the SetupTask's output.
143
- with self.input().open("r") as f:
144
  plan_prompt = f.read()
145
 
 
 
 
146
  # Build the query.
147
- query = f"Initial plan: {plan_prompt}\n\n"
 
 
 
148
 
149
  # Get an instance of your LLM.
150
  llm = get_llm(self.llm_model)
@@ -167,13 +588,13 @@ class ProjectPlanTask(PlanTask):
167
  def requires(self):
168
  """
169
  This task depends on:
170
- - SetupTask: produces the plan prompt (001-plan.txt)
171
- - AssumptionsTask: produces the distilled assumptions (003-distill_assumptions.json)
172
  - PreProjectAssessmentTask: produces the pre‑project assessment files
173
  """
174
  return {
175
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
176
- 'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
177
  'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
178
  }
179
 
@@ -188,10 +609,9 @@ class ProjectPlanTask(PlanTask):
188
  with setup_target.open("r") as f:
189
  plan_prompt = f.read()
190
 
191
- # Read assumptions from the distilled assumptions output.
192
- assumptions_file = self.input()['assumptions']
193
- with assumptions_file.open("r") as f:
194
- assumption_list = json.load(f)
195
 
196
  # Read the pre-project assessment from its file.
197
  pre_project_assessment_file = self.input()['preproject']['clean']
@@ -200,9 +620,9 @@ class ProjectPlanTask(PlanTask):
200
 
201
  # Build the query.
202
  query = (
203
- f"Initial plan: {plan_prompt}\n\n"
204
- f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
205
- f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}"
206
  )
207
 
208
  # Get an LLM instance.
@@ -222,7 +642,7 @@ class FindTeamMembersTask(PlanTask):
222
  def requires(self):
223
  return {
224
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
225
- 'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
226
  'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
227
  'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
228
  }
@@ -236,19 +656,19 @@ class FindTeamMembersTask(PlanTask):
236
  def run(self):
237
  logger.info("FindTeamMembers. Loading files...")
238
 
239
- # 1. Read the plan prompt from SetupTask.
240
  with self.input()['setup'].open("r") as f:
241
  plan_prompt = f.read()
242
 
243
- # 2. Read the distilled assumptions from AssumptionsTask.
244
- with self.input()['assumptions'].open("r") as f:
245
- assumption_list = json.load(f)
246
 
247
- # 3. Read the pre-project assessment from PreProjectAssessmentTask.
248
  with self.input()['preproject']['clean'].open("r") as f:
249
  pre_project_assessment_dict = json.load(f)
250
 
251
- # 4. Read the project plan from ProjectPlanTask.
252
  with self.input()['project_plan'].open("r") as f:
253
  project_plan_dict = json.load(f)
254
 
@@ -256,10 +676,10 @@ class FindTeamMembersTask(PlanTask):
256
 
257
  # Build the query.
258
  query = (
259
- f"Initial plan: {plan_prompt}\n\n"
260
- f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
261
- f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
262
- f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}"
263
  )
264
 
265
  # Create LLM instance.
@@ -290,7 +710,7 @@ class EnrichTeamMembersWithContractTypeTask(PlanTask):
290
  def requires(self):
291
  return {
292
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
293
- 'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
294
  'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
295
  'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
296
  'find_team_members': FindTeamMembersTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
@@ -305,23 +725,23 @@ class EnrichTeamMembersWithContractTypeTask(PlanTask):
305
  def run(self):
306
  logger.info("EnrichTeamMembersWithContractType. Loading files...")
307
 
308
- # 1. Read the plan prompt from SetupTask.
309
  with self.input()['setup'].open("r") as f:
310
  plan_prompt = f.read()
311
 
312
- # 2. Read the distilled assumptions from AssumptionsTask.
313
- with self.input()['assumptions'].open("r") as f:
314
- assumption_list = json.load(f)
315
 
316
- # 3. Read the pre-project assessment from PreProjectAssessmentTask.
317
  with self.input()['preproject']['clean'].open("r") as f:
318
  pre_project_assessment_dict = json.load(f)
319
 
320
- # 4. Read the project plan from ProjectPlanTask.
321
  with self.input()['project_plan'].open("r") as f:
322
  project_plan_dict = json.load(f)
323
 
324
- # 5. Read the team_member_list from FindTeamMembersTask.
325
  with self.input()['find_team_members']['clean'].open("r") as f:
326
  team_member_list = json.load(f)
327
 
@@ -329,11 +749,11 @@ class EnrichTeamMembersWithContractTypeTask(PlanTask):
329
 
330
  # Build the query.
331
  query = (
332
- f"Initial plan: {plan_prompt}\n\n"
333
- f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
334
- f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
335
- f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
336
- f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
337
  )
338
 
339
  # Create LLM instance.
@@ -364,7 +784,7 @@ class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
364
  def requires(self):
365
  return {
366
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
367
- 'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
368
  'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
369
  'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
370
  'enrich_team_members_with_contract_type': EnrichTeamMembersWithContractTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
@@ -379,23 +799,23 @@ class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
379
  def run(self):
380
  logger.info("EnrichTeamMembersWithBackgroundStoryTask. Loading files...")
381
 
382
- # 1. Read the plan prompt from SetupTask.
383
  with self.input()['setup'].open("r") as f:
384
  plan_prompt = f.read()
385
 
386
- # 2. Read the distilled assumptions from AssumptionsTask.
387
- with self.input()['assumptions'].open("r") as f:
388
- assumption_list = json.load(f)
389
 
390
- # 3. Read the pre-project assessment from PreProjectAssessmentTask.
391
  with self.input()['preproject']['clean'].open("r") as f:
392
  pre_project_assessment_dict = json.load(f)
393
 
394
- # 4. Read the project plan from ProjectPlanTask.
395
  with self.input()['project_plan'].open("r") as f:
396
  project_plan_dict = json.load(f)
397
 
398
- # 5. Read the team_member_list from EnrichTeamMembersWithContractTypeTask.
399
  with self.input()['enrich_team_members_with_contract_type']['clean'].open("r") as f:
400
  team_member_list = json.load(f)
401
 
@@ -403,11 +823,11 @@ class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
403
 
404
  # Build the query.
405
  query = (
406
- f"Initial plan: {plan_prompt}\n\n"
407
- f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
408
- f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
409
- f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
410
- f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
411
  )
412
 
413
  # Create LLM instance.
@@ -438,7 +858,7 @@ class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
438
  def requires(self):
439
  return {
440
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
441
- 'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
442
  'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
443
  'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
444
  'enrich_team_members_with_background_story': EnrichTeamMembersWithBackgroundStoryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
@@ -453,23 +873,23 @@ class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
453
  def run(self):
454
  logger.info("EnrichTeamMembersWithEnvironmentInfoTask. Loading files...")
455
 
456
- # 1. Read the plan prompt from SetupTask.
457
  with self.input()['setup'].open("r") as f:
458
  plan_prompt = f.read()
459
 
460
- # 2. Read the distilled assumptions from AssumptionsTask.
461
- with self.input()['assumptions'].open("r") as f:
462
- assumption_list = json.load(f)
463
 
464
- # 3. Read the pre-project assessment from PreProjectAssessmentTask.
465
  with self.input()['preproject']['clean'].open("r") as f:
466
  pre_project_assessment_dict = json.load(f)
467
 
468
- # 4. Read the project plan from ProjectPlanTask.
469
  with self.input()['project_plan'].open("r") as f:
470
  project_plan_dict = json.load(f)
471
 
472
- # 5. Read the team_member_list from EnrichTeamMembersWithBackgroundStoryTask.
473
  with self.input()['enrich_team_members_with_background_story']['clean'].open("r") as f:
474
  team_member_list = json.load(f)
475
 
@@ -477,11 +897,11 @@ class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
477
 
478
  # Build the query.
479
  query = (
480
- f"Initial plan: {plan_prompt}\n\n"
481
- f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
482
- f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
483
- f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
484
- f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
485
  )
486
 
487
  # Create LLM instance.
@@ -512,7 +932,7 @@ class ReviewTeamTask(PlanTask):
512
  def requires(self):
513
  return {
514
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
515
- 'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
516
  'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
517
  'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
518
  'enrich_team_members_with_environment_info': EnrichTeamMembersWithEnvironmentInfoTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
@@ -524,23 +944,23 @@ class ReviewTeamTask(PlanTask):
524
  def run(self):
525
  logger.info("ReviewTeamTask. Loading files...")
526
 
527
- # 1. Read the plan prompt from SetupTask.
528
  with self.input()['setup'].open("r") as f:
529
  plan_prompt = f.read()
530
 
531
- # 2. Read the distilled assumptions from AssumptionsTask.
532
- with self.input()['assumptions'].open("r") as f:
533
- assumption_list = json.load(f)
534
 
535
- # 3. Read the pre-project assessment from PreProjectAssessmentTask.
536
  with self.input()['preproject']['clean'].open("r") as f:
537
  pre_project_assessment_dict = json.load(f)
538
 
539
- # 4. Read the project plan from ProjectPlanTask.
540
  with self.input()['project_plan'].open("r") as f:
541
  project_plan_dict = json.load(f)
542
 
543
- # 5. Read the team_member_list from EnrichTeamMembersWithEnvironmentInfoTask.
544
  with self.input()['enrich_team_members_with_environment_info']['clean'].open("r") as f:
545
  team_member_list = json.load(f)
546
 
@@ -553,11 +973,11 @@ class ReviewTeamTask(PlanTask):
553
 
554
  # Build the query.
555
  query = (
556
- f"Initial plan: {plan_prompt}\n\n"
557
- f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
558
- f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
559
- f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
560
- f"Document with team members:\n{team_document_markdown}"
561
  )
562
 
563
  # Create LLM instance.
@@ -617,7 +1037,7 @@ class SWOTAnalysisTask(PlanTask):
617
  def requires(self):
618
  return {
619
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
620
- 'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
621
  'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
622
  'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
623
  }
@@ -631,19 +1051,19 @@ class SWOTAnalysisTask(PlanTask):
631
  def run(self):
632
  logger.info("SWOTAnalysisTask. Loading files...")
633
 
634
- # 1. Read the plan prompt from SetupTask.
635
  with self.input()['setup'].open("r") as f:
636
  plan_prompt = f.read()
637
 
638
- # 2. Read the distilled assumptions from AssumptionsTask.
639
- with self.input()['assumptions'].open("r") as f:
640
- assumption_list = json.load(f)
641
 
642
- # 3. Read the pre-project assessment from PreProjectAssessmentTask.
643
  with self.input()['preproject']['clean'].open("r") as f:
644
  pre_project_assessment_dict = json.load(f)
645
 
646
- # 4. Read the project plan from ProjectPlanTask.
647
  with self.input()['project_plan'].open("r") as f:
648
  project_plan_dict = json.load(f)
649
 
@@ -651,10 +1071,10 @@ class SWOTAnalysisTask(PlanTask):
651
 
652
  # Build the query for SWOT analysis.
653
  query = (
654
- f"Initial plan: {plan_prompt}\n\n"
655
- f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
656
- f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
657
- f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}"
658
  )
659
 
660
  # Create LLM instances for SWOT analysis.
@@ -1289,6 +1709,7 @@ class ReportTask(PlanTask):
1289
 
1290
  def requires(self):
1291
  return {
 
1292
  'team_markdown': TeamMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1293
  'swot_analysis': SWOTAnalysisTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1294
  'pitch_markdown': ConvertPitchToMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
@@ -1298,6 +1719,7 @@ class ReportTask(PlanTask):
1298
 
1299
  def run(self):
1300
  rg = ReportGenerator()
 
1301
  rg.append_pitch_markdown(self.input()['pitch_markdown']['markdown'].path)
1302
  rg.append_swot_analysis_markdown(self.input()['swot_analysis']['markdown'].path)
1303
  rg.append_team_markdown(self.input()['team_markdown'].path)
@@ -1311,7 +1733,14 @@ class FullPlanPipeline(PlanTask):
1311
  def requires(self):
1312
  return {
1313
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
1314
- 'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
 
 
 
 
 
 
 
1315
  'pre_project_assessment': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1316
  'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1317
  'find_team_members': FindTeamMembersTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
 
16
  from src.plan.speedvsdetail import SpeedVsDetailEnum
17
  from src.plan.plan_file import PlanFile
18
  from src.plan.find_plan_prompt import find_plan_prompt
19
+ from src.assume.identify_plan_type import IdentifyPlanType
20
+ from src.assume.physical_locations import PhysicalLocations
21
+ from src.assume.currency_strategy import CurrencyStrategy
22
+ 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
 
81
  plan_file = PlanFile.create(plan_prompt)
82
  plan_file.save(self.output().path)
83
 
84
+ class PlanTypeTask(PlanTask):
85
  """
86
+ Determine if the plan is purely digital or requires physical locations.
87
  Depends on:
88
  - SetupTask (for the initial plan)
89
  """
 
93
  return SetupTask(run_id=self.run_id)
94
 
95
  def output(self):
96
+ return {
97
+ 'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.PLAN_TYPE_RAW))),
98
+ 'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.PLAN_TYPE_MARKDOWN)))
99
+ }
100
 
101
  def run(self):
102
+ logger.info("Identifying PlanType of the plan...")
103
 
104
  # Read inputs from required tasks.
105
  with self.input().open("r") as f:
106
  plan_prompt = f.read()
107
 
108
+ llm = get_llm(self.llm_model)
109
+
110
+ identify_plan_type = IdentifyPlanType.execute(llm, plan_prompt)
111
+
112
+ # Write the result to disk.
113
+ output_raw_path = self.output()['raw'].path
114
+ identify_plan_type.save_raw(str(output_raw_path))
115
+ output_markdown_path = self.output()['markdown'].path
116
+ identify_plan_type.save_markdown(str(output_markdown_path))
117
+
118
+
119
+ class PhysicalLocationsTask(PlanTask):
120
+ """
121
+ Identify/suggest physical locations for the plan.
122
+ Depends on:
123
+ - SetupTask (for the initial plan)
124
+ - PlanTypeTask (for the plan type)
125
+ """
126
+ llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
127
+
128
+ def requires(self):
129
+ return {
130
+ 'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
131
+ 'plan_type': PlanTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
132
+ }
133
+
134
+ def output(self):
135
+ return {
136
+ 'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.PHYSICAL_LOCATIONS_RAW))),
137
+ 'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.PHYSICAL_LOCATIONS_MARKDOWN)))
138
+ }
139
+
140
+ def run(self):
141
+ logger.info("Identify/suggest physical locations for the plan...")
142
+
143
+ # Read inputs from required tasks.
144
+ with self.input()['setup'].open("r") as f:
145
+ plan_prompt = f.read()
146
+
147
+ with self.input()['plan_type']['raw'].open("r") as f:
148
+ plan_type_dict = json.load(f)
149
+
150
+ output_raw_path = self.output()['raw'].path
151
+ output_markdown_path = self.output()['markdown'].path
152
+
153
+ llm = get_llm(self.llm_model)
154
+
155
+ plan_type = plan_type_dict.get("plan_type")
156
+ if plan_type == "physical":
157
+ query = (
158
+ f"File 'plan.txt':\n{plan_prompt}\n\n"
159
+ f"File 'plan_type.json':\n{format_json_for_use_in_query(plan_type_dict)}"
160
+ )
161
+
162
+ physical_locations = PhysicalLocations.execute(llm, query)
163
+
164
+ # Write the physical locations to disk.
165
+ physical_locations.save_raw(str(output_raw_path))
166
+ physical_locations.save_markdown(str(output_markdown_path))
167
  else:
168
+ # Write an empty file to indicate that there are no physical locations.
169
+ data = {
170
+ "comment": "The plan is purely digital, without any physical locations."
171
+ }
172
+ with open(output_raw_path, "w") as f:
173
+ json.dump(data, f, indent=2)
174
+
175
+ with open(output_markdown_path, "w", encoding='utf-8') as f:
176
+ f.write("The plan is purely digital, without any physical locations.")
177
+
178
+ class CurrencyStrategyTask(PlanTask):
179
+ """
180
+ Identify/suggest what currency to use for the plan, depending on the physical locations.
181
+ Depends on:
182
+ - SetupTask (for the initial plan)
183
+ - PlanTypeTask (for the plan type)
184
+ - PhysicalLocationsTask (for the physical locations)
185
+ """
186
+ llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
187
+
188
+ def requires(self):
189
+ return {
190
+ 'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
191
+ 'plan_type': PlanTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
192
+ 'physical_locations': PhysicalLocationsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
193
+ }
194
+
195
+ def output(self):
196
+ return {
197
+ 'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.CURRENCY_STRATEGY_RAW))),
198
+ 'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.CURRENCY_STRATEGY_MARKDOWN)))
199
+ }
200
+
201
+ def run(self):
202
+ logger.info("Currency strategy for the plan...")
203
+
204
+ # Read inputs from required tasks.
205
+ with self.input()['setup'].open("r") as f:
206
+ plan_prompt = f.read()
207
+
208
+ with self.input()['plan_type']['raw'].open("r") as f:
209
+ plan_type_dict = json.load(f)
210
+
211
+ with self.input()['physical_locations']['raw'].open("r") as f:
212
+ physical_locations_dict = json.load(f)
213
+
214
+ query = (
215
+ f"File 'plan.txt':\n{plan_prompt}\n\n"
216
+ f"File 'plan_type.json':\n{format_json_for_use_in_query(plan_type_dict)}\n\n"
217
+ f"File 'physical_locations.json':\n{format_json_for_use_in_query(physical_locations_dict)}"
218
+ )
219
 
220
  llm = get_llm(self.llm_model)
221
 
222
+ currency_strategy = CurrencyStrategy.execute(llm, query)
223
+
224
+ # Write the result to disk.
225
+ output_raw_path = self.output()['raw'].path
226
+ currency_strategy.save_raw(str(output_raw_path))
227
+ output_markdown_path = self.output()['markdown'].path
228
+ currency_strategy.save_markdown(str(output_markdown_path))
229
+
230
+
231
+ class IdentifyRisksTask(PlanTask):
232
+ """
233
+ Identify risks for the plan, depending on the physical locations.
234
+ Depends on:
235
+ - SetupTask (for the initial plan)
236
+ - PlanTypeTask (for the plan type)
237
+ - PhysicalLocationsTask (for the physical locations)
238
+ - CurrencyStrategy (for the currency strategy)
239
+ """
240
+ llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
241
+
242
+ def requires(self):
243
+ return {
244
+ 'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
245
+ 'plan_type': PlanTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
246
+ 'physical_locations': PhysicalLocationsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
247
+ 'currency_strategy': CurrencyStrategyTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
248
+ }
249
+
250
+ def output(self):
251
+ return {
252
+ 'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.IDENTIFY_RISKS_RAW))),
253
+ 'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.IDENTIFY_RISKS_MARKDOWN)))
254
+ }
255
 
256
+ def run(self):
257
+ logger.info("Identifying risks for the plan...")
258
+
259
+ # Read inputs from required tasks.
260
+ with self.input()['setup'].open("r") as f:
261
+ plan_prompt = f.read()
262
+
263
+ with self.input()['plan_type']['raw'].open("r") as f:
264
+ plan_type_dict = json.load(f)
265
+
266
+ with self.input()['physical_locations']['raw'].open("r") as f:
267
+ physical_locations_dict = json.load(f)
268
+
269
+ with self.input()['currency_strategy']['raw'].open("r") as f:
270
+ currency_strategy_dict = json.load(f)
271
+
272
+ query = (
273
+ f"File 'plan.txt':\n{plan_prompt}\n\n"
274
+ f"File 'plan_type.json':\n{format_json_for_use_in_query(plan_type_dict)}\n\n"
275
+ f"File 'physical_locations.json':\n{format_json_for_use_in_query(physical_locations_dict)}\n\n"
276
+ f"File 'currency_strategy.json':\n{format_json_for_use_in_query(currency_strategy_dict)}"
277
+ )
278
+
279
+ llm = get_llm(self.llm_model)
280
+
281
+ identify_risks = IdentifyRisks.execute(llm, query)
282
 
283
+ # Write the result to disk.
284
+ output_raw_path = self.output()['raw'].path
285
+ identify_risks.save_raw(str(output_raw_path))
286
+ output_markdown_path = self.output()['markdown'].path
287
+ identify_risks.save_markdown(str(output_markdown_path))
288
+
289
+
290
+ class MakeAssumptionsTask(PlanTask):
291
+ """
292
+ Make assumptions about the plan.
293
+ Depends on:
294
+ - SetupTask (for the initial plan)
295
+ - PlanTypeTask (for the plan type)
296
+ - PhysicalLocationsTask (for the physical locations)
297
+ - CurrencyStrategy (for the currency strategy)
298
+ - IdentifyRisksTask (for the identified risks)
299
+ """
300
+ llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
301
+
302
+ def requires(self):
303
+ return {
304
+ 'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
305
+ 'plan_type': PlanTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
306
+ 'physical_locations': PhysicalLocationsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
307
+ 'currency_strategy': CurrencyStrategyTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
308
+ 'identify_risks': IdentifyRisksTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
309
+ }
310
+
311
+ def output(self):
312
+ return {
313
+ 'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.MAKE_ASSUMPTIONS_RAW))),
314
+ 'clean': luigi.LocalTarget(str(self.file_path(FilenameEnum.MAKE_ASSUMPTIONS_CLEAN))),
315
+ 'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.MAKE_ASSUMPTIONS_MARKDOWN)))
316
+ }
317
+
318
+ def run(self):
319
+ logger.info("Making assumptions about the plan...")
320
+
321
+ # Read inputs from required tasks.
322
+ with self.input()['setup'].open("r") as f:
323
+ plan_prompt = f.read()
324
+
325
+ with self.input()['plan_type']['raw'].open("r") as f:
326
+ plan_type_dict = json.load(f)
327
+
328
+ with self.input()['physical_locations']['raw'].open("r") as f:
329
+ physical_locations_dict = json.load(f)
330
+
331
+ with self.input()['currency_strategy']['raw'].open("r") as f:
332
+ currency_strategy_dict = json.load(f)
333
+
334
+ with self.input()['identify_risks']['raw'].open("r") as f:
335
+ identify_risks_dict = json.load(f)
336
+
337
+ query = (
338
+ f"File 'plan.txt':\n{plan_prompt}\n\n"
339
+ f"File 'plan_type.json':\n{format_json_for_use_in_query(plan_type_dict)}\n\n"
340
+ f"File 'physical_locations.json':\n{format_json_for_use_in_query(physical_locations_dict)}\n\n"
341
+ f"File 'currency_strategy.json':\n{format_json_for_use_in_query(currency_strategy_dict)}\n\n"
342
+ f"File 'identify_risks.json':\n{format_json_for_use_in_query(identify_risks_dict)}"
343
+ )
344
+
345
+ llm = get_llm(self.llm_model)
346
+
347
+ make_assumptions = MakeAssumptions.execute(llm, query)
348
+
349
+ # Write the result to disk.
350
+ output_raw_path = self.output()['raw'].path
351
+ make_assumptions.save_raw(str(output_raw_path))
352
+ output_clean_path = self.output()['clean'].path
353
+ make_assumptions.save_assumptions(str(output_clean_path))
354
+ output_markdown_path = self.output()['markdown'].path
355
+ make_assumptions.save_markdown(str(output_markdown_path))
356
+
357
+
358
+ class DistillAssumptionsTask(PlanTask):
359
+ """
360
+ Distill raw assumption data.
361
+ Depends on:
362
+ - SetupTask (for the initial plan)
363
+ - MakeAssumptionsTask (for the draft assumptions)
364
+ """
365
+ llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
366
+
367
+ def requires(self):
368
+ return {
369
+ 'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
370
+ 'make_assumptions': MakeAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
371
+ }
372
+
373
+ def output(self):
374
+ return {
375
+ 'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.DISTILL_ASSUMPTIONS_RAW))),
376
+ 'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.DISTILL_ASSUMPTIONS_MARKDOWN)))
377
+ }
378
+
379
+ def run(self):
380
+ logger.info("Distilling assumptions...")
381
+
382
+ # Read the plan prompt from SetupTask's output.
383
+ setup_target = self.input()['setup']
384
+ with setup_target.open("r") as f:
385
+ plan_prompt = f.read()
386
+
387
+ # Read the assumptions from MakeAssumptionsTask's output.
388
+ make_assumptions_target = self.input()['make_assumptions']['clean']
389
+ with make_assumptions_target.open("r") as f:
390
+ assumptions_raw_data = json.load(f)
391
+
392
+ llm = get_llm(self.llm_model)
393
+
394
+ query = (
395
+ f"{plan_prompt}\n\n"
396
+ f"assumption.json:\n{assumptions_raw_data}"
397
+ )
398
+
399
+ distill_assumptions = DistillAssumptions.execute(llm, query)
400
+
401
+ # Write the result to disk.
402
+ output_raw_path = self.output()['raw'].path
403
+ distill_assumptions.save_raw(str(output_raw_path))
404
+ output_markdown_path = self.output()['markdown'].path
405
+ distill_assumptions.save_markdown(str(output_markdown_path))
406
+
407
+
408
+ class ReviewAssumptionsTask(PlanTask):
409
+ """
410
+ Find issues with the assumptions.
411
+ Depends on:
412
+ - PlanTypeTask
413
+ - PhysicalLocationsTask
414
+ - CurrencyStrategyTask
415
+ - IdentifyRisksTask
416
+ - MakeAssumptionsTask
417
+ - DistillAssumptionsTask
418
+ """
419
+ llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
420
+
421
+ def requires(self):
422
+ return {
423
+ 'plan_type': PlanTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
424
+ 'physical_locations': PhysicalLocationsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
425
+ 'currency_strategy': CurrencyStrategyTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
426
+ 'identify_risks': IdentifyRisksTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
427
+ 'make_assumptions': MakeAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
428
+ 'distill_assumptions': DistillAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
429
+ }
430
+
431
+ def output(self):
432
+ return {
433
+ 'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.REVIEW_ASSUMPTIONS_RAW))),
434
+ 'markdown': luigi.LocalTarget(str(self.file_path(FilenameEnum.REVIEW_ASSUMPTIONS_MARKDOWN)))
435
+ }
436
+
437
+ def run(self):
438
+ # Define the list of (title, path) tuples
439
+ title_path_list = [
440
+ ('Plan Type', self.input()['plan_type']['markdown'].path),
441
+ ('Physical Locations', self.input()['physical_locations']['markdown'].path),
442
+ ('Currency Strategy', self.input()['currency_strategy']['markdown'].path),
443
+ ('Identify Risks', self.input()['identify_risks']['markdown'].path),
444
+ ('Make Assumptions', self.input()['make_assumptions']['markdown'].path),
445
+ ('Distill Assumptions', self.input()['distill_assumptions']['markdown'].path)
446
+ ]
447
+
448
+ # Read the files and handle exceptions
449
+ markdown_chunks = []
450
+ for title, path in title_path_list:
451
+ try:
452
+ with open(path, 'r', encoding='utf-8') as f:
453
+ markdown_chunk = f.read()
454
+ markdown_chunks.append(f"# {title}\n\n{markdown_chunk}")
455
+ except FileNotFoundError:
456
+ logger.warning(f"Markdown file not found: {path} (from {title})")
457
+ markdown_chunks.append(f"**Problem with document:** '{title}'\n\nFile not found.")
458
+ except Exception as e:
459
+ logger.error(f"Error reading markdown file {path} (from {title}): {e}")
460
+ markdown_chunks.append(f"**Problem with document:** '{title}'\n\nError reading markdown file.")
461
+
462
+ # Combine the markdown chunks
463
+ full_markdown = "\n\n".join(markdown_chunks)
464
+
465
+ llm = get_llm(self.llm_model)
466
+
467
+ review_assumptions = ReviewAssumptions.execute(llm, full_markdown)
468
+
469
+ # Write the result to disk.
470
+ output_raw_path = self.output()['raw'].path
471
+ review_assumptions.save_raw(str(output_raw_path))
472
+ output_markdown_path = self.output()['markdown'].path
473
+ review_assumptions.save_markdown(str(output_markdown_path))
474
+
475
+
476
+ class ConsolidateAssumptionsMarkdownTask(PlanTask):
477
+ """
478
+ Combines multiple small markdown documents into a single big document.
479
+ Depends on:
480
+ - PlanTypeTask
481
+ - PhysicalLocationsTask
482
+ - CurrencyStrategyTask
483
+ - IdentifyRisksTask
484
+ - MakeAssumptionsTask
485
+ - DistillAssumptionsTask
486
+ - ReviewAssumptionsTask
487
+ """
488
+ llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
489
+
490
+ def requires(self):
491
+ return {
492
+ 'plan_type': PlanTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
493
+ 'physical_locations': PhysicalLocationsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
494
+ 'currency_strategy': CurrencyStrategyTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
495
+ 'identify_risks': IdentifyRisksTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
496
+ 'make_assumptions': MakeAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
497
+ 'distill_assumptions': DistillAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
498
+ 'review_assumptions': ReviewAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
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),
508
+ ('Physical Locations', self.input()['physical_locations']['markdown'].path),
509
+ ('Currency Strategy', self.input()['currency_strategy']['markdown'].path),
510
+ ('Identify Risks', self.input()['identify_risks']['markdown'].path),
511
+ ('Make Assumptions', self.input()['make_assumptions']['markdown'].path),
512
+ ('Distill Assumptions', self.input()['distill_assumptions']['markdown'].path),
513
+ ('Review Assumptions', self.input()['review_assumptions']['markdown'].path)
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)
541
 
542
  def requires(self):
543
+ return {
544
+ 'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
545
+ 'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
546
+ }
547
 
548
  def output(self):
549
  return {
 
555
  logger.info("Conducting pre-project assessment...")
556
 
557
  # Read the plan prompt from the SetupTask's output.
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.
565
+ query = (
566
+ f"File 'plan.txt':\n{plan_prompt}\n\n"
567
+ f"File 'assumptions.md':\n{consolidate_assumptions_markdown}"
568
+ )
569
 
570
  # Get an instance of your LLM.
571
  llm = get_llm(self.llm_model)
 
588
  def requires(self):
589
  """
590
  This task depends on:
591
+ - SetupTask: produces the plan prompt
592
+ - ConsolidateAssumptionsMarkdownTask: the assumptions and scope.
593
  - PreProjectAssessmentTask: produces the pre‑project assessment files
594
  """
595
  return {
596
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
597
+ 'consolidate_assumptions_markdown': ConsolidateAssumptionsMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
598
  'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
599
  }
600
 
 
609
  with setup_target.open("r") as f:
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.
617
  pre_project_assessment_file = self.input()['preproject']['clean']
 
620
 
621
  # Build the query.
622
  query = (
623
+ f"File 'plan.txt':\n{plan_prompt}\n\n"
624
+ f"File 'assumptions.md':\n{consolidate_assumptions_markdown}\n\n"
625
+ f"File 'pre-project-assessment.json':\n{format_json_for_use_in_query(pre_project_assessment_dict)}"
626
  )
627
 
628
  # Get an LLM instance.
 
642
  def requires(self):
643
  return {
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
  }
 
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
 
 
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.
 
710
  def requires(self):
711
  return {
712
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
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)
 
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
 
 
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.
 
784
  def requires(self):
785
  return {
786
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
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)
 
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
 
 
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.
 
858
  def requires(self):
859
  return {
860
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
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)
 
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
 
 
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.
 
932
  def requires(self):
933
  return {
934
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
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)
 
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
 
 
973
 
974
  # Build the query.
975
  query = (
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.
 
1037
  def requires(self):
1038
  return {
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
  }
 
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
 
 
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.
 
1709
 
1710
  def requires(self):
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),
 
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)
 
1733
  def requires(self):
1734
  return {
1735
  'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
1736
+ 'plan_type': PlanTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1737
+ 'physical_locations': PhysicalLocationsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1738
+ 'currency_strategy': CurrencyStrategyTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1739
+ 'identify_risks': IdentifyRisksTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1740
+ 'make_assumptions': MakeAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1741
+ 'assumptions': DistillAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
1742
+ 'review_assumptions': ReviewAssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
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),
src/report/report_generator.py CHANGED
@@ -5,6 +5,7 @@ PROMPT> python -m src.report.report_generator /path/to/PlanExe_20250216_dir
5
  This generates the report without opening the browser.
6
  PROMPT> python -m src.report.report_generator /path/to/PlanExe_20250216_dir --no-browser
7
  """
 
8
  import json
9
  import logging
10
  import pandas as pd
@@ -80,29 +81,35 @@ class ReportGenerator:
80
  logging.error(f"Error reading CSV file {file_path}: {str(e)}")
81
  return None
82
 
 
 
 
 
 
 
83
  def append_pitch_markdown(self, file_path: Path):
84
  """Append the pitch markdown to the report."""
85
- pitch_md = self.read_markdown_file(file_path)
86
- if pitch_md:
87
- self.report_data['pitch'] = pitch_md
88
 
89
  def append_swot_analysis_markdown(self, file_path: Path):
90
  """Append the SWOT markdown to the report."""
91
- swot_md = self.read_markdown_file(file_path)
92
- if swot_md:
93
- self.report_data['swot'] = swot_md
94
 
95
  def append_team_markdown(self, file_path: Path):
96
  """Append the team markdown to the report."""
97
- swot_md = self.read_markdown_file(file_path)
98
- if swot_md:
99
- self.report_data['team'] = swot_md
100
 
101
  def append_expert_criticism_markdown(self, file_path: Path):
102
  """Append the expert criticism markdown to the report."""
103
- expert_md = self.read_markdown_file(file_path)
104
- if expert_md:
105
- self.report_data['expert_criticism'] = expert_md
106
 
107
  def append_project_plan_csv(self, file_path: Path):
108
  """Append the project plan CSV to the report."""
@@ -115,142 +122,59 @@ class ReportGenerator:
115
 
116
  def generate_html_report(self) -> str:
117
  """Generate an HTML report from the gathered data."""
118
- html_parts = []
119
-
120
- # Header with improved styling
121
- html_parts.append("""
122
- <html>
123
- <head>
124
- <title>PlanExe Project Report</title>
125
- <style>
126
- body {
127
- font-family: Arial, sans-serif;
128
- margin: 40px;
129
- line-height: 1.6;
130
- color: #333;
131
- max-width: 1200px;
132
- margin: 0 auto;
133
- padding: 20px;
134
- }
135
- h1 {
136
- color: #2c3e50;
137
- border-bottom: 2px solid #eee;
138
- padding-bottom: 10px;
139
- }
140
- h2 {
141
- color: #34495e;
142
- margin-top: 30px;
143
- border-bottom: 1px solid #eee;
144
- padding-bottom: 5px;
145
- }
146
- .section {
147
- margin: 20px 0;
148
- padding: 20px;
149
- border: 1px solid #eee;
150
- border-radius: 5px;
151
- background-color: #fff;
152
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
153
- }
154
- table {
155
- border-collapse: collapse;
156
- width: 100%;
157
- margin: 20px 0;
158
- font-size: 14px;
159
- }
160
- th, td {
161
- border: 1px solid #ddd;
162
- padding: 12px 8px;
163
- text-align: left;
164
- }
165
- th {
166
- background-color: #f5f5f5;
167
- font-weight: bold;
168
- }
169
- tr:nth-child(even) {
170
- background-color: #f9f9f9;
171
- }
172
- tr:hover {
173
- background-color: #f5f5f5;
174
- }
175
- .timestamp {
176
- color: #666;
177
- font-size: 0.9em;
178
- margin-bottom: 30px;
179
- }
180
- .dataframe {
181
- overflow-x: auto;
182
- display: block;
183
- }
184
- .source-info {
185
- color: #666;
186
- font-size: 0.9em;
187
- margin-top: 10px;
188
- font-style: italic;
189
- }
190
- </style>
191
- </head>
192
- <body>
193
- """)
194
 
 
 
 
 
 
195
  # Title and Timestamp
196
  html_parts.append(f"""
197
  <h1>PlanExe Project Report</h1>
198
  <p class="timestamp">Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
199
  """)
200
 
201
- # Project Pitch
202
- if 'pitch' in self.report_data:
203
- html_parts.append("""
204
  <div class="section">
205
- <h2>Project Pitch</h2>
 
 
 
 
206
  """)
207
- html_parts.append(markdown.markdown(self.report_data['pitch']))
208
- html_parts.append("</div>")
209
 
210
- # SWOT Analysis
 
 
 
 
 
211
  if 'swot' in self.report_data:
212
- html_parts.append("""
213
- <div class="section">
214
- <h2>SWOT Analysis</h2>
215
- """)
216
- html_parts.append(markdown.markdown(self.report_data['swot']))
217
- html_parts.append("</div>")
218
 
219
- # Team
220
  if 'team' in self.report_data:
221
- html_parts.append("""
222
- <div class="section">
223
- <h2>Team</h2>
224
- """)
225
- html_parts.append(markdown.markdown(self.report_data['team']))
226
- html_parts.append("</div>")
227
 
228
- # Expert Criticism
229
  if 'expert_criticism' in self.report_data:
230
- html_parts.append("""
231
- <div class="section">
232
- <h2>Expert Criticism</h2>
233
- """)
234
- html_parts.append(markdown.markdown(self.report_data['expert_criticism']))
235
- html_parts.append("</div>")
236
 
237
- # Project Plan
238
  if 'project_plan' in self.report_data:
239
- html_parts.append("""
240
- <div class="section">
241
- <h2>Project Plan</h2>
242
- """)
243
  df = self.report_data['project_plan']
244
- html_parts.append(df.to_html(classes='dataframe', index=False, na_rep=''))
245
- html_parts.append("</div>")
246
 
247
- # Footer
248
- html_parts.append("""
249
- </body>
250
- </html>
251
- """)
 
 
 
 
252
 
253
- return '\n'.join(html_parts)
254
 
255
  def save_report(self, output_path: Path) -> None:
256
  """Generate and save the report."""
@@ -290,6 +214,7 @@ def main():
290
 
291
  report_generator = ReportGenerator()
292
  report_generator.append_pitch_markdown(input_path / FilenameEnum.PITCH_MARKDOWN.value)
 
293
  report_generator.append_swot_analysis_markdown(input_path / FilenameEnum.SWOT_MARKDOWN.value)
294
  report_generator.append_team_markdown(input_path / FilenameEnum.TEAM_MARKDOWN.value)
295
  report_generator.append_expert_criticism_markdown(input_path / FilenameEnum.EXPERT_CRITICISM_MARKDOWN.value)
 
5
  This generates the report without opening the browser.
6
  PROMPT> python -m src.report.report_generator /path/to/PlanExe_20250216_dir --no-browser
7
  """
8
+ import re
9
  import json
10
  import logging
11
  import pandas as pd
 
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."""
 
122
 
123
  def generate_html_report(self) -> str:
124
  """Generate an HTML report from the gathered data."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
+ path_to_template = Path(__file__).parent / 'report_template.html'
127
+ with open(path_to_template, 'r') as f:
128
+ html_template = f.read()
129
+
130
+ html_parts = []
131
  # Title and Timestamp
132
  html_parts.append(f"""
133
  <h1>PlanExe Project Report</h1>
134
  <p class="timestamp">Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
135
  """)
136
 
137
+ def add_section(title: str, content: str):
138
+ html_parts.append(f"""
 
139
  <div class="section">
140
+ <button class="collapsible">{title}</button>
141
+ <div class="content">
142
+ {content}
143
+ </div>
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
+
169
+ # Replace the content between <!--CONTENT-START--> and <!--CONTENT-END--> with html_content
170
+ pattern = re.compile(r'<!--CONTENT-START-->.*<!--CONTENT-END-->', re.DOTALL)
171
+ html = re.sub(
172
+ pattern,
173
+ f'<!--CONTENT-START-->\n{html_content}\n<!--CONTENT-END-->',
174
+ html_template
175
+ )
176
 
177
+ return html
178
 
179
  def save_report(self, output_path: Path) -> None:
180
  """Generate and save the report."""
 
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)
src/report/report_template.html ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html>
2
+ <head>
3
+ <title>PlanExe Project Report</title>
4
+ <style>
5
+ body {
6
+ font-family: Arial, sans-serif;
7
+ margin: 40px;
8
+ line-height: 1.6;
9
+ color: #333;
10
+ max-width: 1200px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ }
14
+ h1 {
15
+ color: #2c3e50;
16
+ border-bottom: 2px solid #eee;
17
+ padding-bottom: 10px;
18
+ }
19
+ h2 {
20
+ color: #34495e;
21
+ margin-top: 30px;
22
+ border-bottom: 1px solid #eee;
23
+ padding-bottom: 5px;
24
+ }
25
+ .section {
26
+ margin: 20px 0;
27
+ border: 1px solid #eee;
28
+ border-radius: 5px;
29
+ background-color: #fff;
30
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
31
+ }
32
+ table {
33
+ border-collapse: collapse;
34
+ width: 100%;
35
+ margin: 20px 0;
36
+ font-size: 14px;
37
+ }
38
+ th, td {
39
+ border: 1px solid #ddd;
40
+ padding: 12px 8px;
41
+ text-align: left;
42
+ }
43
+ th {
44
+ background-color: #f5f5f5;
45
+ font-weight: bold;
46
+ }
47
+ tr:nth-child(even) {
48
+ background-color: #f9f9f9;
49
+ }
50
+ tr:hover {
51
+ background-color: #f5f5f5;
52
+ }
53
+ .timestamp {
54
+ color: #666;
55
+ font-size: 0.9em;
56
+ margin-bottom: 30px;
57
+ }
58
+ .dataframe {
59
+ overflow-x: auto;
60
+ display: block;
61
+ }
62
+ .source-info {
63
+ color: #666;
64
+ font-size: 0.9em;
65
+ margin-top: 10px;
66
+ font-style: italic;
67
+ }
68
+ .collapsible {
69
+ background-color: #3498db;
70
+ color: white;
71
+ cursor: pointer;
72
+ padding: 18px;
73
+ width: 100%;
74
+ border: none;
75
+ border-radius: 5px;
76
+ text-align: left;
77
+ outline: none;
78
+ font-size: 18px;
79
+ font-weight: bold;
80
+ transition: background-color 0.3s ease, box-shadow 0.3s ease;
81
+ position: relative;
82
+ }
83
+ .collapsible:hover {
84
+ background-color: #2980b9;
85
+ box-shadow: 0 4px 8px rgba(0,0,0,0.4);
86
+ }
87
+ .collapsible:after {
88
+ content: '+';
89
+ position: absolute;
90
+ right: 20px;
91
+ top: 50%;
92
+ transform: translateY(-50%);
93
+ transition: transform 0.3s ease;
94
+ }
95
+ .active:after {
96
+ content: "−";
97
+ }
98
+ .content {
99
+ padding: 0 20px;
100
+ max-height: 0;
101
+ overflow: hidden;
102
+ transition: max-height 0.2s ease-out;
103
+ }
104
+ </style>
105
+ </head>
106
+ <body>
107
+ <!--CONTENT-START-->
108
+ <h1>PlanExe Project Report</h1>
109
+ <p class="timestamp">Generated on: 1984-12-31 23:59:59</p>
110
+
111
+ <div class="section">
112
+ <button class="collapsible">Project Pitch</button>
113
+ <div class="content">
114
+ <p>Lorem ipsum</p>
115
+ </div>
116
+ </div>
117
+
118
+ <div class="section">
119
+ <button class="collapsible">SWOT Analysis</button>
120
+ <div class="content">
121
+ <p>Lorem ipsum</p>
122
+ </div>
123
+ </div>
124
+ <!--CONTENT-END-->
125
+
126
+ <script>
127
+ var coll = document.getElementsByClassName("collapsible");
128
+ var i;
129
+
130
+ for (i = 0; i < coll.length; i++) {
131
+ coll[i].addEventListener("click", function() {
132
+ this.classList.toggle("active");
133
+ var content = this.nextElementSibling;
134
+ if (content.style.maxHeight){
135
+ content.style.maxHeight = null;
136
+ } else {
137
+ content.style.maxHeight = content.scrollHeight + "px";
138
+ }
139
+ });
140
+ }
141
+ </script>
142
+ </body>
143
+ </html>
src/utils/concat_files_into_string.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ def concat_files_into_string(base_path: str, prefix: str="File: '", suffix: str="'\n", document_separator: str="\n\n") -> str:
4
+ """
5
+ Read the files, and concat their data into a single string
6
+ """
7
+ # Obtain files
8
+ files = os.listdir(base_path)
9
+ files = [f for f in files if not f.startswith('.')]
10
+ files.sort()
11
+
12
+ # Read the files, and concat their data into a single string
13
+ documents = []
14
+ for file in files:
15
+ s = f"{prefix}{file}{suffix}"
16
+ with open(os.path.join(base_path, file), 'r', encoding='utf-8') as f:
17
+ s += f.read()
18
+ documents.append(s)
19
+ all_documents_string = document_separator.join(documents)
20
+ return all_documents_string