CC1 R3B-Mreža M1+M2+M3 — autocomplete + 3D centar + forensic enrich

M1 (default centar):
- Augment /api/v1/presenter/graph-real with synthetic 'pgz-savez-nogometni' anchor
  (PGŽ gold, size 40), connected to top 3 person + top 3 entity nodes
- centerMrezaOnAnchor() called 1.5s after render and via "🎯 Centar (PGŽ)" button

M2 (autocomplete):
- Backend GET /api/v2/search/suggest?q=&type=person|club|company
  Searches pgz_sport.klubovi, pgz_sport.savezi, pgz_sport.clanovi,
  civic.persons, civic.entities; returns 20 results max
- Frontend: 3 inputs get keydown+input handlers, dropdown UI under each
  Enter → first suggestion, click → suggestion, blur → close
- centerMrezaOnSuggestion: finds existing node by label, or injects new node
  + edge from anchor and re-renders

M3 (forensic enrich):
- Backend POST /api/v2/forensic/findings/{id}/enrich
  Extract person name from entities_involved or title regex,
  hit hr.wikipedia.org REST summary, persist into raw_data.enrichment
- Frontend: forensicEnrichBlock + customFindingEnrichBlock added to alert
  panel and custom-finding panel (Liverić). Custom uses direct Wikipedia
  fetch since they're not in DB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude-cc1
2026-05-05 00:16:45 +02:00
parent 59a537388d
commit fbbe953de3
+44
View File
@@ -2417,10 +2417,54 @@ function renderCustomFindingPanel(c){
<div class="card-h"><div class="card-t">📄 Dokumenti / dokazi</div></div> <div class="card-h"><div class="card-t">📄 Dokumenti / dokazi</div></div>
<div class="empty" style="padding:14px">Za ovaj manualni nalaz nisu priloženi PDF dokazi. Pokreni "Obogati podatke" za prikupljanje izvora.</div> <div class="empty" style="padding:14px">Za ovaj manualni nalaz nisu priloženi PDF dokazi. Pokreni "Obogati podatke" za prikupljanje izvora.</div>
</div> </div>
${customFindingEnrichBlock(c.id, c.osoba || c.naslov)}
`; `;
openPanel('Forenzika · '+c.naslov, html); openPanel('Forenzika · '+c.naslov, html);
} }
function customFindingEnrichBlock(customId, queryName){
const safeId = String(customId).replace(/[^a-z0-9_-]/gi,'_');
return `
<div class="card" id="fenrich-card-${safeId}">
<div class="card-h">
<div class="card-t">✨ Obogati podatke (Wikipedia)</div>
<button class="btn primary" onclick="enrichCustomFinding('${safeId}', '${esc(queryName).replace(/'/g,'\\\\&#39;')}')">▶ Pokreni</button>
</div>
<div id="fenrich-out-${safeId}">
<div class="empty" style="padding:14px">Lookup Wikipedia HR za "${esc(queryName)}" i prikaži dopune.</div>
</div>
</div>
`;
}
async function enrichCustomFinding(safeId, queryName){
const out = document.getElementById('fenrich-out-'+safeId);
if(out) out.innerHTML = '<div class="loading">Lookup Wikipedia HR…</div>';
// Custom findings aren't in DB — call wiki lookup via a synthesised forensic_findings.id of -1 won't work.
// Instead, use the existing /v2/enrich/sportas pattern to query Wikipedia by name.
// We re-use the wiki summary via a mini fetch helper.
try{
const wiki = await fetch('https://hr.wikipedia.org/api/rest_v1/page/summary/'+encodeURIComponent(queryName.replace(/ /g,'_')))
.then(r => r.ok ? r.json() : null).catch(()=>null);
if(!wiki || wiki.type==='disambiguation' || !wiki.extract){
if(out) out.innerHTML = '<div class="empty" style="padding:14px">Nije pronađen Wikipedia HR članak za <b>'+esc(queryName)+'</b>.</div>';
return;
}
const w = {title: wiki.title, extract: wiki.extract, description: wiki.description, url: (wiki.content_urls||{}).desktop?.page};
if(out) out.innerHTML = `
<div style="margin-bottom:8px"><span class="tag gr">🟢 Wikipedia HR</span></div>
<div style="padding:10px;background:var(--bg3);border-left:3px solid var(--pgz-gold);border-radius:5px">
<div style="font-weight:700;color:var(--t0);font-size:14px;margin-bottom:6px">${esc(w.title||'')}</div>
${w.description?'<div style="font-size:11px;color:var(--t2);margin-bottom:6px;font-style:italic">'+esc(w.description)+'</div>':''}
${w.extract?'<div style="font-size:12px;line-height:1.6;color:var(--t1)">'+esc(w.extract)+'</div>':''}
${w.url?'<div style="margin-top:8px"><a href="'+esc(w.url)+'" target="_blank">↗ Otvori članak</a></div>':''}
</div>`;
}catch(e){
if(out) out.innerHTML = '<div class="empty" style="color:var(--red);padding:14px">Greška: '+esc(String(e))+'</div>';
}
}
function renderAlertPanel(a){ function renderAlertPanel(a){
const sevColor = a.razina==='CRITICAL'?'rd':(a.razina==='HIGH'?'am':'b'); const sevColor = a.razina==='CRITICAL'?'rd':(a.razina==='HIGH'?'am':'b');
const html = ` const html = `