Simon Strandgaard
commited on
Commit
·
f0808c8
1
Parent(s):
767b265
Snapshot of PlanExe commit 5012c8a07355571673d80b3e1f60b5fc0fc1a715
Browse files- src/plan/data/simple_plan_prompts.jsonl +10 -0
- src/plan/filenames.py +32 -22
- src/plan/run_plan_pipeline.py +409 -0
- src/report/report_generator.py +16 -0
- src/team/create_markdown_document.py +0 -71
- src/team/{enrich_team_members.py → enrich_team_members_with_background_story.py} +116 -45
- src/team/enrich_team_members_with_contract_type.py +214 -0
- src/team/enrich_team_members_with_environment_info.py +118 -45
- src/team/find_team_members.py +142 -51
- src/team/review_team.py +170 -0
- src/team/run_hire_team.py +62 -25
- src/team/team_markdown_document.py +100 -0
- src/team/test_data/solarfarm_roles_list.json +106 -0
- src/team/test_data/solarfarm_team_review.json +49 -0
- src/team/test_data/solarfarm_team_without_review.md +229 -0
src/plan/data/simple_plan_prompts.jsonl
CHANGED
@@ -23,3 +23,13 @@
|
|
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"]}
|
25 |
{"id": "4dc34d55-0d0d-4e9d-92f4-23765f49dd29", "prompt": "Establish a solar farm in Denmark.", "tags": ["denmark", "energy", "sun"]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"]}
|
25 |
{"id": "4dc34d55-0d0d-4e9d-92f4-23765f49dd29", "prompt": "Establish a solar farm in Denmark.", "tags": ["denmark", "energy", "sun"]}
|
26 |
+
{"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"]}
|
27 |
+
{"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"]}
|
28 |
+
{"id": "1ea5076a-95b5-47fe-aff5-f03c8ef00cd7", "prompt": "Implement a comprehensive strategy for providing clean water to areas where it is currently unavailable. Your solution should include infrastructure development for water purification, distribution, and ongoing maintenance, and clarify if you are upgrading existing systems or building new ones from scratch. Assume this project will serve 2 million people in rural areas over 3 years with a total budget of 200 million USD.", "tags": ["water", "sanitation", "infrastructure"]}
|
29 |
+
{"id": "08505146-b6ad-4d7c-b6b6-85c2868e707d", "prompt": "Design an initiative to improve sanitation in both slums and rural areas to reduce disease spread and enhance living conditions. Provide a detailed allocation plan that accounts for the varying needs of urban versus rural contexts, and note that phased investments may be necessary for sustainable infrastructure. Assume the initiative will reach 1.5 million people in 50 different locations over 5 years with a total budget of 150 million USD.", "tags": ["sanitation", "health", "slums"]}
|
30 |
+
{"id": "0bb00fe6-711c-4612-8f83-a9a88e5c7958", "prompt": "Establish accessible healthcare systems in regions where medical facilities are scarce, focusing on both preventive care and emergency services. Clarify whether your approach involves upgrading existing facilities or constructing new ones, and account for the typically higher per capita costs in healthcare infrastructure. Assume the program will cover 4 million people in 20 countries over 6 years with a total budget of 400 million USD.", "tags": ["healthcare", "accessibility", "medicine"]}
|
31 |
+
{"id": "79b62957-cae7-4727-8450-4f5f29d4ddda", "prompt": "Create a detailed plan to increase educational opportunities in poverty-stricken areas. This plan should include building or improving schools, teacher training, and scholarship programs, with a clear breakdown of resource allocation among these components. Recognize that educational initiatives may require higher per capita investments for infrastructure compared to service-based interventions. Assume the plan will support 1 million students over 7 years with a total budget of 350 million USD.", "tags": ["education", "literacy", "development"]}
|
32 |
+
{"id": "6f7a8b9c-0d1e-2f3a-4b5c-6d7e8f90a1b2", "prompt": "Develop comprehensive policies and programs to eradicate child labor by ensuring children have access to education and protection from exploitation. Include details on scaling the initiative and allocating funds among enforcement, educational support, and social services. Assume this effort will benefit 500,000 children in 15 countries over 5 years with a total budget of 100 million USD.", "tags": ["child labor", "education", "protection"]}
|
33 |
+
{"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"]}
|
34 |
+
{"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"]}
|
35 |
+
{"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"]}
|
src/plan/filenames.py
CHANGED
@@ -8,26 +8,36 @@ class FilenameEnum(str, Enum):
|
|
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"
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
PIPELINE_COMPLETE = "999-pipeline_complete.txt"
|
|
|
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"
|
11 |
+
FIND_TEAM_MEMBERS_RAW = "006-1-find_team_members_raw.json"
|
12 |
+
FIND_TEAM_MEMBERS_CLEAN = "006-2-find_team_members.json"
|
13 |
+
ENRICH_TEAM_MEMBERS_CONTRACT_TYPE_RAW = "007-1-enrich_team_members_contract_type_raw.json"
|
14 |
+
ENRICH_TEAM_MEMBERS_CONTRACT_TYPE_CLEAN = "007-2-enrich_team_members_contract_type.json"
|
15 |
+
ENRICH_TEAM_MEMBERS_BACKGROUND_STORY_RAW = "008-1-enrich_team_members_background_story_raw.json"
|
16 |
+
ENRICH_TEAM_MEMBERS_BACKGROUND_STORY_CLEAN = "008-2-enrich_team_members_background_story.json"
|
17 |
+
ENRICH_TEAM_MEMBERS_ENVIRONMENT_INFO_RAW = "009-1-enrich_team_members_environment_info_raw.json"
|
18 |
+
ENRICH_TEAM_MEMBERS_ENVIRONMENT_INFO_CLEAN = "009-2-enrich_team_members_environment_info.json"
|
19 |
+
REVIEW_TEAM_RAW = "010-review_team_raw.json"
|
20 |
+
TEAM_MARKDOWN = "011-team.md"
|
21 |
+
SWOT_RAW = "012-1-swot_analysis_raw.json"
|
22 |
+
SWOT_MARKDOWN = "012-2-swot_analysis.md"
|
23 |
+
EXPERTS_RAW = "013-1-experts_raw.json"
|
24 |
+
EXPERTS_CLEAN = "013-2-experts.json"
|
25 |
+
EXPERT_CRITICISM_RAW_TEMPLATE = "014-1-{}-expert_criticism_raw.json"
|
26 |
+
EXPERT_CRITICISM_MARKDOWN = "014-2-expert_criticism.md"
|
27 |
+
WBS_LEVEL1_RAW = "015-1-wbs_level1_raw.json"
|
28 |
+
WBS_LEVEL1 = "015-2-wbs_level1.json"
|
29 |
+
WBS_LEVEL2_RAW = "016-1-wbs_level2_raw.json"
|
30 |
+
WBS_LEVEL2 = "016-2-wbs_level2.json"
|
31 |
+
WBS_PROJECT_LEVEL1_AND_LEVEL2 = "017-wbs_project_level1_and_level2.json"
|
32 |
+
PITCH_RAW = "018-1-pitch_raw.json"
|
33 |
+
PITCH_CONVERT_TO_MARKDOWN_RAW = "018-2-pitch_to_markdown_raw.json"
|
34 |
+
PITCH_MARKDOWN = "018-3-pitch.md"
|
35 |
+
TASK_DEPENDENCIES_RAW = "019-task_dependencies_raw.json"
|
36 |
+
TASK_DURATIONS_RAW_TEMPLATE = "020-1-{}-task_durations_raw.json"
|
37 |
+
TASK_DURATIONS = "020-2-task_durations.json"
|
38 |
+
WBS_LEVEL3_RAW_TEMPLATE = "021-1-{}-wbs_level3_raw.json"
|
39 |
+
WBS_LEVEL3 = "021-2-wbs_level3.json"
|
40 |
+
WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_FULL = "021-3-wbs_project_level1_and_level2_and_level3.json"
|
41 |
+
WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_CSV = "021-4-wbs_project_level1_and_level2_and_level3.csv"
|
42 |
+
REPORT = "022-report.html"
|
43 |
PIPELINE_COMPLETE = "999-pipeline_complete.txt"
|
src/plan/run_plan_pipeline.py
CHANGED
@@ -31,6 +31,12 @@ from src.pitch.create_pitch import CreatePitch
|
|
31 |
from src.pitch.convert_pitch_to_markdown import ConvertPitchToMarkdown
|
32 |
from src.plan.identify_wbs_task_dependencies import IdentifyWBSTaskDependencies
|
33 |
from src.plan.estimate_wbs_task_durations import EstimateWBSTaskDurations
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
from src.wbs.wbs_task import WBSTask, WBSProject
|
35 |
from src.wbs.wbs_populate import WBSPopulate
|
36 |
from src.llm_factory import get_llm
|
@@ -210,6 +216,401 @@ class ProjectPlanTask(PlanTask):
|
|
210 |
logger.info("Project plan created and saved to %s", output_path)
|
211 |
|
212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
class SWOTAnalysisTask(PlanTask):
|
214 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
215 |
|
@@ -888,6 +1289,7 @@ class ReportTask(PlanTask):
|
|
888 |
|
889 |
def requires(self):
|
890 |
return {
|
|
|
891 |
'swot_analysis': SWOTAnalysisTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
892 |
'pitch_markdown': ConvertPitchToMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
893 |
'wbs_project123': WBSProjectLevel1AndLevel2AndLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
@@ -898,6 +1300,7 @@ class ReportTask(PlanTask):
|
|
898 |
rg = ReportGenerator()
|
899 |
rg.append_pitch_markdown(self.input()['pitch_markdown']['markdown'].path)
|
900 |
rg.append_swot_analysis_markdown(self.input()['swot_analysis']['markdown'].path)
|
|
|
901 |
rg.append_project_plan_csv(self.input()['wbs_project123']['csv'].path)
|
902 |
rg.append_expert_criticism_markdown(self.input()['expert_review'].path)
|
903 |
rg.save_report(self.output().path)
|
@@ -911,6 +1314,12 @@ class FullPlanPipeline(PlanTask):
|
|
911 |
'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
912 |
'pre_project_assessment': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
913 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
|
|
|
|
|
|
|
|
|
|
|
|
914 |
'swot_analysis': SWOTAnalysisTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
915 |
'expert_review': ExpertReviewTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
916 |
'wbs_level1': CreateWBSLevel1Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
|
|
31 |
from src.pitch.convert_pitch_to_markdown import ConvertPitchToMarkdown
|
32 |
from src.plan.identify_wbs_task_dependencies import IdentifyWBSTaskDependencies
|
33 |
from src.plan.estimate_wbs_task_durations import EstimateWBSTaskDurations
|
34 |
+
from src.team.find_team_members import FindTeamMembers
|
35 |
+
from src.team.enrich_team_members_with_contract_type import EnrichTeamMembersWithContractType
|
36 |
+
from src.team.enrich_team_members_with_background_story import EnrichTeamMembersWithBackgroundStory
|
37 |
+
from src.team.enrich_team_members_with_environment_info import EnrichTeamMembersWithEnvironmentInfo
|
38 |
+
from src.team.team_markdown_document import TeamMarkdownDocumentBuilder
|
39 |
+
from src.team.review_team import ReviewTeam
|
40 |
from src.wbs.wbs_task import WBSTask, WBSProject
|
41 |
from src.wbs.wbs_populate import WBSPopulate
|
42 |
from src.llm_factory import get_llm
|
|
|
216 |
logger.info("Project plan created and saved to %s", output_path)
|
217 |
|
218 |
|
219 |
+
class FindTeamMembersTask(PlanTask):
|
220 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
221 |
+
|
222 |
+
def requires(self):
|
223 |
+
return {
|
224 |
+
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
225 |
+
'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
226 |
+
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
227 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
228 |
+
}
|
229 |
+
|
230 |
+
def output(self):
|
231 |
+
return {
|
232 |
+
'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.FIND_TEAM_MEMBERS_RAW))),
|
233 |
+
'clean': luigi.LocalTarget(str(self.file_path(FilenameEnum.FIND_TEAM_MEMBERS_CLEAN)))
|
234 |
+
}
|
235 |
+
|
236 |
+
def run(self):
|
237 |
+
logger.info("FindTeamMembers. Loading files...")
|
238 |
+
|
239 |
+
# 1. Read the plan prompt from SetupTask.
|
240 |
+
with self.input()['setup'].open("r") as f:
|
241 |
+
plan_prompt = f.read()
|
242 |
+
|
243 |
+
# 2. Read the distilled assumptions from AssumptionsTask.
|
244 |
+
with self.input()['assumptions'].open("r") as f:
|
245 |
+
assumption_list = json.load(f)
|
246 |
+
|
247 |
+
# 3. Read the pre-project assessment from PreProjectAssessmentTask.
|
248 |
+
with self.input()['preproject']['clean'].open("r") as f:
|
249 |
+
pre_project_assessment_dict = json.load(f)
|
250 |
+
|
251 |
+
# 4. Read the project plan from ProjectPlanTask.
|
252 |
+
with self.input()['project_plan'].open("r") as f:
|
253 |
+
project_plan_dict = json.load(f)
|
254 |
+
|
255 |
+
logger.info("FindTeamMembers. All files are now ready. Brainstorming a team...")
|
256 |
+
|
257 |
+
# Build the query.
|
258 |
+
query = (
|
259 |
+
f"Initial plan: {plan_prompt}\n\n"
|
260 |
+
f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
|
261 |
+
f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
262 |
+
f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}"
|
263 |
+
)
|
264 |
+
|
265 |
+
# Create LLM instance.
|
266 |
+
llm = get_llm(self.llm_model)
|
267 |
+
|
268 |
+
# Execute.
|
269 |
+
try:
|
270 |
+
find_team_members = FindTeamMembers.execute(llm, query)
|
271 |
+
except Exception as e:
|
272 |
+
logger.error("FindTeamMembers failed: %s", e)
|
273 |
+
raise
|
274 |
+
|
275 |
+
# Save the raw output.
|
276 |
+
raw_dict = find_team_members.to_dict()
|
277 |
+
with self.output()['raw'].open("w") as f:
|
278 |
+
json.dump(raw_dict, f, indent=2)
|
279 |
+
|
280 |
+
# Save the cleaned up result.
|
281 |
+
team_member_list = find_team_members.team_member_list
|
282 |
+
with self.output()['clean'].open("w") as f:
|
283 |
+
json.dump(team_member_list, f, indent=2)
|
284 |
+
|
285 |
+
logger.info("FindTeamMembers complete.")
|
286 |
+
|
287 |
+
class EnrichTeamMembersWithContractTypeTask(PlanTask):
|
288 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
289 |
+
|
290 |
+
def requires(self):
|
291 |
+
return {
|
292 |
+
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
293 |
+
'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
294 |
+
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
295 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
296 |
+
'find_team_members': FindTeamMembersTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
297 |
+
}
|
298 |
+
|
299 |
+
def output(self):
|
300 |
+
return {
|
301 |
+
'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.ENRICH_TEAM_MEMBERS_CONTRACT_TYPE_RAW))),
|
302 |
+
'clean': luigi.LocalTarget(str(self.file_path(FilenameEnum.ENRICH_TEAM_MEMBERS_CONTRACT_TYPE_CLEAN)))
|
303 |
+
}
|
304 |
+
|
305 |
+
def run(self):
|
306 |
+
logger.info("EnrichTeamMembersWithContractType. Loading files...")
|
307 |
+
|
308 |
+
# 1. Read the plan prompt from SetupTask.
|
309 |
+
with self.input()['setup'].open("r") as f:
|
310 |
+
plan_prompt = f.read()
|
311 |
+
|
312 |
+
# 2. Read the distilled assumptions from AssumptionsTask.
|
313 |
+
with self.input()['assumptions'].open("r") as f:
|
314 |
+
assumption_list = json.load(f)
|
315 |
+
|
316 |
+
# 3. Read the pre-project assessment from PreProjectAssessmentTask.
|
317 |
+
with self.input()['preproject']['clean'].open("r") as f:
|
318 |
+
pre_project_assessment_dict = json.load(f)
|
319 |
+
|
320 |
+
# 4. Read the project plan from ProjectPlanTask.
|
321 |
+
with self.input()['project_plan'].open("r") as f:
|
322 |
+
project_plan_dict = json.load(f)
|
323 |
+
|
324 |
+
# 5. Read the team_member_list from FindTeamMembersTask.
|
325 |
+
with self.input()['find_team_members']['clean'].open("r") as f:
|
326 |
+
team_member_list = json.load(f)
|
327 |
+
|
328 |
+
logger.info("EnrichTeamMembersWithContractType. All files are now ready. Processing...")
|
329 |
+
|
330 |
+
# Build the query.
|
331 |
+
query = (
|
332 |
+
f"Initial plan: {plan_prompt}\n\n"
|
333 |
+
f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
|
334 |
+
f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
335 |
+
f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
336 |
+
f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
|
337 |
+
)
|
338 |
+
|
339 |
+
# Create LLM instance.
|
340 |
+
llm = get_llm(self.llm_model)
|
341 |
+
|
342 |
+
# Execute.
|
343 |
+
try:
|
344 |
+
enrich_team_members_with_contract_type = EnrichTeamMembersWithContractType.execute(llm, query, team_member_list)
|
345 |
+
except Exception as e:
|
346 |
+
logger.error("EnrichTeamMembersWithContractType failed: %s", e)
|
347 |
+
raise
|
348 |
+
|
349 |
+
# Save the raw output.
|
350 |
+
raw_dict = enrich_team_members_with_contract_type.to_dict()
|
351 |
+
with self.output()['raw'].open("w") as f:
|
352 |
+
json.dump(raw_dict, f, indent=2)
|
353 |
+
|
354 |
+
# Save the cleaned up result.
|
355 |
+
team_member_list = enrich_team_members_with_contract_type.team_member_list
|
356 |
+
with self.output()['clean'].open("w") as f:
|
357 |
+
json.dump(team_member_list, f, indent=2)
|
358 |
+
|
359 |
+
logger.info("EnrichTeamMembersWithContractType complete.")
|
360 |
+
|
361 |
+
class EnrichTeamMembersWithBackgroundStoryTask(PlanTask):
|
362 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
363 |
+
|
364 |
+
def requires(self):
|
365 |
+
return {
|
366 |
+
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
367 |
+
'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
368 |
+
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
369 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
370 |
+
'enrich_team_members_with_contract_type': EnrichTeamMembersWithContractTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
371 |
+
}
|
372 |
+
|
373 |
+
def output(self):
|
374 |
+
return {
|
375 |
+
'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.ENRICH_TEAM_MEMBERS_BACKGROUND_STORY_RAW))),
|
376 |
+
'clean': luigi.LocalTarget(str(self.file_path(FilenameEnum.ENRICH_TEAM_MEMBERS_BACKGROUND_STORY_CLEAN)))
|
377 |
+
}
|
378 |
+
|
379 |
+
def run(self):
|
380 |
+
logger.info("EnrichTeamMembersWithBackgroundStoryTask. Loading files...")
|
381 |
+
|
382 |
+
# 1. Read the plan prompt from SetupTask.
|
383 |
+
with self.input()['setup'].open("r") as f:
|
384 |
+
plan_prompt = f.read()
|
385 |
+
|
386 |
+
# 2. Read the distilled assumptions from AssumptionsTask.
|
387 |
+
with self.input()['assumptions'].open("r") as f:
|
388 |
+
assumption_list = json.load(f)
|
389 |
+
|
390 |
+
# 3. Read the pre-project assessment from PreProjectAssessmentTask.
|
391 |
+
with self.input()['preproject']['clean'].open("r") as f:
|
392 |
+
pre_project_assessment_dict = json.load(f)
|
393 |
+
|
394 |
+
# 4. Read the project plan from ProjectPlanTask.
|
395 |
+
with self.input()['project_plan'].open("r") as f:
|
396 |
+
project_plan_dict = json.load(f)
|
397 |
+
|
398 |
+
# 5. Read the team_member_list from EnrichTeamMembersWithContractTypeTask.
|
399 |
+
with self.input()['enrich_team_members_with_contract_type']['clean'].open("r") as f:
|
400 |
+
team_member_list = json.load(f)
|
401 |
+
|
402 |
+
logger.info("EnrichTeamMembersWithBackgroundStoryTask. All files are now ready. Processing...")
|
403 |
+
|
404 |
+
# Build the query.
|
405 |
+
query = (
|
406 |
+
f"Initial plan: {plan_prompt}\n\n"
|
407 |
+
f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
|
408 |
+
f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
409 |
+
f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
410 |
+
f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
|
411 |
+
)
|
412 |
+
|
413 |
+
# Create LLM instance.
|
414 |
+
llm = get_llm(self.llm_model)
|
415 |
+
|
416 |
+
# Execute.
|
417 |
+
try:
|
418 |
+
enrich_team_members_with_background_story = EnrichTeamMembersWithBackgroundStory.execute(llm, query, team_member_list)
|
419 |
+
except Exception as e:
|
420 |
+
logger.error("EnrichTeamMembersWithBackgroundStory failed: %s", e)
|
421 |
+
raise
|
422 |
+
|
423 |
+
# Save the raw output.
|
424 |
+
raw_dict = enrich_team_members_with_background_story.to_dict()
|
425 |
+
with self.output()['raw'].open("w") as f:
|
426 |
+
json.dump(raw_dict, f, indent=2)
|
427 |
+
|
428 |
+
# Save the cleaned up result.
|
429 |
+
team_member_list = enrich_team_members_with_background_story.team_member_list
|
430 |
+
with self.output()['clean'].open("w") as f:
|
431 |
+
json.dump(team_member_list, f, indent=2)
|
432 |
+
|
433 |
+
logger.info("EnrichTeamMembersWithBackgroundStoryTask complete.")
|
434 |
+
|
435 |
+
class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask):
|
436 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
437 |
+
|
438 |
+
def requires(self):
|
439 |
+
return {
|
440 |
+
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
441 |
+
'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
442 |
+
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
443 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
444 |
+
'enrich_team_members_with_background_story': EnrichTeamMembersWithBackgroundStoryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
445 |
+
}
|
446 |
+
|
447 |
+
def output(self):
|
448 |
+
return {
|
449 |
+
'raw': luigi.LocalTarget(str(self.file_path(FilenameEnum.ENRICH_TEAM_MEMBERS_ENVIRONMENT_INFO_RAW))),
|
450 |
+
'clean': luigi.LocalTarget(str(self.file_path(FilenameEnum.ENRICH_TEAM_MEMBERS_ENVIRONMENT_INFO_CLEAN)))
|
451 |
+
}
|
452 |
+
|
453 |
+
def run(self):
|
454 |
+
logger.info("EnrichTeamMembersWithEnvironmentInfoTask. Loading files...")
|
455 |
+
|
456 |
+
# 1. Read the plan prompt from SetupTask.
|
457 |
+
with self.input()['setup'].open("r") as f:
|
458 |
+
plan_prompt = f.read()
|
459 |
+
|
460 |
+
# 2. Read the distilled assumptions from AssumptionsTask.
|
461 |
+
with self.input()['assumptions'].open("r") as f:
|
462 |
+
assumption_list = json.load(f)
|
463 |
+
|
464 |
+
# 3. Read the pre-project assessment from PreProjectAssessmentTask.
|
465 |
+
with self.input()['preproject']['clean'].open("r") as f:
|
466 |
+
pre_project_assessment_dict = json.load(f)
|
467 |
+
|
468 |
+
# 4. Read the project plan from ProjectPlanTask.
|
469 |
+
with self.input()['project_plan'].open("r") as f:
|
470 |
+
project_plan_dict = json.load(f)
|
471 |
+
|
472 |
+
# 5. Read the team_member_list from EnrichTeamMembersWithBackgroundStoryTask.
|
473 |
+
with self.input()['enrich_team_members_with_background_story']['clean'].open("r") as f:
|
474 |
+
team_member_list = json.load(f)
|
475 |
+
|
476 |
+
logger.info("EnrichTeamMembersWithEnvironmentInfoTask. All files are now ready. Processing...")
|
477 |
+
|
478 |
+
# Build the query.
|
479 |
+
query = (
|
480 |
+
f"Initial plan: {plan_prompt}\n\n"
|
481 |
+
f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
|
482 |
+
f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
483 |
+
f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
484 |
+
f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
|
485 |
+
)
|
486 |
+
|
487 |
+
# Create LLM instance.
|
488 |
+
llm = get_llm(self.llm_model)
|
489 |
+
|
490 |
+
# Execute.
|
491 |
+
try:
|
492 |
+
enrich_team_members_with_background_story = EnrichTeamMembersWithEnvironmentInfo.execute(llm, query, team_member_list)
|
493 |
+
except Exception as e:
|
494 |
+
logger.error("EnrichTeamMembersWithEnvironmentInfo failed: %s", e)
|
495 |
+
raise
|
496 |
+
|
497 |
+
# Save the raw output.
|
498 |
+
raw_dict = enrich_team_members_with_background_story.to_dict()
|
499 |
+
with self.output()['raw'].open("w") as f:
|
500 |
+
json.dump(raw_dict, f, indent=2)
|
501 |
+
|
502 |
+
# Save the cleaned up result.
|
503 |
+
team_member_list = enrich_team_members_with_background_story.team_member_list
|
504 |
+
with self.output()['clean'].open("w") as f:
|
505 |
+
json.dump(team_member_list, f, indent=2)
|
506 |
+
|
507 |
+
logger.info("EnrichTeamMembersWithEnvironmentInfoTask complete.")
|
508 |
+
|
509 |
+
class ReviewTeamTask(PlanTask):
|
510 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
511 |
+
|
512 |
+
def requires(self):
|
513 |
+
return {
|
514 |
+
'setup': SetupTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail),
|
515 |
+
'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
516 |
+
'preproject': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
517 |
+
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
518 |
+
'enrich_team_members_with_environment_info': EnrichTeamMembersWithEnvironmentInfoTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
519 |
+
}
|
520 |
+
|
521 |
+
def output(self):
|
522 |
+
return luigi.LocalTarget(str(self.file_path(FilenameEnum.REVIEW_TEAM_RAW)))
|
523 |
+
|
524 |
+
def run(self):
|
525 |
+
logger.info("ReviewTeamTask. Loading files...")
|
526 |
+
|
527 |
+
# 1. Read the plan prompt from SetupTask.
|
528 |
+
with self.input()['setup'].open("r") as f:
|
529 |
+
plan_prompt = f.read()
|
530 |
+
|
531 |
+
# 2. Read the distilled assumptions from AssumptionsTask.
|
532 |
+
with self.input()['assumptions'].open("r") as f:
|
533 |
+
assumption_list = json.load(f)
|
534 |
+
|
535 |
+
# 3. Read the pre-project assessment from PreProjectAssessmentTask.
|
536 |
+
with self.input()['preproject']['clean'].open("r") as f:
|
537 |
+
pre_project_assessment_dict = json.load(f)
|
538 |
+
|
539 |
+
# 4. Read the project plan from ProjectPlanTask.
|
540 |
+
with self.input()['project_plan'].open("r") as f:
|
541 |
+
project_plan_dict = json.load(f)
|
542 |
+
|
543 |
+
# 5. Read the team_member_list from EnrichTeamMembersWithEnvironmentInfoTask.
|
544 |
+
with self.input()['enrich_team_members_with_environment_info']['clean'].open("r") as f:
|
545 |
+
team_member_list = json.load(f)
|
546 |
+
|
547 |
+
logger.info("ReviewTeamTask. All files are now ready. Processing...")
|
548 |
+
|
549 |
+
# Convert the team members to a Markdown document.
|
550 |
+
builder = TeamMarkdownDocumentBuilder()
|
551 |
+
builder.append_roles(team_member_list, title=None)
|
552 |
+
team_document_markdown = builder.to_string()
|
553 |
+
|
554 |
+
# Build the query.
|
555 |
+
query = (
|
556 |
+
f"Initial plan: {plan_prompt}\n\n"
|
557 |
+
f"Assumptions:\n{format_json_for_use_in_query(assumption_list)}\n\n"
|
558 |
+
f"Pre-project assessment:\n{format_json_for_use_in_query(pre_project_assessment_dict)}\n\n"
|
559 |
+
f"Project plan:\n{format_json_for_use_in_query(project_plan_dict)}\n\n"
|
560 |
+
f"Document with team members:\n{team_document_markdown}"
|
561 |
+
)
|
562 |
+
|
563 |
+
# Create LLM instance.
|
564 |
+
llm = get_llm(self.llm_model)
|
565 |
+
|
566 |
+
# Execute.
|
567 |
+
try:
|
568 |
+
review_team = ReviewTeam.execute(llm, query)
|
569 |
+
except Exception as e:
|
570 |
+
logger.error("ReviewTeam failed: %s", e)
|
571 |
+
raise
|
572 |
+
|
573 |
+
# Save the raw output.
|
574 |
+
raw_dict = review_team.to_dict()
|
575 |
+
with self.output().open("w") as f:
|
576 |
+
json.dump(raw_dict, f, indent=2)
|
577 |
+
|
578 |
+
logger.info("ReviewTeamTask complete.")
|
579 |
+
|
580 |
+
class TeamMarkdownTask(PlanTask):
|
581 |
+
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
582 |
+
|
583 |
+
def requires(self):
|
584 |
+
return {
|
585 |
+
'enrich_team_members_with_environment_info': EnrichTeamMembersWithEnvironmentInfoTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
586 |
+
'review_team': ReviewTeamTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model)
|
587 |
+
}
|
588 |
+
|
589 |
+
def output(self):
|
590 |
+
return luigi.LocalTarget(str(self.file_path(FilenameEnum.TEAM_MARKDOWN)))
|
591 |
+
|
592 |
+
def run(self):
|
593 |
+
logger.info("TeamMarkdownTask. Loading files...")
|
594 |
+
|
595 |
+
# 1. Read the team_member_list from EnrichTeamMembersWithEnvironmentInfoTask.
|
596 |
+
with self.input()['enrich_team_members_with_environment_info']['clean'].open("r") as f:
|
597 |
+
team_member_list = json.load(f)
|
598 |
+
|
599 |
+
# 2. Read the json from ReviewTeamTask.
|
600 |
+
with self.input()['review_team'].open("r") as f:
|
601 |
+
review_team_json = json.load(f)
|
602 |
+
|
603 |
+
logger.info("TeamMarkdownTask. All files are now ready. Processing...")
|
604 |
+
|
605 |
+
# Combine the team members and the review into a Markdown document.
|
606 |
+
builder = TeamMarkdownDocumentBuilder()
|
607 |
+
builder.append_roles(team_member_list)
|
608 |
+
builder.append_separator()
|
609 |
+
builder.append_full_review(review_team_json)
|
610 |
+
builder.write_to_file(self.output().path)
|
611 |
+
|
612 |
+
logger.info("TeamMarkdownTask complete.")
|
613 |
+
|
614 |
class SWOTAnalysisTask(PlanTask):
|
615 |
llm_model = luigi.Parameter(default=DEFAULT_LLM_MODEL)
|
616 |
|
|
|
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),
|
1295 |
'wbs_project123': WBSProjectLevel1AndLevel2AndLevel3Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
|
|
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)
|
1304 |
rg.append_project_plan_csv(self.input()['wbs_project123']['csv'].path)
|
1305 |
rg.append_expert_criticism_markdown(self.input()['expert_review'].path)
|
1306 |
rg.save_report(self.output().path)
|
|
|
1314 |
'assumptions': AssumptionsTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1315 |
'pre_project_assessment': PreProjectAssessmentTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1316 |
'project_plan': ProjectPlanTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1317 |
+
'find_team_members': FindTeamMembersTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1318 |
+
'enrich_team_members_with_contract_type': EnrichTeamMembersWithContractTypeTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1319 |
+
'enrich_team_members_with_background_story': EnrichTeamMembersWithBackgroundStoryTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1320 |
+
'enrich_team_members_with_environment_info': EnrichTeamMembersWithEnvironmentInfoTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1321 |
+
'review_team': ReviewTeamTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1322 |
+
'team_markdown': TeamMarkdownTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1323 |
'swot_analysis': SWOTAnalysisTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1324 |
'expert_review': ExpertReviewTask(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
1325 |
'wbs_level1': CreateWBSLevel1Task(run_id=self.run_id, speedvsdetail=self.speedvsdetail, llm_model=self.llm_model),
|
src/report/report_generator.py
CHANGED
@@ -92,6 +92,12 @@ class ReportGenerator:
|
|
92 |
if swot_md:
|
93 |
self.report_data['swot'] = swot_md
|
94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
def append_expert_criticism_markdown(self, file_path: Path):
|
96 |
"""Append the expert criticism markdown to the report."""
|
97 |
expert_md = self.read_markdown_file(file_path)
|
@@ -210,6 +216,15 @@ class ReportGenerator:
|
|
210 |
html_parts.append(markdown.markdown(self.report_data['swot']))
|
211 |
html_parts.append("</div>")
|
212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
# Expert Criticism
|
214 |
if 'expert_criticism' in self.report_data:
|
215 |
html_parts.append("""
|
@@ -276,6 +291,7 @@ def main():
|
|
276 |
report_generator = ReportGenerator()
|
277 |
report_generator.append_pitch_markdown(input_path / FilenameEnum.PITCH_MARKDOWN.value)
|
278 |
report_generator.append_swot_analysis_markdown(input_path / FilenameEnum.SWOT_MARKDOWN.value)
|
|
|
279 |
report_generator.append_expert_criticism_markdown(input_path / FilenameEnum.EXPERT_CRITICISM_MARKDOWN.value)
|
280 |
report_generator.append_project_plan_csv(input_path / FilenameEnum.WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_CSV.value)
|
281 |
report_generator.save_report(output_path)
|
|
|
92 |
if swot_md:
|
93 |
self.report_data['swot'] = swot_md
|
94 |
|
95 |
+
def append_team_markdown(self, file_path: Path):
|
96 |
+
"""Append the team markdown to the report."""
|
97 |
+
swot_md = self.read_markdown_file(file_path)
|
98 |
+
if swot_md:
|
99 |
+
self.report_data['team'] = swot_md
|
100 |
+
|
101 |
def append_expert_criticism_markdown(self, file_path: Path):
|
102 |
"""Append the expert criticism markdown to the report."""
|
103 |
expert_md = self.read_markdown_file(file_path)
|
|
|
216 |
html_parts.append(markdown.markdown(self.report_data['swot']))
|
217 |
html_parts.append("</div>")
|
218 |
|
219 |
+
# Team
|
220 |
+
if 'team' in self.report_data:
|
221 |
+
html_parts.append("""
|
222 |
+
<div class="section">
|
223 |
+
<h2>Team</h2>
|
224 |
+
""")
|
225 |
+
html_parts.append(markdown.markdown(self.report_data['team']))
|
226 |
+
html_parts.append("</div>")
|
227 |
+
|
228 |
# Expert Criticism
|
229 |
if 'expert_criticism' in self.report_data:
|
230 |
html_parts.append("""
|
|
|
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)
|
296 |
report_generator.append_project_plan_csv(input_path / FilenameEnum.WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_CSV.value)
|
297 |
report_generator.save_report(output_path)
|
src/team/create_markdown_document.py
DELETED
@@ -1,71 +0,0 @@
|
|
1 |
-
import json
|
2 |
-
|
3 |
-
def create_markdown_document(plan_prompt: str, team_member_list_json_file_path: str, output_file_path: str):
|
4 |
-
"""
|
5 |
-
Reads text content and JSON data, then writes a Markdown document.
|
6 |
-
|
7 |
-
:param text_content: str, the main topic text to include in the Markdown
|
8 |
-
:param json_file_path: str, path to the JSON file
|
9 |
-
:param output_file_path: str, path to output the generated Markdown file
|
10 |
-
"""
|
11 |
-
# Load JSON data
|
12 |
-
with open(team_member_list_json_file_path, 'r', encoding='utf-8') as f:
|
13 |
-
roles_data = json.load(f)
|
14 |
-
|
15 |
-
# Begin constructing the Markdown content
|
16 |
-
rows = []
|
17 |
-
|
18 |
-
# 1. Add the main text content as a top-level section
|
19 |
-
rows.append("# The plan")
|
20 |
-
rows.append("")
|
21 |
-
rows.append(plan_prompt.strip())
|
22 |
-
rows.append("")
|
23 |
-
rows.append("---")
|
24 |
-
rows.append("")
|
25 |
-
|
26 |
-
# 2. Loop through each entry in the JSON and format as Markdown
|
27 |
-
for entry in roles_data:
|
28 |
-
# Each entry will be displayed under a subsection
|
29 |
-
rows.append(f"## Role {entry['id']} - {entry['category']}")
|
30 |
-
if 'explanation' in entry:
|
31 |
-
rows.append("")
|
32 |
-
rows.append(f"**Explanation**:\n{entry['explanation']}")
|
33 |
-
if 'count' in entry:
|
34 |
-
rows.append("")
|
35 |
-
rows.append(f"**People Count**:\n{entry['count']}")
|
36 |
-
if 'typical_job_activities' in entry:
|
37 |
-
rows.append("")
|
38 |
-
rows.append(f"**Typical Activities**:\n{entry['typical_job_activities']}")
|
39 |
-
if 'background_story' in entry:
|
40 |
-
rows.append("")
|
41 |
-
rows.append(f"**Background Story**:\n{entry['background_story']}")
|
42 |
-
if 'equipment_needs' in entry:
|
43 |
-
rows.append("")
|
44 |
-
rows.append(f"**Equipment Needs**:\n{entry['equipment_needs']}")
|
45 |
-
if 'facility_needs' in entry:
|
46 |
-
rows.append("")
|
47 |
-
rows.append(f"**Facility Needs**:\n{entry['facility_needs']}")
|
48 |
-
rows.append("")
|
49 |
-
rows.append("---")
|
50 |
-
rows.append("")
|
51 |
-
|
52 |
-
# Write everything to the output markdown file
|
53 |
-
with open(output_file_path, 'w', encoding='utf-8') as out_f:
|
54 |
-
out_f.write("\n".join(rows))
|
55 |
-
|
56 |
-
print(f"Markdown document has been created at: {output_file_path}")
|
57 |
-
|
58 |
-
|
59 |
-
if __name__ == "__main__":
|
60 |
-
# Your text snippet
|
61 |
-
plan_prompt = "Deep cave exploration to find new lifeforms in extreme conditions."
|
62 |
-
|
63 |
-
# Path to your JSON file
|
64 |
-
# TODO: Eliminate hardcoded paths
|
65 |
-
json_path = "/Users/neoneye/Desktop/planexe_data/005-enriched_team_members_list.json"
|
66 |
-
|
67 |
-
# Output Markdown file path
|
68 |
-
output_path = "output.md"
|
69 |
-
|
70 |
-
# Create the markdown document
|
71 |
-
create_markdown_document(plan_prompt, json_path, output_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/team/{enrich_team_members.py → enrich_team_members_with_background_story.py}
RENAMED
@@ -1,14 +1,20 @@
|
|
1 |
"""
|
2 |
Enrich each team member with a fictional background story and typical job activities.
|
|
|
|
|
3 |
"""
|
4 |
-
import os
|
5 |
import json
|
6 |
import time
|
|
|
7 |
from math import ceil
|
|
|
8 |
from typing import List, Optional
|
9 |
from pydantic import BaseModel, Field
|
10 |
from llama_index.core.llms import ChatMessage, MessageRole
|
11 |
from llama_index.core.llms.llm import LLM
|
|
|
|
|
|
|
12 |
|
13 |
class TeamMember(BaseModel):
|
14 |
"""A human with domain knowledge."""
|
@@ -28,6 +34,8 @@ class TeamDetails(BaseModel):
|
|
28 |
)
|
29 |
|
30 |
ENRICH_TEAM_MEMBERS_SYSTEM_PROMPT = """
|
|
|
|
|
31 |
Write a fictional background story about the person. It must be one paragraph that covers:
|
32 |
- First name and last name.
|
33 |
- Location.
|
@@ -38,58 +46,113 @@ Write a fictional background story about the person. It must be one paragraph th
|
|
38 |
The typical_job_activities describes relevant skills needed for this project.
|
39 |
"""
|
40 |
|
41 |
-
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
-
|
48 |
-
{compact_json}
|
49 |
-
"""
|
50 |
-
# print(user_prompt)
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
chat_message_list
|
55 |
ChatMessage(
|
56 |
role=MessageRole.SYSTEM,
|
57 |
content=system_prompt,
|
|
|
|
|
|
|
|
|
58 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
)
|
|
|
60 |
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
id_to_enriched_team_member = {item['id']: item for item in enriched_team_member_list}
|
86 |
-
for team_member in result_team_member_list:
|
87 |
-
id = team_member['id']
|
88 |
-
enriched_team_member = id_to_enriched_team_member.get(id)
|
89 |
-
if enriched_team_member:
|
90 |
-
team_member['typical_job_activities'] = enriched_team_member['typical_job_activities']
|
91 |
-
team_member['background_story'] = enriched_team_member['job_background_story_of_employee']
|
92 |
-
return result_team_member_list
|
93 |
|
94 |
if __name__ == "__main__":
|
95 |
from src.llm_factory import get_llm
|
@@ -122,5 +185,13 @@ if __name__ == "__main__":
|
|
122 |
}
|
123 |
]
|
124 |
|
125 |
-
|
|
|
|
|
|
|
|
|
126 |
print(json.dumps(json_response, indent=2))
|
|
|
|
|
|
|
|
|
|
1 |
"""
|
2 |
Enrich each team member with a fictional background story and typical job activities.
|
3 |
+
|
4 |
+
PROMPT> python -m src.team.enrich_team_members_with_background_story
|
5 |
"""
|
|
|
6 |
import json
|
7 |
import time
|
8 |
+
import logging
|
9 |
from math import ceil
|
10 |
+
from dataclasses import dataclass
|
11 |
from typing import List, Optional
|
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 TeamMember(BaseModel):
|
20 |
"""A human with domain knowledge."""
|
|
|
34 |
)
|
35 |
|
36 |
ENRICH_TEAM_MEMBERS_SYSTEM_PROMPT = """
|
37 |
+
For each team member provided, enrich them with a fictional background story and typical job activities.
|
38 |
+
|
39 |
Write a fictional background story about the person. It must be one paragraph that covers:
|
40 |
- First name and last name.
|
41 |
- Location.
|
|
|
46 |
The typical_job_activities describes relevant skills needed for this project.
|
47 |
"""
|
48 |
|
49 |
+
@dataclass
|
50 |
+
class EnrichTeamMembersWithBackgroundStory:
|
51 |
+
"""
|
52 |
+
Enrich each team member with a fictional background story and typical job activities.
|
53 |
+
"""
|
54 |
+
system_prompt: str
|
55 |
+
user_prompt: str
|
56 |
+
response: dict
|
57 |
+
metadata: dict
|
58 |
+
team_member_list: list[dict]
|
59 |
+
|
60 |
+
@classmethod
|
61 |
+
def format_query(cls, job_description: str, team_member_list: list[dict]) -> str:
|
62 |
+
if not isinstance(job_description, str):
|
63 |
+
raise ValueError("Invalid job_description.")
|
64 |
+
if not isinstance(team_member_list, list):
|
65 |
+
raise ValueError("Invalid team_member_list.")
|
66 |
+
|
67 |
+
query = (
|
68 |
+
f"Project description:\n{job_description}\n\n"
|
69 |
+
f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
|
70 |
+
)
|
71 |
+
return query
|
72 |
|
73 |
+
@classmethod
|
74 |
+
def execute(cls, llm: LLM, user_prompt: str, team_member_list: list[dict]) -> 'EnrichTeamMembersWithBackgroundStory':
|
75 |
+
"""
|
76 |
+
Invoke LLM with each team member.
|
77 |
+
"""
|
78 |
+
if not isinstance(llm, LLM):
|
79 |
+
raise ValueError("Invalid LLM instance.")
|
80 |
+
if not isinstance(user_prompt, str):
|
81 |
+
raise ValueError("Invalid user_prompt.")
|
82 |
+
if not isinstance(team_member_list, list):
|
83 |
+
raise ValueError("Invalid team_member_list.")
|
84 |
|
85 |
+
logger.debug(f"User Prompt:\n{user_prompt}")
|
|
|
|
|
|
|
86 |
|
87 |
+
system_prompt = ENRICH_TEAM_MEMBERS_SYSTEM_PROMPT.strip()
|
88 |
+
|
89 |
+
chat_message_list = [
|
90 |
ChatMessage(
|
91 |
role=MessageRole.SYSTEM,
|
92 |
content=system_prompt,
|
93 |
+
),
|
94 |
+
ChatMessage(
|
95 |
+
role=MessageRole.USER,
|
96 |
+
content=user_prompt,
|
97 |
)
|
98 |
+
]
|
99 |
+
|
100 |
+
sllm = llm.as_structured_llm(TeamDetails)
|
101 |
+
start_time = time.perf_counter()
|
102 |
+
try:
|
103 |
+
chat_response = sllm.chat(chat_message_list)
|
104 |
+
except Exception as e:
|
105 |
+
logger.debug(f"LLM chat interaction failed: {e}")
|
106 |
+
logger.error("LLM chat interaction failed.", exc_info=True)
|
107 |
+
raise ValueError("LLM chat interaction failed.") from e
|
108 |
+
|
109 |
+
end_time = time.perf_counter()
|
110 |
+
duration = int(ceil(end_time - start_time))
|
111 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
112 |
+
logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
113 |
+
|
114 |
+
json_response = chat_response.raw.model_dump()
|
115 |
+
|
116 |
+
team_member_list_enriched = cls.cleanup_enriched_team_members_and_merge_with_team_members(chat_response.raw, team_member_list)
|
117 |
+
|
118 |
+
metadata = dict(llm.metadata)
|
119 |
+
metadata["llm_classname"] = llm.class_name()
|
120 |
+
metadata["duration"] = duration
|
121 |
+
metadata["response_byte_count"] = response_byte_count
|
122 |
+
|
123 |
+
result = EnrichTeamMembersWithBackgroundStory(
|
124 |
+
system_prompt=system_prompt,
|
125 |
+
user_prompt=user_prompt,
|
126 |
+
response=json_response,
|
127 |
+
metadata=metadata,
|
128 |
+
team_member_list=team_member_list_enriched,
|
129 |
)
|
130 |
+
return result
|
131 |
|
132 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
|
133 |
+
d = self.response.copy()
|
134 |
+
if include_metadata:
|
135 |
+
d['metadata'] = self.metadata
|
136 |
+
if include_system_prompt:
|
137 |
+
d['system_prompt'] = self.system_prompt
|
138 |
+
if include_user_prompt:
|
139 |
+
d['user_prompt'] = self.user_prompt
|
140 |
+
return d
|
141 |
+
|
142 |
+
def cleanup_enriched_team_members_and_merge_with_team_members(team_details: TeamDetails, team_member_list: list[dict]) -> list[dict]:
|
143 |
+
result_team_member_list = team_member_list.copy()
|
144 |
+
enriched_team_member_list = team_details.team_members
|
145 |
+
id_to_enriched_team_member = {item.id: item for item in enriched_team_member_list}
|
146 |
+
for team_member_index, team_member in enumerate(result_team_member_list):
|
147 |
+
if not 'id' in team_member:
|
148 |
+
logger.warning(f"Team member #{team_member_index} does not have an id")
|
149 |
+
continue
|
150 |
+
id = team_member['id']
|
151 |
+
enriched_team_member = id_to_enriched_team_member.get(id)
|
152 |
+
if enriched_team_member:
|
153 |
+
team_member['typical_job_activities'] = enriched_team_member.typical_job_activities
|
154 |
+
team_member['background_story'] = enriched_team_member.job_background_story_of_employee
|
155 |
+
return result_team_member_list
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
|
157 |
if __name__ == "__main__":
|
158 |
from src.llm_factory import get_llm
|
|
|
185 |
}
|
186 |
]
|
187 |
|
188 |
+
query = EnrichTeamMembersWithBackgroundStory.format_query(job_description, team_member_list)
|
189 |
+
print(f"Query:\n{query}\n\n")
|
190 |
+
|
191 |
+
enrich_team_members_with_background_story = EnrichTeamMembersWithBackgroundStory.execute(llm, query, team_member_list)
|
192 |
+
json_response = enrich_team_members_with_background_story.to_dict(include_system_prompt=False, include_user_prompt=False)
|
193 |
print(json.dumps(json_response, indent=2))
|
194 |
+
|
195 |
+
print("\n\nTeam members with extra details:")
|
196 |
+
enriched_json = enrich_team_members_with_background_story.team_member_list
|
197 |
+
print(json.dumps(enriched_json, indent=2))
|
src/team/enrich_team_members_with_contract_type.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Enrich the team members with what kind of contract type they have.
|
3 |
+
|
4 |
+
PROMPT> python -m src.team.enrich_team_members_with_contract_type
|
5 |
+
"""
|
6 |
+
import os
|
7 |
+
import json
|
8 |
+
import time
|
9 |
+
import logging
|
10 |
+
from enum import Enum
|
11 |
+
from math import ceil
|
12 |
+
from dataclasses import dataclass
|
13 |
+
from typing import List, Optional
|
14 |
+
from pydantic import BaseModel, Field
|
15 |
+
from llama_index.core.llms import ChatMessage, MessageRole
|
16 |
+
from llama_index.core.llms.llm import LLM
|
17 |
+
from src.format_json_for_use_in_query import format_json_for_use_in_query
|
18 |
+
|
19 |
+
logger = logging.getLogger(__name__)
|
20 |
+
|
21 |
+
class ContractType(str, Enum):
|
22 |
+
# Full-Time Employee. The individual is a permanent member of the organization.
|
23 |
+
full_time_employee = 'full_time_employee'
|
24 |
+
# Part-Time Employee. Similar to a full-time employee, but they work fewer hours per week.
|
25 |
+
part_time_employee = 'part_time_employee'
|
26 |
+
# Independent Contractor / Consultant. A self-employed individual or business that provides services to your company on a contractual basis.
|
27 |
+
independent_contractor = 'independent_contractor'
|
28 |
+
# Temporary Employee through an Agency. The individual is employed by a staffing agency, who then assigns them to work at your company for a set period.
|
29 |
+
agency_temp = 'agency_temp'
|
30 |
+
# Other. When the contract type does not fit into the above categories.
|
31 |
+
other = 'other'
|
32 |
+
|
33 |
+
class TeamMember(BaseModel):
|
34 |
+
"""A human with domain knowledge."""
|
35 |
+
id: int = Field(
|
36 |
+
description="A unique id for the job_category."
|
37 |
+
)
|
38 |
+
contract_type: ContractType = Field(
|
39 |
+
description="The legal and financial agreement."
|
40 |
+
)
|
41 |
+
justification: str = Field(
|
42 |
+
description="Brief explanation for why that contract type was chosen. Helps justify decisions and allows for easy review."
|
43 |
+
)
|
44 |
+
|
45 |
+
class DocumentDetails(BaseModel):
|
46 |
+
team_members: list[TeamMember] = Field(
|
47 |
+
description="The experts with domain knowledge about the problem."
|
48 |
+
)
|
49 |
+
|
50 |
+
ENRICH_TEAM_MEMBERS_CONTRACT_TYPE_SYSTEM_PROMPT = """
|
51 |
+
You are an expert at determining what contract type are needed for different job roles given a project description.
|
52 |
+
|
53 |
+
"Contract Type" refers to the legal and financial agreement you have with each individual working on the project.
|
54 |
+
It dictates their employment status, compensation, benefits, and overall relationship with your organization (or the project).
|
55 |
+
|
56 |
+
The "contract_type" for each team member is crucial for the following reasons:
|
57 |
+
- Drives Cost Calculations: The type of employment agreement dictates a huge portion of project labor costs. Whether you're paying salary + benefits, or a fixed project fee, this is foundational information.
|
58 |
+
- Impacts Availability and Control: The contract type determines how much control you have over the person and how readily available they will be.
|
59 |
+
- Informs Resource Planning: It influences long-term versus short-term resource commitments.
|
60 |
+
|
61 |
+
Allowed values: "full_time_employee", "part_time_employee", "independent_contractor", "agency_temp"
|
62 |
+
|
63 |
+
For each team member provided, identify the contract_type considering the given project description. Provide concise but descriptive answers.
|
64 |
+
"""
|
65 |
+
|
66 |
+
@dataclass
|
67 |
+
class EnrichTeamMembersWithContractType:
|
68 |
+
"""
|
69 |
+
Enrich each team member with more info.
|
70 |
+
"""
|
71 |
+
system_prompt: str
|
72 |
+
user_prompt: str
|
73 |
+
response: dict
|
74 |
+
metadata: dict
|
75 |
+
team_member_list: list[dict]
|
76 |
+
|
77 |
+
@classmethod
|
78 |
+
def format_query(cls, job_description: str, team_member_list: list[dict]) -> str:
|
79 |
+
if not isinstance(job_description, str):
|
80 |
+
raise ValueError("Invalid job_description.")
|
81 |
+
if not isinstance(team_member_list, list):
|
82 |
+
raise ValueError("Invalid team_member_list.")
|
83 |
+
|
84 |
+
query = (
|
85 |
+
f"Project description:\n{job_description}\n\n"
|
86 |
+
f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
|
87 |
+
)
|
88 |
+
return query
|
89 |
+
|
90 |
+
@classmethod
|
91 |
+
def execute(cls, llm: LLM, user_prompt: str, team_member_list: list[dict]) -> 'EnrichTeamMembersWithContractType':
|
92 |
+
"""
|
93 |
+
Invoke LLM with each team member.
|
94 |
+
"""
|
95 |
+
if not isinstance(llm, LLM):
|
96 |
+
raise ValueError("Invalid LLM instance.")
|
97 |
+
if not isinstance(user_prompt, str):
|
98 |
+
raise ValueError("Invalid user_prompt.")
|
99 |
+
if not isinstance(team_member_list, list):
|
100 |
+
raise ValueError("Invalid team_member_list.")
|
101 |
+
|
102 |
+
logger.debug(f"User Prompt:\n{user_prompt}")
|
103 |
+
|
104 |
+
system_prompt = ENRICH_TEAM_MEMBERS_CONTRACT_TYPE_SYSTEM_PROMPT.strip()
|
105 |
+
|
106 |
+
chat_message_list = [
|
107 |
+
ChatMessage(
|
108 |
+
role=MessageRole.SYSTEM,
|
109 |
+
content=system_prompt,
|
110 |
+
),
|
111 |
+
ChatMessage(
|
112 |
+
role=MessageRole.USER,
|
113 |
+
content=user_prompt,
|
114 |
+
)
|
115 |
+
]
|
116 |
+
|
117 |
+
sllm = llm.as_structured_llm(DocumentDetails)
|
118 |
+
start_time = time.perf_counter()
|
119 |
+
try:
|
120 |
+
chat_response = sllm.chat(chat_message_list)
|
121 |
+
except Exception as e:
|
122 |
+
logger.debug(f"LLM chat interaction failed: {e}")
|
123 |
+
logger.error("LLM chat interaction failed.", exc_info=True)
|
124 |
+
raise ValueError("LLM chat interaction failed.") from e
|
125 |
+
|
126 |
+
end_time = time.perf_counter()
|
127 |
+
duration = int(ceil(end_time - start_time))
|
128 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
129 |
+
logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
130 |
+
|
131 |
+
json_response = chat_response.raw.model_dump()
|
132 |
+
|
133 |
+
team_member_list_enriched = cls.cleanup_and_merge_with_team_members(chat_response.raw, team_member_list)
|
134 |
+
|
135 |
+
metadata = dict(llm.metadata)
|
136 |
+
metadata["llm_classname"] = llm.class_name()
|
137 |
+
metadata["duration"] = duration
|
138 |
+
metadata["response_byte_count"] = response_byte_count
|
139 |
+
|
140 |
+
result = EnrichTeamMembersWithContractType(
|
141 |
+
system_prompt=system_prompt,
|
142 |
+
user_prompt=user_prompt,
|
143 |
+
response=json_response,
|
144 |
+
metadata=metadata,
|
145 |
+
team_member_list=team_member_list_enriched,
|
146 |
+
)
|
147 |
+
return result
|
148 |
+
|
149 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
|
150 |
+
d = self.response.copy()
|
151 |
+
if include_metadata:
|
152 |
+
d['metadata'] = self.metadata
|
153 |
+
if include_system_prompt:
|
154 |
+
d['system_prompt'] = self.system_prompt
|
155 |
+
if include_user_prompt:
|
156 |
+
d['user_prompt'] = self.user_prompt
|
157 |
+
return d
|
158 |
+
|
159 |
+
def cleanup_and_merge_with_team_members(document_details: DocumentDetails, team_member_list: list[dict]) -> list:
|
160 |
+
result_team_member_list = team_member_list.copy()
|
161 |
+
enriched_team_member_list = document_details.team_members
|
162 |
+
id_to_enriched_team_member = {item.id: item for item in enriched_team_member_list}
|
163 |
+
for team_member_index, team_member in enumerate(result_team_member_list):
|
164 |
+
if not 'id' in team_member:
|
165 |
+
logger.warning(f"Team member #{team_member_index} does not have an id")
|
166 |
+
continue
|
167 |
+
id = team_member['id']
|
168 |
+
enriched_team_member = id_to_enriched_team_member.get(id)
|
169 |
+
if enriched_team_member:
|
170 |
+
team_member['contract_type'] = enriched_team_member.contract_type.value
|
171 |
+
team_member['contract_type_justification'] = enriched_team_member.justification
|
172 |
+
return result_team_member_list
|
173 |
+
|
174 |
+
if __name__ == "__main__":
|
175 |
+
from src.llm_factory import get_llm
|
176 |
+
|
177 |
+
llm = get_llm("ollama-llama3.1")
|
178 |
+
# llm = get_llm("deepseek-chat")
|
179 |
+
|
180 |
+
job_description = "Establish a new police station in a high crime area."
|
181 |
+
|
182 |
+
team_member_list = [
|
183 |
+
{
|
184 |
+
"id": 1,
|
185 |
+
"category": "Law Enforcement",
|
186 |
+
"explanation": "Police officers and detectives are essential for patrolling, investigation, and maintaining public safety."
|
187 |
+
},
|
188 |
+
{
|
189 |
+
"id": 2,
|
190 |
+
"category": "Administration",
|
191 |
+
"explanation": "Administrative staff manage paperwork, scheduling, and coordination of police activities."
|
192 |
+
},
|
193 |
+
{
|
194 |
+
"id": 3,
|
195 |
+
"category": "Forensics",
|
196 |
+
"explanation": "Forensic experts analyze crime scene evidence to support investigations."
|
197 |
+
},
|
198 |
+
{
|
199 |
+
"id": 4,
|
200 |
+
"category": "Community Relations",
|
201 |
+
"explanation": "Officers or liaisons engage with the community to build trust and cooperation."
|
202 |
+
}
|
203 |
+
]
|
204 |
+
|
205 |
+
query = EnrichTeamMembersWithContractType.format_query(job_description, team_member_list)
|
206 |
+
print(f"Query:\n{query}\n\n")
|
207 |
+
|
208 |
+
enrich_team_members_with_environment_info = EnrichTeamMembersWithContractType.execute(llm, query, team_member_list)
|
209 |
+
json_response = enrich_team_members_with_environment_info.to_dict(include_system_prompt=False, include_user_prompt=False)
|
210 |
+
print(json.dumps(json_response, indent=2))
|
211 |
+
|
212 |
+
print("\n\nTeam members:")
|
213 |
+
json_team = enrich_team_members_with_environment_info.team_member_list
|
214 |
+
print(json.dumps(json_team, indent=2))
|
src/team/enrich_team_members_with_environment_info.py
CHANGED
@@ -1,14 +1,21 @@
|
|
1 |
"""
|
2 |
Enrich the team members with what kind of equipment and facilities they need for the task.
|
|
|
|
|
3 |
"""
|
4 |
import os
|
5 |
import json
|
6 |
import time
|
|
|
7 |
from math import ceil
|
|
|
8 |
from typing import List, Optional
|
9 |
from pydantic import BaseModel, Field
|
10 |
from llama_index.core.llms import ChatMessage, MessageRole
|
11 |
from llama_index.core.llms.llm import LLM
|
|
|
|
|
|
|
12 |
|
13 |
class TeamMember(BaseModel):
|
14 |
"""A human with domain knowledge."""
|
@@ -28,60 +35,118 @@ class DocumentDetails(BaseModel):
|
|
28 |
)
|
29 |
|
30 |
ENRICH_TEAM_MEMBERS_ENVIRONMENT_INFO_SYSTEM_PROMPT = """
|
31 |
-
|
|
|
|
|
32 |
"""
|
33 |
|
34 |
-
|
35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
chat_message_list.append(
|
46 |
ChatMessage(
|
47 |
role=MessageRole.SYSTEM,
|
48 |
content=system_prompt,
|
|
|
|
|
|
|
|
|
49 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
)
|
|
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
enriched_team_member_list = raw_enriched_team_member_dict['team_members']
|
77 |
-
id_to_enriched_team_member = {item['id']: item for item in enriched_team_member_list}
|
78 |
-
for team_member in result_team_member_list:
|
79 |
-
id = team_member['id']
|
80 |
-
enriched_team_member = id_to_enriched_team_member.get(id)
|
81 |
-
if enriched_team_member:
|
82 |
-
team_member['equipment_needs'] = enriched_team_member['equipment_needs']
|
83 |
-
team_member['facility_needs'] = enriched_team_member['facility_needs']
|
84 |
-
return result_team_member_list
|
85 |
|
86 |
if __name__ == "__main__":
|
87 |
from src.llm_factory import get_llm
|
@@ -114,5 +179,13 @@ if __name__ == "__main__":
|
|
114 |
}
|
115 |
]
|
116 |
|
117 |
-
|
|
|
|
|
|
|
|
|
118 |
print(json.dumps(json_response, indent=2))
|
|
|
|
|
|
|
|
|
|
1 |
"""
|
2 |
Enrich the team members with what kind of equipment and facilities they need for the task.
|
3 |
+
|
4 |
+
PROMPT> python -m src.team.enrich_team_members_with_environment_info
|
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 typing import List, Optional
|
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 |
+
from src.format_json_for_use_in_query import format_json_for_use_in_query
|
17 |
+
|
18 |
+
logger = logging.getLogger(__name__)
|
19 |
|
20 |
class TeamMember(BaseModel):
|
21 |
"""A human with domain knowledge."""
|
|
|
35 |
)
|
36 |
|
37 |
ENRICH_TEAM_MEMBERS_ENVIRONMENT_INFO_SYSTEM_PROMPT = """
|
38 |
+
You are an expert at determining what equipment and facilities are needed for different job roles given a project description.
|
39 |
+
|
40 |
+
For each team member provided, identify the specific equipment and facilities they require to effectively perform their daily tasks within the context of the given project description. Provide concise but descriptive answers.
|
41 |
"""
|
42 |
|
43 |
+
@dataclass
|
44 |
+
class EnrichTeamMembersWithEnvironmentInfo:
|
45 |
+
"""
|
46 |
+
Enrich each team member with more info.
|
47 |
+
"""
|
48 |
+
system_prompt: str
|
49 |
+
user_prompt: str
|
50 |
+
response: dict
|
51 |
+
metadata: dict
|
52 |
+
team_member_list: list[dict]
|
53 |
+
|
54 |
+
@classmethod
|
55 |
+
def format_query(cls, job_description: str, team_member_list: list[dict]) -> str:
|
56 |
+
if not isinstance(job_description, str):
|
57 |
+
raise ValueError("Invalid job_description.")
|
58 |
+
if not isinstance(team_member_list, list):
|
59 |
+
raise ValueError("Invalid team_member_list.")
|
60 |
+
|
61 |
+
query = (
|
62 |
+
f"Project description:\n{job_description}\n\n"
|
63 |
+
f"Here is the list of team members that needs to be enriched:\n{format_json_for_use_in_query(team_member_list)}"
|
64 |
+
)
|
65 |
+
return query
|
66 |
|
67 |
+
@classmethod
|
68 |
+
def execute(cls, llm: LLM, user_prompt: str, team_member_list: list[dict]) -> 'EnrichTeamMembersWithEnvironmentInfo':
|
69 |
+
"""
|
70 |
+
Invoke LLM with each team member.
|
71 |
+
"""
|
72 |
+
if not isinstance(llm, LLM):
|
73 |
+
raise ValueError("Invalid LLM instance.")
|
74 |
+
if not isinstance(user_prompt, str):
|
75 |
+
raise ValueError("Invalid user_prompt.")
|
76 |
+
if not isinstance(team_member_list, list):
|
77 |
+
raise ValueError("Invalid team_member_list.")
|
78 |
|
79 |
+
logger.debug(f"User Prompt:\n{user_prompt}")
|
80 |
+
|
81 |
+
system_prompt = ENRICH_TEAM_MEMBERS_ENVIRONMENT_INFO_SYSTEM_PROMPT.strip()
|
82 |
+
|
83 |
+
chat_message_list = [
|
|
|
84 |
ChatMessage(
|
85 |
role=MessageRole.SYSTEM,
|
86 |
content=system_prompt,
|
87 |
+
),
|
88 |
+
ChatMessage(
|
89 |
+
role=MessageRole.USER,
|
90 |
+
content=user_prompt,
|
91 |
)
|
92 |
+
]
|
93 |
+
|
94 |
+
sllm = llm.as_structured_llm(DocumentDetails)
|
95 |
+
start_time = time.perf_counter()
|
96 |
+
try:
|
97 |
+
chat_response = sllm.chat(chat_message_list)
|
98 |
+
except Exception as e:
|
99 |
+
logger.debug(f"LLM chat interaction failed: {e}")
|
100 |
+
logger.error("LLM chat interaction failed.", exc_info=True)
|
101 |
+
raise ValueError("LLM chat interaction failed.") from e
|
102 |
+
|
103 |
+
end_time = time.perf_counter()
|
104 |
+
duration = int(ceil(end_time - start_time))
|
105 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
106 |
+
logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
107 |
+
|
108 |
+
json_response = chat_response.raw.model_dump()
|
109 |
+
|
110 |
+
team_member_list_enriched = cls.cleanup_enriched_team_members_with_environment_info_and_merge_with_team_members(chat_response.raw, team_member_list)
|
111 |
+
|
112 |
+
metadata = dict(llm.metadata)
|
113 |
+
metadata["llm_classname"] = llm.class_name()
|
114 |
+
metadata["duration"] = duration
|
115 |
+
metadata["response_byte_count"] = response_byte_count
|
116 |
+
|
117 |
+
result = EnrichTeamMembersWithEnvironmentInfo(
|
118 |
+
system_prompt=system_prompt,
|
119 |
+
user_prompt=user_prompt,
|
120 |
+
response=json_response,
|
121 |
+
metadata=metadata,
|
122 |
+
team_member_list=team_member_list_enriched,
|
123 |
)
|
124 |
+
return result
|
125 |
|
126 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
|
127 |
+
d = self.response.copy()
|
128 |
+
if include_metadata:
|
129 |
+
d['metadata'] = self.metadata
|
130 |
+
if include_system_prompt:
|
131 |
+
d['system_prompt'] = self.system_prompt
|
132 |
+
if include_user_prompt:
|
133 |
+
d['user_prompt'] = self.user_prompt
|
134 |
+
return d
|
135 |
+
|
136 |
+
def cleanup_enriched_team_members_with_environment_info_and_merge_with_team_members(document_details: DocumentDetails, team_member_list: list[dict]) -> list:
|
137 |
+
result_team_member_list = team_member_list.copy()
|
138 |
+
enriched_team_member_list = document_details.team_members
|
139 |
+
id_to_enriched_team_member = {item.id: item for item in enriched_team_member_list}
|
140 |
+
for team_member_index, team_member in enumerate(result_team_member_list):
|
141 |
+
if not 'id' in team_member:
|
142 |
+
logger.warning(f"Team member #{team_member_index} does not have an id")
|
143 |
+
continue
|
144 |
+
id = team_member['id']
|
145 |
+
enriched_team_member = id_to_enriched_team_member.get(id)
|
146 |
+
if enriched_team_member:
|
147 |
+
team_member['equipment_needs'] = enriched_team_member.equipment_needs
|
148 |
+
team_member['facility_needs'] = enriched_team_member.facility_needs
|
149 |
+
return result_team_member_list
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
|
151 |
if __name__ == "__main__":
|
152 |
from src.llm_factory import get_llm
|
|
|
179 |
}
|
180 |
]
|
181 |
|
182 |
+
query = EnrichTeamMembersWithEnvironmentInfo.format_query(job_description, team_member_list)
|
183 |
+
print(f"Query:\n{query}\n\n")
|
184 |
+
|
185 |
+
enrich_team_members_with_environment_info = EnrichTeamMembersWithEnvironmentInfo.execute(llm, query, team_member_list)
|
186 |
+
json_response = enrich_team_members_with_environment_info.to_dict(include_system_prompt=False, include_user_prompt=False)
|
187 |
print(json.dumps(json_response, indent=2))
|
188 |
+
|
189 |
+
print("\n\nTeam members:")
|
190 |
+
json_team = enrich_team_members_with_environment_info.team_member_list
|
191 |
+
print(json.dumps(json_team, indent=2))
|
src/team/find_team_members.py
CHANGED
@@ -6,12 +6,16 @@ PROMPT> python -m src.team.find_team_members
|
|
6 |
import os
|
7 |
import json
|
8 |
import time
|
|
|
9 |
from math import ceil
|
|
|
10 |
from typing import List, Optional
|
11 |
from pydantic import BaseModel, Field
|
12 |
from llama_index.core.llms import ChatMessage, MessageRole
|
13 |
from llama_index.core.llms.llm import LLM
|
14 |
|
|
|
|
|
15 |
class TeamMember(BaseModel):
|
16 |
job_category_title: str = Field(
|
17 |
description="Human readable title"
|
@@ -22,6 +26,9 @@ class TeamMember(BaseModel):
|
|
22 |
people_needed: str = Field(
|
23 |
description="Number of people needed."
|
24 |
)
|
|
|
|
|
|
|
25 |
|
26 |
class DocumentDetails(BaseModel):
|
27 |
brainstorm_of_needed_team_members: list[TeamMember] = Field(
|
@@ -29,68 +36,147 @@ class DocumentDetails(BaseModel):
|
|
29 |
)
|
30 |
|
31 |
FIND_TEAM_MEMBERS_SYSTEM_PROMPT = """
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
-
|
|
|
35 |
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
38 |
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
-
|
44 |
-
-
|
45 |
-
- When it can vary, use "min 5,max 10,depending on capacity".
|
46 |
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
"""
|
49 |
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
ChatMessage(
|
55 |
role=MessageRole.SYSTEM,
|
56 |
content=system_prompt,
|
|
|
|
|
|
|
|
|
57 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
|
60 |
-
chat_message_user = ChatMessage(
|
61 |
-
role=MessageRole.USER,
|
62 |
-
content=query,
|
63 |
-
)
|
64 |
-
chat_message_list.append(chat_message_user)
|
65 |
-
|
66 |
-
sllm = llm.as_structured_llm(DocumentDetails)
|
67 |
-
|
68 |
-
start_time = time.perf_counter()
|
69 |
-
chat_response = sllm.chat(chat_message_list)
|
70 |
-
end_time = time.perf_counter()
|
71 |
-
duration = int(ceil(end_time - start_time))
|
72 |
-
|
73 |
-
json_response = json.loads(chat_response.message.content)
|
74 |
-
|
75 |
-
metadata = {
|
76 |
-
"duration": duration,
|
77 |
-
}
|
78 |
-
json_response['metadata'] = metadata
|
79 |
-
return json_response
|
80 |
-
|
81 |
-
def cleanup_team_members_and_assign_id(team_member_dict: dict) -> list:
|
82 |
-
result_list = []
|
83 |
-
team_members = team_member_dict['brainstorm_of_needed_team_members']
|
84 |
-
for i, team_member in enumerate(team_members, start=1):
|
85 |
-
item = {
|
86 |
-
"id": i,
|
87 |
-
"category": team_member['job_category_title'],
|
88 |
-
"explanation": team_member['short_explanation'],
|
89 |
-
"count": team_member['people_needed'],
|
90 |
-
}
|
91 |
-
result_list.append(item)
|
92 |
-
return result_list
|
93 |
-
|
94 |
if __name__ == "__main__":
|
95 |
from src.llm_factory import get_llm
|
96 |
from src.plan.find_plan_prompt import find_plan_prompt
|
@@ -99,5 +185,10 @@ if __name__ == "__main__":
|
|
99 |
plan_prompt = find_plan_prompt("4dc34d55-0d0d-4e9d-92f4-23765f49dd29")
|
100 |
|
101 |
print(f"Query:\n{plan_prompt}\n\n")
|
102 |
-
|
|
|
103 |
print(json.dumps(json_response, indent=2))
|
|
|
|
|
|
|
|
|
|
6 |
import os
|
7 |
import json
|
8 |
import time
|
9 |
+
import logging
|
10 |
from math import ceil
|
11 |
+
from dataclasses import dataclass
|
12 |
from typing import List, Optional
|
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 TeamMember(BaseModel):
|
20 |
job_category_title: str = Field(
|
21 |
description="Human readable title"
|
|
|
26 |
people_needed: str = Field(
|
27 |
description="Number of people needed."
|
28 |
)
|
29 |
+
consequences_of_not_having_this_role: str = Field(
|
30 |
+
description="Consequences of not having this role."
|
31 |
+
)
|
32 |
|
33 |
class DocumentDetails(BaseModel):
|
34 |
brainstorm_of_needed_team_members: list[TeamMember] = Field(
|
|
|
36 |
)
|
37 |
|
38 |
FIND_TEAM_MEMBERS_SYSTEM_PROMPT = """
|
39 |
+
You are a versatile project planning assistant and team architect. Your goal is to analyze the user's project description and decompose it into a comprehensive plan with a focus on human roles and resource allocation—**do not generate any code or technical implementation details.**
|
40 |
+
|
41 |
+
If the project description involves programming tasks or includes requests for code, treat it as a planning challenge. Instead of writing a script or providing code, break down the project into essential phases and identify the key human roles needed to successfully complete the project.
|
42 |
+
|
43 |
+
Based on the user's project description, brainstorm a team of potential human support roles that cover all crucial aspects of the project, including planning & preparation, execution, monitoring & adjustment, and maintenance & sustainability.
|
44 |
+
|
45 |
+
**Output Requirements:**
|
46 |
+
|
47 |
+
1. **Team Size:**
|
48 |
+
Your output **must include exactly 8 candidate roles**.
|
49 |
+
- If your initial analysis identifies fewer than 8 distinct roles, create additional meaningful roles to reach exactly 8.
|
50 |
+
- If your analysis results in more than 8 roles, consolidate or combine roles so that the final output contains exactly 8 candidates.
|
51 |
+
|
52 |
+
2. **Role Titles:**
|
53 |
+
Provide a clear and concise `job_category_title` that accurately describes the role's primary contribution.
|
54 |
+
|
55 |
+
3. **Role Explanations:**
|
56 |
+
Briefly explain each role’s purpose, key responsibilities, and how it contributes actively throughout the project.
|
57 |
|
58 |
+
4. **Consequences:**
|
59 |
+
For each role, note potential risks or consequences of omitting that role.
|
60 |
|
61 |
+
5. **People Count / Resource Level:**
|
62 |
+
Use the `people_needed` field to indicate the number of people required for each role. **Do not simply default to "1" for every role.** Instead, evaluate the complexity and workload of the role relative to the project's scale:
|
63 |
+
- **Single Resource:** If one person is clearly sufficient, use "1".
|
64 |
+
- **Fixed Level:** If the role consistently requires a specific number of people (e.g., "2" or "3"), use that fixed number.
|
65 |
+
- **Variable Level:** If the required support may vary based on factors like project scale, workload, or budget, specify a range. For example, instead of "1", you might write "min 1, max 3, depending on project scale and workload." Be sure to justify why the role may require more than one person.
|
66 |
|
67 |
+
6. **Project Phases / Support Stages:**
|
68 |
+
Ensure the roles collectively address the following phases:
|
69 |
+
- **Planning & Preparation**
|
70 |
+
- **Execution**
|
71 |
+
- **Monitoring & Adjustment**
|
72 |
+
- **Maintenance & Sustainability**
|
|
|
73 |
|
74 |
+
**Essential Considerations for EVERY Role:**
|
75 |
+
|
76 |
+
- **Specific Expertise**
|
77 |
+
- **Key Responsibilities**
|
78 |
+
- **Direct Impact (if applicable)**
|
79 |
+
- **Project Dependencies**
|
80 |
+
- **Relevant Skills**
|
81 |
+
- **Role Priority**
|
82 |
+
|
83 |
+
**Important:**
|
84 |
+
- Do not provide any code or implementation details—even if the prompt is programming-related. Focus solely on planning, decomposing the work, and identifying the essential human roles.
|
85 |
+
- **For personal, trivial, or non-commercial projects, avoid suggesting overly formal or business-oriented roles (e.g., Marketing Specialist, Legal Advisor, Technical Support Specialist) unless they are absolutely necessary.** In such cases, prefer roles that can be integrated or scaled down to suit the project's nature.
|
86 |
"""
|
87 |
|
88 |
+
@dataclass
|
89 |
+
class FindTeamMembers:
|
90 |
+
"""
|
91 |
+
From a project description, find a team for solving the job.
|
92 |
+
"""
|
93 |
+
system_prompt: str
|
94 |
+
user_prompt: str
|
95 |
+
response: dict
|
96 |
+
metadata: dict
|
97 |
+
team_member_list: list[dict]
|
98 |
+
|
99 |
+
@classmethod
|
100 |
+
def execute(cls, llm: LLM, user_prompt: str) -> 'FindTeamMembers':
|
101 |
+
"""
|
102 |
+
Invoke LLM to find a team.
|
103 |
+
"""
|
104 |
+
if not isinstance(llm, LLM):
|
105 |
+
raise ValueError("Invalid LLM instance.")
|
106 |
+
if not isinstance(user_prompt, str):
|
107 |
+
raise ValueError("Invalid user_prompt.")
|
108 |
+
|
109 |
+
logger.debug(f"User Prompt:\n{user_prompt}")
|
110 |
+
|
111 |
+
system_prompt = FIND_TEAM_MEMBERS_SYSTEM_PROMPT.strip()
|
112 |
+
|
113 |
+
chat_message_list = [
|
114 |
ChatMessage(
|
115 |
role=MessageRole.SYSTEM,
|
116 |
content=system_prompt,
|
117 |
+
),
|
118 |
+
ChatMessage(
|
119 |
+
role=MessageRole.USER,
|
120 |
+
content=user_prompt,
|
121 |
)
|
122 |
+
]
|
123 |
+
|
124 |
+
sllm = llm.as_structured_llm(DocumentDetails)
|
125 |
+
start_time = time.perf_counter()
|
126 |
+
try:
|
127 |
+
chat_response = sllm.chat(chat_message_list)
|
128 |
+
except Exception as e:
|
129 |
+
logger.debug(f"LLM chat interaction failed: {e}")
|
130 |
+
logger.error("LLM chat interaction failed.", exc_info=True)
|
131 |
+
raise ValueError("LLM chat interaction failed.") from e
|
132 |
+
|
133 |
+
end_time = time.perf_counter()
|
134 |
+
duration = int(ceil(end_time - start_time))
|
135 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
136 |
+
logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
137 |
+
|
138 |
+
json_response = chat_response.raw.model_dump()
|
139 |
+
|
140 |
+
team_member_list = cls.cleanup_team_members_and_assign_id(chat_response.raw)
|
141 |
+
|
142 |
+
metadata = dict(llm.metadata)
|
143 |
+
metadata["llm_classname"] = llm.class_name()
|
144 |
+
metadata["duration"] = duration
|
145 |
+
metadata["response_byte_count"] = response_byte_count
|
146 |
+
|
147 |
+
result = FindTeamMembers(
|
148 |
+
system_prompt=system_prompt,
|
149 |
+
user_prompt=user_prompt,
|
150 |
+
response=json_response,
|
151 |
+
metadata=metadata,
|
152 |
+
team_member_list=team_member_list,
|
153 |
)
|
154 |
+
return result
|
155 |
+
|
156 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
|
157 |
+
d = self.response.copy()
|
158 |
+
if include_metadata:
|
159 |
+
d['metadata'] = self.metadata
|
160 |
+
if include_system_prompt:
|
161 |
+
d['system_prompt'] = self.system_prompt
|
162 |
+
if include_user_prompt:
|
163 |
+
d['user_prompt'] = self.user_prompt
|
164 |
+
return d
|
165 |
+
|
166 |
+
def cleanup_team_members_and_assign_id(document_details: DocumentDetails) -> list:
|
167 |
+
result_list = []
|
168 |
+
team_members = document_details.brainstorm_of_needed_team_members
|
169 |
+
for i, team_member in enumerate(team_members, start=1):
|
170 |
+
item = {
|
171 |
+
"id": i,
|
172 |
+
"category": team_member.job_category_title,
|
173 |
+
"explanation": team_member.short_explanation,
|
174 |
+
"consequences": team_member.consequences_of_not_having_this_role,
|
175 |
+
"count": team_member.people_needed,
|
176 |
+
}
|
177 |
+
result_list.append(item)
|
178 |
+
return result_list
|
179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
if __name__ == "__main__":
|
181 |
from src.llm_factory import get_llm
|
182 |
from src.plan.find_plan_prompt import find_plan_prompt
|
|
|
185 |
plan_prompt = find_plan_prompt("4dc34d55-0d0d-4e9d-92f4-23765f49dd29")
|
186 |
|
187 |
print(f"Query:\n{plan_prompt}\n\n")
|
188 |
+
result = FindTeamMembers.execute(llm, plan_prompt)
|
189 |
+
json_response = result.to_dict(include_system_prompt=False, include_user_prompt=False)
|
190 |
print(json.dumps(json_response, indent=2))
|
191 |
+
|
192 |
+
print("\n\nTeam members:")
|
193 |
+
json_team = result.team_member_list
|
194 |
+
print(json.dumps(json_team, indent=2))
|
src/team/review_team.py
ADDED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Review the team.
|
3 |
+
|
4 |
+
PROMPT> python -m src.team.review_team
|
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 for the omission/improvement."
|
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 |
+
|
30 |
+
class DocumentDetails(BaseModel):
|
31 |
+
omissions: list[ReviewItem] = Field(
|
32 |
+
description="The most significant omissions."
|
33 |
+
)
|
34 |
+
potential_improvements: list[ReviewItem] = Field(
|
35 |
+
description="Suggestions and recommendations."
|
36 |
+
)
|
37 |
+
|
38 |
+
REVIEW_TEAM_SYSTEM_PROMPT = """
|
39 |
+
You are an expert in designing and evaluating team structures for projects of all scales—from personal or trivial endeavors to large, complex initiatives. Your task is to review a team document that includes a project plan, detailed team roles, and sections on omissions and potential improvements.
|
40 |
+
|
41 |
+
In your analysis, please:
|
42 |
+
|
43 |
+
1. **Review the Team Composition:**
|
44 |
+
- Examine the team roles described, including details such as contract types, typical activities, background stories, and resource needs.
|
45 |
+
- Consider whether the roles sufficiently cover all aspects of the project given its scope.
|
46 |
+
|
47 |
+
2. **Identify Omissions:**
|
48 |
+
- Highlight any significant missing roles, support functions, or expertise areas that are critical for the project's success.
|
49 |
+
- **Important:** When the project is personal or trivial, avoid suggesting overly formal or business-oriented roles (e.g., Marketing Specialist, Legal Advisor, Technical Support Specialist). Instead, suggest simpler or integrative adjustments suitable for a personal context.
|
50 |
+
|
51 |
+
3. **Suggest Potential Improvements:**
|
52 |
+
- Recommend actionable changes that enhance the team's overall effectiveness, communication, and clarity.
|
53 |
+
- Focus on clarifying responsibilities and reducing overlap.
|
54 |
+
- For personal or non-commercial projects, tailor your recommendations to be straightforward and avoid introducing new formal roles that are unnecessary.
|
55 |
+
|
56 |
+
4. **Provide Actionable Recommendations:**
|
57 |
+
- For each identified omission or improvement, offer specific, practical advice on how to address the issue.
|
58 |
+
- Ensure your recommendations are scaled appropriately to the project's nature.
|
59 |
+
|
60 |
+
Your output must be a JSON object with two top-level keys: "omissions" and "potential_improvements". Each key should map to an array of objects, where each object contains:
|
61 |
+
- `"issue"`: A brief title summarizing the omission or improvement.
|
62 |
+
- `"explanation"`: A concise explanation of why this issue is significant in relation to the project's goals.
|
63 |
+
- `"recommendation"`: Specific, actionable advice on how to address the issue.
|
64 |
+
|
65 |
+
Ensure your JSON output strictly follows this structure without any additional commentary or text.
|
66 |
+
"""
|
67 |
+
|
68 |
+
@dataclass
|
69 |
+
class ReviewTeam:
|
70 |
+
"""
|
71 |
+
Take a look at the proposed team and provide feedback on potential omissions and improvements.
|
72 |
+
"""
|
73 |
+
system_prompt: str
|
74 |
+
user_prompt: str
|
75 |
+
response: dict
|
76 |
+
metadata: dict
|
77 |
+
|
78 |
+
@classmethod
|
79 |
+
def format_query(cls, job_description: str, team_document_markdown: str) -> str:
|
80 |
+
if not isinstance(job_description, str):
|
81 |
+
raise ValueError("Invalid job_description.")
|
82 |
+
if not isinstance(team_document_markdown, str):
|
83 |
+
raise ValueError("Invalid team_document_markdown.")
|
84 |
+
|
85 |
+
query = (
|
86 |
+
f"Project description:\n{job_description}\n\n"
|
87 |
+
f"Document with team members:\n{team_document_markdown}"
|
88 |
+
)
|
89 |
+
return query
|
90 |
+
|
91 |
+
@classmethod
|
92 |
+
def execute(cls, llm: LLM, user_prompt: str) -> 'ReviewTeam':
|
93 |
+
"""
|
94 |
+
Invoke LLM with the project description and team document to be reviewed.
|
95 |
+
"""
|
96 |
+
if not isinstance(llm, LLM):
|
97 |
+
raise ValueError("Invalid LLM instance.")
|
98 |
+
if not isinstance(user_prompt, str):
|
99 |
+
raise ValueError("Invalid user_prompt.")
|
100 |
+
|
101 |
+
logger.debug(f"User Prompt:\n{user_prompt}")
|
102 |
+
|
103 |
+
system_prompt = REVIEW_TEAM_SYSTEM_PROMPT.strip()
|
104 |
+
|
105 |
+
chat_message_list = [
|
106 |
+
ChatMessage(
|
107 |
+
role=MessageRole.SYSTEM,
|
108 |
+
content=system_prompt,
|
109 |
+
),
|
110 |
+
ChatMessage(
|
111 |
+
role=MessageRole.USER,
|
112 |
+
content=user_prompt,
|
113 |
+
)
|
114 |
+
]
|
115 |
+
|
116 |
+
sllm = llm.as_structured_llm(DocumentDetails)
|
117 |
+
start_time = time.perf_counter()
|
118 |
+
try:
|
119 |
+
chat_response = sllm.chat(chat_message_list)
|
120 |
+
except Exception as e:
|
121 |
+
logger.debug(f"LLM chat interaction failed: {e}")
|
122 |
+
logger.error("LLM chat interaction failed.", exc_info=True)
|
123 |
+
raise ValueError("LLM chat interaction failed.") from e
|
124 |
+
|
125 |
+
end_time = time.perf_counter()
|
126 |
+
duration = int(ceil(end_time - start_time))
|
127 |
+
response_byte_count = len(chat_response.message.content.encode('utf-8'))
|
128 |
+
logger.info(f"LLM chat interaction completed in {duration} seconds. Response byte count: {response_byte_count}")
|
129 |
+
|
130 |
+
json_response = chat_response.raw.model_dump()
|
131 |
+
|
132 |
+
metadata = dict(llm.metadata)
|
133 |
+
metadata["llm_classname"] = llm.class_name()
|
134 |
+
metadata["duration"] = duration
|
135 |
+
metadata["response_byte_count"] = response_byte_count
|
136 |
+
|
137 |
+
result = ReviewTeam(
|
138 |
+
system_prompt=system_prompt,
|
139 |
+
user_prompt=user_prompt,
|
140 |
+
response=json_response,
|
141 |
+
metadata=metadata,
|
142 |
+
)
|
143 |
+
return result
|
144 |
+
|
145 |
+
def to_dict(self, include_metadata=True, include_system_prompt=True, include_user_prompt=True) -> dict:
|
146 |
+
d = self.response.copy()
|
147 |
+
if include_metadata:
|
148 |
+
d['metadata'] = self.metadata
|
149 |
+
if include_system_prompt:
|
150 |
+
d['system_prompt'] = self.system_prompt
|
151 |
+
if include_user_prompt:
|
152 |
+
d['user_prompt'] = self.user_prompt
|
153 |
+
return d
|
154 |
+
|
155 |
+
if __name__ == "__main__":
|
156 |
+
from src.llm_factory import get_llm
|
157 |
+
|
158 |
+
llm = get_llm("ollama-llama3.1")
|
159 |
+
|
160 |
+
path = os.path.join(os.path.dirname(__file__), 'test_data', "solarfarm_team_without_review.md")
|
161 |
+
with open(path, 'r', encoding='utf-8') as f:
|
162 |
+
team_document_markdown = f.read()
|
163 |
+
job_description = "Establish a solar farm in Denmark."
|
164 |
+
|
165 |
+
query = ReviewTeam.format_query(job_description, team_document_markdown)
|
166 |
+
print(f"Query:\n{query}\n\n")
|
167 |
+
|
168 |
+
review_team = ReviewTeam.execute(llm, query)
|
169 |
+
json_response = review_team.to_dict(include_system_prompt=False, include_user_prompt=False)
|
170 |
+
print(json.dumps(json_response, indent=2))
|
src/team/run_hire_team.py
CHANGED
@@ -4,16 +4,18 @@ PROMPT> python -m src.team.run_hire_team
|
|
4 |
from datetime import datetime
|
5 |
import os
|
6 |
import json
|
7 |
-
from src.team.find_team_members import
|
8 |
-
from src.team.
|
9 |
-
from src.team.
|
10 |
-
from src.team.
|
|
|
|
|
11 |
from src.plan.find_plan_prompt import find_plan_prompt
|
12 |
from src.llm_factory import get_llm
|
13 |
|
14 |
llm = get_llm("ollama-llama3.1")
|
15 |
# llm = get_llm("openrouter-paid-gemini-2.0-flash-001")
|
16 |
-
# llm = get_llm("
|
17 |
|
18 |
plan_prompt = find_plan_prompt("4dc34d55-0d0d-4e9d-92f4-23765f49dd29")
|
19 |
|
@@ -31,12 +33,12 @@ with open(plan_prompt_file, 'w') as f:
|
|
31 |
f.write(plan_prompt)
|
32 |
|
33 |
print("Finding team members for this task...")
|
34 |
-
|
35 |
team_members_raw_file = f'{run_dir}/002-team_members_raw.json'
|
36 |
with open(team_members_raw_file, 'w') as f:
|
37 |
-
f.write(json.dumps(
|
38 |
|
39 |
-
team_members_list =
|
40 |
team_members_list_file = f'{run_dir}/003-team_members_list.json'
|
41 |
with open(team_members_list_file, 'w') as f:
|
42 |
f.write(json.dumps(team_members_list, indent=2))
|
@@ -45,31 +47,66 @@ print(f"Number of team members: {len(team_members_list)}")
|
|
45 |
team_member_category_list = [item['category'] for item in team_members_list]
|
46 |
print(f"Team member categories: {team_member_category_list}")
|
47 |
|
48 |
-
print("Step A: Enriching team members...")
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
|
|
|
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
with open(
|
57 |
-
f.write(json.dumps(
|
58 |
print("Step A: Done enriching team members.")
|
59 |
|
60 |
-
print("Step B: Enriching team members with
|
61 |
-
|
62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
with open(enrich_team_members_with_environment_info_raw_file, 'w') as f:
|
64 |
f.write(json.dumps(enrich_team_members_with_environment_info_raw_dict, indent=2))
|
65 |
|
66 |
-
enrich_team_members_with_environment_info_list =
|
67 |
-
enrich_team_members_with_environment_info_list_file = f'{run_dir}/
|
68 |
with open(enrich_team_members_with_environment_info_list_file, 'w') as f:
|
69 |
f.write(json.dumps(enrich_team_members_with_environment_info_list, indent=2))
|
70 |
-
print("Step
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
print("Creating Markdown document...")
|
73 |
-
|
74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
print("Done creating Markdown document.")
|
|
|
4 |
from datetime import datetime
|
5 |
import os
|
6 |
import json
|
7 |
+
from src.team.find_team_members import FindTeamMembers
|
8 |
+
from src.team.enrich_team_members_with_contract_type import EnrichTeamMembersWithContractType
|
9 |
+
from src.team.enrich_team_members_with_background_story import EnrichTeamMembersWithBackgroundStory
|
10 |
+
from src.team.enrich_team_members_with_environment_info import EnrichTeamMembersWithEnvironmentInfo
|
11 |
+
from src.team.team_markdown_document import TeamMarkdownDocumentBuilder
|
12 |
+
from src.team.review_team import ReviewTeam
|
13 |
from src.plan.find_plan_prompt import find_plan_prompt
|
14 |
from src.llm_factory import get_llm
|
15 |
|
16 |
llm = get_llm("ollama-llama3.1")
|
17 |
# llm = get_llm("openrouter-paid-gemini-2.0-flash-001")
|
18 |
+
# llm = get_llm("openrouter-paid-openai-gpt-4o-mini")
|
19 |
|
20 |
plan_prompt = find_plan_prompt("4dc34d55-0d0d-4e9d-92f4-23765f49dd29")
|
21 |
|
|
|
33 |
f.write(plan_prompt)
|
34 |
|
35 |
print("Finding team members for this task...")
|
36 |
+
find_team_members = FindTeamMembers.execute(llm, plan_prompt)
|
37 |
team_members_raw_file = f'{run_dir}/002-team_members_raw.json'
|
38 |
with open(team_members_raw_file, 'w') as f:
|
39 |
+
f.write(json.dumps(find_team_members.to_dict(), indent=2))
|
40 |
|
41 |
+
team_members_list = find_team_members.team_member_list
|
42 |
team_members_list_file = f'{run_dir}/003-team_members_list.json'
|
43 |
with open(team_members_list_file, 'w') as f:
|
44 |
f.write(json.dumps(team_members_list, indent=2))
|
|
|
47 |
team_member_category_list = [item['category'] for item in team_members_list]
|
48 |
print(f"Team member categories: {team_member_category_list}")
|
49 |
|
50 |
+
print("Step A: Enriching team members with contract type...")
|
51 |
+
enrich_team_members_with_contract_type_query = EnrichTeamMembersWithContractType.format_query(plan_prompt, team_members_list)
|
52 |
+
enrich_team_members_with_contract_type = EnrichTeamMembersWithContractType.execute(llm, enrich_team_members_with_contract_type_query, team_members_list)
|
53 |
+
enrich_team_members_with_contract_type_raw_dict = enrich_team_members_with_contract_type.to_dict()
|
54 |
+
enrich_team_members_with_contract_type_raw_file = f'{run_dir}/004-enrich_team_members_with_contract_type_raw.json'
|
55 |
+
with open(enrich_team_members_with_contract_type_raw_file, 'w') as f:
|
56 |
+
f.write(json.dumps(enrich_team_members_with_contract_type_raw_dict, indent=2))
|
57 |
|
58 |
+
enrich_team_members_with_contract_type_list = enrich_team_members_with_contract_type.team_member_list
|
59 |
+
enrich_team_members_with_contract_type_list_file = f'{run_dir}/005-enrich_team_members_with_contract_type_list.json'
|
60 |
+
with open(enrich_team_members_with_contract_type_list_file, 'w') as f:
|
61 |
+
f.write(json.dumps(enrich_team_members_with_contract_type_list, indent=2))
|
62 |
print("Step A: Done enriching team members.")
|
63 |
|
64 |
+
print("Step B: Enriching team members with background story...")
|
65 |
+
enrich_team_members_with_background_story_query = EnrichTeamMembersWithBackgroundStory.format_query(plan_prompt, enrich_team_members_with_contract_type_list)
|
66 |
+
enrich_team_members_with_background_story = EnrichTeamMembersWithBackgroundStory.execute(llm, enrich_team_members_with_background_story_query, enrich_team_members_with_contract_type_list)
|
67 |
+
enrich_team_members_with_background_story_raw_dict = enrich_team_members_with_background_story.to_dict()
|
68 |
+
enrich_team_members_with_background_story_raw_file = f'{run_dir}/006-enriched_team_members_with_background_story_raw.json'
|
69 |
+
with open(enrich_team_members_with_background_story_raw_file, 'w') as f:
|
70 |
+
f.write(json.dumps(enrich_team_members_with_background_story_raw_dict, indent=2))
|
71 |
+
|
72 |
+
enrich_team_members_with_background_story_list = enrich_team_members_with_background_story.team_member_list
|
73 |
+
enrich_team_members_with_background_story_list_file = f'{run_dir}/007-enrich_team_members_with_background_story_list.json'
|
74 |
+
with open(enrich_team_members_with_background_story_list_file, 'w') as f:
|
75 |
+
f.write(json.dumps(enrich_team_members_with_background_story_list, indent=2))
|
76 |
+
print("Step B: Done enriching team members.")
|
77 |
+
|
78 |
+
print("Step C: Enriching team members with environment info...")
|
79 |
+
enrich_team_members_with_environment_info_query = EnrichTeamMembersWithEnvironmentInfo.format_query(plan_prompt, enrich_team_members_with_background_story_list)
|
80 |
+
enrich_team_members_with_environment_info = EnrichTeamMembersWithEnvironmentInfo.execute(llm, enrich_team_members_with_environment_info_query, enrich_team_members_with_background_story_list)
|
81 |
+
enrich_team_members_with_environment_info_raw_dict = enrich_team_members_with_environment_info.to_dict()
|
82 |
+
enrich_team_members_with_environment_info_raw_file = f'{run_dir}/008-enrich_team_members_with_environment_info_raw.json'
|
83 |
with open(enrich_team_members_with_environment_info_raw_file, 'w') as f:
|
84 |
f.write(json.dumps(enrich_team_members_with_environment_info_raw_dict, indent=2))
|
85 |
|
86 |
+
enrich_team_members_with_environment_info_list = enrich_team_members_with_environment_info.team_member_list
|
87 |
+
enrich_team_members_with_environment_info_list_file = f'{run_dir}/009-enrich_team_members_with_environment_info_list.json'
|
88 |
with open(enrich_team_members_with_environment_info_list_file, 'w') as f:
|
89 |
f.write(json.dumps(enrich_team_members_with_environment_info_list, indent=2))
|
90 |
+
print("Step C: Done enriching team members.")
|
91 |
+
|
92 |
+
print("Step D: Reviewing team...")
|
93 |
+
builder1 = TeamMarkdownDocumentBuilder()
|
94 |
+
builder1.append_roles(enrich_team_members_with_environment_info_list, title=None)
|
95 |
+
review_team_query = ReviewTeam.format_query(plan_prompt, builder1.to_string())
|
96 |
+
review_team = ReviewTeam.execute(llm, review_team_query)
|
97 |
+
review_team_raw_dict = review_team.to_dict()
|
98 |
+
review_team_raw_file = f'{run_dir}/010-review_team_raw.json'
|
99 |
+
with open(review_team_raw_file, 'w') as f:
|
100 |
+
f.write(json.dumps(review_team_raw_dict, indent=2))
|
101 |
+
print("Step D: Reviewing team.")
|
102 |
|
103 |
print("Creating Markdown document...")
|
104 |
+
builder2 = TeamMarkdownDocumentBuilder()
|
105 |
+
builder2.append_plan_prompt(plan_prompt)
|
106 |
+
builder2.append_separator()
|
107 |
+
builder2.append_roles(enrich_team_members_with_environment_info_list)
|
108 |
+
builder2.append_separator()
|
109 |
+
builder2.append_full_review(review_team.response)
|
110 |
+
output_file = f'{run_dir}/011-team.md'
|
111 |
+
builder2.write_to_file(output_file)
|
112 |
print("Done creating Markdown document.")
|
src/team/team_markdown_document.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Create a Markdown document containing details about the team.
|
3 |
+
|
4 |
+
PROMPT> python -m src.team.team_markdown_document
|
5 |
+
"""
|
6 |
+
import json
|
7 |
+
from typing import Optional
|
8 |
+
|
9 |
+
class TeamMarkdownDocumentBuilder:
|
10 |
+
"""
|
11 |
+
A class to build a Markdown document containing details about the team.
|
12 |
+
"""
|
13 |
+
def __init__(self):
|
14 |
+
self.rows = []
|
15 |
+
|
16 |
+
def append_separator(self):
|
17 |
+
self.rows.append("\n---\n")
|
18 |
+
|
19 |
+
def append_plan_prompt(self, plan_prompt: str):
|
20 |
+
"""The main topic text to include in the Markdown"""
|
21 |
+
self.rows.append("# The plan\n")
|
22 |
+
self.rows.append(plan_prompt.strip())
|
23 |
+
|
24 |
+
def append_role(self, entry: dict, role_index: int):
|
25 |
+
self.rows.append(f"\n## {role_index}. {entry['category']}")
|
26 |
+
if 'contract_type' in entry:
|
27 |
+
self.rows.append(f"\n**Contract Type**: `{entry['contract_type']}`")
|
28 |
+
if 'contract_type_justification' in entry:
|
29 |
+
self.rows.append(f"\n**Contract Type Justification**: {entry['contract_type_justification']}")
|
30 |
+
if 'explanation' in entry:
|
31 |
+
self.rows.append(f"\n**Explanation**:\n{entry['explanation']}")
|
32 |
+
if 'consequences' in entry:
|
33 |
+
self.rows.append(f"\n**Consequences**:\n{entry['consequences']}")
|
34 |
+
if 'count' in entry:
|
35 |
+
self.rows.append(f"\n**People Count**:\n{entry['count']}")
|
36 |
+
if 'typical_job_activities' in entry:
|
37 |
+
self.rows.append(f"\n**Typical Activities**:\n{entry['typical_job_activities']}")
|
38 |
+
if 'background_story' in entry:
|
39 |
+
self.rows.append(f"\n**Background Story**:\n{entry['background_story']}")
|
40 |
+
if 'equipment_needs' in entry:
|
41 |
+
self.rows.append(f"\n**Equipment Needs**:\n{entry['equipment_needs']}")
|
42 |
+
if 'facility_needs' in entry:
|
43 |
+
self.rows.append(f"\n**Facility Needs**:\n{entry['facility_needs']}")
|
44 |
+
|
45 |
+
def append_roles(self, roles_list: list[dict], title: Optional[str] = "Roles"):
|
46 |
+
if isinstance(title, str):
|
47 |
+
self.rows.append(f"# {title}")
|
48 |
+
for entry_index, entry in enumerate(roles_list, start=1):
|
49 |
+
self.append_role(entry, entry_index)
|
50 |
+
|
51 |
+
def append_review_item(self, review_item: dict, review_index: int):
|
52 |
+
issue = review_item.get('issue', "Missing Review Issue")
|
53 |
+
self.rows.append(f"\n## {review_index}. {issue}")
|
54 |
+
if 'explanation' in review_item:
|
55 |
+
self.rows.append(f"\n{review_item['explanation']}")
|
56 |
+
if 'recommendation' in review_item:
|
57 |
+
self.rows.append(f"\n**Recommendation**:\n{review_item['recommendation']}")
|
58 |
+
|
59 |
+
def append_review_items(self, review_items: list[dict], title: Optional[str] = "Review Items"):
|
60 |
+
if isinstance(title, str):
|
61 |
+
self.rows.append(f"# {title}")
|
62 |
+
for review_index, review_item in enumerate(review_items, start=1):
|
63 |
+
self.append_review_item(review_item, review_index)
|
64 |
+
|
65 |
+
def append_full_review(self, review: dict):
|
66 |
+
review_omissions = review.get('omissions', [])
|
67 |
+
self.append_review_items(review_omissions, title="Omissions")
|
68 |
+
self.append_separator()
|
69 |
+
review_potential_improvements = review.get('potential_improvements', [])
|
70 |
+
self.append_review_items(review_potential_improvements, title="Potential Improvements")
|
71 |
+
|
72 |
+
def to_string(self) -> str:
|
73 |
+
return "\n".join(self.rows)
|
74 |
+
|
75 |
+
def write_to_file(self, output_file_path: str):
|
76 |
+
markdown_representation = self.to_string()
|
77 |
+
with open(output_file_path, 'w', encoding='utf-8') as out_f:
|
78 |
+
out_f.write(markdown_representation)
|
79 |
+
|
80 |
+
if __name__ == "__main__":
|
81 |
+
import os
|
82 |
+
|
83 |
+
plan_prompt = "Establish a solar farm in Denmark."
|
84 |
+
|
85 |
+
path1 = os.path.join(os.path.dirname(__file__), 'test_data', "solarfarm_roles_list.json")
|
86 |
+
with open(path1, 'r', encoding='utf-8') as f:
|
87 |
+
roles_list = json.load(f)
|
88 |
+
|
89 |
+
path2 = os.path.join(os.path.dirname(__file__), 'test_data', "solarfarm_team_review.json")
|
90 |
+
with open(path2, 'r', encoding='utf-8') as f:
|
91 |
+
team_review = json.load(f)
|
92 |
+
|
93 |
+
builder2 = TeamMarkdownDocumentBuilder()
|
94 |
+
builder2.append_plan_prompt(plan_prompt)
|
95 |
+
builder2.append_separator()
|
96 |
+
builder2.append_roles(roles_list)
|
97 |
+
builder2.append_separator()
|
98 |
+
builder2.append_full_review(team_review)
|
99 |
+
|
100 |
+
print(builder2.to_string())
|
src/team/test_data/solarfarm_roles_list.json
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
{
|
3 |
+
"id": 1,
|
4 |
+
"category": "Project Manager",
|
5 |
+
"explanation": "Oversees the entire project, ensuring it stays on schedule and within budget.",
|
6 |
+
"consequences": "Without a project manager, the project may lack direction, leading to delays and budget overruns.",
|
7 |
+
"count": "1",
|
8 |
+
"contract_type": "full_time_employee",
|
9 |
+
"contract_type_justification": "The Project Manager needs to be fully integrated into the project to ensure consistent oversight and decision-making throughout the project's duration.",
|
10 |
+
"typical_job_activities": "Coordinate project schedules, manage budgets, liaise with stakeholders, oversee project execution, and ensure compliance with regulations.",
|
11 |
+
"background_story": "Lars Jensen, a seasoned project manager based in Aarhus, Denmark, graduated with a Master's degree in Project Management from the University of Aarhus. With over 10 years of experience in managing renewable energy projects, Lars has successfully overseen the construction of multiple wind and solar farms across Scandinavia. His strong leadership skills and ability to navigate complex regulatory environments make him an invaluable asset to the team. Lars is well-versed in the intricacies of project timelines and budgets, ensuring that the solar farm project remains on track and within financial constraints.",
|
12 |
+
"equipment_needs": "Laptop with project management software, communication tools, and access to project documentation.",
|
13 |
+
"facility_needs": "Office space for meetings and collaboration with team members and stakeholders."
|
14 |
+
},
|
15 |
+
{
|
16 |
+
"id": 2,
|
17 |
+
"category": "Site Engineer",
|
18 |
+
"explanation": "Responsible for the technical aspects of the solar farm's construction and installation.",
|
19 |
+
"consequences": "Absence of a site engineer can result in construction errors and safety issues, jeopardizing the project.",
|
20 |
+
"count": "2",
|
21 |
+
"contract_type": "full_time_employee",
|
22 |
+
"contract_type_justification": "The Site Engineer is essential for the technical execution of the project and requires full-time commitment to address ongoing construction challenges.",
|
23 |
+
"typical_job_activities": "Oversee construction activities, ensure compliance with engineering specifications, conduct site inspections, and collaborate with contractors.",
|
24 |
+
"background_story": "Sophie Madsen, a site engineer from Odense, Denmark, holds a Bachelor's degree in Civil Engineering from the Technical University of Denmark. With five years of hands-on experience in solar farm construction, Sophie has developed a keen eye for detail and a strong understanding of the technical requirements involved in such projects. Her familiarity with construction processes and safety regulations makes her a critical member of the team, ensuring that the solar farm is built to the highest standards.",
|
25 |
+
"equipment_needs": "Construction tools, safety gear, surveying equipment, and a laptop with engineering software.",
|
26 |
+
"facility_needs": "Access to the construction site and a temporary office or trailer for on-site coordination."
|
27 |
+
},
|
28 |
+
{
|
29 |
+
"id": 3,
|
30 |
+
"category": "Environmental Consultant",
|
31 |
+
"explanation": "Assesses the environmental impact of the solar farm and ensures compliance with regulations.",
|
32 |
+
"consequences": "Failure to address environmental concerns can lead to legal issues and project delays.",
|
33 |
+
"count": "1",
|
34 |
+
"contract_type": "independent_contractor",
|
35 |
+
"contract_type_justification": "The Environmental Consultant can be engaged on a project basis, providing expertise as needed without the requirement for full-time employment.",
|
36 |
+
"typical_job_activities": "Conduct environmental impact assessments, prepare reports, liaise with regulatory bodies, and recommend mitigation strategies.",
|
37 |
+
"background_story": "Freja S\u00f8rensen, an environmental consultant based in Copenhagen, Denmark, earned her Master's degree in Environmental Science from the University of Copenhagen. With over eight years of experience in environmental assessments for renewable energy projects, Freja is adept at identifying potential ecological impacts and ensuring compliance with local regulations. Her expertise is crucial for the solar farm project, as she will help navigate environmental challenges and secure necessary permits.",
|
38 |
+
"equipment_needs": "Laptop with environmental assessment software, access to environmental databases, and reporting tools.",
|
39 |
+
"facility_needs": "Office space for conducting assessments and meetings with regulatory bodies."
|
40 |
+
},
|
41 |
+
{
|
42 |
+
"id": 4,
|
43 |
+
"category": "Financial Analyst",
|
44 |
+
"explanation": "Evaluates the financial feasibility of the project and manages funding and budgeting.",
|
45 |
+
"consequences": "Without financial oversight, the project may face funding shortages or misallocation of resources.",
|
46 |
+
"count": "1",
|
47 |
+
"contract_type": "full_time_employee",
|
48 |
+
"contract_type_justification": "The Financial Analyst plays a critical role in managing the project's finances and requires continuous involvement to ensure financial health.",
|
49 |
+
"typical_job_activities": "Evaluate financial models, manage budgets, analyze funding options, and prepare financial reports.",
|
50 |
+
"background_story": "Mikkel Thomsen, a financial analyst from Aalborg, Denmark, graduated with a degree in Finance from Aalborg University. With a background in renewable energy financing, Mikkel has spent the last six years analyzing project feasibility and managing budgets for various energy initiatives. His analytical skills and financial acumen are essential for ensuring that the solar farm project remains financially viable and well-funded.",
|
51 |
+
"equipment_needs": "Laptop with financial modeling software, access to financial databases, and reporting tools.",
|
52 |
+
"facility_needs": "Office space for financial analysis and meetings with project stakeholders."
|
53 |
+
},
|
54 |
+
{
|
55 |
+
"id": 5,
|
56 |
+
"category": "Procurement Specialist",
|
57 |
+
"explanation": "Handles the sourcing and purchasing of materials and equipment needed for the solar farm.",
|
58 |
+
"consequences": "Lack of procurement expertise can lead to delays in acquiring necessary materials, affecting the project timeline.",
|
59 |
+
"count": "1",
|
60 |
+
"contract_type": "independent_contractor",
|
61 |
+
"contract_type_justification": "The Procurement Specialist can be contracted for specific phases of the project, focusing on sourcing materials without needing a full-time position.",
|
62 |
+
"typical_job_activities": "Source and negotiate with suppliers, manage purchase orders, track inventory, and ensure timely delivery of materials.",
|
63 |
+
"background_story": "Clara Nielsen, a procurement specialist located in Esbjerg, Denmark, has a degree in Supply Chain Management from the University of Southern Denmark. With over seven years of experience in sourcing materials for construction projects, Clara has developed strong negotiation skills and a vast network of suppliers. Her role is vital for ensuring that the solar farm has all necessary materials and equipment on time and within budget.",
|
64 |
+
"equipment_needs": "Laptop with procurement management software, access to supplier databases, and communication tools.",
|
65 |
+
"facility_needs": "Office space for sourcing materials and negotiating with suppliers."
|
66 |
+
},
|
67 |
+
{
|
68 |
+
"id": 6,
|
69 |
+
"category": "Construction Crew",
|
70 |
+
"explanation": "A team of workers responsible for the physical construction of the solar farm.",
|
71 |
+
"consequences": "Insufficient workforce can slow down construction progress and lead to missed deadlines.",
|
72 |
+
"count": "min 5, max 10, depending on project scale",
|
73 |
+
"contract_type": "agency_temp",
|
74 |
+
"contract_type_justification": "The Construction Crew can be sourced through an agency to provide flexibility in workforce size based on project demands and timelines.",
|
75 |
+
"typical_job_activities": "Install solar panels, perform site preparation, conduct safety checks, and collaborate with site engineers.",
|
76 |
+
"background_story": "The construction crew, led by foreman Jakob Larsen, is a diverse team of skilled workers from various backgrounds in construction and renewable energy. Based in different regions of Denmark, the crew members have undergone specialized training in solar panel installation and safety protocols. Their collective experience and teamwork are essential for the successful physical construction of the solar farm, ensuring that all tasks are completed efficiently and safely.",
|
77 |
+
"equipment_needs": "Construction tools, safety gear, and equipment for solar panel installation.",
|
78 |
+
"facility_needs": "Access to the construction site and temporary facilities for the crew."
|
79 |
+
},
|
80 |
+
{
|
81 |
+
"id": 7,
|
82 |
+
"category": "Quality Assurance Specialist",
|
83 |
+
"explanation": "Ensures that all work meets the required standards and specifications.",
|
84 |
+
"consequences": "Without quality assurance, the project may face issues with system performance and reliability.",
|
85 |
+
"count": "1",
|
86 |
+
"contract_type": "part_time_employee",
|
87 |
+
"contract_type_justification": "The Quality Assurance Specialist may not need to be full-time, as their role can be fulfilled on a part-time basis during critical phases of construction.",
|
88 |
+
"typical_job_activities": "Conduct quality inspections, develop quality assurance protocols, document findings, and collaborate with construction teams.",
|
89 |
+
"background_story": "Anna Pedersen, a quality assurance specialist from Randers, Denmark, holds a degree in Quality Management from the University of Aarhus. With over four years of experience in quality control within the renewable energy sector, Anna is skilled in developing and implementing quality assurance protocols. Her attention to detail and commitment to excellence are crucial for ensuring that the solar farm meets all required standards and specifications.",
|
90 |
+
"equipment_needs": "Laptop with quality management software, inspection tools, and documentation materials.",
|
91 |
+
"facility_needs": "Office space for conducting quality inspections and meetings with construction teams."
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"id": 8,
|
95 |
+
"category": "Maintenance Technician",
|
96 |
+
"explanation": "Responsible for the ongoing maintenance and troubleshooting of the solar farm post-construction.",
|
97 |
+
"consequences": "Lack of maintenance can lead to system failures and decreased efficiency over time.",
|
98 |
+
"count": "min 1, max 3, depending on project scale and operational needs",
|
99 |
+
"contract_type": "independent_contractor",
|
100 |
+
"contract_type_justification": "The Maintenance Technician can be contracted as needed post-construction, allowing for flexibility in scheduling maintenance activities.",
|
101 |
+
"typical_job_activities": "Perform routine maintenance, troubleshoot system issues, conduct performance assessments, and coordinate with the project team for repairs.",
|
102 |
+
"background_story": "Oliver Kristensen, a maintenance technician based in Kolding, Denmark, has a background in electrical engineering from the University of Southern Denmark. With three years of experience in maintaining solar energy systems, Oliver is adept at troubleshooting and performing routine maintenance to ensure optimal performance. His role post-construction is vital for the long-term efficiency and reliability of the solar farm.",
|
103 |
+
"equipment_needs": "Maintenance tools, diagnostic equipment, and a laptop for performance assessments.",
|
104 |
+
"facility_needs": "Access to the solar farm for maintenance activities and a workshop for repairs."
|
105 |
+
}
|
106 |
+
]
|
src/team/test_data/solarfarm_team_review.json
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"omissions": [
|
3 |
+
{
|
4 |
+
"issue": "Community Engagement Coordinator",
|
5 |
+
"explanation": "Engaging with the local community is crucial for gaining support and addressing concerns related to the solar farm project.",
|
6 |
+
"recommendation": "Consider appointing a Community Engagement Coordinator to facilitate communication with local residents, address their concerns, and promote the benefits of the solar farm."
|
7 |
+
},
|
8 |
+
{
|
9 |
+
"issue": "Health and Safety Officer",
|
10 |
+
"explanation": "A dedicated Health and Safety Officer is essential to ensure compliance with safety regulations and to promote a safe working environment on-site.",
|
11 |
+
"recommendation": "Introduce a Health and Safety Officer role to oversee safety protocols, conduct training, and ensure that all safety measures are adhered to during construction."
|
12 |
+
},
|
13 |
+
{
|
14 |
+
"issue": "Legal Advisor",
|
15 |
+
"explanation": "Legal expertise is necessary to navigate contracts, permits, and compliance with local laws and regulations.",
|
16 |
+
"recommendation": "Engage a Legal Advisor on a consulting basis to review contracts, assist with regulatory compliance, and address any legal issues that may arise during the project."
|
17 |
+
}
|
18 |
+
],
|
19 |
+
"potential_improvements": [
|
20 |
+
{
|
21 |
+
"issue": "Clarification of Roles",
|
22 |
+
"explanation": "Some roles may have overlapping responsibilities, which can lead to confusion and inefficiencies.",
|
23 |
+
"recommendation": "Clearly define the responsibilities of each team member, particularly between the Site Engineer and the Quality Assurance Specialist, to ensure accountability and streamline processes."
|
24 |
+
},
|
25 |
+
{
|
26 |
+
"issue": "Regular Team Meetings",
|
27 |
+
"explanation": "Frequent communication among team members is vital for project alignment and addressing issues promptly.",
|
28 |
+
"recommendation": "Implement regular team meetings to discuss progress, challenges, and updates, ensuring that all team members are informed and engaged."
|
29 |
+
},
|
30 |
+
{
|
31 |
+
"issue": "Documentation and Reporting",
|
32 |
+
"explanation": "Proper documentation is essential for tracking progress and ensuring compliance with regulations.",
|
33 |
+
"recommendation": "Establish a centralized documentation system where all team members can access and update project-related documents, ensuring transparency and accountability."
|
34 |
+
}
|
35 |
+
],
|
36 |
+
"metadata": {
|
37 |
+
"context_window": 3900,
|
38 |
+
"num_output": 8192,
|
39 |
+
"is_chat_model": true,
|
40 |
+
"is_function_calling_model": false,
|
41 |
+
"model_name": "openai/gpt-4o-mini",
|
42 |
+
"system_role": "system",
|
43 |
+
"llm_classname": "OpenRouter_LLM",
|
44 |
+
"duration": 10,
|
45 |
+
"response_byte_count": 2130
|
46 |
+
},
|
47 |
+
"system_prompt": "You are an expert in designing and evaluating team structures for projects of all scales\u2014from personal or trivial endeavors to large, complex initiatives. Your task is to review a team document that includes a project plan, detailed team roles, and sections on omissions and potential improvements.\n\nIn your analysis, please:\n\n1. **Review the Team Composition:**\n - Examine the team roles described, including details such as contract types, typical activities, background stories, and resource needs.\n - Consider whether the roles sufficiently cover all aspects of the project given its scope.\n\n2. **Identify Omissions:**\n - Highlight any significant missing roles, support functions, or expertise areas that are critical for the project's success.\n - **Important:** When the project is personal or trivial, avoid suggesting overly formal or business-oriented roles (e.g., Marketing Specialist, Legal Advisor, Technical Support Specialist). Instead, suggest simpler or integrative adjustments suitable for a personal context.\n\n3. **Suggest Potential Improvements:**\n - Recommend actionable changes that enhance the team's overall effectiveness, communication, and clarity.\n - Focus on clarifying responsibilities and reducing overlap.\n - For personal or non-commercial projects, tailor your recommendations to be straightforward and avoid introducing new formal roles that are unnecessary.\n\n4. **Provide Actionable Recommendations:**\n - For each identified omission or improvement, offer specific, practical advice on how to address the issue.\n - Ensure your recommendations are scaled appropriately to the project's nature.\n\nYour output must be a JSON object with two top-level keys: \"omissions\" and \"potential_improvements\". Each key should map to an array of objects, where each object contains:\n- `\"issue\"`: A brief title summarizing the omission or improvement.\n- `\"explanation\"`: A concise explanation of why this issue is significant in relation to the project's goals.\n- `\"recommendation\"`: Specific, actionable advice on how to address the issue.\n\nEnsure your JSON output strictly follows this structure without any additional commentary or text.",
|
48 |
+
"user_prompt": "Project description:\nEstablish a solar farm in Denmark.\n\nDocument with team members:\n\n## 1. Project Manager\n\n**Contract Type**: `full_time_employee`\n\n**Contract Type Justification**: The Project Manager needs to be fully integrated into the project to ensure consistent oversight and decision-making throughout the project's duration.\n\n**Explanation**:\nOversees the entire project, ensuring it stays on schedule and within budget.\n\n**Consequences**:\nWithout a project manager, the project may lack direction, leading to delays and budget overruns.\n\n**People Count**:\n1\n\n**Typical Activities**:\nCoordinate project schedules, manage budgets, liaise with stakeholders, oversee project execution, and ensure compliance with regulations.\n\n**Background Story**:\nLars Jensen, a seasoned project manager based in Aarhus, Denmark, graduated with a Master's degree in Project Management from the University of Aarhus. With over 10 years of experience in managing renewable energy projects, Lars has successfully overseen the construction of multiple wind and solar farms across Scandinavia. His strong leadership skills and ability to navigate complex regulatory environments make him an invaluable asset to the team. Lars is well-versed in the intricacies of project timelines and budgets, ensuring that the solar farm project remains on track and within financial constraints.\n\n**Equipment Needs**:\nLaptop with project management software, communication tools, and access to project documentation.\n\n**Facility Needs**:\nOffice space for meetings and collaboration with team members and stakeholders.\n\n## 2. Site Engineer\n\n**Contract Type**: `full_time_employee`\n\n**Contract Type Justification**: The Site Engineer is essential for the technical execution of the project and requires full-time commitment to address ongoing construction challenges.\n\n**Explanation**:\nResponsible for the technical aspects of the solar farm's construction and installation.\n\n**Consequences**:\nAbsence of a site engineer can result in construction errors and safety issues, jeopardizing the project.\n\n**People Count**:\n2\n\n**Typical Activities**:\nOversee construction activities, ensure compliance with engineering specifications, conduct site inspections, and collaborate with contractors.\n\n**Background Story**:\nSophie Madsen, a site engineer from Odense, Denmark, holds a Bachelor's degree in Civil Engineering from the Technical University of Denmark. With five years of hands-on experience in solar farm construction, Sophie has developed a keen eye for detail and a strong understanding of the technical requirements involved in such projects. Her familiarity with construction processes and safety regulations makes her a critical member of the team, ensuring that the solar farm is built to the highest standards.\n\n**Equipment Needs**:\nConstruction tools, safety gear, surveying equipment, and a laptop with engineering software.\n\n**Facility Needs**:\nAccess to the construction site and a temporary office or trailer for on-site coordination.\n\n## 3. Environmental Consultant\n\n**Contract Type**: `independent_contractor`\n\n**Contract Type Justification**: The Environmental Consultant can be engaged on a project basis, providing expertise as needed without the requirement for full-time employment.\n\n**Explanation**:\nAssesses the environmental impact of the solar farm and ensures compliance with regulations.\n\n**Consequences**:\nFailure to address environmental concerns can lead to legal issues and project delays.\n\n**People Count**:\n1\n\n**Typical Activities**:\nConduct environmental impact assessments, prepare reports, liaise with regulatory bodies, and recommend mitigation strategies.\n\n**Background Story**:\nFreja S\u00f8rensen, an environmental consultant based in Copenhagen, Denmark, earned her Master's degree in Environmental Science from the University of Copenhagen. With over eight years of experience in environmental assessments for renewable energy projects, Freja is adept at identifying potential ecological impacts and ensuring compliance with local regulations. Her expertise is crucial for the solar farm project, as she will help navigate environmental challenges and secure necessary permits.\n\n**Equipment Needs**:\nLaptop with environmental assessment software, access to environmental databases, and reporting tools.\n\n**Facility Needs**:\nOffice space for conducting assessments and meetings with regulatory bodies.\n\n## 4. Financial Analyst\n\n**Contract Type**: `full_time_employee`\n\n**Contract Type Justification**: The Financial Analyst plays a critical role in managing the project's finances and requires continuous involvement to ensure financial health.\n\n**Explanation**:\nEvaluates the financial feasibility of the project and manages funding and budgeting.\n\n**Consequences**:\nWithout financial oversight, the project may face funding shortages or misallocation of resources.\n\n**People Count**:\n1\n\n**Typical Activities**:\nEvaluate financial models, manage budgets, analyze funding options, and prepare financial reports.\n\n**Background Story**:\nMikkel Thomsen, a financial analyst from Aalborg, Denmark, graduated with a degree in Finance from Aalborg University. With a background in renewable energy financing, Mikkel has spent the last six years analyzing project feasibility and managing budgets for various energy initiatives. His analytical skills and financial acumen are essential for ensuring that the solar farm project remains financially viable and well-funded.\n\n**Equipment Needs**:\nLaptop with financial modeling software, access to financial databases, and reporting tools.\n\n**Facility Needs**:\nOffice space for financial analysis and meetings with project stakeholders.\n\n## 5. Procurement Specialist\n\n**Contract Type**: `independent_contractor`\n\n**Contract Type Justification**: The Procurement Specialist can be contracted for specific phases of the project, focusing on sourcing materials without needing a full-time position.\n\n**Explanation**:\nHandles the sourcing and purchasing of materials and equipment needed for the solar farm.\n\n**Consequences**:\nLack of procurement expertise can lead to delays in acquiring necessary materials, affecting the project timeline.\n\n**People Count**:\n1\n\n**Typical Activities**:\nSource and negotiate with suppliers, manage purchase orders, track inventory, and ensure timely delivery of materials.\n\n**Background Story**:\nClara Nielsen, a procurement specialist located in Esbjerg, Denmark, has a degree in Supply Chain Management from the University of Southern Denmark. With over seven years of experience in sourcing materials for construction projects, Clara has developed strong negotiation skills and a vast network of suppliers. Her role is vital for ensuring that the solar farm has all necessary materials and equipment on time and within budget.\n\n**Equipment Needs**:\nLaptop with procurement management software, access to supplier databases, and communication tools.\n\n**Facility Needs**:\nOffice space for sourcing materials and negotiating with suppliers.\n\n## 6. Construction Crew\n\n**Contract Type**: `agency_temp`\n\n**Contract Type Justification**: The Construction Crew can be sourced through an agency to provide flexibility in workforce size based on project demands and timelines.\n\n**Explanation**:\nA team of workers responsible for the physical construction of the solar farm.\n\n**Consequences**:\nInsufficient workforce can slow down construction progress and lead to missed deadlines.\n\n**People Count**:\nmin 5, max 10, depending on project scale\n\n**Typical Activities**:\nInstall solar panels, perform site preparation, conduct safety checks, and collaborate with site engineers.\n\n**Background Story**:\nThe construction crew, led by foreman Jakob Larsen, is a diverse team of skilled workers from various backgrounds in construction and renewable energy. Based in different regions of Denmark, the crew members have undergone specialized training in solar panel installation and safety protocols. Their collective experience and teamwork are essential for the successful physical construction of the solar farm, ensuring that all tasks are completed efficiently and safely.\n\n**Equipment Needs**:\nConstruction tools, safety gear, and equipment for solar panel installation.\n\n**Facility Needs**:\nAccess to the construction site and temporary facilities for the crew.\n\n## 7. Quality Assurance Specialist\n\n**Contract Type**: `part_time_employee`\n\n**Contract Type Justification**: The Quality Assurance Specialist may not need to be full-time, as their role can be fulfilled on a part-time basis during critical phases of construction.\n\n**Explanation**:\nEnsures that all work meets the required standards and specifications.\n\n**Consequences**:\nWithout quality assurance, the project may face issues with system performance and reliability.\n\n**People Count**:\n1\n\n**Typical Activities**:\nConduct quality inspections, develop quality assurance protocols, document findings, and collaborate with construction teams.\n\n**Background Story**:\nAnna Pedersen, a quality assurance specialist from Randers, Denmark, holds a degree in Quality Management from the University of Aarhus. With over four years of experience in quality control within the renewable energy sector, Anna is skilled in developing and implementing quality assurance protocols. Her attention to detail and commitment to excellence are crucial for ensuring that the solar farm meets all required standards and specifications.\n\n**Equipment Needs**:\nLaptop with quality management software, inspection tools, and documentation materials.\n\n**Facility Needs**:\nOffice space for conducting quality inspections and meetings with construction teams.\n\n## 8. Maintenance Technician\n\n**Contract Type**: `independent_contractor`\n\n**Contract Type Justification**: The Maintenance Technician can be contracted as needed post-construction, allowing for flexibility in scheduling maintenance activities.\n\n**Explanation**:\nResponsible for the ongoing maintenance and troubleshooting of the solar farm post-construction.\n\n**Consequences**:\nLack of maintenance can lead to system failures and decreased efficiency over time.\n\n**People Count**:\nmin 1, max 3, depending on project scale and operational needs\n\n**Typical Activities**:\nPerform routine maintenance, troubleshoot system issues, conduct performance assessments, and coordinate with the project team for repairs.\n\n**Background Story**:\nOliver Kristensen, a maintenance technician based in Kolding, Denmark, has a background in electrical engineering from the University of Southern Denmark. With three years of experience in maintaining solar energy systems, Oliver is adept at troubleshooting and performing routine maintenance to ensure optimal performance. His role post-construction is vital for the long-term efficiency and reliability of the solar farm.\n\n**Equipment Needs**:\nMaintenance tools, diagnostic equipment, and a laptop for performance assessments.\n\n**Facility Needs**:\nAccess to the solar farm for maintenance activities and a workshop for repairs."
|
49 |
+
}
|
src/team/test_data/solarfarm_team_without_review.md
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## Role 1 - Project Manager
|
2 |
+
|
3 |
+
**Contract Type**: full_time_employee
|
4 |
+
|
5 |
+
**Contract Type Justification**: The Project Manager requires consistent involvement and oversight throughout the project lifecycle, making a full-time employee the most suitable option.
|
6 |
+
|
7 |
+
**Explanation**:
|
8 |
+
Oversees all aspects of the solar farm project, ensuring it stays on schedule and within budget. Manages communication between different teams and stakeholders.
|
9 |
+
|
10 |
+
**Consequences**:
|
11 |
+
Project delays, cost overruns, and miscommunication between teams.
|
12 |
+
|
13 |
+
**People Count**:
|
14 |
+
1
|
15 |
+
|
16 |
+
**Typical Activities**:
|
17 |
+
Developing project plans, managing budgets, coordinating teams, communicating with stakeholders, and ensuring project milestones are met on time and within budget.
|
18 |
+
|
19 |
+
**Background Story**:
|
20 |
+
Astrid Nielsen, a seasoned project manager from Copenhagen, has a proven track record of successfully delivering large-scale infrastructure projects across Denmark. With a Master's degree in Engineering Management from the Technical University of Denmark and over 15 years of experience in the construction and renewable energy sectors, Astrid possesses a comprehensive understanding of project lifecycles, risk management, and stakeholder communication. She has previously managed the construction of a large wind farm in Jutland, giving her direct experience with the challenges and opportunities of renewable energy projects in the Danish landscape. Astrid's meticulous planning, proactive problem-solving skills, and ability to foster collaboration make her the ideal candidate to lead the solar farm project.
|
21 |
+
|
22 |
+
**Equipment Needs**:
|
23 |
+
Laptop, project management software, communication tools (phone, video conferencing).
|
24 |
+
|
25 |
+
**Facility Needs**:
|
26 |
+
Office space, meeting rooms.
|
27 |
+
|
28 |
+
---
|
29 |
+
|
30 |
+
## Role 2 - Solar Energy Engineer
|
31 |
+
|
32 |
+
**Contract Type**: full_time_employee
|
33 |
+
|
34 |
+
**Contract Type Justification**: The Solar Energy Engineer's role is critical for the entire project and requires in-depth knowledge of the project specifics. A full-time employee ensures consistent involvement and expertise.
|
35 |
+
|
36 |
+
**Explanation**:
|
37 |
+
Designs the solar farm layout, selects appropriate solar panel technology, and optimizes energy production based on the Danish climate and grid infrastructure.
|
38 |
+
|
39 |
+
**Consequences**:
|
40 |
+
Inefficient energy production, suboptimal panel selection, and potential grid integration issues.
|
41 |
+
|
42 |
+
**People Count**:
|
43 |
+
1
|
44 |
+
|
45 |
+
**Typical Activities**:
|
46 |
+
Designing solar farm layouts, selecting solar panel technology, optimizing energy production, conducting energy yield assessments, and ensuring grid integration compliance.
|
47 |
+
|
48 |
+
**Background Story**:
|
49 |
+
Erik Hansen, a highly skilled Solar Energy Engineer hailing from Aarhus, possesses a deep understanding of photovoltaic systems and renewable energy technologies. He holds a PhD in Solar Energy Engineering from Aarhus University and has spent the last decade designing and optimizing solar energy systems for various applications, including residential, commercial, and utility-scale projects. Erik's expertise lies in solar panel technology selection, energy yield modeling, and grid integration strategies. He is intimately familiar with the Danish climate and grid infrastructure, having previously worked on several solar energy projects in the region. Erik's analytical skills, technical expertise, and passion for renewable energy make him an invaluable asset to the solar farm project.
|
50 |
+
|
51 |
+
**Equipment Needs**:
|
52 |
+
High-performance computer, specialized solar design software (e.g., PVsyst, Helioscope), data analysis tools.
|
53 |
+
|
54 |
+
**Facility Needs**:
|
55 |
+
Office space, access to meteorological data and grid information.
|
56 |
+
|
57 |
+
---
|
58 |
+
|
59 |
+
## Role 3 - Environmental Impact Assessor
|
60 |
+
|
61 |
+
**Contract Type**: independent_contractor
|
62 |
+
|
63 |
+
**Contract Type Justification**: The Environmental Impact Assessor's role is project-specific and requires specialized expertise. An independent contractor can provide this expertise on a project basis.
|
64 |
+
|
65 |
+
**Explanation**:
|
66 |
+
Conducts a thorough environmental impact assessment to identify and mitigate potential negative effects on the local ecosystem, ensuring compliance with Danish environmental regulations.
|
67 |
+
|
68 |
+
**Consequences**:
|
69 |
+
Regulatory delays, potential environmental damage, and negative public perception.
|
70 |
+
|
71 |
+
**People Count**:
|
72 |
+
1
|
73 |
+
|
74 |
+
**Typical Activities**:
|
75 |
+
Conducting environmental impact assessments, identifying potential environmental impacts, developing mitigation strategies, preparing environmental reports, and ensuring compliance with environmental regulations.
|
76 |
+
|
77 |
+
**Background Story**:
|
78 |
+
Birgitte Christensen, an independent Environmental Impact Assessor based in Odense, has extensive experience in evaluating the environmental consequences of development projects in Denmark. With a Master's degree in Environmental Science from the University of Southern Denmark and over 8 years of experience in environmental consulting, Birgitte has conducted numerous environmental impact assessments for a wide range of projects, including wind farms, infrastructure developments, and industrial facilities. She is intimately familiar with Danish environmental regulations and best practices for mitigating environmental impacts. Birgitte's thoroughness, attention to detail, and commitment to environmental sustainability make her the perfect choice to assess the environmental impact of the solar farm project.
|
79 |
+
|
80 |
+
**Equipment Needs**:
|
81 |
+
Field equipment for environmental sampling and monitoring, data analysis software, report writing software.
|
82 |
+
|
83 |
+
**Facility Needs**:
|
84 |
+
Access to environmental databases, laboratory access (if required for sample analysis), transportation to site.
|
85 |
+
|
86 |
+
---
|
87 |
+
|
88 |
+
## Role 4 - Land Acquisition Specialist
|
89 |
+
|
90 |
+
**Contract Type**: independent_contractor
|
91 |
+
|
92 |
+
**Contract Type Justification**: The Land Acquisition Specialist's role is specific to the initial phase of the project. An independent contractor with local expertise is well-suited for this task.
|
93 |
+
|
94 |
+
**Explanation**:
|
95 |
+
Identifies and secures suitable land for the solar farm, negotiating purchase or lease agreements with landowners, and navigating Danish land use regulations.
|
96 |
+
|
97 |
+
**Consequences**:
|
98 |
+
Difficulty finding suitable land, delays in project commencement, and increased project costs.
|
99 |
+
|
100 |
+
**People Count**:
|
101 |
+
1
|
102 |
+
|
103 |
+
**Typical Activities**:
|
104 |
+
Identifying suitable land for the solar farm, negotiating purchase or lease agreements with landowners, conducting due diligence on land parcels, and navigating Danish land use regulations.
|
105 |
+
|
106 |
+
**Background Story**:
|
107 |
+
Jens Petersen, a seasoned Land Acquisition Specialist from Aalborg, has a proven track record of successfully securing land for development projects across Denmark. With over 12 years of experience in real estate and land acquisition, Jens possesses a deep understanding of Danish land use regulations, property valuation, and negotiation strategies. He has previously worked on several renewable energy projects, including wind farms and biomass plants, giving him direct experience with the challenges and opportunities of land acquisition in the renewable energy sector. Jens's local knowledge, negotiation skills, and ability to build relationships with landowners make him the ideal candidate to secure suitable land for the solar farm project.
|
108 |
+
|
109 |
+
**Equipment Needs**:
|
110 |
+
Transportation, access to land registry databases, GIS software, negotiation tools.
|
111 |
+
|
112 |
+
**Facility Needs**:
|
113 |
+
Office space (potentially remote), access to legal resources.
|
114 |
+
|
115 |
+
---
|
116 |
+
|
117 |
+
## Role 5 - Grid Connection Specialist
|
118 |
+
|
119 |
+
**Contract Type**: independent_contractor
|
120 |
+
|
121 |
+
**Contract Type Justification**: The Grid Connection Specialist requires specialized knowledge of the Danish power grid and regulations. An independent contractor with relevant experience is a good fit.
|
122 |
+
|
123 |
+
**Explanation**:
|
124 |
+
Manages the interconnection of the solar farm to the Danish power grid, ensuring compliance with grid operator requirements and optimizing energy delivery.
|
125 |
+
|
126 |
+
**Consequences**:
|
127 |
+
Delays in grid connection, potential energy losses, and non-compliance with grid regulations.
|
128 |
+
|
129 |
+
**People Count**:
|
130 |
+
1
|
131 |
+
|
132 |
+
**Typical Activities**:
|
133 |
+
Managing the interconnection of the solar farm to the Danish power grid, ensuring compliance with grid operator requirements, optimizing energy delivery, and conducting power system studies.
|
134 |
+
|
135 |
+
**Background Story**:
|
136 |
+
Lars Jensen, an independent Grid Connection Specialist based in Esbjerg, has extensive experience in managing the interconnection of renewable energy projects to the Danish power grid. With a Master's degree in Electrical Engineering from the University of Aalborg and over 10 years of experience in the power industry, Lars possesses a deep understanding of grid operator requirements, grid connection procedures, and power system analysis. He has previously worked on several large-scale renewable energy projects, including wind farms and solar power plants, giving him direct experience with the challenges and opportunities of grid integration in Denmark. Lars's technical expertise, problem-solving skills, and ability to navigate complex grid regulations make him an invaluable asset to the solar farm project.
|
137 |
+
|
138 |
+
**Equipment Needs**:
|
139 |
+
Specialized software for power system studies (e.g., PSS/E, DigSilent), communication equipment for liaising with grid operators.
|
140 |
+
|
141 |
+
**Facility Needs**:
|
142 |
+
Office space, access to grid connection standards and regulations.
|
143 |
+
|
144 |
+
---
|
145 |
+
|
146 |
+
## Role 6 - Construction Supervisor
|
147 |
+
|
148 |
+
**Contract Type**: full_time_employee
|
149 |
+
|
150 |
+
**Contract Type Justification**: The Construction Supervisor needs to be on-site throughout the construction phase, ensuring quality and safety. A full-time employee provides consistent oversight.
|
151 |
+
|
152 |
+
**Explanation**:
|
153 |
+
Oversees the physical construction of the solar farm, ensuring adherence to engineering specifications, safety regulations, and quality standards.
|
154 |
+
|
155 |
+
**Consequences**:
|
156 |
+
Poor construction quality, safety hazards, and potential structural failures.
|
157 |
+
|
158 |
+
**People Count**:
|
159 |
+
1
|
160 |
+
|
161 |
+
**Typical Activities**:
|
162 |
+
Overseeing the physical construction of the solar farm, ensuring adherence to engineering specifications, enforcing safety regulations, and maintaining quality standards.
|
163 |
+
|
164 |
+
**Background Story**:
|
165 |
+
Helle Mortensen, a highly experienced Construction Supervisor from Roskilde, has a proven track record of successfully overseeing the construction of large-scale infrastructure projects across Denmark. With a Bachelor's degree in Civil Engineering from the Technical University of Denmark and over 18 years of experience in the construction industry, Helle possesses a comprehensive understanding of construction methods, safety regulations, and quality control procedures. She has previously supervised the construction of several renewable energy projects, including wind farms and solar power plants, giving her direct experience with the challenges and opportunities of construction in the renewable energy sector. Helle's leadership skills, attention to detail, and commitment to safety make her the ideal candidate to oversee the construction of the solar farm.
|
166 |
+
|
167 |
+
**Equipment Needs**:
|
168 |
+
Personal Protective Equipment (PPE), communication devices (radio, phone), inspection tools, access to engineering drawings and specifications.
|
169 |
+
|
170 |
+
**Facility Needs**:
|
171 |
+
On-site office/trailer, access to construction site, safety equipment storage.
|
172 |
+
|
173 |
+
---
|
174 |
+
|
175 |
+
## Role 7 - Legal Counsel (Danish Law)
|
176 |
+
|
177 |
+
**Contract Type**: independent_contractor
|
178 |
+
|
179 |
+
**Contract Type Justification**: Legal Counsel is needed for specific legal tasks and advice. An independent contractor specializing in Danish law can provide this expertise as needed.
|
180 |
+
|
181 |
+
**Explanation**:
|
182 |
+
Provides legal expertise on Danish regulations related to renewable energy, land use, environmental compliance, and grid connection agreements.
|
183 |
+
|
184 |
+
**Consequences**:
|
185 |
+
Legal challenges, regulatory fines, and project delays due to non-compliance.
|
186 |
+
|
187 |
+
**People Count**:
|
188 |
+
min 1, max 2, depending on the complexity of the legal and regulatory landscape. This is measured by the number of unique legal challenges encountered during the project.
|
189 |
+
|
190 |
+
**Typical Activities**:
|
191 |
+
Providing legal expertise on Danish regulations related to renewable energy, land use, environmental compliance, and grid connection agreements, drafting and reviewing legal documents, and representing the project in legal proceedings.
|
192 |
+
|
193 |
+
**Background Story**:
|
194 |
+
Peter Olsen, a highly regarded Legal Counsel specializing in Danish law, operates his independent practice from Copenhagen. With a Juris Doctor degree from the University of Copenhagen and over 15 years of experience in legal practice, Peter possesses a deep understanding of Danish regulations related to renewable energy, land use, environmental compliance, and grid connection agreements. He has advised numerous clients on renewable energy projects, providing legal expertise on a wide range of issues, including permitting, financing, and contract negotiation. Peter's legal acumen, attention to detail, and commitment to client service make him the perfect choice to provide legal counsel on the solar farm project.
|
195 |
+
|
196 |
+
**Equipment Needs**:
|
197 |
+
Legal research databases, document drafting software, communication tools.
|
198 |
+
|
199 |
+
**Facility Needs**:
|
200 |
+
Office space, access to legal library/resources.
|
201 |
+
|
202 |
+
---
|
203 |
+
|
204 |
+
## Role 8 - Financial Analyst
|
205 |
+
|
206 |
+
**Contract Type**: full_time_employee
|
207 |
+
|
208 |
+
**Contract Type Justification**: The Financial Analyst needs to be involved throughout the project lifecycle, managing finances and securing funding. A full-time employee ensures consistent financial oversight.
|
209 |
+
|
210 |
+
**Explanation**:
|
211 |
+
Develops and manages the project's financial model, secures funding, and monitors financial performance throughout the project lifecycle.
|
212 |
+
|
213 |
+
**Consequences**:
|
214 |
+
Difficulty securing funding, cost overruns, and potential financial losses.
|
215 |
+
|
216 |
+
**People Count**:
|
217 |
+
min 1, max 2, depending on the size of the solar farm and the complexity of the financing structure. This is measured by the total capital expenditure of the project.
|
218 |
+
|
219 |
+
**Typical Activities**:
|
220 |
+
Developing and managing the project's financial model, securing funding, monitoring financial performance, and preparing financial reports.
|
221 |
+
|
222 |
+
**Background Story**:
|
223 |
+
Rasmus Christensen, a seasoned Financial Analyst from Odense, has a proven track record of successfully managing the financial aspects of large-scale infrastructure projects across Denmark. With a Master's degree in Finance from Copenhagen Business School and over 12 years of experience in the financial industry, Rasmus possesses a comprehensive understanding of financial modeling, project finance, and investment analysis. He has previously worked on several renewable energy projects, including wind farms and solar power plants, giving him direct experience with the challenges and opportunities of financing renewable energy projects in Denmark. Rasmus's analytical skills, financial expertise, and ability to secure funding make him an invaluable asset to the solar farm project.
|
224 |
+
|
225 |
+
**Equipment Needs**:
|
226 |
+
Financial modeling software, data analysis tools, communication tools for investor relations.
|
227 |
+
|
228 |
+
**Facility Needs**:
|
229 |
+
Office space, access to financial databases.
|