Corin1998 commited on
Commit
f7b6d64
·
verified ·
1 Parent(s): 88d3747

Create rag/ingest.py

Browse files
Files changed (1) hide show
  1. rag/ingest.py +90 -0
rag/ingest.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io, os, re, zipfile, requests, datetime
2
+ import fitz # PyMuPDF
3
+ from tqdm import tqdm
4
+ from app.config import settings
5
+ from app.deps import add_to_index
6
+
7
+ EDINET_LIST = "https://api.edinet-fsa.go.jp/api/v2/documents.json"
8
+ EDINET_GET = "https://api.edinet-fsa.go.jp/api/v2/documents/{doc_id}"
9
+
10
+ # 公式仕様: 書類一覧APIは ?date=YYYY-MM-DD&type=2&Subscription-Key=APIキー
11
+ # 取得APIは /documents/{docID}?type=2(PDFなど)&Subscription-Key=APIキー。:contentReference[oaicite:4]{index=4}
12
+
13
+ def list_edinet(date: str):
14
+ params = {"date": date, "type": 2, "Subscription-Key": settings.EDINET_API_KEY}
15
+ r = requests.get(EDINET_LIST, params=params, timeout=60)
16
+ r.raise_for_status()
17
+ js = r.json()
18
+ return js.get("results", [])
19
+
20
+ def download_edinet_pdf(doc_id: str) -> bytes:
21
+ params = {"type": 2, "Subscription-Key": settings.EDINET_API_KEY} # 2 = PDF
22
+ url = EDINET_GET.format(doc_id=doc_id)
23
+ r = requests.get(url, params=params, timeout=120)
24
+ r.raise_for_status()
25
+ # 返り値はZIPのこともある。PDFが単体/複数含まれるので展開して結合
26
+ content = r.content
27
+ try:
28
+ with zipfile.ZipFile(io.BytesIO(content)) as zf:
29
+ pdf_bytes = b""
30
+ for name in zf.namelist():
31
+ if name.lower().endswith(".pdf"):
32
+ pdf_bytes += zf.read(name)
33
+ if not pdf_bytes:
34
+ # XBRLやCSVのみ等のケースはスキップ
35
+ return b""
36
+ return pdf_bytes
37
+ except zipfile.BadZipFile:
38
+ # 直接PDFが返るケース
39
+ return content
40
+
41
+ def pdf_to_text(pdf_bytes: bytes) -> str:
42
+ text_all = []
43
+ with fitz.open(stream=pdf_bytes, filetype="pdf") as doc:
44
+ for page in doc:
45
+ text_all.append(page.get_text("text"))
46
+ return "\n".join(text_all)
47
+
48
+ def chunk_text(text: str, max_chars=1000, overlap=150):
49
+ text = re.sub(r"\s+", " ", text).strip()
50
+ chunks = []
51
+ i = 0
52
+ while i < len(text):
53
+ chunk = text[i:i+max_chars]
54
+ chunks.append(chunk)
55
+ i += max_chars - overlap
56
+ return chunks
57
+
58
+ def build_source_url(doc_id: str) -> str:
59
+ # APIキー露出を避けるため、自前プロキシURLにする(/proxy/edinet/{doc_id})
60
+ return f"/proxy/edinet/{doc_id}?type=pdf"
61
+
62
+ def ingest_edinet_for_company(edinet_code: str, date: str):
63
+ recs = []
64
+ for row in list_edinet(date):
65
+ if row.get("edinetCode") != edinet_code:
66
+ continue
67
+ if row.get("pdfFlag") != "1":
68
+ continue
69
+ doc_id = row["docID"]
70
+ pdf = download_edinet_pdf(doc_id)
71
+ if not pdf:
72
+ continue
73
+ text = pdf_to_text(pdf)
74
+ for j, chunk in enumerate(chunk_text(text)):
75
+ recs.append({
76
+ "text": chunk,
77
+ "source_url": build_source_url(doc_id),
78
+ "title": row.get("docDescription", ""),
79
+ "doc_id": doc_id,
80
+ "chunk_id": f"{doc_id}-{j:04d}"
81
+ })
82
+ if recs:
83
+ add_to_index(recs)
84
+ return len(recs)
85
+
86
+ # --- TDnet(任意: 有料API or RSSフォールバック) ---
87
+ def ingest_tdnet_by_rss(sec_code: str):
88
+ # JPX公式TDnet APIは有料。契約がない場合は外部RSS等で代替取得に留める。:contentReference[oaicite:5]{index=5}
89
+ # 実装は必要に応じて追加(MVPではEDINET中心で十分)
90
+ return 0