orbitkit 0.8.78__tar.gz → 0.9.11__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.
- {orbitkit-0.8.78/orbitkit.egg-info → orbitkit-0.9.11}/PKG-INFO +1 -1
- orbitkit-0.9.11/orbitkit/VERSION +1 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/data_preprocessing.py +1 -0
- orbitkit-0.9.11/orbitkit/airflow_handler/file_flow_entry.py +46 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/file_flow_entry_process.py +23 -6
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/file_flow_exit_process.py +43 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/file_handler_v2.py +14 -7
- orbitkit-0.9.11/orbitkit/cdn_uploader/__init__.py +16 -0
- orbitkit-0.9.11/orbitkit/cdn_uploader/uploader.py +151 -0
- orbitkit-0.9.11/orbitkit/pdf_transformer/__init__.py +16 -0
- orbitkit-0.9.11/orbitkit/pdf_transformer/mineru/__init__.py +41 -0
- orbitkit-0.9.11/orbitkit/pdf_transformer/mineru/bbox_rotation.py +218 -0
- orbitkit-0.9.11/orbitkit/pdf_transformer/mineru/result_transformer.py +687 -0
- orbitkit-0.9.11/orbitkit/pdf_validator/__init__.py +35 -0
- orbitkit-0.9.11/orbitkit/pdf_validator/filters/__init__.py +1 -0
- orbitkit-0.9.11/orbitkit/pdf_validator/filters/drop_non_text.py +19 -0
- orbitkit-0.9.11/orbitkit/pdf_validator/filters/drop_vlm_artifacts.py +69 -0
- orbitkit-0.9.11/orbitkit/pdf_validator/filters/repeated_text.py +128 -0
- orbitkit-0.9.11/orbitkit/pdf_validator/pipeline.py +103 -0
- orbitkit-0.9.11/orbitkit/pdf_validator/registry.py +64 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11/orbitkit.egg-info}/PKG-INFO +1 -1
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/SOURCES.txt +14 -0
- orbitkit-0.8.78/orbitkit/VERSION +0 -1
- {orbitkit-0.8.78 → orbitkit-0.9.11}/LICENSE +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/MANIFEST.in +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/README.md +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/file_handler.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/audio_transcoder/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/audio_transcoder/netmind_extract_v1.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/constant/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/constant/report_schema.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/id_srv/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/id_srv/id_gen.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/id_srv/id_perm_like.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/lark_send/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/lark_send/lark.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/llm_tools/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/llm_tools/quick_rag_chat.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/orbit_type/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/orbit_type/doc_4_compile_rule.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/orbit_type/orbit_type_simple.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/orbit_type/tools.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_embedding/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_embedding/pdf_txt_embedding.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_embedding/pdf_txt_embedding_v2.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/a_stock_extractor_v1.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/exceptions.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/mineru_demo.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_block_extractor_base.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_block_extractor_v1.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_block_extractor_v2.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_azure.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_minerU_v1.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_netmind_v1.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_netmind_v2.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_netmind_v3.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_orbit.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/base.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/cloud_provider.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/core.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/exceptions.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/extractors.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/utils.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_writer/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_writer/pdf_writer_simple.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/__init__.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/cache_asset_downloader.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/common.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/customize_regix_manager.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/is_xbrl_structure.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/secret_manager.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/universal_extractor.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_aliyun.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_aliyun_oss_simple.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_aws.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_aws_s3_wrapper.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_date.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_html.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_kafka.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_md5.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_selenium.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_simple_timer.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_str.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_type_mapping.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_url.py +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/dependency_links.txt +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/not-zip-safe +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/requires.txt +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/top_level.txt +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/setup.cfg +0 -0
- {orbitkit-0.8.78 → orbitkit-0.9.11}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.9.11
|
|
@@ -160,6 +160,7 @@ class DocumentProcessor:
|
|
|
160
160
|
"title": title,
|
|
161
161
|
"published": doc['x_reported_at_utc_date'],
|
|
162
162
|
"tickers": [],
|
|
163
|
+
"tickers_map_arr": [],
|
|
163
164
|
"perm_id_list": doc['x_orbit_data']['perm_id_list'],
|
|
164
165
|
"report_type_id_list_str": doc['x_orbit_data']['report_type_id_list']
|
|
165
166
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""file_flow 任务下发入口(独立运维工具,旁路)。
|
|
2
|
+
|
|
3
|
+
通过 orbitkit.airflow_handler.FilingOfficialProcessor 把 MongoDB query 命中的 ID
|
|
4
|
+
派发到 worker 队列。本入口与 .claude/skills/attachments-process-* 4-skill 体系并行,
|
|
5
|
+
**不经 runtime registry**,仅作运维直跑使用。
|
|
6
|
+
|
|
7
|
+
queue_type 选项:
|
|
8
|
+
|
|
9
|
+
- ``MineruExtract`` : CPU 队列,默认,吞吐适中、成本低
|
|
10
|
+
- ``MineruExtract-GPU`` : GPU 队列,吞吐高,需要 GPU 资源就绪
|
|
11
|
+
|
|
12
|
+
切换 queue_type 时通常同步调整 priority(GPU 路径占用更高优先级)。
|
|
13
|
+
|
|
14
|
+
与 4-skill 体系的关系见项目根 ``CLAUDE.md`` 的「attachments_process/(独立运维工具集,旁路)」段。
|
|
15
|
+
"""
|
|
16
|
+
import csv
|
|
17
|
+
import datetime
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import asyncio
|
|
21
|
+
from orbitkit.airflow_handler.file_flow_entry_process import FilingOfficialProcessor
|
|
22
|
+
from orbitkit.util.secret_manager import SecretManager
|
|
23
|
+
|
|
24
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def main_loop(max_process_count):
|
|
29
|
+
SecretManager.load_secret_2_envs(['prod/fileflow/data'])
|
|
30
|
+
processor = FilingOfficialProcessor(
|
|
31
|
+
mongo_uri=os.environ['MONGO_URI'],
|
|
32
|
+
postgres_uri=os.environ['POSTGRES_URI'],
|
|
33
|
+
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
|
|
34
|
+
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
|
|
35
|
+
pi2_postgres_uri=os.environ['PG_URI_CX45_USER_FILING']
|
|
36
|
+
)
|
|
37
|
+
query_dict = {
|
|
38
|
+
'_id': 'f_aXz7Y4n9qTlTRyuTc8sWsb',
|
|
39
|
+
}
|
|
40
|
+
await processor.process_task_entry(source='filing_data_staging', query=query_dict,
|
|
41
|
+
tags=['daily_increment_data'], allow_parse_over_limit=True,
|
|
42
|
+
priority='1', is_important=False, important_level=1, queue_type='MineruExtract-GPU')
|
|
43
|
+
|
|
44
|
+
if __name__ == '__main__':
|
|
45
|
+
asyncio.run(main_loop(1))
|
|
46
|
+
|
|
@@ -29,6 +29,7 @@ class FilingOfficialProcessor:
|
|
|
29
29
|
|
|
30
30
|
self.mongo_client = pymongo.MongoClient(mongo_uri)
|
|
31
31
|
self.data_xbrl_convert_collection = self.mongo_client['filing_reports']['data_xbrl_convert']
|
|
32
|
+
self.sm_orbit_entity_id_cache_collection = self.mongo_client['stat_cache']['sm_orbit_entity_id_cache']
|
|
32
33
|
|
|
33
34
|
postgres_uri = os.environ.get('PG_URI_AIRFLOW12_USER_NEWSFEEDSITE') if not postgres_uri else postgres_uri
|
|
34
35
|
if not postgres_uri:
|
|
@@ -139,19 +140,33 @@ class FilingOfficialProcessor:
|
|
|
139
140
|
|
|
140
141
|
if row.ticker is not None:
|
|
141
142
|
orbit_entity_id_ticker_map[row.orbit_entity_id].append(row.ticker)
|
|
143
|
+
# 批量查询 organization_name / primary_ticker(走查 doc 的同一个 mongo 连接)
|
|
144
|
+
orbit_entity_id_org_map = {}
|
|
145
|
+
cache_cursor = self.sm_orbit_entity_id_cache_collection.find(
|
|
146
|
+
{'orbit_entity_id': {'$in': perm_id_list}},
|
|
147
|
+
{'orbit_entity_id': 1, 'organization_name': 1, 'primary_ticker': 1}
|
|
148
|
+
).batch_size(self.max_batch_size)
|
|
149
|
+
for row in cache_cursor:
|
|
150
|
+
orbit_entity_id_org_map[row['orbit_entity_id']] = (row.get('organization_name'), row.get('primary_ticker'))
|
|
151
|
+
|
|
142
152
|
for step_info, records in file_flow_info.items():
|
|
143
153
|
for record in records:
|
|
144
154
|
if 'extends' in record and record.get('extends') is not None:
|
|
145
155
|
tickers = set()
|
|
156
|
+
tickers_map_arr = []
|
|
146
157
|
for i in record['extends']['perm_id_list']:
|
|
147
158
|
tickers.update(orbit_entity_id_ticker_map.get(i, []))
|
|
159
|
+
org_info = orbit_entity_id_org_map.get(i)
|
|
160
|
+
if org_info and org_info[0] is not None:
|
|
161
|
+
tickers_map_arr.append({org_info[0]: org_info[1]})
|
|
148
162
|
record['extends']['tickers'] = list(tickers)
|
|
163
|
+
record['extends']['tickers_map_arr'] = tickers_map_arr
|
|
149
164
|
|
|
150
165
|
record['extends']['report_type_id_list_str'] = [self.report_type_id_name_map.get(i) for i in record['extends']['report_type_id_list_str']]
|
|
151
166
|
|
|
152
167
|
return file_flow_info
|
|
153
168
|
|
|
154
|
-
def send_task(self, file_flow_info, tags, is_important, priority, spider_name_source_type, queue_type):
|
|
169
|
+
def send_task(self, file_flow_info, tags, is_important, priority, spider_name_source_type, queue_type, allow_parse_over_limit=False):
|
|
155
170
|
for step_str, records in file_flow_info.items():
|
|
156
171
|
steps = step_str.split('@__@')
|
|
157
172
|
start_stage = steps[0]
|
|
@@ -173,13 +188,15 @@ class FilingOfficialProcessor:
|
|
|
173
188
|
priority=priority,
|
|
174
189
|
source_type=spider_name_source_type[
|
|
175
190
|
x_spider_name],
|
|
176
|
-
queue_type=queue_type
|
|
191
|
+
queue_type=queue_type,
|
|
192
|
+
allow_parse_over_limit=allow_parse_over_limit)
|
|
177
193
|
else:
|
|
178
194
|
status, ids, message = self.file_handler.entry_point(records=records, start_stage=start_stage,
|
|
179
195
|
target_stage=target_stage, tags=tags,tag=x_spider_name,
|
|
180
196
|
priority=priority,
|
|
181
197
|
source_type=spider_name_source_type[x_spider_name],
|
|
182
|
-
queue_type=queue_type
|
|
198
|
+
queue_type=queue_type,
|
|
199
|
+
allow_parse_over_limit=allow_parse_over_limit)
|
|
183
200
|
self.all_stat_count['file_flow'] += len(records)
|
|
184
201
|
logger.info(f"{len(records)}--{start_stage}-{target_stage}-{x_spider_name} status: {status}, message: {message}")
|
|
185
202
|
|
|
@@ -215,7 +232,7 @@ class FilingOfficialProcessor:
|
|
|
215
232
|
|
|
216
233
|
async def process_task_entry(self, source: str,
|
|
217
234
|
query: dict, tags: list[str], priority: str,
|
|
218
|
-
is_important: bool = False, custom_step: Optional[list[str]] = None, important_level = None, db_name: str = None, queue_type: str = 'Default'):
|
|
235
|
+
is_important: bool = False, custom_step: Optional[list[str]] = None, important_level = None, db_name: str = None, queue_type: str = 'Default', allow_parse_over_limit: bool = False):
|
|
219
236
|
|
|
220
237
|
if source == 'filing_data':
|
|
221
238
|
raise ValueError(f"Cannot update documents from 'filing_data' source. ")
|
|
@@ -253,7 +270,7 @@ class FilingOfficialProcessor:
|
|
|
253
270
|
self.all_stat_count['doc_error'] += len(doc_error_list)
|
|
254
271
|
self.all_stat_count['step_error'] += len(except_id_list)
|
|
255
272
|
self.all_stat_count['xbrl'] += len(xbrl_data)
|
|
256
|
-
self.send_task(file_flow_info, tags, is_important, priority, spider_name_source_type, queue_type)
|
|
273
|
+
self.send_task(file_flow_info, tags, is_important, priority, spider_name_source_type, queue_type, allow_parse_over_limit)
|
|
257
274
|
self.send_xbrl_data_to_mongo(xbrl_data)
|
|
258
275
|
self.update_doc_status_to_convert(collection, doc_error_list)
|
|
259
276
|
process_data.clear()
|
|
@@ -266,7 +283,7 @@ class FilingOfficialProcessor:
|
|
|
266
283
|
self.all_stat_count['doc_error'] += len(doc_error_list)
|
|
267
284
|
self.all_stat_count['step_error'] += len(except_id_list)
|
|
268
285
|
self.all_stat_count['xbrl'] += len(xbrl_data)
|
|
269
|
-
self.send_task(file_flow_info, tags, is_important, priority, spider_name_source_type, queue_type)
|
|
286
|
+
self.send_task(file_flow_info, tags, is_important, priority, spider_name_source_type, queue_type, allow_parse_over_limit)
|
|
270
287
|
self.send_xbrl_data_to_mongo(xbrl_data)
|
|
271
288
|
self.update_doc_status_to_convert(collection, doc_error_list)
|
|
272
289
|
process_data.clear()
|
|
@@ -91,6 +91,43 @@ class FlowUpdater:
|
|
|
91
91
|
else:
|
|
92
92
|
raise ValueError(f"Unknown step_stage: {step_stage}")
|
|
93
93
|
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _build_x_parse_record(attachments_pdf):
|
|
96
|
+
"""整合同一报告下所有 x_attachments_pdf 行的 x_parse_record,回写 mongo。
|
|
97
|
+
|
|
98
|
+
逐附件无损保留到 details(附加 store_path);summary 里放去重的 tools / tool_versions
|
|
99
|
+
及 is_mixed,方便报告级查询「用了什么工具/什么版本」。无有效记录返回 None。
|
|
100
|
+
"""
|
|
101
|
+
details = []
|
|
102
|
+
tools = []
|
|
103
|
+
tool_versions = []
|
|
104
|
+
for att in attachments_pdf or []:
|
|
105
|
+
rec = att.get('x_parse_record')
|
|
106
|
+
if not rec: # None / [] / {}
|
|
107
|
+
continue
|
|
108
|
+
detail = dict(rec)
|
|
109
|
+
detail['store_path'] = att.get('store_path')
|
|
110
|
+
details.append(detail)
|
|
111
|
+
name = rec.get('parsing_tool_name')
|
|
112
|
+
version = rec.get('parsing_tool_version')
|
|
113
|
+
if name:
|
|
114
|
+
tools.append(name)
|
|
115
|
+
if version:
|
|
116
|
+
tool_versions.append(f"{name}@{version}")
|
|
117
|
+
|
|
118
|
+
if not details:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
tool_versions = sorted(set(tool_versions))
|
|
122
|
+
return {
|
|
123
|
+
'summary': {
|
|
124
|
+
'tools': sorted(set(tools)),
|
|
125
|
+
'tool_versions': tool_versions,
|
|
126
|
+
'is_mixed': len(tool_versions) > 1
|
|
127
|
+
},
|
|
128
|
+
'details': details
|
|
129
|
+
}
|
|
130
|
+
|
|
94
131
|
def update_mongo_data(self, report_id, data_source, update_params, kafka_ignore):
|
|
95
132
|
if kafka_ignore:
|
|
96
133
|
update_params.update({self.kafka_ignore.X_OTHERS_K_IGNORE_KEY: self.kafka_ignore.get_k_ignore_val()})
|
|
@@ -157,6 +194,12 @@ class FlowUpdater:
|
|
|
157
194
|
item = self._step_handle(step, step_status, attachments, db_store_path, attachments_pdf)
|
|
158
195
|
update_params.update(item)
|
|
159
196
|
|
|
197
|
+
# 提取阶段完成即回写解析工具记录(不论解析成功与否)
|
|
198
|
+
if 'extract' in self.step_tuple[start_index:end_index + 1]:
|
|
199
|
+
parse_record = self._build_x_parse_record(attachments_pdf)
|
|
200
|
+
if parse_record is not None:
|
|
201
|
+
update_params['x_parse_record'] = parse_record
|
|
202
|
+
|
|
160
203
|
if update_params:
|
|
161
204
|
# logger.info(json.dumps(update_params, ensure_ascii=False, indent=2))
|
|
162
205
|
self.update_mongo_data(report_id, data_source, update_params, kafka_ignore)
|
|
@@ -142,7 +142,8 @@ class FileFlowHandleV2:
|
|
|
142
142
|
'tags': params['tags'],
|
|
143
143
|
'tag': params['tag'],
|
|
144
144
|
'important_level': record.get('important_level', 0),
|
|
145
|
-
'queue': params['queue']
|
|
145
|
+
'queue': params['queue'],
|
|
146
|
+
'allow_parse_over_limit': params.get('allow_parse_over_limit', False)
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
step = {
|
|
@@ -226,7 +227,8 @@ class FileFlowHandleV2:
|
|
|
226
227
|
tag: str = None,
|
|
227
228
|
priority: str = '1',
|
|
228
229
|
source_type: Optional[str] = None,
|
|
229
|
-
queue_type: str = 'Default'
|
|
230
|
+
queue_type: str = 'Default',
|
|
231
|
+
allow_parse_over_limit: bool = False
|
|
230
232
|
) -> Tuple[bool, Any, str]:
|
|
231
233
|
"""
|
|
232
234
|
普通任务接口
|
|
@@ -254,7 +256,8 @@ class FileFlowHandleV2:
|
|
|
254
256
|
"""
|
|
255
257
|
return self._file_flow_entry_point_internal(
|
|
256
258
|
records, start_stage, target_stage, tags, priority, source_type,tag,
|
|
257
|
-
urgent=False, clean_exist_data=False, queue_type=queue_type
|
|
259
|
+
urgent=False, clean_exist_data=False, queue_type=queue_type,
|
|
260
|
+
allow_parse_over_limit=allow_parse_over_limit
|
|
258
261
|
)
|
|
259
262
|
|
|
260
263
|
def entry_point_urgent(
|
|
@@ -266,7 +269,8 @@ class FileFlowHandleV2:
|
|
|
266
269
|
tag: str = None,
|
|
267
270
|
priority: str = '1',
|
|
268
271
|
source_type: Optional[str] = None,
|
|
269
|
-
queue_type: str = 'Default'
|
|
272
|
+
queue_type: str = 'Default',
|
|
273
|
+
allow_parse_over_limit: bool = False
|
|
270
274
|
) -> Tuple[bool, Any, str]:
|
|
271
275
|
"""
|
|
272
276
|
加急任务接口
|
|
@@ -294,7 +298,8 @@ class FileFlowHandleV2:
|
|
|
294
298
|
"""
|
|
295
299
|
return self._file_flow_entry_point_internal(
|
|
296
300
|
records, start_stage, target_stage, tags, priority, source_type, tag,
|
|
297
|
-
urgent=True, clean_exist_data=True, queue_type=queue_type
|
|
301
|
+
urgent=True, clean_exist_data=True, queue_type=queue_type,
|
|
302
|
+
allow_parse_over_limit=allow_parse_over_limit
|
|
298
303
|
)
|
|
299
304
|
|
|
300
305
|
def _file_flow_entry_point_internal(
|
|
@@ -308,7 +313,8 @@ class FileFlowHandleV2:
|
|
|
308
313
|
tag: str,
|
|
309
314
|
urgent: bool,
|
|
310
315
|
clean_exist_data: bool = False,
|
|
311
|
-
queue_type: str = 'Default'
|
|
316
|
+
queue_type: str = 'Default',
|
|
317
|
+
allow_parse_over_limit: bool = False
|
|
312
318
|
) -> Tuple[bool, Any, str]:
|
|
313
319
|
"""核心处理逻辑"""
|
|
314
320
|
params = {
|
|
@@ -320,7 +326,8 @@ class FileFlowHandleV2:
|
|
|
320
326
|
'source_type': source_type,
|
|
321
327
|
'tag': tag,
|
|
322
328
|
'urgent': urgent,
|
|
323
|
-
'queue': queue_type
|
|
329
|
+
'queue': queue_type,
|
|
330
|
+
'allow_parse_over_limit': allow_parse_over_limit
|
|
324
331
|
}
|
|
325
332
|
|
|
326
333
|
is_valid, msg = self._validate_params(params)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""cdn_uploader:经自建 CDN 网关做对象下载 / 上传(基于 requests,不用 boto3)。
|
|
2
|
+
|
|
3
|
+
用法::
|
|
4
|
+
|
|
5
|
+
from orbitkit.cdn_uploader import CdnUploader
|
|
6
|
+
|
|
7
|
+
with CdnUploader("https://oassets.orbitfin.ai", upload_key=KEY) as cdn:
|
|
8
|
+
cdn.upload_files("filing-reports", "txt-vector/<key>", result.to_files())
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .uploader import DEFAULT_CONTENT_TYPE, CdnUploader
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CdnUploader",
|
|
15
|
+
"DEFAULT_CONTENT_TYPE",
|
|
16
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""自建 CDN 网关客户端(省 S3 出站费),基于 ``requests``,不依赖 boto3。
|
|
2
|
+
|
|
3
|
+
设了 CDN 网关(形如 ``https://oassets.orbitfin.ai``,路径 ``/{bucket}/{key}``)
|
|
4
|
+
后,把对象的下载 / 上传改走网关而不是直连 S3。收益:S3→CDN 回源免费 +
|
|
5
|
+
CDN 出站费率低 / 有免费额度 → 省 egress。
|
|
6
|
+
|
|
7
|
+
用**独立的 requests.Session(trust_env=False,不读全局环境代理)**,是否走代理
|
|
8
|
+
由 ``proxies`` 显式控制:
|
|
9
|
+
- 传了 ``proxies`` → CDN 请求经该代理出境(例如 xproxy 开启时跟着走 xproxy);
|
|
10
|
+
- 不传 → 直连网关域名。
|
|
11
|
+
|
|
12
|
+
接口(与生产网关约定一致):
|
|
13
|
+
下载 GET {base}/{bucket}/{key}
|
|
14
|
+
- 带 ``x-upload-key`` 头(网关不需要会忽略)
|
|
15
|
+
- 成功 2xx,body = 原始字节
|
|
16
|
+
上传 PUT {base}/{bucket}/{key}
|
|
17
|
+
- ``x-upload-key``: 鉴权 key(**绝不硬编码**,由调用方传入)
|
|
18
|
+
- ``x-amz-content-sha256``: body 的 sha256 十六进制小写
|
|
19
|
+
- ``Content-Type``
|
|
20
|
+
- body = 文件内容
|
|
21
|
+
- 成功 2xx
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import hashlib
|
|
27
|
+
import logging
|
|
28
|
+
from typing import Dict, Mapping, Optional
|
|
29
|
+
from urllib.parse import quote
|
|
30
|
+
|
|
31
|
+
import requests
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
DEFAULT_CONTENT_TYPE = "application/octet-stream"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _proxies_arg(proxies: Optional[Mapping[str, str]], proxy_url: Optional[str]):
|
|
39
|
+
if proxies:
|
|
40
|
+
return dict(proxies)
|
|
41
|
+
if proxy_url:
|
|
42
|
+
return {"http": proxy_url, "https": proxy_url}
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CdnUploader:
|
|
47
|
+
"""经自建 CDN 网关做对象的下载 / 上传(同步)。
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
base_url: 网关根地址,如 ``https://oassets.orbitfin.ai``。
|
|
51
|
+
upload_key: 网关鉴权 key(``x-upload-key``);下载时也一并带上(网关
|
|
52
|
+
不需要会忽略)。**绝不硬编码**,由调用方从环境 / secret 传入。
|
|
53
|
+
timeout_seconds: 单次请求超时。
|
|
54
|
+
proxy_url: 简写:出境代理 URL(同时用于 http/https)。
|
|
55
|
+
proxies: requests 风格的 ``{"http": ..., "https": ...}``;给了则优先于
|
|
56
|
+
``proxy_url``。两者都不给 = 直连。
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
base_url: str,
|
|
62
|
+
upload_key: Optional[str] = None,
|
|
63
|
+
*,
|
|
64
|
+
timeout_seconds: int = 300,
|
|
65
|
+
proxy_url: Optional[str] = None,
|
|
66
|
+
proxies: Optional[Mapping[str, str]] = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
if not base_url:
|
|
69
|
+
raise ValueError("CdnUploader requires a non-empty base_url")
|
|
70
|
+
self._base = base_url.rstrip("/")
|
|
71
|
+
self._upload_key = upload_key
|
|
72
|
+
self._timeout = float(timeout_seconds)
|
|
73
|
+
self._proxies = _proxies_arg(proxies, proxy_url)
|
|
74
|
+
self._session = requests.Session()
|
|
75
|
+
# trust_env=False:不读全局 HTTP_PROXY/HTTPS_PROXY / netrc;代理只在
|
|
76
|
+
# 显式给了 proxies/proxy_url 时对这个 session 生效。
|
|
77
|
+
self._session.trust_env = False
|
|
78
|
+
|
|
79
|
+
def _url(self, bucket: str, key: str) -> str:
|
|
80
|
+
# key 里的 '/' 是路径分隔符要保留;其它特殊字符做 URL 编码。
|
|
81
|
+
safe_key = quote(key.lstrip("/"), safe="/")
|
|
82
|
+
return f"{self._base}/{bucket}/{safe_key}"
|
|
83
|
+
|
|
84
|
+
def download(self, bucket: str, key: str) -> bytes:
|
|
85
|
+
headers: Dict[str, str] = {}
|
|
86
|
+
if self._upload_key:
|
|
87
|
+
headers["x-upload-key"] = self._upload_key
|
|
88
|
+
resp = self._session.get(
|
|
89
|
+
self._url(bucket, key),
|
|
90
|
+
headers=headers,
|
|
91
|
+
proxies=self._proxies,
|
|
92
|
+
timeout=self._timeout,
|
|
93
|
+
)
|
|
94
|
+
resp.raise_for_status()
|
|
95
|
+
return resp.content
|
|
96
|
+
|
|
97
|
+
def upload(
|
|
98
|
+
self,
|
|
99
|
+
bucket: str,
|
|
100
|
+
key: str,
|
|
101
|
+
body: bytes,
|
|
102
|
+
content_type: str = DEFAULT_CONTENT_TYPE,
|
|
103
|
+
) -> None:
|
|
104
|
+
headers = {
|
|
105
|
+
"x-amz-content-sha256": hashlib.sha256(body).hexdigest(),
|
|
106
|
+
"Content-Type": content_type,
|
|
107
|
+
}
|
|
108
|
+
if self._upload_key:
|
|
109
|
+
headers["x-upload-key"] = self._upload_key
|
|
110
|
+
resp = self._session.put(
|
|
111
|
+
self._url(bucket, key),
|
|
112
|
+
headers=headers,
|
|
113
|
+
data=body,
|
|
114
|
+
proxies=self._proxies,
|
|
115
|
+
timeout=self._timeout,
|
|
116
|
+
)
|
|
117
|
+
resp.raise_for_status()
|
|
118
|
+
|
|
119
|
+
def upload_files(
|
|
120
|
+
self,
|
|
121
|
+
bucket: str,
|
|
122
|
+
key_prefix: str,
|
|
123
|
+
files: Mapping[str, bytes],
|
|
124
|
+
*,
|
|
125
|
+
content_type_for: Optional[Mapping[str, str]] = None,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""把 ``{相对路径: bytes}`` 批量上传到 ``{bucket}/{key_prefix}/{相对路径}``。
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
bucket: 目标 bucket。
|
|
131
|
+
key_prefix: key 前缀(会与相对路径以 ``/`` 拼接;空前缀直接用相对路径)。
|
|
132
|
+
files: 相对路径 → bytes,通常是 ``TransformResult.to_files()`` 的返回。
|
|
133
|
+
content_type_for: 可选,按相对路径覆盖 Content-Type;缺省用
|
|
134
|
+
``application/octet-stream``。
|
|
135
|
+
"""
|
|
136
|
+
prefix = key_prefix.strip("/")
|
|
137
|
+
for rel_path, body in files.items():
|
|
138
|
+
key = f"{prefix}/{rel_path}" if prefix else rel_path
|
|
139
|
+
ctype = DEFAULT_CONTENT_TYPE
|
|
140
|
+
if content_type_for and rel_path in content_type_for:
|
|
141
|
+
ctype = content_type_for[rel_path]
|
|
142
|
+
self.upload(bucket, key, body, ctype)
|
|
143
|
+
|
|
144
|
+
def close(self) -> None:
|
|
145
|
+
self._session.close()
|
|
146
|
+
|
|
147
|
+
def __enter__(self) -> "CdnUploader":
|
|
148
|
+
return self
|
|
149
|
+
|
|
150
|
+
def __exit__(self, *exc) -> None:
|
|
151
|
+
self.close()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""pdf_transformer:各模型专属的 raw 结果 → 统一 blocks / pages 转换器集合。
|
|
2
|
+
|
|
3
|
+
每个模型一个子包,产出同一套与模型无关的 blocks / pages 结构,交给
|
|
4
|
+
:mod:`orbitkit.pdf_validator` 做减法过滤、:mod:`orbitkit.cdn_uploader` 上传:
|
|
5
|
+
|
|
6
|
+
- :mod:`orbitkit.pdf_transformer.mineru` —— MinerU Router ZIP 转换器(现有)。
|
|
7
|
+
- 后续:``datalab`` 等其它模型。
|
|
8
|
+
|
|
9
|
+
用法::
|
|
10
|
+
|
|
11
|
+
from orbitkit.pdf_transformer.mineru import transform_zip, transform_chunk_zips
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from . import mineru
|
|
15
|
+
|
|
16
|
+
__all__ = ["mineru"]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""MinerU 结果转换:raw ZIP → 结构化 blocks / pages(已旋转纠正 + 已 Y 翻转)+ 透传文件。
|
|
2
|
+
|
|
3
|
+
本子包只负责 **模型专属的解析 + 几何归一**,产出与模型无关的 blocks / pages
|
|
4
|
+
结构(:class:`TransformResult`)。**内容过滤**(减法)交给
|
|
5
|
+
:mod:`orbitkit.pdf_validator`;**上传**交给 :mod:`orbitkit.cdn_uploader`。
|
|
6
|
+
|
|
7
|
+
MinerU 是 :mod:`orbitkit.pdf_transformer` 下的其中一个模型实现(后续会有
|
|
8
|
+
``datalab`` 等),各模型都产出同一套 blocks / pages 结构以复用 pdf_validator。
|
|
9
|
+
|
|
10
|
+
典型用法::
|
|
11
|
+
|
|
12
|
+
from orbitkit.pdf_transformer.mineru import transform_zip
|
|
13
|
+
from orbitkit.pdf_validator import apply_block_filters, resolve_filters, parse_filter_names
|
|
14
|
+
|
|
15
|
+
result = transform_zip(zip_bytes, page_geometry=geom, bbox_rotate_correction=True)
|
|
16
|
+
result.blocks, result.pages = apply_block_filters(
|
|
17
|
+
result.blocks, result.pages,
|
|
18
|
+
resolve_filters(parse_filter_names("drop_non_text,drop_vlm_artifacts")),
|
|
19
|
+
)
|
|
20
|
+
files = result.to_files() # Dict[相对路径, bytes],可直接上传
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .bbox_rotation import (
|
|
24
|
+
PageGeometry,
|
|
25
|
+
bbox_inverse_rotate,
|
|
26
|
+
correct_middle_bbox_rotation,
|
|
27
|
+
)
|
|
28
|
+
from .result_transformer import (
|
|
29
|
+
TransformResult,
|
|
30
|
+
transform_chunk_zips,
|
|
31
|
+
transform_zip,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"TransformResult",
|
|
36
|
+
"transform_zip",
|
|
37
|
+
"transform_chunk_zips",
|
|
38
|
+
"PageGeometry",
|
|
39
|
+
"bbox_inverse_rotate",
|
|
40
|
+
"correct_middle_bbox_rotation",
|
|
41
|
+
]
|