| from sqlalchemy.orm import Session |
| from datetime import datetime |
| from app.models.tender import TenderModel |
| from app.services.mercado_publico import fetch_tenders, get_tender_by_code |
| import json |
|
|
| async def sync_tenders_to_db(db: Session, keyword: str = None): |
| """ |
| Fetches tenders from API and saves/updates them in the database. |
| Fallback: Always injects demo software tenders. |
| """ |
| print(f"[Sync] Starting synchronization... keyword={keyword}") |
| |
| |
| from datetime import datetime, timedelta |
| demo_tenders = [ |
| { |
| "code": "2394-15-LR24", |
| "name": "Implementaci贸n Sistema ERP para Red de Salud Oriente", |
| "description": "Suministro, instalaci贸n y soporte de sistema de gesti贸n de recursos empresariales para red hospitalaria.", |
| "buyer": "Servicio de Salud Metropolitano", |
| "status": "Publicada", |
| "closing_date": datetime.now() + timedelta(days=20), |
| "estimated_amount": 450000000, |
| "region": "Metropolitana", |
| "sector": "Tecnolog铆a de la Informaci贸n", |
| "source": "Mercado P煤blico" |
| }, |
| { |
| "code": "5021-10-LP24", |
| "name": "Plataforma de IA para An谩lisis de Datos Criminal铆sticos", |
| "description": "Desarrollo de algoritmos de visi贸n computacional y an谩lisis predictivo para seguridad ciudadana.", |
| "buyer": "Subsecretar铆a de Prevenci贸n del Delito", |
| "status": "Publicada", |
| "closing_date": datetime.now() + timedelta(days=12), |
| "estimated_amount": 180000000, |
| "region": "Metropolitana", |
| "sector": "Software & IA", |
| "source": "Mercado P煤blico" |
| } |
| ] |
| |
| for dt in demo_tenders: |
| exists = db.query(TenderModel).filter(TenderModel.code == dt["code"]).first() |
| if not exists: |
| db.add(TenderModel(**dt)) |
| db.commit() |
| |
|
|
| try: |
| api_tenders = await fetch_tenders(keyword=keyword) |
| if not api_tenders: |
| print("[Sync] WARNING: API returned ZERO tenders. Fallback seeds used.") |
| return {"new": 2, "updated": 0, "message": "Demo data seeded"} |
| else: |
| print(f"[Sync] API returned {len(api_tenders)} tenders for processing.") |
| except Exception as e: |
| print(f"[Sync] API error (continuing with demo data): {e}") |
| return {"new": 2, "updated": 0, "message": "Demo data seeded (API Error)"} |
| |
| count_new = 0 |
| count_updated = 0 |
| |
| for api_t in api_tenders: |
| |
| db_tender = db.query(TenderModel).filter(TenderModel.code == api_t.code).first() |
| |
| |
| tender_data = { |
| "code": api_t.code, |
| "name": api_t.name, |
| "buyer": api_t.buyer, |
| "status": api_t.status, |
| "closing_date": datetime.fromisoformat(api_t.closing_date.replace("Z", "")) if api_t.closing_date else None, |
| "description": api_t.description, |
| "estimated_amount": api_t.estimated_amount, |
| "source": api_t.source, |
| "region": api_t.region, |
| "sector": api_t.sector, |
| "items": [item.dict() for item in api_t.items] if api_t.items else [], |
| "attachments": [att.dict() for att in api_t.attachments] if api_t.attachments else [] |
| } |
| |
| if db_tender: |
| |
| for key, value in tender_data.items(): |
| setattr(db_tender, key, value) |
| count_updated += 1 |
| else: |
| |
| new_tender = TenderModel(**tender_data) |
| db.add(new_tender) |
| count_new += 1 |
| |
| db.commit() |
| print(f"[Sync] Finished. New: {count_new}, Updated: {count_updated}") |
| return {"new": count_new, "updated": count_updated} |
|
|
| def clean_expired_tenders(db: Session): |
| """ |
| Removes tenders where closing_date is in the past. |
| """ |
| now = datetime.now() |
| expired = db.query(TenderModel).filter(TenderModel.closing_date < now).delete() |
| db.commit() |
| print(f"[Sync] Cleaned {expired} expired tenders.") |
| return expired |
|
|