Álvaro Valenzuela Valdes commited on
Commit
5f3c146
·
1 Parent(s): df56059

💎 Deep Analysis: Enabled extraction and display of Evaluation Criteria and Attachments from Mercado Público

Browse files
backend/app/models/tender.py CHANGED
@@ -24,6 +24,8 @@ class TenderModel(Base):
24
  # Storage for nested structures as JSON for simplicity in this hackathon
25
  items = Column(JSON, nullable=True)
26
  attachments = Column(JSON, nullable=True)
 
 
27
 
28
  # Metadata for the app logic
29
  last_updated = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
 
24
  # Storage for nested structures as JSON for simplicity in this hackathon
25
  items = Column(JSON, nullable=True)
26
  attachments = Column(JSON, nullable=True)
27
+ evaluation_criteria = Column(JSON, nullable=True)
28
+ contract_duration = Column(String(255), nullable=True)
29
 
30
  # Metadata for the app logic
31
  last_updated = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
backend/app/schemas/tender.py CHANGED
@@ -35,4 +35,6 @@ class Tender(BaseModel):
35
  sector: Optional[str] = None
36
  items: List[TenderItem] = []
37
  attachments: List[TenderAttachment] = []
 
 
38
  raw_data: Optional[dict] = None # Store the full response if needed
 
35
  sector: Optional[str] = None
36
  items: List[TenderItem] = []
37
  attachments: List[TenderAttachment] = []
38
+ evaluation_criteria: List[dict] = []
39
+ contract_duration: Optional[str] = None
40
  raw_data: Optional[dict] = None # Store the full response if needed
backend/app/services/mercado_publico.py CHANGED
@@ -123,9 +123,30 @@ def map_raw_to_tender(item: Dict[str, Any]) -> Tender:
123
  ]
124
  buyer_fallback = institutions[code_hash % len(institutions)]
125
 
126
- buyer_name = item.get("Comprador", {}).get("NombreOrganismo")
127
- if not buyer_name or buyer_name == "Unknown":
128
- buyer_name = buyer_fallback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  return Tender(
131
  code=item.get("CodigoExterno", ""),
@@ -144,7 +165,9 @@ def map_raw_to_tender(item: Dict[str, Any]) -> Tender:
144
  region=item.get("Comprador", {}).get("RegionUnidad", "Nacional"),
145
  sector="Public",
146
  items=items_list,
147
- attachments=[],
 
 
148
  raw_data=item
149
  )
150
 
 
123
  ]
124
  buyer_fallback = institutions[code_hash % len(institutions)]
125
 
126
+ # Extract Attachments
127
+ attachments_list = []
128
+ raw_docs = item.get("Documentos", {})
129
+ if isinstance(raw_docs, dict) and "Listado" in raw_docs:
130
+ for doc in raw_docs["Listado"]:
131
+ attachments_list.append({
132
+ "name": doc.get("Nombre", "Adjunto"),
133
+ "url": doc.get("Url", "")
134
+ })
135
+
136
+ # Extract Evaluation Criteria
137
+ criteria_list = []
138
+ raw_criteria = item.get("Criterios", {})
139
+ if isinstance(raw_criteria, dict) and "Listado" in raw_criteria:
140
+ for crit in raw_criteria["Listado"]:
141
+ criteria_list.append({
142
+ "name": crit.get("NombreCriterio"),
143
+ "weight": crit.get("Puntaje"),
144
+ "description": crit.get("Notas")
145
+ })
146
+
147
+ # Extract Duration
148
+ plazos = item.get("Plazos", {})
149
+ duration = plazos.get("DuracionContrato")
150
 
151
  return Tender(
152
  code=item.get("CodigoExterno", ""),
 
165
  region=item.get("Comprador", {}).get("RegionUnidad", "Nacional"),
166
  sector="Public",
167
  items=items_list,
168
+ attachments=attachments_list,
169
+ evaluation_criteria=criteria_list,
170
+ contract_duration=duration,
171
  raw_data=item
172
  )
173
 
backend/app/services/sync.py CHANGED
@@ -64,7 +64,9 @@ async def sync_tenders_to_db(db: Session, keyword: str = None):
64
  "region": api_t.region,
65
  "sector": api_t.sector,
66
  "items": [item.model_dump() for item in api_t.items] if api_t.items else [],
67
- "attachments": [att.model_dump() for att in api_t.attachments] if api_t.attachments else []
 
 
68
  }
69
 
70
  if db_tender:
 
64
  "region": api_t.region,
65
  "sector": api_t.sector,
66
  "items": [item.model_dump() for item in api_t.items] if api_t.items else [],
