Repository navigation
Expand file tree
/
Copy pathtesta.py
More file actions
522 lines (475 loc) · 21.8 KB
/
Copy pathtesta.py
File metadata and controls
522 lines (475 loc) · 21.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
import fitz
import pandas as pd
import argparse
import logging
import os
import re
import json
from datetime import datetime
logging.basicConfig(
filename="error.log",
level=logging.WARNING,
format="%(asctime)s - %(levelname)s - %(message)s",
)
EXCEL_COLUMNS = [
"Source_Page",
"Tag Number", "Service", "P&ID No.",
"Design Pressure Min", "Design Pressure Max",
"Design Temperature Min", "Design Temperature Max",
"Fluid Name", "Fluid State", "Fluid Phase",
"Flow Min", "Flow Nor", "Flow Max",
"Pressure @ Flow Min", "Pressure @ Flow Nor", "Pressure @ Flow Max",
"Temperature Min", "Temperature Nor", "Temperature Max",
"Viscosity Min", "Viscosity Nor", "Viscosity Max",
"Density (Liq.) Min", "Density (Liq.) Nor", "Density (Liq.) Max",
"Vapour Pressure Min", "Vapour Pressure Nor", "Vapour Pressure Max",
"Cp/Cv Min", "Cp/Cv Max",
"Z (Compressibility) Min", "Z (Compressibility) Max",
"% Solid", "Density @ Standard (Liq.)", "Mol. Wt. (Gas)",
"Allowable Max Permanent Pressure Loss",
"Orifice Type", "Orifice Design Standard", "Orifice Plate Material",
"Holder Ring Material", "Plate Thickness",
"Drain/Vent Hole Dia 1", "Drain/Vent Hole Dia 2",
"Centre Distance", "Number of Stages",
"Flow Full Scale/Meter Max", "Diff Press @ Full Scale",
"Beta Ratio", "Bore Diameter",
"Permanent Pressure Loss @ Max Flow", "Noise (dBA)",
"Connection Size", "Connection Rating", "Connection Facing", "Connection Finish",
"Taps Type", "Taps Size",
"Tapping Orientation", "No of Tappings per Flange",
"Gasket Material", "Gasket Thickness",
"Pipe Material", "Flange Material",
"Plug Material", "Flange Type",
"Stud Bolt/Nut Material", "Pair of Jack Screws Material",
"Spool Piece Length", "End Connection Size", "End Connection Rating",
"End Connection Facing", "End Connection Finish",
"NACE Requirement", "IBR Requirement", "PWHT Requirement", "H2 Service Applicable",
"Line No.", "Line Size", "Line Class", "Line Schedule", "Line Internal Diameter",
"Manufacturer", "Model", "Requisition No.",
]
NA = "NA"
SECTION_HEADERS = [
"PURCHASE", "LINE", "OPTIONS", "METER RUN", "ORIFICE FLANGE",
"CALCULATION RESULT", "ORIFICE PLATE", "PROCESS CONDITIONS", "GENERAL",
]
def split_sections(text, headers=SECTION_HEADERS):
positions = []
for h in headers:
m = re.search(r'(?<![A-Za-z])' + re.escape(h) + r'(?![A-Za-z])', text)
if m:
positions.append((m.start(), m.end(), h))
positions.sort(key=lambda x: x[0])
sections = {}
for idx, (start, end, h) in enumerate(positions):
nxt = positions[idx + 1][0] if idx + 1 < len(positions) else len(text)
sections[h] = text[end:nxt].strip()
return sections
def rx(pattern, text, group=1, default=NA, flags=0):
m = re.search(pattern, text, flags)
if not m:
return default
try:
val = m.group(group)
except IndexError:
return default
val = (val or "").strip()
return val if val else default
def rx_groups(pattern, text, n, default=NA, flags=0):
m = re.search(pattern, text, flags)
if not m:
return [default] * n
out = []
for i in range(1, n + 1):
try:
v = (m.group(i) or "").strip()
except IndexError:
v = ""
out.append(v if v else default)
return out
def extract_page_text(page, page_num, sort=False):
# sort=True: reading-order (top-to-bottom, left-to-right) text nikalta hai —
# regex.json (-j mode) ke patterns isi order pe bane/test hue hain.
# Default (sort=False) purana wala hi behavior hai, taaki existing
# extract_fields_regex() na tute.
text = page.get_text(sort=sort)
if not text.strip():
msg = f"Page {page_num}: text layer khali hai (scanned image ho sakta hai) — skip."
print(f" [WARN] {msg}")
logging.warning(msg)
return text
def extract_fields_regex(raw_text, page_num):
if not raw_text.strip():
return None
t = re.sub(r'\s+', ' ', raw_text).strip()
sec = split_sections(t)
general = sec.get("GENERAL", "")
process = sec.get("PROCESS CONDITIONS", "")
plate = sec.get("ORIFICE PLATE", "")
calc = sec.get("CALCULATION RESULT", "")
flange = sec.get("ORIFICE FLANGE", "")
meter = sec.get("METER RUN", "")
options = sec.get("OPTIONS", "")
line = sec.get("LINE", "")
purchase = sec.get("PURCHASE", "")
data = {"Source_Page": f"Page {page_num}"}
data["Tag Number"] = rx(r'Tag Number\s+(\S+)', general)
data["Service"] = rx(r'Service\s+(.+?)\s+P&ID No\.', general)
data["P&ID No."] = rx(r'P&ID No\.\s+(\S+)', general)
dp_min, dp_max = rx_groups(r'Design Pressure\s+Kg/cm2\s*g\s+(\S+)\s+(\S+)', general, 2)
data["Design Pressure Min"] = dp_min
data["Design Pressure Max"] = dp_max
dt_min, dt_max = rx_groups(r'Design Temperature\s+°C\s+(\S+)\s+(\S+)', general, 2)
data["Design Temperature Min"] = dt_min
data["Design Temperature Max"] = dt_max
fluid_block = rx(r'Fluid State Fluid Phase\s+(.+?)\s+Min\.\s+Nor\.\s+Max\.', process)
if fluid_block != NA:
ftoks = fluid_block.split()
if len(ftoks) >= 3:
fphase = " ".join(ftoks[-2:])
fstate = ftoks[-3]
fname = " ".join(ftoks[:-3]) if len(ftoks) > 3 else NA
elif ftoks:
fname, fstate, fphase = NA, NA, " ".join(ftoks)
else:
fname, fstate, fphase = NA, NA, NA
else:
fname, fstate, fphase = NA, NA, NA
data["Fluid Name"] = fname
data["Fluid State"] = fstate
data["Fluid Phase"] = fphase
process_fixed = re.sub(r'(\d(?:\.\d+)?)\s+(\(HOLD\))', r'\1\2', process)
fl_min, fl_nor, fl_max = rx_groups(r'Flow\s+(?:Kg/hr|M3/hr)\s+(\S+)\s+(\S+)\s+(\S+)', process_fixed, 3)
data["Flow Min"], data["Flow Nor"], data["Flow Max"] = fl_min, fl_nor, fl_max
pf_min, pf_nor, pf_max = rx_groups(r'Pressure @ Flow Kg/cm2\s*g\s+(\S+)\s+(\S+)\s+(\S+)', process, 3)
data["Pressure @ Flow Min"] = pf_min
data["Pressure @ Flow Nor"] = pf_nor
data["Pressure @ Flow Max"] = pf_max
tmp_min, tmp_nor = rx_groups(r'(?<!Design )Temperature\s+°C\s+(\S+)\s+(\S+)', process, 2)
data["Temperature Min"] = tmp_min
data["Temperature Nor"] = tmp_nor
m_visc = re.search(r'Viscosity\s+cP\s+(.*?)\s+Density\s*\(Liq', t)
visc_toks = m_visc.group(1).split() if m_visc else []
if len(visc_toks) >= 3:
v_min, v_nor, v_max = visc_toks[0], visc_toks[1], visc_toks[2]
elif len(visc_toks) == 2:
v_min, v_nor, v_max = visc_toks[0], visc_toks[1], "-"
elif len(visc_toks) == 1:
v_min, v_nor, v_max = visc_toks[0], "-", "-"
else:
v_min, v_nor, v_max = "-", "-", "-"
data["Viscosity Min"], data["Viscosity Nor"], data["Viscosity Max"] = v_min, v_nor, v_max
dl_min, dl_nor, dl_max = rx_groups(r'Density\s*\(Liq\.\s*\)\s*kg/m[³3]\s+(\S+)\s+(\S+)\s+(\S+)', t, 3)
data["Density (Liq.) Min"] = dl_min
data["Density (Liq.) Nor"] = dl_nor
data["Density (Liq.) Max"] = dl_max
m_vp = re.search(r'Vapour Pressure\s*\(Liq\.\)\s*Kg/cm2\s*a\s*(.*?)\s*Cp\s*/\s*Cv\s*\(Gas\)', t)
vp_toks = m_vp.group(1).split() if m_vp else []
if len(vp_toks) >= 3:
vp_min, vp_nor, vp_max = vp_toks[0], vp_toks[1], vp_toks[2]
elif len(vp_toks) == 2:
vp_min, vp_nor, vp_max = "-", vp_toks[0], vp_toks[1]
elif len(vp_toks) == 1:
vp_min, vp_nor, vp_max = "-", vp_toks[0], "-"
else:
vp_min, vp_nor, vp_max = "-", "-", "-"
data["Vapour Pressure Min"] = vp_min
data["Vapour Pressure Nor"] = vp_nor
data["Vapour Pressure Max"] = vp_max
m_cpcv = re.search(r'Cp\s*/\s*Cv\s*\(Gas\)\s+(.*?)\s+Viscosity\s+cP', t)
cpcv_tokens = m_cpcv.group(1).split() if m_cpcv else []
def _looks_like_relocated_temp(tok):
try:
return abs(float(tok)) > 3
except ValueError:
return False
if len(cpcv_tokens) >= 3:
cpcv_a, cpcv_b, temp_max = cpcv_tokens[0], cpcv_tokens[1], cpcv_tokens[2]
elif len(cpcv_tokens) == 2:
t0, t1 = cpcv_tokens
if _looks_like_relocated_temp(t1):
cpcv_a, cpcv_b, temp_max = t0, "-", t1
else:
cpcv_a, cpcv_b, temp_max = t0, t1, "-"
elif len(cpcv_tokens) == 1:
cpcv_a, cpcv_b, temp_max = cpcv_tokens[0], "-", "-"
else:
cpcv_a, cpcv_b, temp_max = "-", "-", "-"
data["Cp/Cv Min"], data["Cp/Cv Max"] = cpcv_a, cpcv_b
data["Temperature Max"] = temp_max
z_a, z_b = rx_groups(r'Z\s*\(Compressibility\)\s*\(Gas\)\s+(\S+)\s+(\S+)', t, 2)
data["Z (Compressibility) Min"], data["Z (Compressibility) Max"] = z_a, z_b
pct_a, pct_b = rx_groups(r'%\s*Solid\s+(\S+)\s+(\S+)', t, 2)
if re.fullmatch(r'\d{1,2}', pct_a or "") and re.fullmatch(r'\d{1,2}|mmH2o', pct_b or ""):
data["% Solid"] = NA
else:
data["% Solid"] = pct_a
m_dstd = re.search(r'Density @ Standard\s*\(Liq\.\)\s*kg/m3\s*(.*?)\s*Mol\.\s*Wt\.\s*\(Gas\)', t)
dstd_toks = m_dstd.group(1).split() if m_dstd else []
data["Density @ Standard (Liq.)"] = dstd_toks[-1] if dstd_toks else "-"
data["Mol. Wt. (Gas)"] = rx(r'Mol\.\s*Wt\.\s*\(Gas\)\s+(\S+)', t)
data["Allowable Max Permanent Pressure Loss"] = rx(r'Allowable Max\.?\s*Permanent Pressure Loss\s+(\S+)', general + " " + process + " " + t)
data["Orifice Type"] = rx(r'^Type\s+(.+?)\s+Design Standard', plate)
data["Orifice Design Standard"] = rx(r'Design Standard\s+(.+?)\s+Material', plate)
data["Orifice Plate Material"] = rx(r'Design Standard\s+.+?\s+Material\s+(.+?)\s+Holder Ring Material', plate)
data["Holder Ring Material"] = rx(r'Holder Ring Material\s+(.+?)\s+Thickness', plate)
data["Plate Thickness"] = rx(r'Thickness\s+([\d.]+)', plate)
dv1, dv2 = rx_groups(r'Drain or Vent Hole\s*Diameter\s*\(mm\)\s+(\S+)\s+(\S+)', plate, 2)
data["Drain/Vent Hole Dia 1"], data["Drain/Vent Hole Dia 2"] = dv1, dv2
data["Centre Distance"] = rx(r'Centre Distance\s*\(mm\)\s+(\S+)', plate)
data["Number of Stages"] = rx(r'Number of Stages\s*\(For Restriction Orifice\)\s*(\S*?)(?=\s*Z\s*\(Compressibility\)|$)',plate,)
m_ffs = re.search(r'Flow Full Scale\s*/\s*Meter Max\s+(?:Kg/hr|M3/hr)\s+(\S+)', calc, re.IGNORECASE)
if m_ffs:
ffs_val = m_ffs.group(1)
elif re.search(r'Flow Full Scale\s*/\s*Meter Max\s+HOLD', calc, re.IGNORECASE):
ffs_val = "HOLD"
else:
ffs_val = NA
data["Flow Full Scale/Meter Max"] = ffs_val
data["Diff Press @ Full Scale"] = rx(r'Diff\.?\s*Press\.?\s*@\s*Full Scale\s+mmH2O\s+(\S+)', calc)
beta, bore = rx_groups(r'Beta Ratio\s*Bore Diameter\s*\(mm\)\s+(\S+)\s+(\S+)', calc, 2)
data["Beta Ratio"], data["Bore Diameter"] = beta, bore
data["Permanent Pressure Loss @ Max Flow"] = rx(r'Permanent Pressure Loss @ Max\.?\s*Flow\s+kg/cm2\s+(\S+)', calc)
data["Noise (dBA)"] = rx(r'Noise\s*\(dBA\)\s+(\S+)', calc)
data["Connection Size"] = rx(r'Connection Size Rating Facing Finish\s+(\S+)', t)
crating, cfacing, cfinish = rx_groups(r'^(\S+)\s+(\S+)\s+(\S+)\s+Taps Type', flange, 3)
data["Connection Rating"] = crating
data["Connection Facing"] = cfacing
data["Connection Finish"] = cfinish
taps_type, taps_size = rx_groups(r'Taps Type\s*Taps Size\s+(.+?)\s+(\S+"\s*NPT\S*|\S+)(?=\s+Tapping orientation)',flange, 2,)
data["Taps Type"] = taps_type
data["Taps Size"] = taps_size
orient, no_taps = rx_groups(r'Tapping orientation\s*No of tappings per flange\s+(\S+)\s+(.+?)(?=\s+Gasket Material)', flange, 2,)
data["Tapping Orientation"] = orient
data["No of Tappings per Flange"] = no_taps
gmat, gthk = rx_groups(r'Gasket Material\s*Gasket Thickness\s+(.+?)\s+(\S+)(?=\s+Pipe Material)', flange, 2,)
data["Gasket Material"] = gmat
data["Gasket Thickness"] = gthk
pipe_flange_raw = rx(r'Pipe Material\s*Flange Material\s+(.+?)\s+Plug material', flange)
if pipe_flange_raw != NA:
parts = [p.strip() for p in re.split(r'(?=ASTM)', pipe_flange_raw) if p.strip()]
data["Pipe Material"] = parts[0] if len(parts) >= 1 else pipe_flange_raw
data["Flange Material"] = parts[1] if len(parts) >= 2 else NA
else:
data["Pipe Material"] = NA
data["Flange Material"] = NA
m_plug = re.search(r'Plug material\s*Flange type\s+(-|HOLD)?\s*(\S+)', flange)
if m_plug:
plug_mat = m_plug.group(1) if m_plug.group(1) else "-"
flange_type = m_plug.group(2)
else:
plug_mat, flange_type = NA, NA
data["Plug Material"] = plug_mat
data["Flange Type"] = flange_type
data["Stud Bolt/Nut Material"] = rx(r'Stud Bolt/Nut Material\s+(.+?)(?=\s+Pair of Jack Screws Material)', options)
data["Pair of Jack Screws Material"] = rx(r'Pair of Jack Screws Material\s+(.+?)$', options)
data["Spool Piece Length"] = rx(r'Spool Piece Length\s*\(Up-Stream\s*/\s*Down-Stream\)\s+(\S+)', meter)
end_first = rx(r'End Connection Size Rating Facing Finish\s+(\S+)', t)
if end_first.upper() == "NA":
esize = erating = efacing = efinish = "NA"
else:
esize, erating, efacing, efinish = rx_groups(r'End Connection Size Rating Facing Finish\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', t, 4)
data["End Connection Size"] = esize
data["End Connection Rating"] = erating
data["End Connection Facing"] = efacing
data["End Connection Finish"] = efinish
data["NACE Requirement"] = rx(r'NACE Requirement\s+(\S+)', options)
data["IBR Requirement"] = rx(r'IBR Requirement\s+(\S+)', options)
data["PWHT Requirement"] = rx(r'PWHT Requirement\s+(\S+)', options)
data["H2 Service Applicable"] = rx(r'H2 Service Applicable\s+(\S+)', options)
data["Line No."] = rx(r'Line No\.\s+(\S+)', line)
lsize, lclass, lsch = rx_groups(r'Class Sch\.\s+(\S+)\s+(\S+)\s+(\S+)', line, 3)
data["Line Size"] = lsize
data["Line Class"] = lclass
data["Line Schedule"] = lsch
data["Line Internal Diameter"] = rx(r'Line Internal Diameter\s*\(mm\)\s+([\d.]+)', line)
data["Manufacturer"] = rx(r'Manufacturer\s+(\S+)', purchase)
data["Model"] = rx(r'Model\s+(\S+)', purchase)
data["Requisition No."] = rx(r'Requisition No\.\s+(\S+)', purchase)
data["Doc No."] = rx(r'DOC\.NO:\s*(\S+)', t)
data["Doc Rev"] = rx(r'Rev\.:\s*(\S+)', t)
return data
def text(row):
tag = row.get("Tag Number", NA)
if not tag or tag == NA:
return False
non_na_fields = sum(
1 for k, v in row.items()
if k not in ("Source_Page", "Tag Number") and v not in (None, "", NA)
)
return non_na_fields > 0
def save_excel(all_rows, output_path):
df = pd.DataFrame(all_rows)
df = df.reindex(columns=EXCEL_COLUMNS)
df.to_excel(output_path, index=False)
print(f" Excel save ho gayi: {output_path}")
# ============================================================
# NAYA FUNCTION: client-specific regex.json ke basis pe extraction
# (jab -j / --json_path diya jaye)
# ============================================================
def load_client_json(json_path):
"""Client ka regex.json load karta hai (jaise regex_configs/<client>/regex.json)."""
with open(json_path, "r", encoding="utf-8") as f:
return json.load(f)
def build_json_columns(config):
"""regex.json ke 'fields' se Excel column list banata hai.
Agar field me 'groups' hai to har group ka apna column banega
(naam: <field_key>_<group_name>), warna sirf <field_key>."""
cols = ["Source_Page"]
for key, spec in config.get("fields", {}).items():
groups = spec.get("groups")
if groups:
cols.extend(f"{key}_{g}" for g in groups)
else:
cols.append(key)
return cols
def extract_fields_from_json(raw_text, page_num, config):
"""regex.json ke fields ke patterns ko raw page text pe apply karta hai.
Har field ka pattern re.search se match hota hai; 'groups' diya ho to
multiple capture groups alag columns me split ho jate hain."""
if not raw_text.strip():
return None
data = {"Source_Page": f"Page {page_num}"}
fields = config.get("fields", {})
for key, spec in fields.items():
pattern = spec.get("pattern")
groups = spec.get("groups")
if not pattern:
continue
try:
m = re.search(pattern, raw_text, re.MULTILINE)
except re.error as e:
msg = f"Page {page_num}: field '{key}' ka regex galat hai — {e}"
logging.warning(msg)
m = None
if not m:
if groups:
for gname in groups:
data[f"{key}_{gname}"] = NA
else:
data[key] = NA
continue
if groups:
for idx, gname in enumerate(groups, start=1):
try:
val = (m.group(idx) or "").strip()
except IndexError:
val = ""
data[f"{key}_{gname}"] = val if val else NA
else:
try:
val = (m.group(1) or "").strip()
except IndexError:
val = ""
data[key] = val if val else NA
return data
def row_has_data(row):
"""Tag Number field ka naam har client-json me alag ho sakta hai,
isliye generic check: Source_Page ke alawa kam se kam ek field bhara ho."""
non_na_fields = sum(
1 for k, v in row.items()
if k != "Source_Page" and v not in (None, "", NA)
)
return non_na_fields > 0
def drop_all_na_rows(all_rows, config):
"""Sirf pehle field (jaise tag_number) ke basis pe check karta hai —
agar wo NA hai to us page pe real data nahi hai, row drop kar do.
Footer fields (doc_no/revision/sheet/code) har page pe match ho jate
hain isliye 'koi bhi field non-NA' wali check kaam nahi karti."""
fields = config.get("fields", {})
if not fields:
return all_rows
first_key = next(iter(fields))
groups = fields[first_key].get("groups")
primary_cols = [f"{first_key}_{g}" for g in groups] if groups else [first_key]
cleaned = []
for row in all_rows:
if any(row.get(c, NA) != NA for c in primary_cols):
cleaned.append(row)
return cleaned
def save_excel_dynamic(all_rows, output_path, columns):
df = pd.DataFrame(all_rows)
df = df.reindex(columns=columns)
df.to_excel(output_path, index=False)
print(f" Excel save ho gayi: {output_path}")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--pdf_path", required=True, help="PDF file ka path")
parser.add_argument(
"-j", "--json_path", required=False, default=None,
help="Client ka regex.json path (agar diya to JSON-based generic extraction chalegi, warna default hardcoded extraction chalegi)",
)
args = parser.parse_args()
pdf_path = args.pdf_path
pdf_name = os.path.splitext(os.path.basename(pdf_path))[0]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# output folder hamesha script ke pass banega, PDF jahan bhi ho wahan nahi
script_dir = os.path.dirname(os.path.abspath(__file__))
output_folder = os.path.join(script_dir, "output")
os.makedirs(output_folder, exist_ok=True)
output_path = os.path.join(output_folder, f"{pdf_name}_{timestamp}.xlsx")
print(f"\n{'='*55}")
print(f" PDF: {pdf_name}")
print(f" Run: {timestamp}")
if args.json_path:
print(f" Mode: JSON-based ({args.json_path})")
else:
print(f" Mode: Default hardcoded regex")
print(f"{'='*55}")
print("\n[1/2] PDF khol raha hoon...")
doc = fitz.open(pdf_path)
total_pages = len(doc)
print(f" Total Pages: {total_pages}")
# ---- Mode 1: -j diya gaya hai -> client regex.json se generic extraction ----
if args.json_path:
config = load_client_json(args.json_path)
columns = build_json_columns(config)
all_rows = []
for i in range(total_pages):
page_num = i + 1
raw_text = extract_page_text(doc[i], page_num, sort=True)
row = extract_fields_from_json(raw_text, page_num, config)
if row and row_has_data(row):
all_rows.append(row)
elif row:
print(f" [SKIP] Page {page_num}: koi data nahi mila — row skip ki.")
if page_num % 50 == 0 or page_num == total_pages:
print(f" ...{page_num}/{total_pages} pages processed")
doc.close()
if not all_rows:
print("\n[ERROR] Kisi bhi page se data nahi mila. Exit.")
return
all_rows = drop_all_na_rows(all_rows, config)
if not all_rows:
print("\n[ERROR] Sab rows NA thi, kuch bhi bacha nahi. Exit.")
return
print(f"\n[2/2] {len(all_rows)} rows mili — Excel me save ho rahi hain...")
save_excel_dynamic(all_rows, output_path, columns)
print(f"\n{'='*55}")
print(f" Ho gaya! Excel: {output_path}")
print(f"{'='*55}\n")
return
# ---- Mode 2 (default, jaise pehle tha): hardcoded extract_fields_regex ----
all_rows = []
for i in range(total_pages):
page_num = i + 1
raw_text = extract_page_text(doc[i], page_num)
row = extract_fields_regex(raw_text, page_num)
if row and text(row):
all_rows.append(row)
elif row:
print(f" [SKIP] Page {page_num}: koi Tag Number/data nahi mila — row skip ki.")
if page_num % 50 == 0 or page_num == total_pages:
print(f" ...{page_num}/{total_pages} pages processed")
doc.close()
if not all_rows:
print("\n[ERROR] Kisi bhi page se data nahi mila. Exit.")
return
print(f"\n[2/2] {len(all_rows)} rows mili — Excel me save ho rahi hain...")
save_excel(all_rows, output_path)
print(f"\n{'='*55}")
print(f" Ho gaya! Excel: {output_path}")
print(f"{'='*55}\n")
if __name__ == "__main__":
main()
# python testa.py -p pdf_path.pdf
# python testa.py -p pdf_path.pdf -j regex_configs/<client>/regex.json