AeroViz 0.1.21__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 (180) hide show
  1. AeroViz/__init__.py +13 -0
  2. AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
  3. AeroViz/data/DEFAULT_DATA.csv +1417 -0
  4. AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
  5. AeroViz/data/hysplit_example_data.txt +101 -0
  6. AeroViz/dataProcess/Chemistry/__init__.py +149 -0
  7. AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
  8. AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
  9. AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
  10. AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
  11. AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
  12. AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
  13. AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
  14. AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
  15. AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
  16. AeroViz/dataProcess/Optical/__init__.py +281 -0
  17. AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
  18. AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
  19. AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
  20. AeroViz/dataProcess/Optical/_derived.py +518 -0
  21. AeroViz/dataProcess/Optical/_extinction.py +123 -0
  22. AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
  23. AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
  24. AeroViz/dataProcess/Optical/coefficient.py +72 -0
  25. AeroViz/dataProcess/Optical/fRH.pkl +0 -0
  26. AeroViz/dataProcess/Optical/mie_theory.py +260 -0
  27. AeroViz/dataProcess/README.md +271 -0
  28. AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
  29. AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
  30. AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
  31. AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
  32. AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
  33. AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
  34. AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
  35. AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
  36. AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
  37. AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
  38. AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
  39. AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
  40. AeroViz/dataProcess/SizeDistr/prop.py +62 -0
  41. AeroViz/dataProcess/VOC/__init__.py +14 -0
  42. AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
  43. AeroViz/dataProcess/VOC/_potential_par.py +108 -0
  44. AeroViz/dataProcess/VOC/support_voc.json +446 -0
  45. AeroViz/dataProcess/__init__.py +66 -0
  46. AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
  47. AeroViz/dataProcess/core/__init__.py +272 -0
  48. AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
  49. AeroViz/mcp_server.py +352 -0
  50. AeroViz/plot/__init__.py +13 -0
  51. AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
  52. AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
  53. AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
  54. AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
  55. AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
  56. AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
  57. AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
  58. AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
  59. AeroViz/plot/bar.py +126 -0
  60. AeroViz/plot/box.py +69 -0
  61. AeroViz/plot/distribution/__init__.py +1 -0
  62. AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
  63. AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
  64. AeroViz/plot/distribution/distribution.py +576 -0
  65. AeroViz/plot/meteorology/CBPF.py +295 -0
  66. AeroViz/plot/meteorology/__init__.py +3 -0
  67. AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
  68. AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
  69. AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
  70. AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
  71. AeroViz/plot/meteorology/hysplit.py +93 -0
  72. AeroViz/plot/meteorology/wind_rose.py +77 -0
  73. AeroViz/plot/optical/__init__.py +1 -0
  74. AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
  75. AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
  76. AeroViz/plot/optical/optical.py +388 -0
  77. AeroViz/plot/pie.py +210 -0
  78. AeroViz/plot/radar.py +184 -0
  79. AeroViz/plot/regression.py +200 -0
  80. AeroViz/plot/scatter.py +174 -0
  81. AeroViz/plot/templates/__init__.py +6 -0
  82. AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  83. AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
  84. AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
  85. AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
  86. AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
  87. AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
  88. AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
  89. AeroViz/plot/templates/ammonium_rich.py +34 -0
  90. AeroViz/plot/templates/contour.py +47 -0
  91. AeroViz/plot/templates/corr_matrix.py +267 -0
  92. AeroViz/plot/templates/diurnal_pattern.py +61 -0
  93. AeroViz/plot/templates/koschmieder.py +95 -0
  94. AeroViz/plot/templates/metal_heatmap.py +164 -0
  95. AeroViz/plot/timeseries/__init__.py +2 -0
  96. AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
  97. AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
  98. AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
  99. AeroViz/plot/timeseries/template.py +47 -0
  100. AeroViz/plot/timeseries/timeseries.py +446 -0
  101. AeroViz/plot/utils/__init__.py +4 -0
  102. AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  103. AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
  104. AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
  105. AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
  106. AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
  107. AeroViz/plot/utils/_color.py +71 -0
  108. AeroViz/plot/utils/_unit.py +55 -0
  109. AeroViz/plot/utils/fRH.json +390 -0
  110. AeroViz/plot/utils/plt_utils.py +92 -0
  111. AeroViz/plot/utils/sklearn_utils.py +49 -0
  112. AeroViz/plot/utils/units.json +89 -0
  113. AeroViz/plot/violin.py +80 -0
  114. AeroViz/rawDataReader/FLOW.md +138 -0
  115. AeroViz/rawDataReader/__init__.py +220 -0
  116. AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
  117. AeroViz/rawDataReader/config/__init__.py +0 -0
  118. AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
  119. AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
  120. AeroViz/rawDataReader/config/supported_instruments.py +135 -0
  121. AeroViz/rawDataReader/core/__init__.py +658 -0
  122. AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
  123. AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
  124. AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
  125. AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
  126. AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
  127. AeroViz/rawDataReader/core/logger.py +171 -0
  128. AeroViz/rawDataReader/core/pre_process.py +308 -0
  129. AeroViz/rawDataReader/core/qc.py +961 -0
  130. AeroViz/rawDataReader/core/report.py +579 -0
  131. AeroViz/rawDataReader/script/AE33.py +173 -0
  132. AeroViz/rawDataReader/script/AE43.py +151 -0
  133. AeroViz/rawDataReader/script/APS.py +339 -0
  134. AeroViz/rawDataReader/script/Aurora.py +191 -0
  135. AeroViz/rawDataReader/script/BAM1020.py +90 -0
  136. AeroViz/rawDataReader/script/BC1054.py +161 -0
  137. AeroViz/rawDataReader/script/EPA.py +79 -0
  138. AeroViz/rawDataReader/script/GRIMM.py +68 -0
  139. AeroViz/rawDataReader/script/IGAC.py +140 -0
  140. AeroViz/rawDataReader/script/MA350.py +179 -0
  141. AeroViz/rawDataReader/script/Minion.py +218 -0
  142. AeroViz/rawDataReader/script/NEPH.py +199 -0
  143. AeroViz/rawDataReader/script/OCEC.py +173 -0
  144. AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
  145. AeroViz/rawDataReader/script/SMPS.py +389 -0
  146. AeroViz/rawDataReader/script/TEOM.py +181 -0
  147. AeroViz/rawDataReader/script/VOC.py +106 -0
  148. AeroViz/rawDataReader/script/Xact.py +244 -0
  149. AeroViz/rawDataReader/script/__init__.py +28 -0
  150. AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
  151. AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
  152. AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
  153. AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
  154. AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
  155. AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
  156. AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
  157. AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
  158. AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
  159. AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
  160. AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
  161. AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
  162. AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
  163. AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
  164. AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
  165. AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
  166. AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
  167. AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
  168. AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
  169. AeroViz/tools/__init__.py +2 -0
  170. AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  171. AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
  172. AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
  173. AeroViz/tools/database.py +95 -0
  174. AeroViz/tools/dataclassifier.py +117 -0
  175. AeroViz/tools/dataprinter.py +58 -0
  176. aeroviz-0.1.21.dist-info/METADATA +294 -0
  177. aeroviz-0.1.21.dist-info/RECORD +180 -0
  178. aeroviz-0.1.21.dist-info/WHEEL +5 -0
  179. aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
  180. aeroviz-0.1.21.dist-info/top_level.txt +1 -0
