Simon Strandgaard
commited on
Commit
·
22a5c6c
1
Parent(s):
8628c58
Snapshot of PlanExe commit d1eee4b3f276c3f079b95539dafb7f1f794abfa4
Browse files- src/assume/README.md +33 -1
- src/assume/assumption_orchestrator.py +0 -72
- src/assume/currency_strategy.py +343 -0
- src/assume/distill_assumptions.py +39 -6
- src/assume/identify_plan_type.py +290 -0
- src/assume/identify_risks.py +231 -0
- src/assume/make_assumptions.py +41 -29
- src/assume/physical_locations.py +284 -0
- src/assume/review_assumptions.py +235 -0
- src/assume/test_data/currency_strategy1/001-plan.txt +7 -0
- src/assume/test_data/currency_strategy1/002-physical_locations.json +43 -0
- src/assume/test_data/currency_strategy2/001-plan.txt +7 -0
- src/assume/test_data/currency_strategy2/002-physical_locations.json +7 -0
- src/assume/test_data/currency_strategy3/001-plan.txt +7 -0
- src/assume/test_data/currency_strategy3/002-physical_locations.json +35 -0
- src/assume/test_data/currency_strategy4/001-plan.txt +7 -0
- src/assume/test_data/currency_strategy4/002-physical_locations.json +10 -0
- src/assume/test_data/currency_strategy5/001-plan.txt +7 -0
- src/assume/test_data/currency_strategy5/002-physical_locations.json +35 -0
- src/assume/test_data/currency_strategy6/001-plan.txt +7 -0
- src/assume/test_data/currency_strategy6/002-physical_locations.json +27 -0
- src/assume/test_data/currency_strategy7/001-plan.txt +7 -0
- src/assume/test_data/currency_strategy7/002-physical_locations.json +19 -0
- src/assume/test_data/review_assumptions1/001-plan.txt +7 -0
- src/assume/test_data/review_assumptions1/002-make_assumptions.json +42 -0
- src/assume/test_data/review_assumptions1/003-distill_assumptions.json +12 -0
- src/assume/test_data/review_assumptions2/001-plan.txt +7 -0
- src/assume/test_data/review_assumptions2/002-make_assumptions.json +42 -0
- src/assume/test_data/review_assumptions2/003-distill_assumptions.json +12 -0
- src/expert/pre_project_assessment.py +2 -0
- src/llm_util/ollama_info.py +63 -46
- src/pitch/convert_pitch_to_markdown.py +1 -1
- src/plan/app_text2plan.py +7 -0
- src/plan/data/simple_plan_prompts.jsonl +5 -1
- src/plan/filenames.py +16 -3
- src/plan/run_plan_pipeline.py +541 -112
- src/report/report_generator.py +53 -128
- src/report/report_template.html +143 -0
- 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 |
-
|
159 |
if system_prompt:
|
160 |
-
|
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 |
-
|
173 |
|
174 |
sllm = llm.as_structured_llm(AssumptionDetails)
|
175 |
|
176 |
logger.debug("Starting LLM chat interaction.")
|
177 |
start_time = time.perf_counter()
|
178 |
-
|
179 |
end_time = time.perf_counter()
|
180 |
duration = int(ceil(end_time - start_time))
|
181 |
-
response_byte_count = len(
|
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(
|
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
|
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 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
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 |
-
|
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(
|
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(
|
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 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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 |
-
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
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
|
80 |
"""
|
81 |
-
|
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
|
|
|
|
|
|
|
92 |
|
93 |
def run(self):
|
94 |
-
logger.info("
|
95 |
|
96 |
# Read inputs from required tasks.
|
97 |
with self.input().open("r") as f:
|
98 |
plan_prompt = f.read()
|
99 |
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
else:
|
106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
|
108 |
llm = get_llm(self.llm_model)
|
109 |
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
|
122 |
-
# Write the
|
123 |
-
|
124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
|
126 |
|
127 |
class PreProjectAssessmentTask(PlanTask):
|
128 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
129 |
|
130 |
def requires(self):
|
131 |
-
return
|
|
|
|
|
|
|
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 =
|
|
|
|
|
|
|
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
|
171 |
-
-
|
172 |
- PreProjectAssessmentTask: produces the pre‑project assessment files
|
173 |
"""
|
174 |
return {
|
175 |
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
176 |
-
'
|
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 |
-
#
|
192 |
-
|
193 |
-
|
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"
|
204 |
-
f"
|
205 |
-
f"
|
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 |
-
'
|
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 |
-
#
|
240 |
with self.input()['setup'].open("r") as f:
|
241 |
plan_prompt = f.read()
|
242 |
|
243 |
-
#
|
244 |
-
with self.input()['
|
245 |
-
|
246 |
|
247 |
-
#
|
248 |
with self.input()['preproject']['clean'].open("r") as f:
|
249 |
pre_project_assessment_dict = json.load(f)
|
250 |
|
251 |
-
#
|
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"
|
260 |
-
f"
|
261 |
-
f"
|
262 |
-
f"
|
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 |
-
'
|
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 |
-
#
|
309 |
with self.input()['setup'].open("r") as f:
|
310 |
plan_prompt = f.read()
|
311 |
|
312 |
-
#
|
313 |
-
with self.input()['
|
314 |
-
|
315 |
|
316 |
-
#
|
317 |
with self.input()['preproject']['clean'].open("r") as f:
|
318 |
pre_project_assessment_dict = json.load(f)
|
319 |
|
320 |
-
#
|
321 |
with self.input()['project_plan'].open("r") as f:
|
322 |
project_plan_dict = json.load(f)
|
323 |
|
324 |
-
#
|
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"
|
333 |
-
f"
|
334 |
-
f"
|
335 |
-
f"
|
336 |
-
f"
|
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 |
-
'
|
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 |
-
#
|
383 |
with self.input()['setup'].open("r") as f:
|
384 |
plan_prompt = f.read()
|
385 |
|
386 |
-
#
|
387 |
-
with self.input()['
|
388 |
-
|
389 |
|
390 |
-
#
|
391 |
with self.input()['preproject']['clean'].open("r") as f:
|
392 |
pre_project_assessment_dict = json.load(f)
|
393 |
|
394 |
-
#
|
395 |
with self.input()['project_plan'].open("r") as f:
|
396 |
project_plan_dict = json.load(f)
|
397 |
|
398 |
-
#
|
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"
|
407 |
-
f"
|
408 |
-
f"
|
409 |
-
f"
|
410 |
-
f"
|
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 |
-
'
|
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 |
-
#
|
457 |
with self.input()['setup'].open("r") as f:
|
458 |
plan_prompt = f.read()
|
459 |
|
460 |
-
#
|
461 |
-
with self.input()['
|
462 |
-
|
463 |
|
464 |
-
#
|
465 |
with self.input()['preproject']['clean'].open("r") as f:
|
466 |
pre_project_assessment_dict = json.load(f)
|
467 |
|
468 |
-
#
|
469 |
with self.input()['project_plan'].open("r") as f:
|
470 |
project_plan_dict = json.load(f)
|
471 |
|
472 |
-
#
|
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"
|
481 |
-
f"
|
482 |
-
f"
|
483 |
-
f"
|
484 |
-
f"
|
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 |
-
'
|
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 |
-
#
|
528 |
with self.input()['setup'].open("r") as f:
|
529 |
plan_prompt = f.read()
|
530 |
|
531 |
-
#
|
532 |
-
with self.input()['
|
533 |
-
|
534 |
|
535 |
-
#
|
536 |
with self.input()['preproject']['clean'].open("r") as f:
|
537 |
pre_project_assessment_dict = json.load(f)
|
538 |
|
539 |
-
#
|
540 |
with self.input()['project_plan'].open("r") as f:
|
541 |
project_plan_dict = json.load(f)
|
542 |
|
543 |
-
#
|
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"
|
557 |
-
f"
|
558 |
-
f"
|
559 |
-
f"
|
560 |
-
f"
|
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 |
-
'
|
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 |
-
#
|
635 |
with self.input()['setup'].open("r") as f:
|
636 |
plan_prompt = f.read()
|
637 |
|
638 |
-
#
|
639 |
-
with self.input()['
|
640 |
-
|
641 |
|
642 |
-
#
|
643 |
with self.input()['preproject']['clean'].open("r") as f:
|
644 |
pre_project_assessment_dict = json.load(f)
|
645 |
|
646 |
-
#
|
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"
|
655 |
-
f"
|
656 |
-
f"
|
657 |
-
f"
|
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 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
86 |
-
if
|
87 |
-
self.report_data['pitch'] =
|
88 |
|
89 |
def append_swot_analysis_markdown(self, file_path: Path):
|
90 |
"""Append the SWOT markdown to the report."""
|
91 |
-
|
92 |
-
if
|
93 |
-
self.report_data['swot'] =
|
94 |
|
95 |
def append_team_markdown(self, file_path: Path):
|
96 |
"""Append the team markdown to the report."""
|
97 |
-
|
98 |
-
if
|
99 |
-
self.report_data['team'] =
|
100 |
|
101 |
def append_expert_criticism_markdown(self, file_path: Path):
|
102 |
"""Append the expert criticism markdown to the report."""
|
103 |
-
|
104 |
-
if
|
105 |
-
self.report_data['expert_criticism'] =
|
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 |
-
|
202 |
-
|
203 |
-
html_parts.append("""
|
204 |
<div class="section">
|
205 |
-
<
|
|
|
|
|
|
|
|
|
206 |
""")
|
207 |
-
html_parts.append(markdown.markdown(self.report_data['pitch']))
|
208 |
-
html_parts.append("</div>")
|
209 |
|
210 |
-
|
|
|
|
|
|
|
|
|
|
|
211 |
if 'swot' in self.report_data:
|
212 |
-
|
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 |
-
|
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 |
-
|
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 |
-
|
245 |
-
|
246 |
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
|
|
|
|
|
|
|
|
252 |
|
253 |
-
return
|
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
|