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.
- AeroViz/__init__.py +13 -0
- AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/data/DEFAULT_DATA.csv +1417 -0
- AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
- AeroViz/data/hysplit_example_data.txt +101 -0
- AeroViz/dataProcess/Chemistry/__init__.py +149 -0
- AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
- AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
- AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
- AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
- AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
- AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
- AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
- AeroViz/dataProcess/Optical/__init__.py +281 -0
- AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/_derived.py +518 -0
- AeroViz/dataProcess/Optical/_extinction.py +123 -0
- AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
- AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
- AeroViz/dataProcess/Optical/coefficient.py +72 -0
- AeroViz/dataProcess/Optical/fRH.pkl +0 -0
- AeroViz/dataProcess/Optical/mie_theory.py +260 -0
- AeroViz/dataProcess/README.md +271 -0
- AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
- AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
- AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
- AeroViz/dataProcess/SizeDistr/prop.py +62 -0
- AeroViz/dataProcess/VOC/__init__.py +14 -0
- AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/VOC/_potential_par.py +108 -0
- AeroViz/dataProcess/VOC/support_voc.json +446 -0
- AeroViz/dataProcess/__init__.py +66 -0
- AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/core/__init__.py +272 -0
- AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/mcp_server.py +352 -0
- AeroViz/plot/__init__.py +13 -0
- AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +69 -0
- AeroViz/plot/distribution/__init__.py +1 -0
- AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/distribution.py +576 -0
- AeroViz/plot/meteorology/CBPF.py +295 -0
- AeroViz/plot/meteorology/__init__.py +3 -0
- AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/hysplit.py +93 -0
- AeroViz/plot/meteorology/wind_rose.py +77 -0
- AeroViz/plot/optical/__init__.py +1 -0
- AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
- AeroViz/plot/optical/optical.py +388 -0
- AeroViz/plot/pie.py +210 -0
- AeroViz/plot/radar.py +184 -0
- AeroViz/plot/regression.py +200 -0
- AeroViz/plot/scatter.py +174 -0
- AeroViz/plot/templates/__init__.py +6 -0
- AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
- AeroViz/plot/templates/ammonium_rich.py +34 -0
- AeroViz/plot/templates/contour.py +47 -0
- AeroViz/plot/templates/corr_matrix.py +267 -0
- AeroViz/plot/templates/diurnal_pattern.py +61 -0
- AeroViz/plot/templates/koschmieder.py +95 -0
- AeroViz/plot/templates/metal_heatmap.py +164 -0
- AeroViz/plot/timeseries/__init__.py +2 -0
- AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/template.py +47 -0
- AeroViz/plot/timeseries/timeseries.py +446 -0
- AeroViz/plot/utils/__init__.py +4 -0
- AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/_color.py +71 -0
- AeroViz/plot/utils/_unit.py +55 -0
- AeroViz/plot/utils/fRH.json +390 -0
- AeroViz/plot/utils/plt_utils.py +92 -0
- AeroViz/plot/utils/sklearn_utils.py +49 -0
- AeroViz/plot/utils/units.json +89 -0
- AeroViz/plot/violin.py +80 -0
- AeroViz/rawDataReader/FLOW.md +138 -0
- AeroViz/rawDataReader/__init__.py +220 -0
- AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__init__.py +0 -0
- AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/supported_instruments.py +135 -0
- AeroViz/rawDataReader/core/__init__.py +658 -0
- AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/logger.py +171 -0
- AeroViz/rawDataReader/core/pre_process.py +308 -0
- AeroViz/rawDataReader/core/qc.py +961 -0
- AeroViz/rawDataReader/core/report.py +579 -0
- AeroViz/rawDataReader/script/AE33.py +173 -0
- AeroViz/rawDataReader/script/AE43.py +151 -0
- AeroViz/rawDataReader/script/APS.py +339 -0
- AeroViz/rawDataReader/script/Aurora.py +191 -0
- AeroViz/rawDataReader/script/BAM1020.py +90 -0
- AeroViz/rawDataReader/script/BC1054.py +161 -0
- AeroViz/rawDataReader/script/EPA.py +79 -0
- AeroViz/rawDataReader/script/GRIMM.py +68 -0
- AeroViz/rawDataReader/script/IGAC.py +140 -0
- AeroViz/rawDataReader/script/MA350.py +179 -0
- AeroViz/rawDataReader/script/Minion.py +218 -0
- AeroViz/rawDataReader/script/NEPH.py +199 -0
- AeroViz/rawDataReader/script/OCEC.py +173 -0
- AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
- AeroViz/rawDataReader/script/SMPS.py +389 -0
- AeroViz/rawDataReader/script/TEOM.py +181 -0
- AeroViz/rawDataReader/script/VOC.py +106 -0
- AeroViz/rawDataReader/script/Xact.py +244 -0
- AeroViz/rawDataReader/script/__init__.py +28 -0
- AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__init__.py +2 -0
- AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
- AeroViz/tools/database.py +95 -0
- AeroViz/tools/dataclassifier.py +117 -0
- AeroViz/tools/dataprinter.py +58 -0
- aeroviz-0.1.21.dist-info/METADATA +294 -0
- aeroviz-0.1.21.dist-info/RECORD +180 -0
- aeroviz-0.1.21.dist-info/WHEEL +5 -0
- aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
- 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
|
|
Binary file
|
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())
|
AeroViz/plot/__init__.py
ADDED
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|