birdrecord-cli 0.1.1__tar.gz → 0.1.3__tar.gz
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.
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/PKG-INFO +5 -5
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/README.md +2 -2
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/cli/adcode/__init__.py +6 -2
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/cli/core.py +2 -2
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/cli/report/__init__.py +7 -1
- birdrecord_cli-0.1.3/birdrecord_cli/cli/search/__init__.py +282 -0
- birdrecord_cli-0.1.3/birdrecord_cli/cli/search/report_map.py +433 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/cli/taxon/__init__.py +7 -1
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/client.py +26 -10
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/i18n.py +1 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/cli/stdout.py +7 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/cli/unified_search.py +3 -1
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/activity_payloads.py +2 -6
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/activity_requests.py +3 -1
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/taxon.py +4 -4
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli.egg-info/PKG-INFO +5 -5
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli.egg-info/SOURCES.txt +1 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli.egg-info/requires.txt +1 -1
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/pyproject.toml +4 -3
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/tests/test_birdrecord_client.py +46 -19
- birdrecord_cli-0.1.1/birdrecord_cli/cli/search/__init__.py +0 -143
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/LICENSE +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/__init__.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/cli/__init__.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/cli_main.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/constants.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/crypto.py +1 -1
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/__init__.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/cli/__init__.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/__init__.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/adcode.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/chart_payloads.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/chart_requests.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/envelopes.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/report_payloads.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli/models/client/report_requests.py +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli.egg-info/dependency_links.txt +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli.egg-info/entry_points.txt +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/birdrecord_cli.egg-info/top_level.txt +0 -0
- {birdrecord_cli-0.1.1 → birdrecord_cli-0.1.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: birdrecord-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: CLI for China Bird Record (birdreport.cn); default API host weixin.birdrecord.cn.
|
|
5
5
|
Author: yoshino-s
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,7 +10,7 @@ Project-URL: Documentation, https://github.com/yoshino-s/birdrecord-cli#readme
|
|
|
10
10
|
Project-URL: Issues, https://github.com/yoshino-s/birdrecord-cli/issues
|
|
11
11
|
Project-URL: Changelog, https://github.com/yoshino-s/birdrecord-cli/releases
|
|
12
12
|
Project-URL: PyPI, https://pypi.org/project/birdrecord-cli/
|
|
13
|
-
Keywords: birdrecord,birding,cli,weixin,miniprogram,
|
|
13
|
+
Keywords: birdrecord,birding,cli,weixin,miniprogram,requests,click
|
|
14
14
|
Classifier: Development Status :: 4 - Beta
|
|
15
15
|
Classifier: Environment :: Console
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
@@ -22,7 +22,7 @@ Requires-Python: >=3.12
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: click>=8.1.0
|
|
25
|
-
Requires-Dist:
|
|
25
|
+
Requires-Dist: requests>=2.32.0
|
|
26
26
|
Requires-Dist: pycryptodome>=3.21.0
|
|
27
27
|
Requires-Dist: pydantic>=2.10.0
|
|
28
28
|
Requires-Dist: pypinyin>=0.53.0
|
|
@@ -69,8 +69,8 @@ Package index: [pypi.org/project/birdrecord-cli](https://pypi.org/project/birdre
|
|
|
69
69
|
[uv](https://docs.astral.sh/uv/) downloads the package into an ephemeral environment. Pin the version for reproducible behavior:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
uvx --from 'birdrecord-cli==0.1.
|
|
73
|
-
uvx --from 'birdrecord-cli==0.1.
|
|
72
|
+
uvx --from 'birdrecord-cli==0.1.3' birdrecord-cli --help
|
|
73
|
+
uvx --from 'birdrecord-cli==0.1.3' birdrecord-cli provinces --pretty
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
Use the latest release version from PyPI if it differs from the example above.
|
|
@@ -39,8 +39,8 @@ Package index: [pypi.org/project/birdrecord-cli](https://pypi.org/project/birdre
|
|
|
39
39
|
[uv](https://docs.astral.sh/uv/) downloads the package into an ephemeral environment. Pin the version for reproducible behavior:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
uvx --from 'birdrecord-cli==0.1.
|
|
43
|
-
uvx --from 'birdrecord-cli==0.1.
|
|
42
|
+
uvx --from 'birdrecord-cli==0.1.3' birdrecord-cli --help
|
|
43
|
+
uvx --from 'birdrecord-cli==0.1.3' birdrecord-cli provinces --pretty
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Use the latest release version from PyPI if it differs from the example above.
|
|
@@ -68,7 +68,9 @@ def register_adcode_commands(group: click.Group) -> None:
|
|
|
68
68
|
filtered = filter_region_rows_by_query(
|
|
69
69
|
list(raw.payload), query, label_attr="province_name"
|
|
70
70
|
)
|
|
71
|
-
emit_call(
|
|
71
|
+
emit_call(
|
|
72
|
+
cfg, _standard_list_call_after_query_filter(raw, filtered, query=query)
|
|
73
|
+
)
|
|
72
74
|
|
|
73
75
|
@group.command(
|
|
74
76
|
"cities",
|
|
@@ -127,4 +129,6 @@ def register_adcode_commands(group: click.Group) -> None:
|
|
|
127
129
|
filtered = filter_region_rows_by_query(
|
|
128
130
|
list(raw.payload), query, label_attr="city_name"
|
|
129
131
|
)
|
|
130
|
-
emit_call(
|
|
132
|
+
emit_call(
|
|
133
|
+
cfg, _standard_list_call_after_query_filter(raw, filtered, query=query)
|
|
134
|
+
)
|
|
@@ -8,7 +8,7 @@ from dataclasses import dataclass
|
|
|
8
8
|
from typing import Any, Callable, Mapping, Type
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
|
-
import
|
|
11
|
+
import requests
|
|
12
12
|
from pydantic import BaseModel
|
|
13
13
|
|
|
14
14
|
from birdrecord_cli.client import BirdrecordApiError, BirdrecordCall, BirdrecordClient
|
|
@@ -119,7 +119,7 @@ class BirdrecordGroup(click.Group):
|
|
|
119
119
|
if e.envelope is not None:
|
|
120
120
|
emit_json(e.envelope, pretty=pretty)
|
|
121
121
|
raise click.exceptions.Exit(1) from e
|
|
122
|
-
except
|
|
122
|
+
except requests.exceptions.RequestException as e:
|
|
123
123
|
click.echo(
|
|
124
124
|
f"{_cli_txt('HTTP error:', 'HTTP 错误:')} {e}",
|
|
125
125
|
err=True,
|
|
@@ -4,7 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from birdrecord_cli.cli.core import
|
|
7
|
+
from birdrecord_cli.cli.core import (
|
|
8
|
+
CliConfig,
|
|
9
|
+
client_from_cfg,
|
|
10
|
+
emit_enveloped_model,
|
|
11
|
+
json_schema_text,
|
|
12
|
+
with_client_config,
|
|
13
|
+
)
|
|
8
14
|
from birdrecord_cli.i18n import _cli_txt
|
|
9
15
|
from birdrecord_cli.models.client import ReportBundleResult
|
|
10
16
|
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""CLI: chart search statistic and optional activity drill-down."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import requests
|
|
9
|
+
from click._utils import FLAG_NEEDS_VALUE
|
|
10
|
+
|
|
11
|
+
from birdrecord_cli.cli.core import (
|
|
12
|
+
CliConfig,
|
|
13
|
+
client_from_cfg,
|
|
14
|
+
emit_json,
|
|
15
|
+
json_schema_text_object,
|
|
16
|
+
parse_cli_body_json,
|
|
17
|
+
with_client_config,
|
|
18
|
+
)
|
|
19
|
+
from birdrecord_cli.i18n import _cli_txt
|
|
20
|
+
from birdrecord_cli.models.client import (
|
|
21
|
+
ChartActivityReportRow,
|
|
22
|
+
ChartActivityTaxonRow,
|
|
23
|
+
build_common_list_taxon_request,
|
|
24
|
+
build_common_page_activity_request,
|
|
25
|
+
)
|
|
26
|
+
from birdrecord_cli.models.cli import (
|
|
27
|
+
UnifiedSearchRequest,
|
|
28
|
+
UnifiedSearchResult,
|
|
29
|
+
coerce_unified_search_request,
|
|
30
|
+
unified_search_to_common_activity,
|
|
31
|
+
unified_search_to_region_chart,
|
|
32
|
+
)
|
|
33
|
+
from birdrecord_cli.cli.search.report_map import (
|
|
34
|
+
render_report_map_html,
|
|
35
|
+
upload_report_map_html,
|
|
36
|
+
write_report_map_html,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class OptionalValueFlagOption(click.Option):
|
|
41
|
+
"""Flag option that optionally consumes a following value token."""
|
|
42
|
+
|
|
43
|
+
_previous_parser_process: Callable[[Any, Any], None]
|
|
44
|
+
|
|
45
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
46
|
+
self.optional_value = kwargs.pop("optional_value", None)
|
|
47
|
+
super().__init__(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
def add_to_parser(self, parser: Any, ctx: click.Context) -> None:
|
|
50
|
+
super().add_to_parser(parser, ctx)
|
|
51
|
+
|
|
52
|
+
def parser_process(value: Any, state: Any) -> None:
|
|
53
|
+
if value is FLAG_NEEDS_VALUE:
|
|
54
|
+
value = self.optional_value
|
|
55
|
+
self._previous_parser_process(value, state)
|
|
56
|
+
|
|
57
|
+
for opt in self.opts:
|
|
58
|
+
option = parser._long_opt.get(opt) or parser._short_opt.get(opt)
|
|
59
|
+
if option is not None:
|
|
60
|
+
self._previous_parser_process = option.process
|
|
61
|
+
option.process = parser_process
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def register_search_commands(group: click.Group) -> None:
|
|
66
|
+
@group.command(
|
|
67
|
+
"search",
|
|
68
|
+
short_help=_cli_txt(
|
|
69
|
+
"Chart search statistic; optional activity --taxon / --report.",
|
|
70
|
+
"图表检索统计;可选活动下钻 --taxon / --report。",
|
|
71
|
+
),
|
|
72
|
+
help=_cli_txt(
|
|
73
|
+
(
|
|
74
|
+
"Chart search: per-month breakdown and rolled-up totals (--body-json). "
|
|
75
|
+
"Add --taxon for species ranking and/or --report for paged cards; omit both to skip those calls."
|
|
76
|
+
),
|
|
77
|
+
(
|
|
78
|
+
"图表检索:按月拆分与汇总(--body-json)。"
|
|
79
|
+
"需要活动下钻时加 --taxon(鸟种排行)和/或 --report(分页记录);两者都不传则不请求这两项。"
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
@click.option(
|
|
84
|
+
"--taxon",
|
|
85
|
+
"want_taxon",
|
|
86
|
+
is_flag=True,
|
|
87
|
+
help=_cli_txt(
|
|
88
|
+
"Include per-species record counts for the chart month (common/list).",
|
|
89
|
+
"包含图表月份内各鸟种记录数(common/list)。",
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
@click.option(
|
|
93
|
+
"--report",
|
|
94
|
+
"want_report",
|
|
95
|
+
is_flag=True,
|
|
96
|
+
help=_cli_txt(
|
|
97
|
+
"Include paged observation report cards (common/page).",
|
|
98
|
+
"包含分页观鸟记录卡片(common/page)。",
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
@click.option(
|
|
102
|
+
"--report-map",
|
|
103
|
+
"report_map",
|
|
104
|
+
cls=OptionalValueFlagOption,
|
|
105
|
+
is_flag=False,
|
|
106
|
+
required=False,
|
|
107
|
+
type=str,
|
|
108
|
+
optional_value="output/report_map.html",
|
|
109
|
+
metavar="[OUTPUT_HTML]",
|
|
110
|
+
help=_cli_txt(
|
|
111
|
+
"Generate REPORT MAP: local HTML path (default output/report_map.html) or ONLINE to upload and return URL.",
|
|
112
|
+
"生成 REPORT MAP:本地 HTML 路径(默认 output/report_map.html)或 ONLINE(上传并返回 URL)。",
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
@click.option(
|
|
116
|
+
"--report-limit",
|
|
117
|
+
"report_limit",
|
|
118
|
+
default=None,
|
|
119
|
+
type=int,
|
|
120
|
+
help=_cli_txt(
|
|
121
|
+
"Max total report rows to fetch across pages (stops paging once reached); default = fetch all pages.",
|
|
122
|
+
"跨分页最多获取的记录条数上限(达到后停止翻页);默认获取全部分页。",
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
@click.option(
|
|
126
|
+
"--body-json",
|
|
127
|
+
default=None,
|
|
128
|
+
help=_cli_txt(
|
|
129
|
+
"Unified filter JSON (UnifiedSearchRequest): chart fields plus optional taxon_month, report_month, outside_type for drill-down.",
|
|
130
|
+
"统一筛选 JSON(UnifiedSearchRequest):图表字段 + 下钻可选 taxon_month、report_month、outside_type。",
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
@click.option(
|
|
134
|
+
"--schema",
|
|
135
|
+
is_flag=True,
|
|
136
|
+
help=_cli_txt(
|
|
137
|
+
"Print JSON Schemas for request (UnifiedSearchRequest) and response (UnifiedSearchResult) only (no HTTP).",
|
|
138
|
+
"仅打印请求(UnifiedSearchRequest)与响应(UnifiedSearchResult)的 JSON Schema(不发起 HTTP)。",
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
@click.pass_context
|
|
142
|
+
@with_client_config
|
|
143
|
+
def cmd_search(
|
|
144
|
+
ctx: click.Context,
|
|
145
|
+
want_taxon: bool,
|
|
146
|
+
want_report: bool,
|
|
147
|
+
report_map: str | None,
|
|
148
|
+
report_limit: int | None,
|
|
149
|
+
body_json: str | None,
|
|
150
|
+
schema: bool,
|
|
151
|
+
) -> None:
|
|
152
|
+
if report_map and not want_report:
|
|
153
|
+
raise click.UsageError(
|
|
154
|
+
_cli_txt(
|
|
155
|
+
"--report-map requires --report.",
|
|
156
|
+
"--report-map 需要与 --report 一起使用。",
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
if schema:
|
|
160
|
+
click.echo(
|
|
161
|
+
json_schema_text_object(
|
|
162
|
+
{
|
|
163
|
+
"request": UnifiedSearchRequest,
|
|
164
|
+
"response": UnifiedSearchResult,
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
return
|
|
169
|
+
cfg = ctx.obj
|
|
170
|
+
assert isinstance(cfg, CliConfig)
|
|
171
|
+
raw_body = parse_cli_body_json(body_json)
|
|
172
|
+
unified = coerce_unified_search_request(raw_body)
|
|
173
|
+
with client_from_cfg(cfg) as client:
|
|
174
|
+
stat_fetch = client.fetch_search_statistic(
|
|
175
|
+
unified_search_to_region_chart(unified),
|
|
176
|
+
collect_envelopes=cfg.envelope,
|
|
177
|
+
)
|
|
178
|
+
taxon_rows: list[ChartActivityTaxonRow] | None = None
|
|
179
|
+
report_rows: list[ChartActivityReportRow] | None = None
|
|
180
|
+
envelopes = dict(stat_fetch.envelopes)
|
|
181
|
+
if want_taxon or want_report:
|
|
182
|
+
base = unified_search_to_common_activity(unified)
|
|
183
|
+
if want_taxon:
|
|
184
|
+
tcall = client.common_list_activity_taxon(
|
|
185
|
+
build_common_list_taxon_request(base)
|
|
186
|
+
)
|
|
187
|
+
taxon_rows = tcall.payload
|
|
188
|
+
if cfg.envelope:
|
|
189
|
+
envelopes["taxon"] = tcall.envelope.model_dump()
|
|
190
|
+
if want_report:
|
|
191
|
+
report_rows = []
|
|
192
|
+
page = 1
|
|
193
|
+
while True:
|
|
194
|
+
rcall = client.common_page_activity(
|
|
195
|
+
build_common_page_activity_request(
|
|
196
|
+
base,
|
|
197
|
+
report_month=unified.report_month,
|
|
198
|
+
start=page,
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
batch = rcall.payload or []
|
|
202
|
+
report_rows.extend(batch)
|
|
203
|
+
if cfg.envelope:
|
|
204
|
+
envelopes[f"report_page_{page}"] = (
|
|
205
|
+
rcall.envelope.model_dump()
|
|
206
|
+
)
|
|
207
|
+
# Stop if no rows returned
|
|
208
|
+
if not batch:
|
|
209
|
+
break
|
|
210
|
+
# Stop if reached the limit
|
|
211
|
+
if (
|
|
212
|
+
report_limit is not None
|
|
213
|
+
and len(report_rows) >= report_limit
|
|
214
|
+
):
|
|
215
|
+
report_rows = report_rows[:report_limit]
|
|
216
|
+
break
|
|
217
|
+
# Stop if we've consumed all available pages
|
|
218
|
+
env = rcall.envelope
|
|
219
|
+
total = getattr(env, "total", None)
|
|
220
|
+
size = getattr(env, "size", None) or len(batch)
|
|
221
|
+
if total is not None and len(report_rows) >= total:
|
|
222
|
+
break
|
|
223
|
+
if len(batch) < size:
|
|
224
|
+
break
|
|
225
|
+
page += 1
|
|
226
|
+
report_map_ref: str | None = None
|
|
227
|
+
if report_map and report_rows is not None:
|
|
228
|
+
if report_map.strip().casefold() == "online":
|
|
229
|
+
html = render_report_map_html(
|
|
230
|
+
report_rows,
|
|
231
|
+
province=unified.province,
|
|
232
|
+
city=unified.city,
|
|
233
|
+
district=unified.district,
|
|
234
|
+
)
|
|
235
|
+
try:
|
|
236
|
+
report_map_ref = upload_report_map_html(
|
|
237
|
+
html,
|
|
238
|
+
timeout=cfg.timeout,
|
|
239
|
+
)
|
|
240
|
+
except (requests.RequestException, ValueError) as exc:
|
|
241
|
+
raise click.ClickException(
|
|
242
|
+
_cli_txt(
|
|
243
|
+
f"Failed to upload REPORT MAP ONLINE: {exc}",
|
|
244
|
+
f"REPORT MAP ONLINE 上传失败:{exc}",
|
|
245
|
+
)
|
|
246
|
+
) from exc
|
|
247
|
+
click.echo(
|
|
248
|
+
_cli_txt(
|
|
249
|
+
f"Generated REPORT MAP URL: {report_map_ref}",
|
|
250
|
+
f"已生成 REPORT MAP URL:{report_map_ref}",
|
|
251
|
+
),
|
|
252
|
+
err=True,
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
out_path = write_report_map_html(
|
|
256
|
+
report_rows,
|
|
257
|
+
output_path=report_map,
|
|
258
|
+
province=unified.province,
|
|
259
|
+
city=unified.city,
|
|
260
|
+
district=unified.district,
|
|
261
|
+
)
|
|
262
|
+
report_map_ref = str(out_path)
|
|
263
|
+
click.echo(
|
|
264
|
+
_cli_txt(
|
|
265
|
+
f"Generated REPORT MAP HTML: {out_path}",
|
|
266
|
+
f"已生成 REPORT MAP HTML:{out_path}",
|
|
267
|
+
),
|
|
268
|
+
err=True,
|
|
269
|
+
)
|
|
270
|
+
out = UnifiedSearchResult(
|
|
271
|
+
statistic=stat_fetch.result,
|
|
272
|
+
taxon=taxon_rows,
|
|
273
|
+
report=report_rows,
|
|
274
|
+
report_map=report_map_ref,
|
|
275
|
+
)
|
|
276
|
+
if cfg.envelope:
|
|
277
|
+
emit_json(
|
|
278
|
+
{"envelope": envelopes, "payload": out.model_dump(mode="json")},
|
|
279
|
+
pretty=cfg.pretty,
|
|
280
|
+
)
|
|
281
|
+
else:
|
|
282
|
+
emit_json(out.model_dump(mode="json"), pretty=cfg.pretty)
|