@@ -0,0 +1,272 @@
1
+ import functools
2
+ import pickle as pkl
3
+ import warnings
4
+ from datetime import datetime as dtm
5
+ from pathlib import Path
6
+
7
+ from pandas import concat
8
+
9
+ # Ensure all deprecation warnings are displayed
10
+ warnings.filterwarnings('always', category=DeprecationWarning)
11
+ warnings.filterwarnings('always', category=FutureWarning)
12
+
13
+
14
+ class Writer:
15
+ """
16
+ Base class for data output management with various file format support.
17
+
18
+ This class provides functionality to save processed data in multiple formats,
19
+ including pickle, Excel, and CSV. It handles file permission issues gracefully.
20
+
21
+ Parameters
22
+ ----------
23
+ path_out : str or Path, optional
24
+ Directory path where output files will be saved
25
+ excel : bool, default=True
26
+ Whether to save outputs as Excel files
27
+ csv : bool, default=False
28
+ Whether to save outputs as CSV files
29
+ """
30
+
31
+ def __init__(self, path_out=None, excel=True, csv=False):
32
+ self.path_out = Path(path_out) if path_out is not None else path_out
33
+ self.excel = excel
34
+ self.csv = csv
35
+
36
+ @staticmethod
37
+ def pre_process(_out):
38
+ """
39
+ Prepare data for output by ensuring proper index naming.
40
+
41
+ Parameters
42
+ ----------
43
+ _out : DataFrame or dict of DataFrames
44
+ Data to be prepared for output
45
+
46
+ Returns
47
+ -------
48
+ DataFrame or dict of DataFrames
49
+ Processed data with properly named index
50
+ """
51
+ if isinstance(_out, dict):
52
+ for _ky, _df in _out.items():
53
+ _df.index.name = 'time'
54
+ else:
55
+ _out.index.name = 'time'
56
+
57
+ return _out
58
+
59
+ def save_out(self, _nam, _out):
60
+ """
61
+ Save processed data to disk in specified formats.
62
+
63
+ Handles various output formats (pickle, Excel, CSV) and manages file
64
+ permission errors by prompting the user to close open files.
65
+
66
+ Parameters
67
+ ----------
68
+ _nam : str
69
+ Base name for the output files
70
+ _out : DataFrame or dict of DataFrames
71
+ Data to be saved
72
+ """
73
+ _check = True
74
+ while _check:
75
+ try:
76
+ if self.path_out is not None:
77
+ self.path_out.mkdir(exist_ok=True, parents=True)
78
+ with (self.path_out / f'{_nam}.pkl').open('wb') as f:
79
+ pkl.dump(_out, f, protocol=pkl.HIGHEST_PROTOCOL)
80
+
81
+ if self.excel:
82
+ from pandas import ExcelWriter
83
+ with ExcelWriter(self.path_out / f'{_nam}.xlsx') as f:
84
+ if type(_out) == dict:
85
+ for _key, _val in _out.items():
86
+ _val.to_excel(f, sheet_name=f'{_key}')
87
+ else:
88
+ _out.to_excel(f, sheet_name=f'{_nam}')
89
+
90
+ if self.csv:
91
+ if isinstance(_out, dict):
92
+ _path_out = self.path_out / _nam
93
+ _path_out.mkdir(exist_ok=True, parents=True)
94
+
95
+ for _key, _val in _out.items():
96
+ _val.to_csv(_path_out / f'{_key}.csv')
97
+ else:
98
+ _out.to_csv(self.path_out / f'{_nam}.csv')
99
+
100
+ _check = False
101
+
102
+ except PermissionError as _err:
103
+ print('\n', _err)
104
+ input('\t\t\33[41m Please Close The File And Press "Enter" \33[0m\n')
105
+
106
+
107
+ def run_process(*_ini_set: str):
108
+ """
109
+ Decorator for standardizing data processing functions.
110
+
111
+ This decorator wraps processing functions to provide consistent logging,
112
+ output formatting, and file saving behavior.
113
+
114
+ Parameters
115
+ ----------
116
+ *_ini_set : str
117
+ Two strings: process display name and output file name
118
+
119
+ Returns
120
+ -------
121
+ callable
122
+ Decorated function that handles the entire process flow
123
+
124
+ Examples
125
+ --------
126
+ @run_process('Process Description', 'output_filename')
127
+ def process_function(self, data):
128
+ # Process data
129
+ return self, processed_data
130
+ """
131
+
132
+ def _decorator(_prcs_fc):
133
+ def _wrap(*arg, **kwarg):
134
+ _fc_name, _nam = _ini_set
135
+
136
+ if kwarg.get('nam') is not None:
137
+ _nam = kwarg.pop('nam')
138
+
139
+ print(f"\n\t{dtm.now().strftime('%m/%d %X')} : Process \033[92m{_fc_name}\033[0m -> {_nam}")
140
+
141
+ _class, _out = _prcs_fc(*arg, **kwarg)
142
+ _out = _class.pre_process(_out)
143
+
144
+ _class.save_out(_nam, _out)
145
+
146
+ return _out
147
+
148
+ return _wrap
149
+
150
+ return _decorator
151
+
152
+
153
+ def union_index(*_df_arg):
154
+ """
155
+ Reindex multiple DataFrames to a common union index.
156
+
157
+ Creates a unified index from all input DataFrames and reindexes each
158
+ DataFrame to this common index, handling None values appropriately.
159
+
160
+ Parameters
161
+ ----------
162
+ *_df_arg : DataFrame
163
+ One or more pandas DataFrames to reindex
164
+
165
+ Returns
166
+ -------
167
+ list
168
+ List of reindexed DataFrames in the same order as input
169
+ """
170
+ _idx = concat(_df_arg, axis=1).index
171
+
172
+ return [_df.reindex(_idx) if _df is not None else None for _df in _df_arg]
173
+
174
+
175
+ def validate_inputs(df, required_columns, func_name, column_descriptions=None):
176
+ """
177
+ Validate that DataFrame contains all required columns.
178
+
179
+ Parameters
180
+ ----------
181
+ df : DataFrame
182
+ Input DataFrame to validate.
183
+ required_columns : list
184
+ List of required column names.
185
+ func_name : str
186
+ Name of the calling function (for error message).
187
+ column_descriptions : dict, optional
188
+ Dictionary mapping column names to descriptions.
189
+ Example: {'AS': 'Ammonium Sulfate 硫酸銨 (ug/m3)'}
190
+
191
+ Raises
192
+ ------
193
+ ValueError
194
+ If DataFrame is None/empty or any required columns are missing.
195
+
196
+ Examples
197
+ --------
198
+ >>> REQUIRED = ['AS', 'AN', 'OM']
199
+ >>> DESCRIPTIONS = {'AS': 'Ammonium Sulfate', 'AN': 'Ammonium Nitrate', 'OM': 'Organic Matter'}
200
+ >>> validate_inputs(df, REQUIRED, 'my_function', DESCRIPTIONS)
201
+ """
202
+ if df is None:
203
+ raise ValueError(
204
+ f"\n{func_name}() 輸入資料為 None!\n"
205
+ f" 需要欄位: {required_columns}"
206
+ )
207
+
208
+ if hasattr(df, 'empty') and df.empty:
209
+ raise ValueError(
210
+ f"\n{func_name}() 輸入資料為空!\n"
211
+ f" 需要欄位: {required_columns}"
212
+ )
213
+
214
+ existing_columns = set(df.columns)
215
+ required_set = set(required_columns)
216
+ missing = required_set - existing_columns
217
+
218
+ if missing:
219
+ error_msg = (
220
+ f"\n{func_name}() 缺少必要欄位!\n"
221
+ f" 需要欄位: {required_columns}\n"
222
+ f" 缺少欄位: {sorted(missing)}\n"
223
+ f" 現有欄位: {sorted(existing_columns)}"
224
+ )
225
+
226
+ if column_descriptions:
227
+ error_msg += "\n\n欄位說明:"
228
+ for col in required_columns:
229
+ if col in column_descriptions:
230
+ error_msg += f"\n {col:6s} - {column_descriptions[col]}"
231
+
232
+ raise ValueError(error_msg)
233
+
234
+
235
+ def deprecated(message):
236
+ """
237
+ Decorator to mark functions as deprecated.
238
+
239
+ This decorator adds a warning message when a deprecated function is called,
240
+ informing users about the deprecation and suggesting alternatives.
241
+
242
+ Parameters
243
+ ----------
244
+ message : str
245
+ Message explaining why the function is deprecated and what to use instead
246
+
247
+ Returns
248
+ -------
249
+ callable
250
+ Decorator function that adds deprecation warnings
251
+
252
+ Examples
253
+ --------
254
+ @deprecated("Use new_function() instead.")
255
+ def old_function():
256
+ # Function implementation
257
+ pass
258
+ """
259
+
260
+ def decorator(func):
261
+ @functools.wraps(func)
262
+ def wrapper(*args, **kwargs):
263
+ warnings.warn(
264
+ f"{func.__name__} is deprecated and will be removed in a future version. {message}",
265
+ category=DeprecationWarning,
266
+ stacklevel=2
267
+ )
268
+ return func(*args, **kwargs)
269
+
270
+ return wrapper
271
+
272
+ return decorator
AeroViz/mcp_server.py ADDED
@@ -0,0 +1,352 @@
1
+ """
2
+ AeroViz MCP Server
3
+
4
+ Provides AI assistants with access to aerosol data processing capabilities.
5
+
6
+ Installation:
7
+ pip install mcp
8
+
9
+ Usage in Claude Code settings (~/.claude/settings.json):
10
+ {
11
+ "mcpServers": {
12
+ "aeroviz": {
13
+ "command": "python",
14
+ "args": ["/path/to/AeroViz/AeroViz/mcp_server.py"],
15
+ "env": {
16
+ "PYTHONPATH": "/path/to/AeroViz"
17
+ }
18
+ }
19
+ }
20
+ }
21
+ """
22
+
23
+ import json
24
+ import sys
25
+ from datetime import datetime
26
+ from pathlib import Path
27
+
28
+ # MCP imports
29
+ try:
30
+ from mcp.server import Server
31
+ from mcp.server.stdio import stdio_server
32
+ from mcp.types import Tool, TextContent
33
+ except ImportError:
34
+ print("MCP not installed. Run: pip install mcp", file=sys.stderr)
35
+ sys.exit(1)
36
+
37
+ # Create server instance
38
+ server = Server("aeroviz")
39
+
40
+ # Supported instruments metadata
41
+ SUPPORTED_INSTRUMENTS = {
42
+ "AE33": {"description": "Aethalometer - Black carbon at 7 wavelengths", "freq": "1min"},
43
+ "AE43": {"description": "Aethalometer - Black carbon at 7 wavelengths", "freq": "1min"},
44
+ "BC1054": {"description": "Black carbon monitor", "freq": "1min"},
45
+ "MA350": {"description": "MicroAeth - Portable black carbon", "freq": "1min"},
46
+ "SMPS": {"description": "Scanning Mobility Particle Sizer - Size distribution 10-1000nm", "freq": "6min"},
47
+ "APS": {"description": "Aerodynamic Particle Sizer - Size distribution 0.5-20μm", "freq": "6min"},
48
+ "GRIMM": {"description": "Optical particle counter", "freq": "6min"},
49
+ "TEOM": {"description": "Tapered Element Oscillating Microbalance - PM mass", "freq": "6min"},
50
+ "BAM1020": {"description": "Beta Attenuation Monitor - PM mass", "freq": "1h"},
51
+ "NEPH": {"description": "Nephelometer - Scattering coefficients", "freq": "5min"},
52
+ "Aurora": {"description": "Aurora nephelometer - Scattering at 3 wavelengths", "freq": "1min"},
53
+ "OCEC": {"description": "OC/EC analyzer - Organic/Elemental carbon", "freq": "1h"},
54
+ "Xact": {"description": "XRF analyzer - Heavy metals (Fe, Zn, Pb, etc.)", "freq": "1h"},
55
+ "IGAC": {"description": "Ion chromatograph - Water-soluble ions", "freq": "1h"},
56
+ "VOC": {"description": "VOC analyzer - Volatile organic compounds", "freq": "1h"},
57
+ "Q-ACSM": {"description": "Aerosol Chemical Speciation Monitor", "freq": "30min"},
58
+ "EPA": {"description": "EPA air quality data", "freq": "1h"},
59
+ "Minion": {"description": "Minion sensor", "freq": "1h"},
60
+ }
61
+
62
+
63
+ @server.list_tools()
64
+ async def list_tools():
65
+ """List available AeroViz tools."""
66
+ return [
67
+ Tool(
68
+ name="list_instruments",
69
+ description="List all supported aerosol instruments in AeroViz",
70
+ inputSchema={
71
+ "type": "object",
72
+ "properties": {},
73
+ "required": []
74
+ }
75
+ ),
76
+ Tool(
77
+ name="read_instrument_data",
78
+ description="Read and process aerosol instrument data with automatic QC",
79
+ inputSchema={
80
+ "type": "object",
81
+ "properties": {
82
+ "instrument": {
83
+ "type": "string",
84
+ "description": "Instrument name (e.g., AE33, SMPS, APS, TEOM)",
85
+ "enum": list(SUPPORTED_INSTRUMENTS.keys())
86
+ },
87
+ "path": {
88
+ "type": "string",
89
+ "description": "Directory path containing raw data files"
90
+ },
91
+ "start": {
92
+ "type": "string",
93
+ "description": "Start date in ISO format (e.g., 2024-01-01)"
94
+ },
95
+ "end": {
96
+ "type": "string",
97
+ "description": "End date in ISO format (e.g., 2024-12-31)"
98
+ },
99
+ "mean_freq": {
100
+ "type": "string",
101
+ "description": "Averaging frequency (default: 1h)",
102
+ "default": "1h"
103
+ },
104
+ "qc": {
105
+ "type": "boolean",
106
+ "description": "Apply quality control (default: true)",
107
+ "default": True
108
+ },
109
+ "reset": {
110
+ "type": "boolean",
111
+ "description": "Force reprocess from raw files (default: false)",
112
+ "default": False
113
+ }
114
+ },
115
+ "required": ["instrument", "path", "start", "end"]
116
+ }
117
+ ),
118
+ Tool(
119
+ name="get_data_summary",
120
+ description="Get summary statistics of processed aerosol data",
121
+ inputSchema={
122
+ "type": "object",
123
+ "properties": {
124
+ "instrument": {
125
+ "type": "string",
126
+ "description": "Instrument name",
127
+ "enum": list(SUPPORTED_INSTRUMENTS.keys())
128
+ },
129
+ "path": {
130
+ "type": "string",
131
+ "description": "Directory path containing raw data files"
132
+ },
133
+ "start": {
134
+ "type": "string",
135
+ "description": "Start date in ISO format"
136
+ },
137
+ "end": {
138
+ "type": "string",
139
+ "description": "End date in ISO format"
140
+ }
141
+ },
142
+ "required": ["instrument", "path", "start", "end"]
143
+ }
144
+ ),
145
+ Tool(
146
+ name="get_instrument_info",
147
+ description="Get detailed information about a specific instrument",
148
+ inputSchema={
149
+ "type": "object",
150
+ "properties": {
151
+ "instrument": {
152
+ "type": "string",
153
+ "description": "Instrument name",
154
+ "enum": list(SUPPORTED_INSTRUMENTS.keys())
155
+ }
156
+ },
157
+ "required": ["instrument"]
158
+ }
159
+ ),
160
+ Tool(
161
+ name="calculate_optical_properties",
162
+ description="Calculate optical properties (AAE, SAE, SSA, etc.) from aerosol data",
163
+ inputSchema={
164
+ "type": "object",
165
+ "properties": {
166
+ "absorption_data": {
167
+ "type": "string",
168
+ "description": "JSON string of absorption coefficient data"
169
+ },
170
+ "scattering_data": {
171
+ "type": "string",
172
+ "description": "JSON string of scattering coefficient data (optional)"
173
+ }
174
+ },
175
+ "required": ["absorption_data"]
176
+ }
177
+ )
178
+ ]
179
+
180
+
181
+ @server.call_tool()
182
+ async def call_tool(name: str, arguments: dict):
183
+ """Handle tool calls."""
184
+
185
+ if name == "list_instruments":
186
+ result = []
187
+ for inst, info in SUPPORTED_INSTRUMENTS.items():
188
+ result.append(f"- **{inst}**: {info['description']} (freq: {info['freq']})")
189
+ return [TextContent(
190
+ type="text",
191
+ text="# Supported Instruments in AeroViz\n\n" + "\n".join(result)
192
+ )]
193
+
194
+ elif name == "get_instrument_info":
195
+ instrument = arguments.get("instrument")
196
+ if instrument not in SUPPORTED_INSTRUMENTS:
197
+ return [TextContent(type="text", text=f"Unknown instrument: {instrument}")]
198
+
199
+ info = SUPPORTED_INSTRUMENTS[instrument]
200
+
201
+ # Get additional info from meta config
202
+ from AeroViz.rawDataReader.config.supported_instruments import meta
203
+ meta_info = meta.get(instrument, {})
204
+
205
+ result = f"""# {instrument}
206
+
207
+ **Description**: {info['description']}
208
+ **Native Frequency**: {info['freq']}
209
+ **File Patterns**: {', '.join(meta_info.get('pattern', ['Unknown']))}
210
+
211
+ ## Usage Example
212
+ ```python
213
+ from AeroViz import RawDataReader
214
+
215
+ df = RawDataReader(
216
+ instrument='{instrument}',
217
+ path='/path/to/data',
218
+ start='2024-01-01',
219
+ end='2024-12-31',
220
+ mean_freq='1h',
221
+ qc=True
222
+ )
223
+ ```
224
+ """
225
+ # Add MDL info if available
226
+ if 'MDL' in meta_info:
227
+ result += "\n## Minimum Detection Limits (MDL)\n"
228
+ for elem, mdl in list(meta_info['MDL'].items())[:10]:
229
+ if mdl is not None:
230
+ result += f"- {elem}: {mdl} ng/m³\n"
231
+ if len(meta_info['MDL']) > 10:
232
+ result += f"- ... and {len(meta_info['MDL']) - 10} more elements\n"
233
+
234
+ return [TextContent(type="text", text=result)]
235
+
236
+ elif name == "read_instrument_data":
237
+ try:
238
+ from AeroViz import RawDataReader
239
+
240
+ instrument = arguments["instrument"]
241
+ path = arguments["path"]
242
+ start = arguments["start"]
243
+ end = arguments["end"]
244
+ mean_freq = arguments.get("mean_freq", "1h")
245
+ qc = arguments.get("qc", True)
246
+ reset = arguments.get("reset", False)
247
+
248
+ # Validate path
249
+ if not Path(path).exists():
250
+ return [TextContent(type="text", text=f"Error: Path does not exist: {path}")]
251
+
252
+ df = RawDataReader(
253
+ instrument=instrument,
254
+ path=path,
255
+ start=start,
256
+ end=end,
257
+ mean_freq=mean_freq,
258
+ qc=qc,
259
+ reset=reset
260
+ )
261
+
262
+ # Return summary + first/last rows
263
+ result = f"""# {instrument} Data Loaded Successfully
264
+
265
+ **Time Range**: {df.index.min()} to {df.index.max()}
266
+ **Rows**: {len(df):,}
267
+ **Columns**: {', '.join(df.columns[:10])}{'...' if len(df.columns) > 10 else ''}
268
+
269
+ ## Summary Statistics
270
+ ```
271
+ {df.describe().to_string()}
272
+ ```
273
+
274
+ ## First 5 Rows
275
+ ```
276
+ {df.head().to_string()}
277
+ ```
278
+
279
+ ## Last 5 Rows
280
+ ```
281
+ {df.tail().to_string()}
282
+ ```
283
+ """
284
+ return [TextContent(type="text", text=result)]
285
+
286
+ except Exception as e:
287
+ return [TextContent(type="text", text=f"Error reading data: {str(e)}")]
288
+
289
+ elif name == "get_data_summary":
290
+ try:
291
+ from AeroViz import RawDataReader
292
+
293
+ df = RawDataReader(
294
+ instrument=arguments["instrument"],
295
+ path=arguments["path"],
296
+ start=arguments["start"],
297
+ end=arguments["end"],
298
+ mean_freq="1h",
299
+ qc=True
300
+ )
301
+
302
+ # Calculate comprehensive summary
303
+ summary = {
304
+ "time_range": {
305
+ "start": str(df.index.min()),
306
+ "end": str(df.index.max()),
307
+ "total_hours": len(df)
308
+ },
309
+ "data_completeness": {
310
+ col: f"{(df[col].notna().sum() / len(df) * 100):.1f}%"
311
+ for col in df.columns[:10]
312
+ },
313
+ "statistics": df.describe().to_dict()
314
+ }
315
+
316
+ return [TextContent(type="text", text=f"```json\n{json.dumps(summary, indent=2, default=str)}\n```")]
317
+
318
+ except Exception as e:
319
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
320
+
321
+ elif name == "calculate_optical_properties":
322
+ try:
323
+ from AeroViz.dataProcess.Optical import Optical
324
+ import pandas as pd
325
+
326
+ abs_data = json.loads(arguments["absorption_data"])
327
+ df = pd.DataFrame(abs_data)
328
+
329
+ result = "# Optical Properties Calculation\n\n"
330
+ result += "Use AeroViz.dataProcess.Optical for:\n"
331
+ result += "- AAE (Absorption Ångström Exponent)\n"
332
+ result += "- SAE (Scattering Ångström Exponent)\n"
333
+ result += "- SSA (Single Scattering Albedo)\n"
334
+ result += "- Mass absorption/scattering coefficients\n"
335
+
336
+ return [TextContent(type="text", text=result)]
337
+
338
+ except Exception as e:
339
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
340
+
341
+ return [TextContent(type="text", text=f"Unknown tool: {name}")]
342
+
343
+
344
+ async def main():
345
+ """Run the MCP server."""
346
+ async with stdio_server() as (read_stream, write_stream):
347
+ await server.run(read_stream, write_stream, server.create_initialization_options())
348
+
349
+
350
+ if __name__ == "__main__":
351
+ import asyncio
352
+ asyncio.run(main())
@@ -0,0 +1,13 @@
1
+ from . import distribution
2
+ from . import meteorology
3
+ from . import optical
4
+ from .bar import bar
5
+ from .box import box
6
+ from .pie import pie, donuts
7
+ from .radar import radar
8
+ from .regression import linear_regression, multiple_linear_regression
9
+ from .scatter import scatter
10
+ from .templates import *
11
+ from .timeseries import timeseries, timeseries_template, timeseries_stacked
12
+ from .utils import *
13
+ from .violin import violin