forked from Sanyamkothari/solar
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
202 lines (174 loc) Β· 8.79 KB
/
Copy pathmain.py
File metadata and controls
202 lines (174 loc) Β· 8.79 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
"""
Main Orchestrator for the QC Automation System.
Runs a continuous loop over the input directory, coordinates the batch manager, validation, and reporting.
"""
import time
import argparse
from input_handler import InputHandler
from validator import Validator, ValidationError, ValidationWarning
from quality_rules import QualityEvaluator
from report_generator import ReportGenerator
from batch_manager import BatchManager
from cross_verifier import CrossVerifier
from logger import logger
from config import CATEGORY_DATA_ERROR, CATEGORY_VERIFICATION_FAILED, CATEGORY_MANUAL_REVIEW
def process_file(filepath, excel_ref_path=None, steps_callback=None):
"""
Processes a single factory file drop.
If excel_ref_path is provided, cross-verifies image OCR data against Excel before QC rules.
Returns a dict with step-by-step results for dashboard visibility.
Optional steps_callback(step_name, status, detail) for live UI updates.
"""
result = {
"batch_id": None,
"filename": filepath.name,
"steps": [], # List of {name, status, detail, timestamp}
"matrix": None,
"eval_report": None,
"decision": None,
"report_path": None,
"verification_report": None,
"matrix_source": None,
}
def add_step(name, status, detail=""):
import datetime
step = {"name": name, "status": status, "detail": detail, "time": datetime.datetime.now().strftime("%H:%M:%S")}
result["steps"].append(step)
if steps_callback:
steps_callback(name, status, detail)
# STEP 1: Batch Init
batch = BatchManager()
result["batch_id"] = batch.batch_id
batch.log_context(f"Detected new file: {filepath.name}")
add_step("π File Detection", "β
PASS", f"Detected: {filepath.name}")
# STEP 2: Route & Extract
add_step("π Extraction", "β³ Running", "Routing to parser...")
matrix, category_override = InputHandler.route_file(filepath, batch)
if matrix is None:
add_step("π Extraction", "β FAIL", "Could not extract data from file.")
logger.error(f"[{batch.batch_id}] Extraction Failed.")
InputHandler.move_file(filepath, success=False, batch_id=batch.batch_id)
result["decision"] = "DATA_ERROR"
add_step("π¦ File Moved", "β οΈ MOVED", "File moved to /failed")
return result
result["matrix"] = matrix
rows = len(matrix)
cols = len(matrix[0]) if matrix else 0
add_step("π Extraction", "β
PASS", f"Extracted {rows}Γ{cols} matrix.")
# STEP 2b: Cross-Verification (Image vs Excel)
if excel_ref_path is not None:
add_step("π Cross-Verification", "β³ Running", "Comparing image data with Excel reference...")
excel_ref_matrix = InputHandler.extract_excel_reference(excel_ref_path)
if excel_ref_matrix is None:
add_step("π Cross-Verification", "β FAIL", "Could not parse reference Excel file.")
else:
verification = CrossVerifier.verify(matrix, excel_ref_matrix)
result["verification_report"] = verification
match_pct = verification["match_percentage"]
mismatches = verification["mismatch_count"]
if verification["passed"]:
add_step("π Cross-Verification", "β
PASS",
f"{match_pct}% match ({mismatches} mismatches within tolerance).")
else:
add_step("π Cross-Verification", "β FAIL",
f"Only {match_pct}% match β {mismatches} cells differ beyond tolerance.")
category_override = CATEGORY_VERIFICATION_FAILED
# Conditional: choose which matrix to trust for QC evaluation
chosen_matrix, source_label = CrossVerifier.choose_matrix(verification, matrix, excel_ref_matrix)
matrix = chosen_matrix
result["matrix"] = matrix
result["matrix_source"] = source_label
rows = len(matrix)
cols = len(matrix[0]) if matrix else 0
add_step("π Matrix Source", "βΉοΈ INFO", source_label)
elif filepath.suffix.lower() in ('.xlsx', '.xls'):
result["matrix_source"] = "EXCEL (direct upload)"
else:
result["matrix_source"] = "IMAGE (OCR β no Excel reference provided)"
# STEP 3: Data Cleaning (already done inside parser, but we confirm)
add_step("π§Ή Data Cleaning", "β
PASS", f"All values converted to numeric floats.")
# STEP 4: Hard Validation
add_step("π Validation", "β³ Running", "Checking 16Γ7 = 112 structure...")
try:
validation_warning = Validator.validate_matrix(matrix)
total = sum(len(r) for r in matrix)
if validation_warning:
add_step("π Validation", "β οΈ PARTIAL", f"{validation_warning} ({rows}Γ{cols} = {total} points)")
if not category_override:
category_override = CATEGORY_MANUAL_REVIEW
else:
add_step("π Validation", "β
PASS", f"Structure OK: {rows} bars Γ {cols} pts = {total} total.")
except ValidationError as ve:
add_step("π Validation", "β FAIL", str(ve))
logger.error(f"[{batch.batch_id}] Hard Validation Failed: {ve}")
InputHandler.move_file(filepath, success=False, batch_id=batch.batch_id)
result["decision"] = "DATA_ERROR"
add_step("π¦ File Moved", "β οΈ MOVED", "File moved to /failed")
return result
# STEP 5: Quality Rules Evaluation
add_step("π Rule A Check", "β³ Running", "Checking >0.8 threshold...")
eval_report = QualityEvaluator.evaluate_batch(matrix)
result["eval_report"] = eval_report
metrics = eval_report["metrics"]
# Rule A detail
ra = metrics["rule_A"]
ra_status = "β
PASS" if ra["passed"] else "β FAIL"
add_step("π Rule A Check", ra_status, f"{ra['points_gt_08']}/{ra['required']} points > 0.8")
# Rule B detail
rb = metrics["rule_B"]
rb_status = "β
PASS" if rb["passed"] else "β FAIL"
failed_bars_b = [f"Bar {k+1}: {v}" for k, v in rb["failures_per_bar"].items() if v > 2]
rb_detail = "All bars OK (β€2 points β€0.35)" if rb["passed"] else f"Bars exceeding limit: {', '.join(failed_bars_b)}"
add_step("π Rule B Check", rb_status, rb_detail)
# Rule C detail
rc = metrics["rule_C"]
rc_status = "β
PASS" if rc["passed"] else "β FAIL"
failed_bars_c = [f"Bar {k+1}: {v}" for k, v in rc["failures_per_bar"].items() if v > 1]
rc_detail = f"Total β€0.1: {rc['total_failures']}/8 max."
if failed_bars_c:
rc_detail += f" Bars over limit: {', '.join(failed_bars_c)}"
add_step("π Rule C Check", rc_status, rc_detail)
# STEP 6: Final Decision
if category_override:
logger.warning(f"[{batch.batch_id}] Overriding decision with {category_override}")
eval_report['decision'] = category_override
add_step("β οΈ OCR Confidence", "π‘ OVERRIDE", f"Low confidence β {category_override}")
decision = eval_report["decision"]
result["decision"] = decision
dec_icon = "β
" if decision == "APPROVED" else "β"
add_step(f"π Final Decision", f"{dec_icon} {decision}", f"Batch {batch.batch_id}")
# STEP 7: Report Generation
add_step("π Report Generation", "β³ Running", "Writing Excel report...")
report_path = ReportGenerator.generate_report(
batch_id=batch.batch_id,
matrix=matrix,
eval_report=eval_report,
verification_report=result.get("verification_report"),
matrix_source=result.get("matrix_source"),
)
result["report_path"] = str(report_path)
add_step("π Report Generation", "β
PASS", f"Saved: {report_path.name}")
# STEP 8: File Archival
InputHandler.move_file(filepath, success=True, batch_id=batch.batch_id)
add_step("π¦ File Archived", "β
DONE", "Original moved to /processed")
logger.info(f"[{batch.batch_id}] Batch sequence complete. Final Decision: {decision}")
return result
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Manufacturing QC Automation Pipeline")
parser.add_argument("--once", action="store_true", help="Run once then exit, instead of continuous loop.")
args = parser.parse_args()
logger.info("QC Automation System Started.")
try:
while True:
pending_files = InputHandler.get_pending_files()
for file in pending_files:
try:
process_file(file)
except Exception as e:
logger.error(f"Critical error processing file {file.name}: {e}")
InputHandler.move_file(file, success=False, batch_id="CRASHED")
if args.once:
break
time.sleep(2) # Polling interval
except KeyboardInterrupt:
logger.info("System gracefully shut down by operator.")