MUTHUKUMARAN K commited on
Commit
20076d4
·
1 Parent(s): e37ebce

Add ALWAS ML inference server with LightGBM/XGBoost models

Browse files
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ COPY --chown=user models/ ./models/
13
+ COPY --chown=user inference_server.py .
14
+
15
+ ENV MODEL_DIR=/app/models
16
+ EXPOSE 7860
17
+
18
+ CMD ["python", "inference_server.py"]
inference_server.py ADDED
@@ -0,0 +1,486 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ALWAS ML Inference Server
3
+ FastAPI-based inference API for all 4 ALWAS ML models.
4
+ Designed to replace the Groq API dependency and add predictive capabilities.
5
+ """
6
+ import os
7
+ import json
8
+ import numpy as np
9
+ import joblib
10
+ from datetime import datetime, timedelta
11
+ from typing import Optional, List
12
+ from pydantic import BaseModel, Field
13
+
14
+ # === Load Models & Configs ===
15
+ MODEL_DIR = os.environ.get('MODEL_DIR', './models')
16
+
17
+ # Load all models
18
+ hours_model = joblib.load(f'{MODEL_DIR}/hours_estimator.joblib')
19
+ complexity_xgb = joblib.load(f'{MODEL_DIR}/complexity_xgb.joblib')
20
+ complexity_lgb = joblib.load(f'{MODEL_DIR}/complexity_lgb.joblib')
21
+ bottleneck_model = joblib.load(f'{MODEL_DIR}/bottleneck_predictor.joblib')
22
+ completion_model = joblib.load(f'{MODEL_DIR}/completion_predictor.joblib')
23
+
24
+ # Load encoders
25
+ tech_node_encoder = joblib.load(f'{MODEL_DIR}/tech_node_encoder.joblib')
26
+ block_type_encoder = joblib.load(f'{MODEL_DIR}/block_type_encoder.joblib')
27
+ priority_encoder = joblib.load(f'{MODEL_DIR}/priority_encoder.joblib')
28
+ complexity_encoder = joblib.load(f'{MODEL_DIR}/complexity_encoder.joblib')
29
+ bottleneck_encoder = joblib.load(f'{MODEL_DIR}/bottleneck_encoder.joblib')
30
+
31
+ # Load config
32
+ with open(f'{MODEL_DIR}/feature_config.json', 'r') as f:
33
+ feature_config = json.load(f)
34
+
35
+ with open(f'{MODEL_DIR}/metrics.json', 'r') as f:
36
+ model_metrics = json.load(f)
37
+
38
+ # === Pydantic Models ===
39
+ class BlockEstimateRequest(BaseModel):
40
+ """Request for complexity & hours estimation (replaces Groq API)."""
41
+ block_type: str = Field(..., description="Block type (e.g., 'ADC', 'PLL', 'LDO')")
42
+ tech_node: str = Field(..., description="Technology node (e.g., '7nm', '28nm')")
43
+ priority: str = Field(default='P3-Medium', description="Priority level")
44
+ transistor_count: Optional[int] = Field(default=None, description="Estimated transistor count")
45
+ has_dependencies: bool = Field(default=False)
46
+ num_dependencies: int = Field(default=0)
47
+ constraint_complexity: float = Field(default=1.0, ge=0, le=3.0)
48
+ drc_iterations: int = Field(default=2)
49
+ engineer_skill_factor: float = Field(default=1.0, ge=0.5, le=1.5)
50
+
51
+ class BottleneckRequest(BaseModel):
52
+ """Request for bottleneck risk prediction."""
53
+ block_type: str
54
+ tech_node: str
55
+ priority: str = 'P3-Medium'
56
+ transistor_count: Optional[int] = None
57
+ has_dependencies: bool = False
58
+ num_dependencies: int = 0
59
+ constraint_complexity: float = 1.0
60
+ estimated_hours: float = 20.0
61
+ hours_logged: float = 0.0
62
+ drc_iterations: int = 2
63
+ drc_violations_total: int = 0
64
+ lvs_mismatches_total: int = 0
65
+ current_stage: str = 'In Progress'
66
+ days_in_current_stage: float = 0.0
67
+ engineer_skill_factor: float = 1.0
68
+ is_overdue: bool = False
69
+
70
+ class CompletionRequest(BaseModel):
71
+ """Request for completion time prediction."""
72
+ block_type: str
73
+ tech_node: str
74
+ priority: str = 'P3-Medium'
75
+ transistor_count: Optional[int] = None
76
+ has_dependencies: bool = False
77
+ num_dependencies: int = 0
78
+ constraint_complexity: float = 1.0
79
+ estimated_hours: float = 20.0
80
+ engineer_skill_factor: float = 1.0
81
+ drc_iterations: int = 2
82
+ current_stage: str = 'In Progress'
83
+ cumulative_hours: float = 0.0
84
+ cumulative_days: float = 0.0
85
+ cumulative_drc_violations: int = 0
86
+ cumulative_lvs_mismatches: int = 0
87
+
88
+ class BulkBlockRequest(BaseModel):
89
+ """Bulk estimation for multiple blocks."""
90
+ blocks: List[BlockEstimateRequest]
91
+
92
+ # === Helper Functions ===
93
+ STAGES = ['Not Started', 'In Progress', 'DRC', 'LVS', 'ERC', 'Review', 'Completed']
94
+ STAGE_IDX = {s: i for i, s in enumerate(STAGES)}
95
+ PRIORITY_MAP = {'P1-Critical': 1, 'P2-High': 2, 'P3-Medium': 3, 'P4-Low': 4}
96
+
97
+ # Default transistor counts by block type
98
+ DEFAULT_TRANSISTOR_COUNTS = {
99
+ 'ADC': 50000, 'DAC': 35000, 'PLL': 80000, 'LDO': 8000, 'BGR': 5000,
100
+ 'OTA': 3000, 'Comparator': 2000, 'SerDes': 120000, 'VCO': 15000,
101
+ 'Mixer': 10000, 'LNA': 6000, 'PA': 20000, 'TIA': 4000, 'SampleHold': 3500,
102
+ 'LVDS_Driver': 8000, 'BandgapRef': 3000, 'CurrentMirror': 1500,
103
+ 'DiffAmp': 2500, 'Oscillator': 12000, 'PowerDetector': 5000
104
+ }
105
+
106
+ def safe_encode(encoder, value, default=0):
107
+ """Safely encode a value, returning default if unknown."""
108
+ try:
109
+ return encoder.transform([value])[0]
110
+ except (ValueError, KeyError):
111
+ return default
112
+
113
+ def safe_priority_encode(priority):
114
+ """Encode priority safely."""
115
+ try:
116
+ return priority_encoder.transform([[priority]])[0][0]
117
+ except:
118
+ return 2 # default to medium
119
+
120
+ def get_transistor_count(block_type, provided_count):
121
+ """Get transistor count, using default if not provided."""
122
+ if provided_count and provided_count > 0:
123
+ return provided_count
124
+ return DEFAULT_TRANSISTOR_COUNTS.get(block_type, 10000)
125
+
126
+
127
+ # === Prediction Functions ===
128
+ def predict_complexity_and_hours(req: BlockEstimateRequest):
129
+ """Predict complexity class and estimated hours for a new block."""
130
+ tc = get_transistor_count(req.block_type, req.transistor_count)
131
+ tc_log = np.log1p(tc)
132
+
133
+ tech_enc = safe_encode(tech_node_encoder, req.tech_node)
134
+ type_enc = safe_encode(block_type_encoder, req.block_type)
135
+ priority_enc = safe_priority_encode(req.priority)
136
+ priority_num = PRIORITY_MAP.get(req.priority, 3)
137
+
138
+ type_node_int = tech_enc * 10 + type_enc
139
+ complexity_score = req.constraint_complexity * tc_log
140
+ size_priority_int = tc_log * priority_num
141
+
142
+ # Hours estimation features
143
+ hours_features = np.array([[
144
+ tech_enc, type_enc, priority_enc, tc, tc_log,
145
+ int(req.has_dependencies), req.num_dependencies,
146
+ req.constraint_complexity, req.drc_iterations,
147
+ req.engineer_skill_factor, type_node_int,
148
+ complexity_score, size_priority_int
149
+ ]])
150
+
151
+ estimated_hours = float(hours_model.predict(hours_features)[0])
152
+ estimated_hours = max(4.0, round(estimated_hours, 1))
153
+
154
+ # Complexity classification features
155
+ complexity_features = np.array([[
156
+ tech_enc, type_enc, priority_enc, tc, tc_log,
157
+ int(req.has_dependencies), req.num_dependencies,
158
+ req.constraint_complexity, req.drc_iterations,
159
+ type_node_int, complexity_score, size_priority_int
160
+ ]])
161
+
162
+ xgb_proba = complexity_xgb.predict_proba(complexity_features)[0]
163
+ lgb_proba = complexity_lgb.predict_proba(complexity_features)[0]
164
+ ensemble_proba = (xgb_proba + lgb_proba) / 2
165
+
166
+ complexity_idx = int(np.argmax(ensemble_proba))
167
+ complexity_label = complexity_encoder.classes_[complexity_idx]
168
+ confidence = float(ensemble_proba[complexity_idx])
169
+
170
+ # Generate reasoning
171
+ reasoning = generate_reasoning(req, complexity_label, estimated_hours, tc)
172
+
173
+ # Risk assessment
174
+ risk_level = 'low' if complexity_label == 'Low' else ('medium' if complexity_label == 'Medium' else 'high')
175
+
176
+ # Suggested skill level
177
+ skill_needed = 'senior' if complexity_label == 'High' else ('mid' if complexity_label == 'Medium' else 'junior')
178
+
179
+ return {
180
+ 'complexity': complexity_label,
181
+ 'estimated_hours': estimated_hours,
182
+ 'confidence': round(confidence, 3),
183
+ 'risk_level': risk_level,
184
+ 'reasoning': reasoning,
185
+ 'recommended_drc_iterations': max(req.drc_iterations, 2 if complexity_label == 'High' else 1),
186
+ 'suggested_engineer_skill_level': skill_needed,
187
+ 'complexity_probabilities': {
188
+ cls: round(float(p), 3)
189
+ for cls, p in zip(complexity_encoder.classes_, ensemble_proba)
190
+ },
191
+ 'estimated_days': round(estimated_hours / 8, 1),
192
+ 'model_version': '1.0.0',
193
+ }
194
+
195
+ def generate_reasoning(req, complexity, hours, tc):
196
+ """Generate human-readable reasoning for the estimate."""
197
+ parts = []
198
+
199
+ if complexity == 'High':
200
+ if req.tech_node in ['5nm', '7nm', '12nm']:
201
+ parts.append(f"Advanced {req.tech_node} node requires extensive DRC/LVS iterations with tight design rules")
202
+ if tc > 50000:
203
+ parts.append(f"Large transistor count (~{tc:,}) significantly increases layout complexity and verification time")
204
+ if req.block_type in ['PLL', 'SerDes', 'ADC', 'PA']:
205
+ parts.append(f"{req.block_type} blocks require precision analog matching and careful signal routing")
206
+ if req.has_dependencies:
207
+ parts.append(f"Inter-block dependencies ({req.num_dependencies}) add integration and timing closure overhead")
208
+ if req.constraint_complexity > 2.0:
209
+ parts.append(f"High constraint complexity ({req.constraint_complexity:.1f}/3.0) demands extensive floor planning")
210
+ elif complexity == 'Medium':
211
+ parts.append(f"{req.block_type} at {req.tech_node} presents moderate layout challenges")
212
+ if req.constraint_complexity > 1.5:
213
+ parts.append("Analog constraints require careful floor planning and routing")
214
+ if tc > 20000:
215
+ parts.append(f"Moderate transistor count (~{tc:,}) requires systematic verification")
216
+ else:
217
+ parts.append(f"{req.block_type} at {req.tech_node} is a well-characterized block with established layout patterns")
218
+ if tc < 10000:
219
+ parts.append("Small transistor count allows straightforward layout")
220
+
221
+ if not parts:
222
+ parts.append(f"Standard {req.block_type} layout at {req.tech_node} technology node")
223
+
224
+ parts.append(f"Estimated {hours:.0f} hours ({hours/8:.1f} working days) for completion")
225
+
226
+ return "; ".join(parts) + "."
227
+
228
+
229
+ def predict_bottleneck_risk(req: BottleneckRequest):
230
+ """Predict bottleneck risk for a block."""
231
+ tc = get_transistor_count(req.block_type, req.transistor_count)
232
+ tc_log = np.log1p(tc)
233
+
234
+ tech_enc = safe_encode(tech_node_encoder, req.tech_node)
235
+ type_enc = safe_encode(block_type_encoder, req.block_type)
236
+ priority_enc = safe_priority_encode(req.priority)
237
+
238
+ complexity_score = req.constraint_complexity * tc_log
239
+ hours_ratio = req.hours_logged / max(req.estimated_hours, 1)
240
+ stage_idx = STAGE_IDX.get(req.current_stage, 1)
241
+ hours_budget_pct = req.hours_logged / max(req.estimated_hours, 1) * 100
242
+ stage_velocity = req.hours_logged / max(stage_idx, 1)
243
+
244
+ features = np.array([[
245
+ tech_enc, type_enc, priority_enc, tc_log,
246
+ int(req.has_dependencies), req.num_dependencies,
247
+ req.constraint_complexity, req.estimated_hours, req.hours_logged,
248
+ req.drc_iterations, req.drc_violations_total,
249
+ req.lvs_mismatches_total, stage_idx,
250
+ req.engineer_skill_factor, complexity_score,
251
+ hours_budget_pct, stage_velocity
252
+ ]])
253
+
254
+ risk_idx = bottleneck_model.predict(features)[0]
255
+ risk_proba = bottleneck_model.predict_proba(features)[0]
256
+ risk_label = bottleneck_encoder.classes_[risk_idx]
257
+
258
+ # Generate actionable recommendations
259
+ recommendations = []
260
+ if risk_label == 'High':
261
+ if hours_ratio > 1.3:
262
+ recommendations.append("Block is significantly over budget — consider reassignment or scope review")
263
+ if req.days_in_current_stage > 5:
264
+ recommendations.append(f"Block stuck in {req.current_stage} for {req.days_in_current_stage:.0f} days — escalate to manager")
265
+ if req.drc_violations_total > 10:
266
+ recommendations.append(f"High DRC violations ({req.drc_violations_total}) — review design rule compliance")
267
+ if req.is_overdue:
268
+ recommendations.append("Block is past due date — prioritize or adjust timeline")
269
+ elif risk_label == 'Medium':
270
+ if hours_ratio > 1.0:
271
+ recommendations.append("Hours approaching estimate — monitor closely")
272
+ if req.days_in_current_stage > 3:
273
+ recommendations.append(f"Consider checking progress — {req.days_in_current_stage:.0f} days in {req.current_stage}")
274
+
275
+ return {
276
+ 'risk_level': risk_label,
277
+ 'confidence': round(float(risk_proba[risk_idx]), 3),
278
+ 'risk_probabilities': {
279
+ cls: round(float(p), 3)
280
+ for cls, p in zip(bottleneck_encoder.classes_, risk_proba)
281
+ },
282
+ 'hours_over_budget_ratio': round(hours_ratio, 2),
283
+ 'recommendations': recommendations if recommendations else ['Block progressing normally'],
284
+ 'should_alert': risk_label == 'High',
285
+ 'model_version': '1.0.0',
286
+ }
287
+
288
+
289
+ def predict_completion_time(req: CompletionRequest):
290
+ """Predict remaining hours to completion."""
291
+ tc = get_transistor_count(req.block_type, req.transistor_count)
292
+ tc_log = np.log1p(tc)
293
+
294
+ tech_enc = safe_encode(tech_node_encoder, req.tech_node)
295
+ type_enc = safe_encode(block_type_encoder, req.block_type)
296
+ priority_num = PRIORITY_MAP.get(req.priority, 3)
297
+ stage_idx = STAGE_IDX.get(req.current_stage, 1)
298
+ stages_completed = stage_idx
299
+
300
+ hours_ratio = req.cumulative_hours / max(req.estimated_hours, 1)
301
+ avg_hours_per_stage = req.cumulative_hours / max(stages_completed, 1)
302
+ avg_days_per_stage = req.cumulative_days / max(stages_completed, 1)
303
+
304
+ features = np.array([[
305
+ tech_enc, type_enc, priority_num, tc_log,
306
+ int(req.has_dependencies), req.num_dependencies,
307
+ req.constraint_complexity, req.estimated_hours,
308
+ req.engineer_skill_factor, req.drc_iterations,
309
+ stage_idx, req.cumulative_hours, req.cumulative_days,
310
+ req.cumulative_drc_violations, req.cumulative_lvs_mismatches,
311
+ hours_ratio, stages_completed, avg_hours_per_stage, avg_days_per_stage
312
+ ]])
313
+
314
+ remaining_hours = float(completion_model.predict(features)[0])
315
+ remaining_hours = max(0, round(remaining_hours, 1))
316
+ remaining_days = remaining_hours / 8
317
+
318
+ # Project completion date
319
+ now = datetime.now()
320
+ estimated_completion = now + timedelta(days=remaining_days)
321
+
322
+ return {
323
+ 'remaining_hours': remaining_hours,
324
+ 'remaining_days': round(remaining_days, 1),
325
+ 'estimated_completion_date': estimated_completion.strftime('%Y-%m-%d'),
326
+ 'total_estimated_hours': round(req.cumulative_hours + remaining_hours, 1),
327
+ 'progress_percent': round(req.cumulative_hours / max(req.cumulative_hours + remaining_hours, 1) * 100, 1),
328
+ 'current_stage': req.current_stage,
329
+ 'model_version': '1.0.0',
330
+ }
331
+
332
+
333
+ # === FastAPI App ===
334
+ try:
335
+ from fastapi import FastAPI, HTTPException
336
+ from fastapi.middleware.cors import CORSMiddleware
337
+ import uvicorn
338
+
339
+ app = FastAPI(
340
+ title="ALWAS ML API",
341
+ description="Machine Learning models for the Analog Layout Workflow Automation System",
342
+ version="1.0.0",
343
+ )
344
+
345
+ app.add_middleware(
346
+ CORSMiddleware,
347
+ allow_origins=["*"],
348
+ allow_credentials=True,
349
+ allow_methods=["*"],
350
+ allow_headers=["*"],
351
+ )
352
+
353
+ @app.get("/")
354
+ def root():
355
+ return {
356
+ "service": "ALWAS ML API",
357
+ "version": "1.0.0",
358
+ "models": {
359
+ "hours_estimation": model_metrics.get('hours_estimation', {}),
360
+ "complexity_classification": model_metrics.get('complexity_classification', {}),
361
+ "bottleneck_prediction": model_metrics.get('bottleneck_prediction', {}),
362
+ "completion_prediction": model_metrics.get('completion_prediction', {}),
363
+ },
364
+ "endpoints": [
365
+ "/predict/estimate",
366
+ "/predict/bottleneck",
367
+ "/predict/completion",
368
+ "/predict/bulk-estimate",
369
+ "/health",
370
+ ]
371
+ }
372
+
373
+ @app.get("/health")
374
+ def health():
375
+ return {"status": "healthy", "models_loaded": 5, "timestamp": datetime.now().isoformat()}
376
+
377
+ @app.post("/predict/estimate")
378
+ def estimate_block(req: BlockEstimateRequest):
379
+ """Estimate complexity and hours for a new block. Direct replacement for Groq AI estimation."""
380
+ try:
381
+ return predict_complexity_and_hours(req)
382
+ except Exception as e:
383
+ raise HTTPException(status_code=500, detail=str(e))
384
+
385
+ @app.post("/predict/bottleneck")
386
+ def predict_bottleneck(req: BottleneckRequest):
387
+ """Predict bottleneck risk for an in-progress block."""
388
+ try:
389
+ return predict_bottleneck_risk(req)
390
+ except Exception as e:
391
+ raise HTTPException(status_code=500, detail=str(e))
392
+
393
+ @app.post("/predict/completion")
394
+ def predict_completion(req: CompletionRequest):
395
+ """Predict remaining time to completion."""
396
+ try:
397
+ return predict_completion_time(req)
398
+ except Exception as e:
399
+ raise HTTPException(status_code=500, detail=str(e))
400
+
401
+ @app.post("/predict/bulk-estimate")
402
+ def bulk_estimate(req: BulkBlockRequest):
403
+ """Bulk estimation for multiple blocks at once."""
404
+ try:
405
+ results = [predict_complexity_and_hours(block) for block in req.blocks]
406
+ return {
407
+ "count": len(results),
408
+ "estimates": results,
409
+ "total_estimated_hours": round(sum(r['estimated_hours'] for r in results), 1),
410
+ }
411
+ except Exception as e:
412
+ raise HTTPException(status_code=500, detail=str(e))
413
+
414
+ @app.get("/model/metrics")
415
+ def get_metrics():
416
+ """Get model performance metrics."""
417
+ return model_metrics
418
+
419
+ @app.get("/model/supported-values")
420
+ def get_supported_values():
421
+ """Get list of supported block types, tech nodes, etc."""
422
+ return {
423
+ "tech_nodes": feature_config['tech_nodes'],
424
+ "block_types": feature_config['block_types'],
425
+ "priorities": feature_config['priorities'],
426
+ "stages": STAGES,
427
+ "complexity_classes": feature_config['complexity_classes'],
428
+ "bottleneck_classes": feature_config['bottleneck_classes'],
429
+ }
430
+
431
+ HAS_FASTAPI = True
432
+
433
+ except ImportError:
434
+ HAS_FASTAPI = False
435
+ print("FastAPI not installed — running in library mode only")
436
+
437
+
438
+ # === Standalone Test ===
439
+ if __name__ == '__main__':
440
+ print("=" * 60)
441
+ print("ALWAS ML Inference Server — Test Mode")
442
+ print("=" * 60)
443
+
444
+ # Test 1: Complexity & Hours estimation
445
+ print("\n--- Test: PLL at 7nm ---")
446
+ result = predict_complexity_and_hours(BlockEstimateRequest(
447
+ block_type='PLL', tech_node='7nm', priority='P1-Critical',
448
+ transistor_count=80000, has_dependencies=True, num_dependencies=3,
449
+ constraint_complexity=2.5, drc_iterations=4, engineer_skill_factor=0.8
450
+ ))
451
+ print(json.dumps(result, indent=2))
452
+
453
+ # Test 2: Simple block
454
+ print("\n--- Test: CurrentMirror at 45nm ---")
455
+ result = predict_complexity_and_hours(BlockEstimateRequest(
456
+ block_type='CurrentMirror', tech_node='45nm', priority='P4-Low',
457
+ transistor_count=1500, constraint_complexity=0.5
458
+ ))
459
+ print(json.dumps(result, indent=2))
460
+
461
+ # Test 3: Bottleneck risk
462
+ print("\n--- Test: Bottleneck Risk ---")
463
+ result = predict_bottleneck_risk(BottleneckRequest(
464
+ block_type='ADC', tech_node='7nm', priority='P1-Critical',
465
+ estimated_hours=60, hours_logged=80,
466
+ drc_violations_total=15, current_stage='DRC',
467
+ days_in_current_stage=7, is_overdue=True
468
+ ))
469
+ print(json.dumps(result, indent=2))
470
+
471
+ # Test 4: Completion time
472
+ print("\n--- Test: Completion Prediction ---")
473
+ result = predict_completion_time(CompletionRequest(
474
+ block_type='DAC', tech_node='12nm', priority='P2-High',
475
+ estimated_hours=40, current_stage='LVS',
476
+ cumulative_hours=25, cumulative_days=4,
477
+ cumulative_drc_violations=5
478
+ ))
479
+ print(json.dumps(result, indent=2))
480
+
481
+ # Start server if FastAPI available
482
+ if HAS_FASTAPI:
483
+ print(f"\n{'=' * 60}")
484
+ print("Starting ALWAS ML API server on http://0.0.0.0:7860")
485
+ print("=" * 60)
486
+ uvicorn.run(app, host="0.0.0.0", port=7860)
models/block_type_encoder.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cdddcc2e0aa2402612b01713879c9674ca69b3802881cf832aeb99b26ca60330
3
+ size 657
models/bottleneck_encoder.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:da3a2045b4291d87a127f90f4f04179cf6962cfbdae885e97adea770ffdae2fe
3
+ size 495
models/bottleneck_predictor.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d1e215477fcecf4316c829407b9e72d0bd7a5f14d57bd6a6e0aeb4a9005d3b9
3
+ size 2742644
models/completion_predictor.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ad76ed4dbbb64eb81865856dc9d9be2f926fe7dfb859f3dc316984fc0ba201fe
3
+ size 1215116
models/complexity_encoder.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:da3a2045b4291d87a127f90f4f04179cf6962cfbdae885e97adea770ffdae2fe
3
+ size 495
models/complexity_lgb.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:df00ab3276b2d73f1ae9c1b364d269138b6857b9c0f35a9f98cc4f15d3fcdb09
3
+ size 761580
models/complexity_xgb.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:afffc9c27a7f29703f1beb7fb1c8ddf15f0abdc9281d8e0a75234453ca673137
3
+ size 673364
models/engineer_encoder.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a36bff5f4c0a17ca7f29a44766e710057da4df0774964d30dbf4da559ef2862f
3
+ size 973
models/feature_config.json ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "hours_features": [
3
+ "tech_node_encoded",
4
+ "block_type_encoded",
5
+ "priority_encoded",
6
+ "transistor_count",
7
+ "transistor_count_log",
8
+ "has_dependencies",
9
+ "num_dependencies",
10
+ "constraint_complexity",
11
+ "drc_iterations",
12
+ "engineer_skill_factor",
13
+ "type_node_interaction",
14
+ "complexity_score",
15
+ "size_priority_interaction"
16
+ ],
17
+ "complexity_features": [
18
+ "tech_node_encoded",
19
+ "block_type_encoded",
20
+ "priority_encoded",
21
+ "transistor_count",
22
+ "transistor_count_log",
23
+ "has_dependencies",
24
+ "num_dependencies",
25
+ "constraint_complexity",
26
+ "drc_iterations",
27
+ "type_node_interaction",
28
+ "complexity_score",
29
+ "size_priority_interaction"
30
+ ],
31
+ "bottleneck_features": [
32
+ "tech_node_encoded",
33
+ "block_type_encoded",
34
+ "priority_encoded",
35
+ "transistor_count_log",
36
+ "has_dependencies",
37
+ "num_dependencies",
38
+ "constraint_complexity",
39
+ "estimated_hours",
40
+ "hours_logged",
41
+ "drc_iterations",
42
+ "drc_violations_total",
43
+ "lvs_mismatches_total",
44
+ "current_stage_idx",
45
+ "engineer_skill_factor",
46
+ "complexity_score",
47
+ "hours_budget_pct",
48
+ "stage_velocity"
49
+ ],
50
+ "completion_features": [
51
+ "tech_node_encoded",
52
+ "block_type_encoded",
53
+ "priority_numeric",
54
+ "transistor_count_log",
55
+ "has_dependencies",
56
+ "num_dependencies",
57
+ "constraint_complexity",
58
+ "estimated_hours",
59
+ "engineer_skill_factor",
60
+ "drc_iterations",
61
+ "current_stage_idx",
62
+ "cumulative_hours",
63
+ "cumulative_days",
64
+ "cumulative_drc_violations",
65
+ "cumulative_lvs_mismatches",
66
+ "hours_vs_estimate_ratio",
67
+ "stages_completed",
68
+ "avg_hours_per_stage_so_far",
69
+ "avg_days_per_stage_so_far"
70
+ ],
71
+ "tech_nodes": [
72
+ "12nm",
73
+ "14nm",
74
+ "22nm",
75
+ "28nm",
76
+ "45nm",
77
+ "5nm",
78
+ "65nm",
79
+ "7nm"
80
+ ],
81
+ "block_types": [
82
+ "ADC",
83
+ "BGR",
84
+ "BandgapRef",
85
+ "Comparator",
86
+ "CurrentMirror",
87
+ "DAC",
88
+ "DiffAmp",
89
+ "LDO",
90
+ "LNA",
91
+ "LVDS_Driver",
92
+ "Mixer",
93
+ "OTA",
94
+ "Oscillator",
95
+ "PA",
96
+ "PLL",
97
+ "PowerDetector",
98
+ "SampleHold",
99
+ "SerDes",
100
+ "TIA",
101
+ "VCO"
102
+ ],
103
+ "priorities": [
104
+ "P4-Low",
105
+ "P3-Medium",
106
+ "P2-High",
107
+ "P1-Critical"
108
+ ],
109
+ "complexity_classes": [
110
+ "High",
111
+ "Low",
112
+ "Medium"
113
+ ],
114
+ "bottleneck_classes": [
115
+ "High",
116
+ "Low",
117
+ "Medium"
118
+ ]
119
+ }
models/hours_estimator.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c931086c5f4a7c8f17c685e845032cd68c24c1d9058700017ad4d8a8d1b992f8
3
+ size 464347
models/metrics.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "hours_estimation": {
3
+ "train_r2": 0.9487,
4
+ "test_r2": 0.8882,
5
+ "train_mae": 3.87,
6
+ "test_mae": 5.66,
7
+ "gap": 0.0605,
8
+ "cv_r2_mean": 0.8894,
9
+ "cv_r2_std": 0.0121
10
+ },
11
+ "complexity_classification": {
12
+ "xgb_train": 0.9342,
13
+ "xgb_test": 0.9267,
14
+ "xgb_gap": 0.0075,
15
+ "lgb_train": 0.9508,
16
+ "lgb_test": 0.9283,
17
+ "lgb_gap": 0.0225,
18
+ "ensemble_accuracy": 0.9317,
19
+ "ensemble_f1": 0.9316,
20
+ "cv_accuracy_mean": 0.915,
21
+ "cv_accuracy_std": 0.0064
22
+ },
23
+ "bottleneck_prediction": {
24
+ "train_accuracy": 0.9094,
25
+ "test_accuracy": 0.9287,
26
+ "gap": -0.0194,
27
+ "f1_weighted": 0.9268,
28
+ "cv_accuracy_mean": 0.9095,
29
+ "cv_accuracy_std": 0.1377,
30
+ "features_used": "SAFE (no leaky features)"
31
+ },
32
+ "completion_prediction": {
33
+ "train_r2": 0.9599,
34
+ "test_r2": 0.8936,
35
+ "gap": 0.0663,
36
+ "train_mae": 1.64,
37
+ "test_mae": 2.48,
38
+ "group_cv_r2_mean": 0.8993,
39
+ "group_cv_r2_std": 0.0064,
40
+ "split_type": "group-aware (block-level)"
41
+ },
42
+ "training_data": {
43
+ "total_samples": 4000,
44
+ "completed_blocks": 3000,
45
+ "in_progress_blocks": 1000,
46
+ "completion_train_samples": 14400
47
+ }
48
+ }
models/priority_encoder.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c8804e4ce71738054c2489a14d45f04cf5ec560008426bc503edd92c3942e4e3
3
+ size 1006
models/tech_node_encoder.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bce43b4699c2fdc15aaa6a5af5baea4233055e973bef3f20807880cfc6704ceb
3
+ size 527
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ xgboost>=3.0.0
2
+ lightgbm>=4.0.0
3
+ scikit-learn>=1.5.0
4
+ numpy>=2.0.0
5
+ pandas>=2.0.0
6
+ joblib>=1.4.0
7
+ fastapi>=0.115.0
8
+ uvicorn>=0.34.0
9
+ pydantic>=2.0.0