67
+ "attachments": api_t.attachments,
68
+ "evaluation_criteria": api_t.evaluation_criteria,
69
+ "contract_duration": api_t.contract_duration
70
  }
71
 
72
  if db_tender:
frontend/components/TenderSearch.tsx CHANGED
@@ -252,6 +252,28 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
252
  <h4 className="text-[10px] font-black uppercase tracking-[0.3em] text-slate-500 mb-4">Detailed Description</h4>
253
  <div className="text-slate-300 leading-relaxed text-lg bg-white/[0.02] p-10 rounded-[2.5rem] border border-white/5 whitespace-pre-wrap">{tender.description || "No description provided."}</div>
254
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
  {tender.items && tender.items.length > 0 && (
257
  <div>
@@ -291,9 +313,12 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
291
  {tender.attachments && tender.attachments.length > 0 ? (
292
  <div className="space-y-3">
293
  {tender.attachments.map((att, idx) => (
294
- <a key={idx} href={att.url} target="_blank" rel="noopener noreferrer" className="flex items-center justify-between p-4 rounded-xl bg-black/40 border border-white/5 hover:border-purple-500/40 transition-all group">
295
- <span className="text-[11px] text-slate-400 group-hover:text-white truncate max-w-[180px]">{att.name}</span>
296
- <span className="text-xs">📥</span>
 
 
 
297
  </a>
298
  ))}
299
  </div>
 
252
  <h4 className="text-[10px] font-black uppercase tracking-[0.3em] text-slate-500 mb-4">Detailed Description</h4>
253
  <div className="text-slate-300 leading-relaxed text-lg bg-white/[0.02] p-10 rounded-[2.5rem] border border-white/5 whitespace-pre-wrap">{tender.description || "No description provided."}</div>
254
  </div>
255
+
256
+ {tender.evaluation_criteria && tender.evaluation_criteria.length > 0 && (
257
+ <div>
258
+ <h4 className="text-[10px] font-black uppercase tracking-[0.3em] text-slate-500 mb-6 flex items-center gap-2">
259
+ <span className="text-purple-500">⚖️</span> Evaluation Criteria
260
+ </h4>
261
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
262
+ {tender.evaluation_criteria.map((crit, idx) => (
263
+ <div key={idx} className="p-6 rounded-3xl bg-slate-900/60 border border-white/5 hover:border-purple-500/30 transition-all group relative overflow-hidden">
264
+ <div className="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
265
+ <span className="text-4xl font-black">{crit.weight}%</span>
266
+ </div>
267
+ <div className="flex justify-between items-start mb-2">
268
+ <span className="text-sm font-bold text-slate-200 group-hover:text-purple-400 transition-colors pr-12">{crit.name}</span>
269
+ <span className="text-[10px] font-black text-purple-400 bg-purple-400/10 px-2 py-1 rounded-lg border border-purple-400/20">{crit.weight}%</span>
270
+ </div>
271
+ {crit.description && <p className="text-[11px] text-slate-500 line-clamp-3 italic leading-relaxed">{crit.description}</p>}
272
+ </div>
273
+ ))}
274
+ </div>
275
+ </div>
276
+ )}
277
 
278
  {tender.items && tender.items.length > 0 && (
279
  <div>
 
313
  {tender.attachments && tender.attachments.length > 0 ? (
314
  <div className="space-y-3">
315
  {tender.attachments.map((att, idx) => (
316
+ <a key={idx} href={att.url} target="_blank" rel="noopener noreferrer" className="flex items-center justify-between p-5 rounded-3xl bg-black/40 border border-white/5 hover:bg-white/5 hover:border-purple-500/40 transition-all group">
317
+ <div className="flex items-center gap-3 overflow-hidden">
318
+ <span className="text-xl group-hover:scale-110 transition">📄</span>
319
+ <span className="text-[11px] text-slate-400 group-hover:text-white truncate max-w-[160px]">{att.name}</span>
320
+ </div>
321
+ <span className="text-xs opacity-40 group-hover:opacity-100">📥</span>
322
  </a>
323
  ))}
324
  </div>
frontend/lib/types.ts CHANGED
@@ -31,6 +31,8 @@ export type Tender = {
31
  sector?: string;
32
  items?: TenderItem[];
33
  attachments?: TenderAttachment[];
 
 
34
  };
35
 
36
  export type CompanyProfile = {
 
31
  sector?: string;
32
  items?: TenderItem[];
33
  attachments?: TenderAttachment[];
34
+ evaluation_criteria?: { name?: string; weight?: string; description?: string }[];
35
+ contract_duration?: string;
36
  };
37
 
38
  export type CompanyProfile = {