unique_toolkit 1.42.9__py3-none-any.whl → 1.43.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. unique_toolkit/_common/experimental/write_up_agent/README.md +848 -0
  2. unique_toolkit/_common/experimental/write_up_agent/__init__.py +22 -0
  3. unique_toolkit/_common/experimental/write_up_agent/agent.py +170 -0
  4. unique_toolkit/_common/experimental/write_up_agent/config.py +42 -0
  5. unique_toolkit/_common/experimental/write_up_agent/examples/data.csv +13 -0
  6. unique_toolkit/_common/experimental/write_up_agent/examples/example_usage.py +78 -0
  7. unique_toolkit/_common/experimental/write_up_agent/examples/report.md +154 -0
  8. unique_toolkit/_common/experimental/write_up_agent/schemas.py +36 -0
  9. unique_toolkit/_common/experimental/write_up_agent/services/__init__.py +13 -0
  10. unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/__init__.py +19 -0
  11. unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/exceptions.py +29 -0
  12. unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/service.py +150 -0
  13. unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/utils.py +130 -0
  14. unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/__init__.py +27 -0
  15. unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/config.py +56 -0
  16. unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/exceptions.py +79 -0
  17. unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/config.py +34 -0
  18. unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/system_prompt.j2 +15 -0
  19. unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/user_prompt.j2 +21 -0
  20. unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/service.py +369 -0
  21. unique_toolkit/_common/experimental/write_up_agent/services/template_handler/__init__.py +29 -0
  22. unique_toolkit/_common/experimental/write_up_agent/services/template_handler/default_template.j2 +37 -0
  23. unique_toolkit/_common/experimental/write_up_agent/services/template_handler/exceptions.py +39 -0
  24. unique_toolkit/_common/experimental/write_up_agent/services/template_handler/service.py +191 -0
  25. unique_toolkit/_common/experimental/write_up_agent/services/template_handler/utils.py +182 -0
  26. unique_toolkit/_common/experimental/write_up_agent/utils.py +24 -0
  27. {unique_toolkit-1.42.9.dist-info → unique_toolkit-1.43.0.dist-info}/METADATA +4 -1
  28. {unique_toolkit-1.42.9.dist-info → unique_toolkit-1.43.0.dist-info}/RECORD +30 -4
  29. {unique_toolkit-1.42.9.dist-info → unique_toolkit-1.43.0.dist-info}/LICENSE +0 -0
  30. {unique_toolkit-1.42.9.dist-info → unique_toolkit-1.43.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,22 @@
1
+ """
2
+ Write-Up Agent: Template-driven DataFrame summarization and report generation.
3
+ """
4
+
5
+ from unique_toolkit._common.experimental.write_up_agent.agent import WriteUpAgent
6
+ from unique_toolkit._common.experimental.write_up_agent.config import (
7
+ WriteUpAgentConfig,
8
+ )
9
+ from unique_toolkit._common.experimental.write_up_agent.schemas import (
10
+ GroupData,
11
+ ProcessedGroup,
12
+ )
13
+
14
+ __all__ = [
15
+ # Main agent
16
+ "WriteUpAgent",
17
+ # Configuration
18
+ "WriteUpAgentConfig",
19
+ # Data schemas
20
+ "GroupData",
21
+ "ProcessedGroup",
22
+ ]
@@ -0,0 +1,170 @@
1
+ """
2
+ Write-Up Agent - Main pipeline orchestrator.
3
+ """
4
+
5
+ import logging
6
+
7
+ import pandas as pd
8
+
9
+ from unique_toolkit._common.experimental.write_up_agent.config import (
10
+ WriteUpAgentConfig,
11
+ )
12
+ from unique_toolkit._common.experimental.write_up_agent.schemas import GroupData
13
+ from unique_toolkit._common.experimental.write_up_agent.services.dataframe_handler import (
14
+ DataFrameHandler,
15
+ )
16
+ from unique_toolkit._common.experimental.write_up_agent.services.dataframe_handler.exceptions import (
17
+ DataFrameGroupingError,
18
+ DataFrameHandlerError,
19
+ DataFrameProcessingError,
20
+ DataFrameValidationError,
21
+ )
22
+ from unique_toolkit._common.experimental.write_up_agent.services.generation_handler import (
23
+ GenerationHandler,
24
+ GenerationHandlerError,
25
+ )
26
+ from unique_toolkit._common.experimental.write_up_agent.services.template_handler import (
27
+ TemplateHandler,
28
+ )
29
+ from unique_toolkit._common.experimental.write_up_agent.services.template_handler.exceptions import (
30
+ ColumnExtractionError,
31
+ TemplateHandlerError,
32
+ TemplateParsingError,
33
+ TemplateRenderingError,
34
+ TemplateStructureError,
35
+ )
36
+ from unique_toolkit.language_model.service import LanguageModelService
37
+
38
+ _LOGGER = logging.getLogger(__name__)
39
+
40
+
41
+ class WriteUpAgent:
42
+ """
43
+ Main pipeline orchestrator for DataFrame summarization.
44
+
45
+ Orchestrates the complete pipeline:
46
+ 1. Extract template info (grouping + columns)
47
+ 2. Validate DataFrame
48
+ 3. Create groups
49
+ 4. Render each group
50
+ 5. Process with LLM
51
+ 6. Return results
52
+ """
53
+
54
+ def __init__(self, config: WriteUpAgentConfig):
55
+ """
56
+ Initialize WriteUpAgent.
57
+
58
+ Args:
59
+ config: Configuration with template and settings
60
+ """
61
+ self._config = config
62
+ self._template_handler = TemplateHandler(config.template)
63
+ self._dataframe_handler = DataFrameHandler()
64
+
65
+ # Create generation handler with injected renderer
66
+ def renderer(group_data: GroupData) -> str:
67
+ return self._template_handler.render_group(group_data)
68
+
69
+ # TODO [UN-16142]: Find a better way to inject the renderer
70
+ self._generation_handler = GenerationHandler(
71
+ self._config.generation_handler_config, renderer
72
+ )
73
+
74
+ def process(self, df: pd.DataFrame, llm_service: LanguageModelService) -> str:
75
+ """
76
+ Execute complete pipeline and generate final report.
77
+
78
+ Args:
79
+ df: pandas DataFrame to process
80
+ llm_service: LanguageModelService to use for generating summaries
81
+
82
+ Returns:
83
+ Final markdown report as a single string with all groups processed
84
+
85
+ Raises:
86
+ Various handler exceptions if processing fails
87
+
88
+ Example:
89
+ >>> config = WriteUpAgentConfig(template="...", max_rows_per_group=10)
90
+ >>> agent = WriteUpAgent(config)
91
+ >>> report = agent.process(df)
92
+ >>> print(report)
93
+ """
94
+ # TODO [UN-16142]: Add error handling for each step separately
95
+ try:
96
+ # Step 1: Extract template structure
97
+ _LOGGER.info("Extracting template structure...")
98
+ grouping_column = self._template_handler.get_grouping_column()
99
+ selected_columns = self._template_handler.get_selected_columns()
100
+ _LOGGER.info(f"Detected grouping column: {grouping_column}")
101
+ _LOGGER.info(f"Detected data columns: {selected_columns}")
102
+
103
+ # Step 2: Validate DataFrame
104
+ _LOGGER.info("Validating DataFrame columns...")
105
+ self._dataframe_handler.validate_columns(
106
+ df, grouping_column, selected_columns
107
+ )
108
+
109
+ # Step 3: Create groups
110
+ _LOGGER.info("Creating groups from DataFrame...")
111
+ groups = self._dataframe_handler.create_groups(
112
+ df, grouping_column, selected_columns
113
+ )
114
+ _LOGGER.info(f"Created {len(groups)} groups")
115
+
116
+ # Step 4: Process groups with GenerationHandler
117
+ _LOGGER.info("Processing groups with GenerationHandler...")
118
+ processed_groups = self._generation_handler.process_groups(
119
+ groups, grouping_column, llm_service
120
+ )
121
+ _LOGGER.info(f"Generation complete for {len(processed_groups)} groups")
122
+
123
+ # Step 5: Render final report with LLM responses
124
+ _LOGGER.info("Rendering final report...")
125
+
126
+ final_report = self._template_handler.render_all_groups(processed_groups)
127
+
128
+ _LOGGER.info(f"Report generated ({len(final_report)} characters)")
129
+
130
+ return final_report
131
+
132
+ except TemplateParsingError as e:
133
+ _LOGGER.error(f"Template parsing failed: {e}")
134
+ raise
135
+
136
+ except TemplateStructureError as e:
137
+ _LOGGER.error(f"Template structure invalid: {e}")
138
+ raise
139
+
140
+ except ColumnExtractionError as e:
141
+ _LOGGER.error(f"Column extraction failed: {e}")
142
+ raise
143
+
144
+ except DataFrameValidationError as e:
145
+ _LOGGER.error(f"DataFrame validation failed: {e}")
146
+ raise
147
+
148
+ except DataFrameGroupingError as e:
149
+ _LOGGER.error(f"DataFrame grouping failed: {e}")
150
+ raise
151
+
152
+ except DataFrameProcessingError as e:
153
+ _LOGGER.error(f"DataFrame processing failed: {e}")
154
+ raise
155
+
156
+ except GenerationHandlerError as e:
157
+ _LOGGER.error(f"Generation failed: {e}")
158
+ raise
159
+
160
+ except TemplateRenderingError as e:
161
+ _LOGGER.error(f"Final rendering failed: {e}")
162
+ raise
163
+
164
+ except (TemplateHandlerError, DataFrameHandlerError) as e:
165
+ _LOGGER.error(f"Handler error: {e}")
166
+ raise
167
+
168
+ except Exception as e:
169
+ _LOGGER.error(f"Unexpected error: {e}", exc_info=True)
170
+ raise
@@ -0,0 +1,42 @@
1
+ from pydantic import BaseModel, Field, field_validator
2
+
3
+ from unique_toolkit._common.experimental.write_up_agent.services.generation_handler.config import (
4
+ GenerationHandlerConfig,
5
+ )
6
+ from unique_toolkit._common.experimental.write_up_agent.services.template_handler import (
7
+ default_jinja_template_loader,
8
+ )
9
+ from unique_toolkit._common.pydantic_helpers import get_configuration_dict
10
+
11
+
12
+ class WriteUpAgentConfig(BaseModel):
13
+ """Configuration for the Write-Up Agent that generates summaries from DataFrame data.
14
+
15
+ The agent uses a Jinja template as the single source of truth for data structure.
16
+ The template is parsed to automatically detect grouping columns and data references.
17
+ """
18
+
19
+ model_config = get_configuration_dict()
20
+
21
+ # Template Configuration (single source of truth)
22
+ template: str = Field(
23
+ default_factory=default_jinja_template_loader,
24
+ description=(
25
+ "Jinja template string that defines the structure of the summary. "
26
+ "The template is parsed to automatically detect grouping columns and data references. "
27
+ "If not provided, loads the default Q&A template. "
28
+ "Example: '{% for g in groups %}## {{ g.section }}{% endfor %}'"
29
+ ),
30
+ )
31
+
32
+ generation_handler_config: GenerationHandlerConfig = Field(
33
+ default_factory=GenerationHandlerConfig,
34
+ description="Configuration for the generation handler.",
35
+ )
36
+
37
+ @field_validator("template")
38
+ @classmethod
39
+ def validate_template_not_empty(cls, v: str) -> str:
40
+ if not v.strip():
41
+ raise ValueError("Template must not be empty")
42
+ return v
@@ -0,0 +1,13 @@
1
+ section,question,answer
2
+ Introduction,What is the Write-Up Agent?,The Write-Up Agent is a tool that automatically generates summaries from structured DataFrame data using LLM technology.
3
+ Introduction,Who should use this tool?,Data scientists and analysts who need to convert tabular data into readable reports.
4
+ Introduction,What are the key benefits?,Automated report generation with customizable templates and intelligent summarization.
5
+ Methods,How does the agent process data?,The agent groups data by sections and generates summaries for each group using LLM calls with adaptive batching.
6
+ Methods,What is adaptive batching?,A technique that splits large groups into smaller batches to fit within token limits while maintaining context.
7
+ Methods,Can I customize the output format?,Yes! You can provide custom Jinja templates to control the structure and style of the generated report.
8
+ Results,What level of accuracy can I expect?,The agent leverages state-of-the-art LLMs to produce accurate and contextually relevant summaries.
9
+ Results,How fast is the processing?,Processing speed depends on the LLM provider and the size of your dataset. Batching helps optimize performance.
10
+ Results,Can it handle large datasets?,Yes! The agent automatically batches large groups to handle datasets of any size efficiently.
11
+ Conclusion,Is this production-ready?,Yes! The agent includes robust error handling and type-safe operations using Pydantic schemas.
12
+ Conclusion,Where can I find more examples?,Check the examples directory for additional use cases and custom template examples.
13
+
@@ -0,0 +1,78 @@
1
+ """
2
+ Example: Using the Write-Up Agent to generate summaries from DataFrame data.
3
+ """
4
+
5
+ # TODO [UN-16142]: Add example usage in tutorial instead of here
6
+
7
+ import logging
8
+ from pathlib import Path
9
+
10
+ import pandas as pd
11
+
12
+ from unique_toolkit._common.experimental.write_up_agent import (
13
+ WriteUpAgent,
14
+ WriteUpAgentConfig,
15
+ )
16
+ from unique_toolkit._common.experimental.write_up_agent.services.generation_handler.config import (
17
+ GenerationHandlerConfig,
18
+ )
19
+ from unique_toolkit.app.unique_settings import UniqueSettings
20
+ from unique_toolkit.language_model.service import LanguageModelService
21
+
22
+ logging.basicConfig(level=logging.DEBUG)
23
+
24
+ # Setup paths
25
+ current_dir = Path(__file__).parent
26
+ env_path = current_dir / "unique.env"
27
+ data_path = current_dir / "data.csv"
28
+
29
+ # Initialize SDK with your API keys
30
+ _SETTINGS = UniqueSettings.from_env(env_file=env_path)
31
+ _SETTINGS.init_sdk()
32
+
33
+ # Configure the Write-Up Agent
34
+ # Using default configuration which expects: section, question, answer columns
35
+ write_up_agent_config = WriteUpAgentConfig(
36
+ generation_handler_config=GenerationHandlerConfig(
37
+ # Optional: Customize generation settings
38
+ # max_rows_per_batch=20, # Max rows per batch (default: 20)
39
+ # max_tokens_per_batch=4000, # Max tokens per batch (default: 4000)
40
+ # common_instruction="You are a technical writer...", # Custom system prompt
41
+ # group_specific_instructions={
42
+ # # IMPORTANT: Both column and value must be in snake_case
43
+ # # DataFrame: Section="Introduction" → Key: "section:introduction"
44
+ # "section:introduction": "Be welcoming and engaging",
45
+ # "section:methods": "Be precise and technical"
46
+ # }
47
+ )
48
+ )
49
+
50
+ # Initialize the agent with LLM service
51
+ write_up_agent = WriteUpAgent(
52
+ config=write_up_agent_config,
53
+ )
54
+
55
+ # Load your DataFrame
56
+ # IMPORTANT: DataFrame must have columns: section, question, answer (otherwise adapt the template)
57
+ df = pd.read_csv(data_path)
58
+
59
+ print(f"Processing {len(df)} rows across {df['section'].nunique()} sections...")
60
+ print(f"Columns in DataFrame: {list(df.columns)}")
61
+ print()
62
+
63
+ llm_service = LanguageModelService.from_settings(_SETTINGS)
64
+
65
+ # Generate the report
66
+ report = write_up_agent.process(df, llm_service=llm_service)
67
+
68
+ # Display the result
69
+ print("=" * 80)
70
+ print("GENERATED REPORT")
71
+ print("=" * 80)
72
+ print(report)
73
+
74
+ # Optional: Save to file
75
+ output_path = current_dir / "report.md"
76
+ output_path.write_text(report)
77
+ print()
78
+ print(f"Report saved to: {output_path}")
@@ -0,0 +1,154 @@
1
+
2
+ # Personal
3
+
4
+
5
+
6
+ 📌 **Core Information**
7
+ - **Name**: Purushottam Sharma
8
+ - **Prename(s)**: Purushottam
9
+ - **Date of Birth**: 03.04.1986
10
+ - **Place of Birth**: Mumbai, India
11
+ - **Verification**: All personal details are confirmed via a valid Indian passport (DS435645G04), issued on 05.10.2021 and expiring on 05.10.2031. The document verifies name, date of birth, nationality, and place of origin. No additional documentation is required.
12
+
13
+ 📌 **Marital Status**
14
+ - **Status**: Unmarried
15
+ - **Verification**: No evidence of marriage (e.g., marriage certificate, joint tax filings, or public records) was found. No indirect indicators, such as cohabitation records or joint assets, suggest a marital relationship. The absence of evidence over time supports the conclusion of being unmarried.
16
+
17
+ 📌 **Nationality**
18
+ - **Nationality**: Indian (IND)
19
+ - **Verification**: The Indian passport (DS435645G04) serves as the primary proof of nationality. No evidence of dual or additional citizenships was identified.
20
+
21
+ 📌 **Political Exposure**
22
+ - No indications of managerial roles, political activity, or Politically Exposed Person (PEP) status were found in any category.
23
+
24
+ All personal details are verified, complete, and supported by official documentation.
25
+
26
+
27
+ ---
28
+
29
+
30
+ # Employment
31
+
32
+
33
+
34
+ Mr. Purushottam R. Sharma’s employment history and income have been thoroughly analyzed, confirming a credible and realistic career trajectory with appropriate compensation for his current role as Chief Investment Officer (CIO) at Al Noor Capital Investments in Dubai’s DIFC.
35
+
36
+ ### Career Trajectory and Compensation
37
+ - **Career Progression**: Mr. Sharma advanced from Financial Analyst (2005–2010) to Portfolio Manager (2010–2020) at Morgan Stanley, followed by his current role as CIO (2025–present). His career reflects steady growth in responsibility and compensation.
38
+ - **Current Compensation**: His annual gross income of AED 700,000 includes a basic salary (AED 540,000), housing allowance (AED 100,000), transport (AED 24,000), and other benefits (AED 36,000). This package aligns with DIFC market benchmarks for mid-tier CIO roles, which range from AED 650,000 to 1,000,000 annually.
39
+ - **Inflation Adjustment**: The current salary is consistent with inflation-adjusted benchmarks for his career progression, reflecting a 110% increase from his estimated entry-level salary in 2005 and a slight increase from his mid-career earnings as a Portfolio Manager.
40
+
41
+ ### Contextual Factors Influencing Compensation
42
+ 1. **Company Size**: Al Noor Capital Investments is a mid-sized, boutique asset management firm, which typically offers lower compensation compared to larger, global institutions.
43
+ 2. **Tenure in Role**: Mr. Sharma’s appointment as CIO began in January 2025, and his salary likely reflects an entry-level package for the position, with potential for future increases based on performance.
44
+ 3. **Marital Status and Lifestyle**: As a single individual with no dependents, Mr. Sharma’s cost of living is lower, reducing the need for additional family-related benefits. His expense-to-income ratio is approximately 43%, allowing for a high savings rate of ~57%.
45
+ 4. **Employment Gap (2020–2025)**: A five-year gap in his career may have influenced his initial compensation upon re-entering the workforce, though no red flags or misconduct were identified during this period.
46
+
47
+ ### Cost of Living and Savings
48
+ - **Living Costs**: Estimated annual expenses in Dubai range from AED 240,000 to 300,000, including housing, transportation, utilities, and leisure. Mr. Sharma’s housing and transport allowances adequately cover these costs.
49
+ - **Savings Capacity**: With zero personal income tax in the UAE and a high savings rate, Mr. Sharma’s disposable income (~AED 400,000 annually) supports a comfortable lifestyle and significant financial security.
50
+
51
+ ### Verification and Credibility
52
+ - **Income Verification**: Employment certificates, payslips, payroll summaries, and tax residency certificates have been provided and verified.
53
+ - **Market Alignment**: The salary is within 15–20% of the median for similar roles in the DIFC, indicating alignment with market standards.
54
+ - **No Red Flags**: No evidence of political exposure, financial stress, or discrepancies in documentation was found.
55
+
56
+ ### Conclusion
57
+ Mr. Sharma’s income is realistic and justified based on his career progression, the size and market position of his current employer, and his personal circumstances. The compensation package is competitive within the DIFC market and supports a high standard of living with substantial savings potential. The five-year employment gap (2020–2025) may have influenced his initial salary, but no further adjustments or concerns are warranted.
58
+
59
+
60
+ ---
61
+
62
+
63
+ # Inheritance And Gifts
64
+
65
+
66
+
67
+ Mr. Purushottam R. Sharma received a single inheritance of USD 15,000,000 from his father, Mr. Rajendra V. Sharma, as the sole beneficiary under a Last Will and Testament dated March 12, 2003. The inheritance was distributed on June 30, 2005, following probate approval in Marin County, California (Case No. PR-2005-11876). The distribution was supported by primary legal documents, including the will, probate court order, executor’s certificate of distribution, and bank transfer records.
68
+
69
+ ### Composition of the Inheritance
70
+ The inheritance, totaling USD 15,000,000, was derived from the following assets:
71
+ - **Cash and Certificates of Deposit (CDs):** $6,200,000
72
+ - **Marketable Securities:** $7,800,000 (liquidated prior to distribution)
73
+ - **Net Proceeds from Real Property Sales:** $2,100,000
74
+ - **Life Insurance Proceeds:** $450,000 (payable to the estate)
75
+
76
+ After deducting debts, taxes, and administrative costs totaling $1,550,000, the net distributable estate amounted to $15,000,000.
77
+
78
+ ### Relationship and Reason for Inheritance
79
+ The gifter, Mr. Rajendra V. Sharma, was the father of the beneficiary, Mr. Purushottam R. Sharma. This relationship is explicitly stated in the will and confirmed by probate documents. The inheritance was part of a planned familial wealth transfer, with no additional conditions or motives beyond standard estate planning.
80
+
81
+ ### Proof of Inheritance
82
+ The inheritance is fully documented and legally validated through:
83
+ - **Last Will and Testament (2003):** Declares Purushottam as the sole beneficiary.
84
+ - **Probate Court Order (February 14, 2005):** Confirms the will’s validity and authorizes distribution.
85
+ - **Executor’s Certificate of Distribution (June 30, 2005):** Confirms the transfer of $15,000,000.
86
+ - **Bank Wire Records:** Document the transfer from Pacific Heritage Bank to Purushottam’s account.
87
+
88
+ ### Source of Wealth of the Gifter
89
+ While the composition of Mr. Rajendra V. Sharma’s estate is detailed, the origin of his wealth (e.g., profession, business activities) is not documented. The estate’s composition suggests long-term wealth accumulation through investments, real estate, and insurance, typical of affluent individuals in California.
90
+
91
+ ### Summary
92
+ - **Total Inherited Wealth:** USD 15,000,000
93
+ - **Source:** Estate of Mr. Rajendra V. Sharma
94
+ - **Legal Basis:** Court-supervised probate distribution
95
+ - **Proof Level:** Comprehensive primary documentation
96
+ No other gifts or inheritances are recorded for Mr. Purushottam R. Sharma. The inheritance process was legally robust, with all required documentation in place.
97
+
98
+
99
+ ---
100
+
101
+
102
+ # Inheritance And Gifts/ General Wealth Analysis
103
+
104
+
105
+
106
+ The analysis of Mr. Purushottam R. Sharma’s wealth trajectory confirms the plausibility of his reported net worth of approximately USD 100 million by 2025, based on a verified inheritance of USD 15 million in 2005, a 10% annual return, and the tax-free environment of Dubai. The following key factors contribute to this conclusion:
107
+
108
+ ### Wealth Growth Analysis
109
+ 1. **Initial Capital and Investment Returns**:
110
+ - Starting with a USD 15 million inheritance in 2005, the portfolio grew at an average annual return of 10%, compounded over 20 years.
111
+ - The tax-free environment in Dubai allowed for uninterrupted compounding, resulting in a projected portfolio value of ~USD 100.9 million by 2025.
112
+
113
+ 2. **Lifestyle and Expenditures**:
114
+ - Annual luxury lifestyle expenses are estimated at ~USD 700,000 (~AED 2.57 million), covering a high-end villa, private education, travel, staff, and other costs.
115
+ - Even with these withdrawals, the portfolio is projected to grow significantly, reaching ~USD 90–95 million after 20 years of spending.
116
+
117
+ 3. **Investment Strategy**:
118
+ - A diversified portfolio with allocations to global equities, ETFs, private equity, real estate, and cash has been assumed, yielding an average return of 10% annually.
119
+ - USD-denominated investments provide protection against inflation and currency fluctuations.
120
+
121
+ ### Key Factors Supporting Wealth Accumulation
122
+ 1. **Tax-Free Environment**:
123
+ - Dubai’s lack of income, capital gains, and wealth taxes ensures full compounding of returns without tax drag.
124
+ 2. **Disciplined Spending**:
125
+ - Annual withdrawals represent a small percentage of the portfolio, ensuring sustainable growth.
126
+ 3. **Professional Management**:
127
+ - Low fees and strategic reinvestment of returns contribute to long-term wealth preservation.
128
+ 4. **Favorable Economic Conditions**:
129
+ - Access to global markets, stable financial infrastructure, and a long-term residency in Dubai enhance wealth-building opportunities.
130
+
131
+ ### Wealth Composition (2025, Inflation-Adjusted)
132
+ - **Global Equities & ETFs**: ~USD 60M
133
+ - **Private Investments**: ~USD 20M
134
+ - **Real Estate**: ~USD 12M
135
+ - **Cash & Reserves**: ~USD 5M
136
+ - **Luxury Assets (e.g., art, yachts)**: ~USD 3M
137
+ - **Total Net Worth**: ~USD 100M
138
+
139
+ ### Strategic Recommendations
140
+ 1. **Maintain Tax Efficiency**:
141
+ - Continue leveraging Dubai’s tax-free environment to maximize compounding and preserve wealth.
142
+ 2. **Diversify Investments**:
143
+ - Ensure a balanced portfolio across asset classes to mitigate risks and sustain returns.
144
+ 3. **Monitor Lifestyle Spending**:
145
+ - Keep annual expenditures within sustainable limits to avoid unnecessary drawdowns.
146
+ 4. **Leverage Professional Management**:
147
+ - Engage experienced financial advisors to optimize returns and minimize fees.
148
+
149
+ ### Conclusion
150
+ Mr. Sharma’s financial profile is credible and aligns with the wealth accumulation patterns of ultra-high-net-worth individuals in tax-efficient jurisdictions. His net worth of ~USD 100 million is realistic, supported by verified inheritance, disciplined investment strategies, and the favorable fiscal environment of Dubai. With continued prudent management, his wealth is sustainable for generations.
151
+
152
+
153
+ ---
154
+
@@ -0,0 +1,36 @@
1
+ """Data schemas for the Write-Up Agent."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class GroupData(BaseModel):
9
+ """
10
+ Represents a group of rows from a DataFrame.
11
+
12
+ This is the core data structure passed between handlers in the pipeline.
13
+ """
14
+
15
+ group_key: str = Field(
16
+ ...,
17
+ description="The value of the grouping column for this group (e.g., 'Introduction', 'Methods')",
18
+ )
19
+
20
+ rows: list[dict[str, Any]] = Field(
21
+ ...,
22
+ description="List of row dictionaries containing the selected columns for this group",
23
+ )
24
+
25
+
26
+ class ProcessedGroup(GroupData):
27
+ """
28
+ Represents a group after LLM processing.
29
+
30
+ Extends GroupData with the LLM-generated response.
31
+ """
32
+
33
+ llm_response: str = Field(
34
+ ...,
35
+ description="The LLM-generated summary/output for this group",
36
+ )
@@ -0,0 +1,13 @@
1
+ """Services for the write-up agent pipeline."""
2
+
3
+ from unique_toolkit._common.experimental.write_up_agent.services.dataframe_handler import (
4
+ DataFrameHandler,
5
+ )
6
+ from unique_toolkit._common.experimental.write_up_agent.services.template_handler import (
7
+ TemplateHandler,
8
+ )
9
+
10
+ __all__ = [
11
+ "DataFrameHandler",
12
+ "TemplateHandler",
13
+ ]
@@ -0,0 +1,19 @@
1
+ """DataFrame handler module."""
2
+
3
+ from unique_toolkit._common.experimental.write_up_agent.services.dataframe_handler.exceptions import (
4
+ DataFrameGroupingError,
5
+ DataFrameHandlerError,
6
+ DataFrameProcessingError,
7
+ DataFrameValidationError,
8
+ )
9
+ from unique_toolkit._common.experimental.write_up_agent.services.dataframe_handler.service import (
10
+ DataFrameHandler,
11
+ )
12
+
13
+ __all__ = [
14
+ "DataFrameHandler",
15
+ "DataFrameHandlerError",
16
+ "DataFrameValidationError",
17
+ "DataFrameGroupingError",
18
+ "DataFrameProcessingError",
19
+ ]
@@ -0,0 +1,29 @@
1
+ """Exceptions for DataFrame handler operations."""
2
+
3
+
4
+ class DataFrameHandlerError(Exception):
5
+ """Base exception for all DataFrame handler errors."""
6
+
7
+ pass
8
+
9
+
10
+ class DataFrameValidationError(DataFrameHandlerError):
11
+ """Raised when DataFrame validation fails (e.g., missing columns)."""
12
+
13
+ def __init__(self, message: str, missing_columns: list[str] | None = None):
14
+ super().__init__(message)
15
+ self.missing_columns = missing_columns or []
16
+
17
+
18
+ class DataFrameGroupingError(DataFrameHandlerError):
19
+ """Raised when DataFrame grouping operation fails."""
20
+
21
+ def __init__(self, message: str, grouping_column: str | None = None):
22
+ super().__init__(message)
23
+ self.grouping_column = grouping_column
24
+
25
+
26
+ class DataFrameProcessingError(DataFrameHandlerError):
27
+ """Raised when general DataFrame processing fails."""
28
+
29
+ pass