diff --git a/mcp-server/src/legal_mcp/services/db.py b/mcp-server/src/legal_mcp/services/db.py index a72b4eb..2def531 100644 --- a/mcp-server/src/legal_mcp/services/db.py +++ b/mcp-server/src/legal_mcp/services/db.py @@ -376,6 +376,16 @@ CREATE TABLE IF NOT EXISTS chair_feedback ( created_at TIMESTAMPTZ DEFAULT now() ); +CREATE TABLE IF NOT EXISTS tag_company_mappings ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tag TEXT NOT NULL, -- appeal_subtype value (e.g. building_permit) + tag_label TEXT NOT NULL DEFAULT '', -- Hebrew display label + company_id TEXT NOT NULL, -- Paperclip company UUID + company_name TEXT NOT NULL DEFAULT '', -- cached company name for display + created_at TIMESTAMPTZ DEFAULT now(), + UNIQUE(tag, company_id) +); + -- ═══════════════════════════════════════════════════════════════════ -- Indexes -- ═══════════════════════════════════════════════════════════════════ diff --git a/web/app.py b/web/app.py index 867eac2..d006975 100644 --- a/web/app.py +++ b/web/app.py @@ -2090,6 +2090,71 @@ async def api_paperclip_create_project(req: PaperclipProjectRequest): return project +# ── Settings: Tag → Company Mappings ────────────────────────────── + +@app.get("/api/settings/paperclip-companies") +async def api_paperclip_companies(): + """List all companies from Paperclip's DB.""" + pc_url = os.environ.get( + "PAPERCLIP_DB_URL", "postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip" + ) + try: + conn = await asyncpg.connect(pc_url) + try: + rows = await conn.fetch( + "SELECT id, name, identifier FROM companies ORDER BY name" + ) + return [{"id": str(r["id"]), "name": r["name"], "identifier": r.get("identifier", "")} for r in rows] + finally: + await conn.close() + except Exception as e: + raise HTTPException(502, f"Cannot reach Paperclip DB: {e}") + + +@app.get("/api/settings/tag-mappings") +async def api_get_tag_mappings(): + """Get all tag → company mappings.""" + pool = await db.get_pool() + rows = await pool.fetch( + "SELECT id, tag, tag_label, company_id, company_name, created_at FROM tag_company_mappings ORDER BY tag" + ) + return [dict(r) for r in rows] + + +class TagMappingRequest(BaseModel): + tag: str + tag_label: str = "" + company_id: str + company_name: str = "" + + +@app.post("/api/settings/tag-mappings") +async def api_add_tag_mapping(req: TagMappingRequest): + """Add a tag → company mapping.""" + pool = await db.get_pool() + try: + row = await pool.fetchrow( + """INSERT INTO tag_company_mappings (tag, tag_label, company_id, company_name) + VALUES ($1, $2, $3, $4) + ON CONFLICT (tag, company_id) DO UPDATE SET tag_label = $2, company_name = $4 + RETURNING id, tag, tag_label, company_id, company_name""", + req.tag, req.tag_label, req.company_id, req.company_name, + ) + return dict(row) + except Exception as e: + raise HTTPException(400, str(e)) + + +@app.delete("/api/settings/tag-mappings/{mapping_id}") +async def api_delete_tag_mapping(mapping_id: str): + """Delete a tag → company mapping.""" + pool = await db.get_pool() + result = await pool.execute("DELETE FROM tag_company_mappings WHERE id = $1::uuid", mapping_id) + if result == "DELETE 0": + raise HTTPException(404, "Mapping not found") + return {"ok": True} + + # ── Skill Management API ─────────────────────────────────────────── diff --git a/web/paperclip_client.py b/web/paperclip_client.py index 8def13e..1654f7e 100644 --- a/web/paperclip_client.py +++ b/web/paperclip_client.py @@ -27,19 +27,41 @@ COMPANIES = { "betterment": "8639e837-4c9d-47fa-a76b-95788d651896", # CMPA — היטלי השבחה } -APPEAL_TYPE_TO_COMPANY = { - "רישוי": "licensing", - "licensing": "licensing", - "היטל השבחה": "betterment", - "betterment_levy": "betterment", - "פיצויים": "betterment", - "compensation": "betterment", +# Fallback mapping — used only when DB lookup returns no results +_FALLBACK_APPEAL_TYPE_TO_COMPANY = { + "רישוי": COMPANIES["licensing"], + "היטל השבחה": COMPANIES["betterment"], + "פיצויים": COMPANIES["betterment"], + "building_permit": COMPANIES["licensing"], + "betterment_levy": COMPANIES["betterment"], + "compensation_197": COMPANIES["betterment"], + "compensation": COMPANIES["betterment"], + "licensing": COMPANIES["licensing"], } +# Legal-AI DB URL for reading tag_company_mappings +_LEGAL_DB_URL = os.environ.get( + "DATABASE_URL", "postgresql://legal:legal@127.0.0.1:5432/legal_ai" +) -def _get_company_id(appeal_type: str) -> str: - key = APPEAL_TYPE_TO_COMPANY.get(appeal_type, "licensing") - return COMPANIES[key] + +async def _get_company_id(appeal_type: str) -> str: + """Resolve appeal_type tag to a Paperclip company ID via DB mappings, with fallback.""" + try: + conn = await asyncpg.connect(_LEGAL_DB_URL) + try: + row = await conn.fetchrow( + "SELECT company_id FROM tag_company_mappings WHERE tag = $1 LIMIT 1", + appeal_type, + ) + if row: + return row["company_id"] + finally: + await conn.close() + except Exception: + logger.debug("DB lookup for tag mapping failed, using fallback for '%s'", appeal_type) + + return _FALLBACK_APPEAL_TYPE_TO_COMPANY.get(appeal_type, COMPANIES["licensing"]) async def create_project( @@ -50,11 +72,16 @@ async def create_project( color: str = "#6366f1", ) -> dict: """Create a project in the Paperclip embedded DB, or return existing one.""" - company_id = _get_company_id(appeal_type) - prefix = "CMP" if _get_company_id(appeal_type) == COMPANIES["licensing"] else "CMPA" + company_id = await _get_company_id(appeal_type) conn = await asyncpg.connect(PAPERCLIP_DB_URL) try: + # Resolve prefix from company identifier in Paperclip DB + comp_row = await conn.fetchrow( + "SELECT identifier FROM companies WHERE id = $1::uuid", company_id, + ) + prefix = comp_row["identifier"] if comp_row and comp_row["identifier"] else "CMP" + # Check for existing project with this case number existing = await conn.fetchrow( "SELECT id, name FROM projects WHERE name LIKE $1 AND company_id = $2::uuid", @@ -216,7 +243,10 @@ async def get_project_url(case_number: str) -> str | None: f"%{case_number}%", ) if row: - prefix = "CMP" if row["company_id"] == uuid.UUID(COMPANIES["licensing"]) else "CMPA" + comp_row = await conn.fetchrow( + "SELECT identifier FROM companies WHERE id = $1::uuid", str(row["company_id"]), + ) + prefix = comp_row["identifier"] if comp_row and comp_row["identifier"] else "CMP" return f"https://pc.nautilus.marcusgroup.org/{prefix}/projects/{row['id']}/issues" return None finally: diff --git a/web/static/index.html b/web/static/index.html index 7624c24..676caaa 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -1881,6 +1881,7 @@ kbd { כתיבה Skills מצב מערכת + הגדרות @@ -2405,6 +2406,45 @@ kbd {
כל תג ערר (סוג תיק) משויך לחברה ב-Paperclip. כשנפתח תיק חדש, הפרויקט נוצר אוטומטית בחברה המתאימה לפי התג.
+ + +