Álvaro Valenzuela Valdes commited on
Commit ·
1c92e64
1
Parent(s): 1a73a61
feat: Final auto-sync logic, manual seed button and REW branding
Browse files- backend/app/main.py +46 -79
- frontend/components/SystemInfo.tsx +37 -2
- frontend/lib/api.ts +9 -10
backend/app/main.py
CHANGED
|
@@ -1,29 +1,27 @@
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
from app.routers import analysis, company, health, tenders, documents
|
| 4 |
-
from app.
|
| 5 |
-
from app.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
# Create tables
|
| 8 |
Base.metadata.create_all(bind=engine)
|
| 9 |
|
| 10 |
app = FastAPI(title="AndesOps AI")
|
| 11 |
|
| 12 |
-
origins = [
|
| 13 |
-
"http://localhost:3000",
|
| 14 |
-
"http://localhost:3001",
|
| 15 |
-
"http://127.0.0.1:3000",
|
| 16 |
-
]
|
| 17 |
-
|
| 18 |
app.add_middleware(
|
| 19 |
CORSMiddleware,
|
| 20 |
-
allow_origins=
|
| 21 |
allow_credentials=True,
|
| 22 |
allow_methods=["*"],
|
| 23 |
allow_headers=["*"],
|
| 24 |
)
|
| 25 |
|
| 26 |
-
#
|
| 27 |
app.include_router(health.router, prefix="/api", tags=["Health"])
|
| 28 |
app.include_router(tenders.router, prefix="/api", tags=["Tenders"])
|
| 29 |
app.include_router(analysis.router, prefix="/api", tags=["Analysis"])
|
|
@@ -32,81 +30,50 @@ app.include_router(documents.router, prefix="/api", tags=["Documents"])
|
|
| 32 |
|
| 33 |
@app.on_event("startup")
|
| 34 |
async def startup_event():
|
| 35 |
-
from app.database import SessionLocal
|
| 36 |
-
from app.models.tender import TenderModel
|
| 37 |
-
from app.models.analysis import AnalysisHistoryModel
|
| 38 |
-
from app.models.company import CompanyProfileModel
|
| 39 |
-
from datetime import datetime, timedelta
|
| 40 |
-
import json
|
| 41 |
-
|
| 42 |
db = SessionLocal()
|
| 43 |
try:
|
| 44 |
count = db.query(TenderModel).count()
|
| 45 |
if count == 0:
|
| 46 |
-
print("
|
| 47 |
-
#
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
db.add(profile)
|
| 58 |
-
|
| 59 |
-
# 2. Software Tenders
|
| 60 |
-
tenders_data = [
|
| 61 |
-
TenderModel(
|
| 62 |
-
code="2394-15-LR24",
|
| 63 |
-
name="Implementación Sistema ERP para Red de Salud Oriente",
|
| 64 |
-
description="Suministro, instalación y soporte de sistema de gestión de recursos empresariales para red hospitalaria.",
|
| 65 |
-
buyer="Servicio de Salud Metropolitano",
|
| 66 |
-
status="Publicada",
|
| 67 |
-
closing_date=(datetime.now() + timedelta(days=20)).strftime("%Y-%m-%d"),
|
| 68 |
-
estimated_amount=450000000,
|
| 69 |
-
region="Metropolitana",
|
| 70 |
-
sector="Tecnología de la Información",
|
| 71 |
-
source="Mercado Público"
|
| 72 |
-
),
|
| 73 |
-
TenderModel(
|
| 74 |
-
code="5021-10-LP24",
|
| 75 |
-
name="Plataforma de IA para Análisis de Datos Criminalísticos",
|
| 76 |
-
description="Desarrollo de algoritmos de visión computacional y análisis predictivo para seguridad ciudadana.",
|
| 77 |
-
buyer="Subsecretaría de Prevención del Delito",
|
| 78 |
-
status="Publicada",
|
| 79 |
-
closing_date=(datetime.now() + timedelta(days=12)).strftime("%Y-%m-%d"),
|
| 80 |
-
estimated_amount=180000000,
|
| 81 |
-
region="Metropolitana",
|
| 82 |
-
sector="Software & IA",
|
| 83 |
-
source="Mercado Público"
|
| 84 |
-
)
|
| 85 |
-
]
|
| 86 |
-
for t in tenders_data:
|
| 87 |
-
db.add(t)
|
| 88 |
|
| 89 |
-
#
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
db.commit()
|
| 109 |
-
|
|
|
|
| 110 |
finally:
|
| 111 |
db.close()
|
| 112 |
|
|
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
from app.routers import analysis, company, health, tenders, documents
|
| 4 |
+
from app.database import engine, Base, SessionLocal
|
| 5 |
+
from app.models.tender import TenderModel
|
| 6 |
+
from app.models.analysis import AnalysisHistoryModel
|
| 7 |
+
from app.models.company import CompanyProfileModel
|
| 8 |
+
from datetime import datetime, timedelta
|
| 9 |
+
import json
|
| 10 |
|
| 11 |
# Create tables
|
| 12 |
Base.metadata.create_all(bind=engine)
|
| 13 |
|
| 14 |
app = FastAPI(title="AndesOps AI")
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
app.add_middleware(
|
| 17 |
CORSMiddleware,
|
| 18 |
+
allow_origins=["*"],
|
| 19 |
allow_credentials=True,
|
| 20 |
allow_methods=["*"],
|
| 21 |
allow_headers=["*"],
|
| 22 |
)
|
| 23 |
|
| 24 |
+
# Routes
|
| 25 |
app.include_router(health.router, prefix="/api", tags=["Health"])
|
| 26 |
app.include_router(tenders.router, prefix="/api", tags=["Tenders"])
|
| 27 |
app.include_router(analysis.router, prefix="/api", tags=["Analysis"])
|
|
|
|
| 30 |
|
| 31 |
@app.on_event("startup")
|
| 32 |
async def startup_event():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
db = SessionLocal()
|
| 34 |
try:
|
| 35 |
count = db.query(TenderModel).count()
|
| 36 |
if count == 0:
|
| 37 |
+
print("Auto-seeding database...")
|
| 38 |
+
# Basic Company Profile
|
| 39 |
+
if not db.query(CompanyProfileModel).first():
|
| 40 |
+
db.add(CompanyProfileModel(
|
| 41 |
+
name="Andes Digital Solutions",
|
| 42 |
+
industry="Software Engineering",
|
| 43 |
+
services="AI & Cloud",
|
| 44 |
+
experience="10 years",
|
| 45 |
+
regions="Nacional",
|
| 46 |
+
documents_available="RUT, Portfolio"
|
| 47 |
+
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
# Software Tenders
|
| 50 |
+
db.add(TenderModel(
|
| 51 |
+
code="2394-15-LR24",
|
| 52 |
+
name="ERP Implementation - Health Sector",
|
| 53 |
+
description="Large scale ERP for hospital network.",
|
| 54 |
+
buyer="Servicio de Salud",
|
| 55 |
+
status="Publicada",
|
| 56 |
+
closing_date=(datetime.now() + timedelta(days=15)).strftime("%Y-%m-%d"),
|
| 57 |
+
estimated_amount=450000000,
|
| 58 |
+
region="Metropolitana",
|
| 59 |
+
sector="Software",
|
| 60 |
+
source="Mercado Público"
|
| 61 |
+
))
|
| 62 |
+
db.add(TenderModel(
|
| 63 |
+
code="5021-10-LP24",
|
| 64 |
+
name="AI Platform for Crime Analysis",
|
| 65 |
+
description="Machine learning and predictive models.",
|
| 66 |
+
buyer="Subsecretaría de Prevención del Delito",
|
| 67 |
+
status="Publicada",
|
| 68 |
+
closing_date=(datetime.now() + timedelta(days=10)).strftime("%Y-%m-%d"),
|
| 69 |
+
estimated_amount=180000000,
|
| 70 |
+
region="Nacional",
|
| 71 |
+
sector="AI & Software",
|
| 72 |
+
source="Mercado Público"
|
| 73 |
+
))
|
| 74 |
db.commit()
|
| 75 |
+
except Exception as e:
|
| 76 |
+
print(f"Seed error: {e}")
|
| 77 |
finally:
|
| 78 |
db.close()
|
| 79 |
|
frontend/components/SystemInfo.tsx
CHANGED
|
@@ -1,6 +1,27 @@
|
|
| 1 |
"use client";
|
| 2 |
|
|
|
|
|
|
|
|
|
|
| 3 |
export default function SystemInfo() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
const techStack = [
|
| 5 |
{ name: "FastAPI", role: "Backend Engine", desc: "High-performance Python framework for AI orchestration." },
|
| 6 |
{ name: "Next.js 14", role: "Frontend Framework", desc: "Modern React framework with server-side capabilities." },
|
|
@@ -20,11 +41,25 @@ export default function SystemInfo() {
|
|
| 20 |
REW
|
| 21 |
</div>
|
| 22 |
<h2 className="text-4xl font-bold text-white mb-4 tracking-tight">About AndesOps AI</h2>
|
| 23 |
-
<p className="text-slate-400 max-w-2xl mx-auto text-lg leading-relaxed">
|
| 24 |
AndesOps AI is a state-of-the-art platform developed by <span className="text-white font-bold italic">REW Agency</span>.
|
| 25 |
Engineered to transform public procurement through agentic intelligence.
|
| 26 |
</p>
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
<a href="https://www.rew.cl" target="_blank" className="px-6 py-3 rounded-2xl bg-white/5 border border-white/10 text-sm font-bold text-slate-300 hover:bg-white/10 transition-all">
|
| 29 |
Visit rew.cl
|
| 30 |
</a>
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import { syncDatabase } from "../lib/api";
|
| 5 |
+
|
| 6 |
export default function SystemInfo() {
|
| 7 |
+
const [isSyncing, setIsSyncing] = useState(false);
|
| 8 |
+
const [syncStatus, setSyncStatus] = useState<string | null>(null);
|
| 9 |
+
|
| 10 |
+
const handleSync = async () => {
|
| 11 |
+
setIsSyncing(true);
|
| 12 |
+
setSyncStatus("Syncing...");
|
| 13 |
+
try {
|
| 14 |
+
await syncDatabase();
|
| 15 |
+
setSyncStatus("Success! Refreshing...");
|
| 16 |
+
setTimeout(() => window.location.reload(), 1500);
|
| 17 |
+
} catch (e) {
|
| 18 |
+
setSyncStatus("Failed to sync.");
|
| 19 |
+
console.error(e);
|
| 20 |
+
} finally {
|
| 21 |
+
setIsSyncing(false);
|
| 22 |
+
}
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
const techStack = [
|
| 26 |
{ name: "FastAPI", role: "Backend Engine", desc: "High-performance Python framework for AI orchestration." },
|
| 27 |
{ name: "Next.js 14", role: "Frontend Framework", desc: "Modern React framework with server-side capabilities." },
|
|
|
|
| 41 |
REW
|
| 42 |
</div>
|
| 43 |
<h2 className="text-4xl font-bold text-white mb-4 tracking-tight">About AndesOps AI</h2>
|
| 44 |
+
<p className="text-slate-400 max-w-2xl mx-auto text-lg leading-relaxed mb-8">
|
| 45 |
AndesOps AI is a state-of-the-art platform developed by <span className="text-white font-bold italic">REW Agency</span>.
|
| 46 |
Engineered to transform public procurement through agentic intelligence.
|
| 47 |
</p>
|
| 48 |
+
|
| 49 |
+
{/* Rescue Button */}
|
| 50 |
+
<div className="flex flex-col items-center gap-4 mb-12">
|
| 51 |
+
<button
|
| 52 |
+
onClick={handleSync}
|
| 53 |
+
disabled={isSyncing}
|
| 54 |
+
className="px-8 py-4 rounded-2xl premium-gradient text-white font-bold shadow-xl shadow-purple-500/20 active:scale-95 transition-all disabled:opacity-50"
|
| 55 |
+
>
|
| 56 |
+
{isSyncing ? "Initializing..." : "Seed Demo Data (Manual Sync)"}
|
| 57 |
+
</button>
|
| 58 |
+
{syncStatus && <p className="text-xs font-mono text-purple-400">{syncStatus}</p>}
|
| 59 |
+
<p className="text-[10px] text-slate-500 max-w-xs uppercase tracking-widest">Use this if the dashboard shows 0 items on first load</p>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<div className="flex items-center justify-center gap-6">
|
| 63 |
<a href="https://www.rew.cl" target="_blank" className="px-6 py-3 rounded-2xl bg-white/5 border border-white/10 text-sm font-bold text-slate-300 hover:bg-white/10 transition-all">
|
| 64 |
Visit rew.cl
|
| 65 |
</a>
|
frontend/lib/api.ts
CHANGED
|
@@ -1,15 +1,6 @@
|
|
| 1 |
import type { AnalysisHistoryItem, AnalysisResult, CompanyProfile, Tender } from "./types";
|
| 2 |
|
| 3 |
-
const
|
| 4 |
-
if (process.env.NEXT_PUBLIC_API_BASE) return process.env.NEXT_PUBLIC_API_BASE;
|
| 5 |
-
if (typeof window !== "undefined") {
|
| 6 |
-
// If we are in a Hugging Face Space, we need to ensure we point to the right origin
|
| 7 |
-
return window.location.origin;
|
| 8 |
-
}
|
| 9 |
-
return "http://localhost:8000";
|
| 10 |
-
};
|
| 11 |
-
|
| 12 |
-
const API_BASE = getApiBase();
|
| 13 |
|
| 14 |
const jsonHeaders = {
|
| 15 |
"Content-Type": "application/json",
|
|
@@ -113,3 +104,11 @@ export async function fetchAnalysisHistory(): Promise<AnalysisHistoryItem[]> {
|
|
| 113 |
}
|
| 114 |
return res.json();
|
| 115 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import type { AnalysisHistoryItem, AnalysisResult, CompanyProfile, Tender } from "./types";
|
| 2 |
|
| 3 |
+
const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
const jsonHeaders = {
|
| 6 |
"Content-Type": "application/json",
|
|
|
|
| 104 |
}
|
| 105 |
return res.json();
|
| 106 |
}
|
| 107 |
+
|
| 108 |
+
export async function syncDatabase() {
|
| 109 |
+
const res = await fetch(`${API_BASE}/api/tenders/sync`, { method: "POST" });
|
| 110 |
+
if (!res.ok) {
|
| 111 |
+
throw new Error("Error syncing database");
|
| 112 |
+
}
|
| 113 |
+
return res.json();
|
| 114 |
+
}
|