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.
Files changed (95) hide show
  1. {orbitkit-0.8.78/orbitkit.egg-info → orbitkit-0.9.11}/PKG-INFO +1 -1
  2. orbitkit-0.9.11/orbitkit/VERSION +1 -0
  3. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/data_preprocessing.py +1 -0
  4. orbitkit-0.9.11/orbitkit/airflow_handler/file_flow_entry.py +46 -0
  5. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/file_flow_entry_process.py +23 -6
  6. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/file_flow_exit_process.py +43 -0
  7. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/file_handler_v2.py +14 -7
  8. orbitkit-0.9.11/orbitkit/cdn_uploader/__init__.py +16 -0
  9. orbitkit-0.9.11/orbitkit/cdn_uploader/uploader.py +151 -0
  10. orbitkit-0.9.11/orbitkit/pdf_transformer/__init__.py +16 -0
  11. orbitkit-0.9.11/orbitkit/pdf_transformer/mineru/__init__.py +41 -0
  12. orbitkit-0.9.11/orbitkit/pdf_transformer/mineru/bbox_rotation.py +218 -0
  13. orbitkit-0.9.11/orbitkit/pdf_transformer/mineru/result_transformer.py +687 -0
  14. orbitkit-0.9.11/orbitkit/pdf_validator/__init__.py +35 -0
  15. orbitkit-0.9.11/orbitkit/pdf_validator/filters/__init__.py +1 -0
  16. orbitkit-0.9.11/orbitkit/pdf_validator/filters/drop_non_text.py +19 -0
  17. orbitkit-0.9.11/orbitkit/pdf_validator/filters/drop_vlm_artifacts.py +69 -0
  18. orbitkit-0.9.11/orbitkit/pdf_validator/filters/repeated_text.py +128 -0
  19. orbitkit-0.9.11/orbitkit/pdf_validator/pipeline.py +103 -0
  20. orbitkit-0.9.11/orbitkit/pdf_validator/registry.py +64 -0
  21. {orbitkit-0.8.78 → orbitkit-0.9.11/orbitkit.egg-info}/PKG-INFO +1 -1
  22. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/SOURCES.txt +14 -0
  23. orbitkit-0.8.78/orbitkit/VERSION +0 -1
  24. {orbitkit-0.8.78 → orbitkit-0.9.11}/LICENSE +0 -0
  25. {orbitkit-0.8.78 → orbitkit-0.9.11}/MANIFEST.in +0 -0
  26. {orbitkit-0.8.78 → orbitkit-0.9.11}/README.md +0 -0
  27. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/__init__.py +0 -0
  28. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/__init__.py +0 -0
  29. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/airflow_handler/file_handler.py +0 -0
  30. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/audio_transcoder/__init__.py +0 -0
  31. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/audio_transcoder/netmind_extract_v1.py +0 -0
  32. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/constant/__init__.py +0 -0
  33. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/constant/report_schema.py +0 -0
  34. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/id_srv/__init__.py +0 -0
  35. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/id_srv/id_gen.py +0 -0
  36. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/id_srv/id_perm_like.py +0 -0
  37. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/lark_send/__init__.py +0 -0
  38. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/lark_send/lark.py +0 -0
  39. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/llm_tools/__init__.py +0 -0
  40. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/llm_tools/quick_rag_chat.py +0 -0
  41. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/orbit_type/__init__.py +0 -0
  42. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/orbit_type/doc_4_compile_rule.py +0 -0
  43. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/orbit_type/orbit_type_simple.py +0 -0
  44. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/orbit_type/tools.py +0 -0
  45. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_embedding/__init__.py +0 -0
  46. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_embedding/pdf_txt_embedding.py +0 -0
  47. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_embedding/pdf_txt_embedding_v2.py +0 -0
  48. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/__init__.py +0 -0
  49. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/a_stock_extractor_v1.py +0 -0
  50. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/exceptions.py +0 -0
  51. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/mineru_demo.py +0 -0
  52. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_block_extractor_base.py +0 -0
  53. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_block_extractor_v1.py +0 -0
  54. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_block_extractor_v2.py +0 -0
  55. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_azure.py +0 -0
  56. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_minerU_v1.py +0 -0
  57. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_netmind_v1.py +0 -0
  58. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_netmind_v2.py +0 -0
  59. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_netmind_v3.py +0 -0
  60. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor/pdf_extractor_orbit.py +0 -0
  61. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/__init__.py +0 -0
  62. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/base.py +0 -0
  63. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/cloud_provider.py +0 -0
  64. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/core.py +0 -0
  65. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/exceptions.py +0 -0
  66. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/extractors.py +0 -0
  67. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_extractor_simple/utils.py +0 -0
  68. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_writer/__init__.py +0 -0
  69. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/pdf_writer/pdf_writer_simple.py +0 -0
  70. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/__init__.py +0 -0
  71. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/cache_asset_downloader.py +0 -0
  72. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/common.py +0 -0
  73. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/customize_regix_manager.py +0 -0
  74. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/is_xbrl_structure.py +0 -0
  75. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/secret_manager.py +0 -0
  76. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/universal_extractor.py +0 -0
  77. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_aliyun.py +0 -0
  78. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_aliyun_oss_simple.py +0 -0
  79. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_aws.py +0 -0
  80. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_aws_s3_wrapper.py +0 -0
  81. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_date.py +0 -0
  82. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_html.py +0 -0
  83. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_kafka.py +0 -0
  84. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_md5.py +0 -0
  85. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_selenium.py +0 -0
  86. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_simple_timer.py +0 -0
  87. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_str.py +0 -0
  88. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_type_mapping.py +0 -0
  89. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit/util/util_url.py +0 -0
  90. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/dependency_links.txt +0 -0
  91. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/not-zip-safe +0 -0
  92. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/requires.txt +0 -0
  93. {orbitkit-0.8.78 → orbitkit-0.9.11}/orbitkit.egg-info/top_level.txt +0 -0
  94. {orbitkit-0.8.78 → orbitkit-0.9.11}/setup.cfg +0 -0
  95. {orbitkit-0.8.78 → orbitkit-0.9.11}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orbitkit
3
- Version: 0.8.78
3
+ Version: 0.9.11
4
4
  Summary: This project is only for Orbit Tech internal use.
5
5
  Home-page: https://github.com/clown-0726/orbitkit
6
6
  Author: Lilu Cao
@@ -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
+ ]