Skip to content

Commit 05f5634

Browse files
committed
pesquisa-jurisprudencia: adiciona CLI cliente PowerShell v2.0.0 com cache SHA256
1 parent 78eb2d8 commit 05f5634

1 file changed

Lines changed: 343 additions & 0 deletions

File tree

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
#Requires -Version 5.1
2+
3+
<#
4+
.SYNOPSIS
5+
Cliente PowerShell para a API Jurisprudencias.ai com cache local.
6+
.DESCRIPTION
7+
Fornece funcoes para buscar decisoes judiciais, consultar tribunais
8+
e resolver processos, com cache JSON para evitar o rate limit de 5 buscas/dia.
9+
#>
10+
11+
$Script:CacheDir = if ($env:JURISPRUDENCIAS_CACHE_DIR) { $env:JURISPRUDENCIAS_CACHE_DIR } else { "$env:USERPROFILE\.jurisprudencias\cache" }
12+
$Script:CacheTTLHours = if ($env:JURISPRUDENCIAS_CACHE_TTL) { [int]$env:JURISPRUDENCIAS_CACHE_TTL } else { 24 }
13+
$Script:ApiBaseUrl = "https://jurisprudencias.ai/api/v1"
14+
15+
$Script:Token = $env:JURISPRUDENCIAS_API_TOKEN
16+
if (-not $Script:Token) {
17+
Write-Warning "Variavel JURISPRUDENCIAS_API_TOKEN nao definida"
18+
Write-Warning "Configure com: `$env:JURISPRUDENCIAS_API_TOKEN = 'jur_...'"
19+
}
20+
21+
function _remove-diacritics($s) {
22+
$normalized = $s.Normalize([System.text.NormalizationForm]::FormD)
23+
return [Text.RegularExpressions.Regex]::Replace($normalized, '\p{M}', '')
24+
}
25+
26+
function _ensure-cache-dir {
27+
if (-not (Test-Path $Script:CacheDir)) {
28+
$null = New-Item -ItemType Directory -Path $Script:CacheDir -Force
29+
}
30+
}
31+
32+
function _cache-path($key) {
33+
$bytes = [text.encoding]::UTF8.GetBytes($key)
34+
$hashAlgo = [Security.Cryptography.SHA256]::Create()
35+
$hashBytes = $hashAlgo.ComputeHash($bytes)
36+
$hash = [System.BitConverter]::ToString($hashBytes) -replace '-', ''
37+
return Join-Path $Script:CacheDir "$hash.json"
38+
}
39+
40+
function _cache-get($key) {
41+
_ensure-cache-dir
42+
$path = _cache-path $key
43+
if (-not (Test-Path $path)) { return $null }
44+
try {
45+
$o = Get-Content -Raw -Encoding UTF8 -LiteralPath $path | ConvertFrom-Json
46+
$age = [datetime]::Now - [datetime]::Parse($o._cached_at)
47+
if ($age.TotalHours -gt $Script:CacheTTLHours) {
48+
Remove-Item -LiteralPath $path -Force
49+
return $null
50+
}
51+
return $o.data
52+
} catch { return $null }
53+
}
54+
55+
function _cache-set($key, $data) {
56+
_ensure-cache-dir
57+
$path = _cache-path $key
58+
$o = [PSCustomObject]@{ _cached_at = [datetime]::Now.ToString('o'); data = $data }
59+
$o | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $path -Encoding UTF8
60+
}
61+
62+
function _api-get($url) {
63+
$token = $Script:Token
64+
if (-not $token) { throw "Token nao disponivel. Defina JURISPRUDENCIAS_API_TOKEN" }
65+
try {
66+
$output = & "curl.exe" -s -w "%{http_code}" -H "Authorization: Bearer $token" $url 2>&1
67+
$combined = $output -join "`n"
68+
$code = $combined.Substring($combined.Length - 3)
69+
$body = $combined.Substring(0, $combined.Length - 3)
70+
if ($code -eq "429") {
71+
Write-Warning "Rate limit excedido (429). Use cache ou aguarde reset a meia-noite."
72+
return $null
73+
}
74+
if ($code -eq "404") { return $null }
75+
if ($code -ne "200") {
76+
Write-Warning "HTTP $code -- $url"
77+
return $null
78+
}
79+
$trimmed = $body.Trim()
80+
if (-not $trimmed) { return @() }
81+
$parsed = $trimmed | ConvertFrom-Json
82+
if ($parsed.PSObject.Properties.Name -contains "data") { return $parsed.data }
83+
return $parsed
84+
} catch {
85+
Write-Warning "Erro na requisicao: $_"
86+
return $null
87+
}
88+
}
89+
90+
function Get-JurCourts {
91+
$url = "$($Script:ApiBaseUrl)/courts"
92+
$data = _api-get $url
93+
if (-not $data) { return }
94+
$data | ForEach-Object {
95+
[PSCustomObject]@{ Slug = $_.id; Nome = $_.name; Decisoes = $_.decisions_count }
96+
}
97+
}
98+
99+
function Search-JurDecisions {
100+
param(
101+
[Parameter(Mandatory)] [string] $Court,
102+
[Parameter(Mandatory)] [string] $Query,
103+
[int] $Page = 0,
104+
[string] $PubFrom,
105+
[string] $PubTo,
106+
[string] $TrialFrom,
107+
[string] $TrialTo,
108+
[switch] $Force,
109+
[switch] $NoCache
110+
)
111+
$eq = [uri]::EscapeDataString($Query)
112+
$url = "{0}/courts/{1}/decisions?q={2}" -f $Script:ApiBaseUrl, $Court, $eq
113+
$url = "{0}{1}page={2}" -f $url, "&", $Page
114+
if ($PubFrom) { $url = "{0}{1}pub_from={2}" -f $url, "&", $PubFrom }
115+
if ($PubTo) { $url = "{0}{1}pub_to={2}" -f $url, "&", $PubTo }
116+
if ($TrialFrom) { $url = "{0}{1}trial_from={2}" -f $url, "&", $TrialFrom }
117+
if ($TrialTo) { $url = "{0}{1}trial_to={2}" -f $url, "&", $TrialTo }
118+
119+
$ck = "{0}|{1}|p={2}|pub={3}-{4}|tri={5}-{6}" -f $Court, $Query, $Page, $PubFrom, $PubTo, $TrialFrom, $TrialTo
120+
121+
if (-not $Force -and -not $NoCache) {
122+
$cached = _cache-get $ck
123+
if ($cached) { Write-Host "[cache]" -NoNewline -ForegroundColor DarkGray; return $cached }
124+
}
125+
$data = _api-get $url
126+
if ($null -eq $data) { return }
127+
if (-not $NoCache -and $data) { _cache-set $ck $data }
128+
return $data
129+
}
130+
131+
function Get-JurDecision {
132+
param(
133+
[Parameter(Mandatory)] [string] $Court,
134+
[Parameter(Mandatory)] [string] $Number
135+
)
136+
$en = [uri]::EscapeDataString($Number)
137+
$url = "{0}/courts/{1}/decisions/lookup?n={2}" -f $Script:ApiBaseUrl, $Court, $en
138+
$data = _api-get $url
139+
if (-not $data) { return }
140+
[PSCustomObject]@{
141+
Processo = $data.process_number
142+
Ementa = $data.summary
143+
URL = $data.url
144+
Tribunal = $data.court
145+
}
146+
}
147+
148+
function Clear-JurCache {
149+
param([int] $OlderThanHours = 0)
150+
_ensure-cache-dir
151+
$files = Get-ChildItem "$($Script:CacheDir)\*.json" -ErrorAction SilentlyContinue
152+
if (-not $files) { Write-Host "Cache vazio."; return }
153+
$removed = 0
154+
$files | ForEach-Object {
155+
if ($OlderThanHours -gt 0) {
156+
$age = ([datetime]::Now - $_.LastWriteTime).TotalHours
157+
if ($age -ge $OlderThanHours) { Remove-Item $_.FullName -Force; $removed++ }
158+
} else { Remove-Item $_.FullName -Force; $removed++ }
159+
}
160+
Write-Host "Cache limpo. Removidos $removed arquivos." -ForegroundColor Green
161+
}
162+
163+
function Get-JurCacheStatus {
164+
_ensure-cache-dir
165+
$files = Get-ChildItem "$($Script:CacheDir)\*.json" -ErrorAction SilentlyContinue
166+
if (-not $files) { Write-Host "Cache vazio."; return }
167+
$total = @($files).Count
168+
$size = ($files | Measure-Object Length -Sum).Sum
169+
$now = [datetime]::Now
170+
$expiredCount = 0
171+
$files | ForEach-Object {
172+
$age = ($now - $_.LastWriteTime).TotalHours
173+
if ($age -gt $Script:CacheTTLHours) { $expiredCount++ }
174+
$shortName = $_.Name.Substring(0, [Math]::Min(16, $_.Name.Length)) + ".."
175+
[PSCustomObject]@{
176+
Arquivo = $shortName
177+
IdadeH = [math]::Round($age, 1)
178+
Status = if ($age -gt $Script:CacheTTLHours) { "expirado" } else { "valido" }
179+
Tamanho = "{0:N1}KB" -f ($_.Length / 1KB)
180+
}
181+
} | Sort-Object IdadeH -Descending | Format-Table -AutoSize
182+
$msg = "Total: {0} itens, {1:N1}KB, {2} expirados (TTL={3}h)" -f $total, ($size/1KB), $expiredCount, $Script:CacheTTLHours
183+
Write-Host $msg -ForegroundColor Cyan
184+
}
185+
186+
function Search-JurCache {
187+
param(
188+
[Parameter(Mandatory)] [string] $Term,
189+
[string] $Court,
190+
[switch] $SimpleOutput
191+
)
192+
_ensure-cache-dir
193+
$files = Get-ChildItem "$($Script:CacheDir)\*.json" -ErrorAction SilentlyContinue
194+
if (-not $files) { Write-Host "Cache vazio. Nada para buscar."; return }
195+
196+
$results = [System.Collections.ArrayList]@()
197+
$termNorm = _remove-diacritics $Term.ToLower()
198+
199+
foreach ($f in $files) {
200+
try {
201+
$o = Get-Content -Raw -Encoding UTF8 -LiteralPath $f.FullName | ConvertFrom-Json
202+
$entries = $o.data
203+
if (-not $entries) { continue }
204+
if ($entries -isnot [array]) { $entries = @($entries) }
205+
foreach ($entry in $entries) {
206+
$courtMatch = (-not $Court) -or ($entry.court -eq $Court) -or ($entry.court_slug -eq $Court)
207+
if (-not $courtMatch) { continue }
208+
209+
$haystackParts = @(
210+
$entry.excerpt; $entry.process_number; $entry.summary
211+
$entry.process_type; $entry.rapporteur
212+
) | Where-Object { $_ } | ForEach-Object { _remove-diacritics $_.ToLower() }
213+
$haystack = $haystackParts -join " "
214+
if ($haystack -match [regex]::Escape($termNorm)) {
215+
$null = $results.Add([PSCustomObject]@{
216+
Processo = $entry.process_number
217+
Tribunal = $entry.court
218+
DataPublicacao = $entry.publication_date
219+
DataJulgamento = $entry.trial_date
220+
Relator = $entry.rapporteur
221+
Ementa = if ($entry.excerpt) { ($entry.excerpt -replace '\s+', ' ').Substring(0, [Math]::Min(200, $entry.excerpt.Length)) + "..." } else { "" }
222+
URL = $entry.url
223+
})
224+
}
225+
}
226+
} catch { continue }
227+
}
228+
if ($results.Count -eq 0) { Write-Host "Nenhum resultado offline para '$Term'."; return }
229+
if ($SimpleOutput) {
230+
$results | Select-Object Processo, DataPublicacao | Format-Table -AutoSize
231+
} else {
232+
$results | Format-Table -Property Processo, Tribunal, DataPublicacao, Ementa -AutoSize -Wrap
233+
}
234+
Write-Host "($($results.Count) resultados offline)" -ForegroundColor Cyan
235+
}
236+
237+
function Export-JurDocs {
238+
param(
239+
[string] $OutputPath = ".\jurisprudencias_offline.html",
240+
[string] $Court,
241+
[switch] $Open
242+
)
243+
_ensure-cache-dir
244+
$files = Get-ChildItem "$($Script:CacheDir)\*.json" -ErrorAction SilentlyContinue
245+
if (-not $files) { Write-Host "Cache vazio."; return }
246+
247+
$allDecisions = [System.Collections.ArrayList]@()
248+
foreach ($f in $files) {
249+
try {
250+
$o = Get-Content -Raw -Encoding UTF8 -LiteralPath $f.FullName | ConvertFrom-Json
251+
$entries = $o.data
252+
if (-not $entries -or ($entries -isnot [array])) { continue }
253+
foreach ($entry in $entries) {
254+
if ($Court -and $entry.court -ne $Court) { continue }
255+
$null = $allDecisions.Add($entry)
256+
}
257+
} catch { continue }
258+
}
259+
if ($allDecisions.Count -eq 0) { Write-Host "Nenhuma decisao no cache para exportar."; return }
260+
261+
$count = $allDecisions.Count
262+
$rows = $allDecisions | Sort-Object publication_date -Descending | ForEach-Object {
263+
$num = $_.process_number; $date = $_.publication_date; $trial = $_.trial_date
264+
$court = $_.court; $rel = $_.rapporteur; $body = $_.excerpt; $url = $_.url
265+
$title = if ($num) { $num } else { "(sem numero)" }
266+
$meta = @()
267+
if ($court) { $meta += "Tribunal: $court" }
268+
if ($date) { $meta += "Publicacao: $date" }
269+
if ($trial) { $meta += "Julgamento: $trial" }
270+
if ($rel) { $meta += "Relator: $rel" }
271+
$metaStr = $meta -join " &middot; "
272+
$bodyHtml = if ($body) {
273+
$escaped = [System.Net.WebUtility]::HtmlEncode(($body -replace '\s+', ' ').Trim())
274+
"<p>$escaped</p>"
275+
} else { "" }
276+
$urlHtml = if ($url) { "<a href=`"$([System.Net.WebUtility]::HtmlEncode($url))`" target=`"_blank`">$([System.Net.WebUtility]::HtmlEncode($url))</a>" } else { "" }
277+
@"
278+
<div class="card"><div class="card-title"><a href="#$([System.Net.WebUtility]::HtmlEncode($num))">$([System.Net.WebUtility]::HtmlEncode($title))</a></div><div class="card-meta">$metaStr</div>$bodyHtml<div class="card-url">$urlHtml</div></div>
279+
"@
280+
}
281+
282+
$html = @"
283+
<!DOCTYPE html><html lang="pt-BR"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
284+
<title>Jurisprudencias - Offline</title>
285+
<style>*{box-sizing:border-box;margin:0;padding:0}
286+
body{font-family:'Segoe UI',system-ui,sans-serif;background:#f5f5f5;color:#222;line-height:1.6;padding:2rem}
287+
.container{max-width:960px;margin:0 auto}
288+
h1{font-size:1.75rem;font-weight:600;margin-bottom:0.25rem}
289+
.subtitle{color:#666;font-size:0.9rem;margin-bottom:2rem}
290+
.stats{display:flex;gap:1rem;margin-bottom:2rem;flex-wrap:wrap}
291+
.stat{background:#fff;border-radius:8px;padding:1rem 1.25rem;box-shadow:0 1px 3px rgba(0,0,0,0.08);flex:1;min-width:120px}
292+
.stat-num{font-size:1.5rem;font-weight:700;color:#1a56db}
293+
.stat-label{font-size:0.8rem;color:#666;text-transform:uppercase;letter-spacing:0.05em}
294+
.card{background:#fff;border-radius:8px;padding:1.25rem;margin-bottom:1rem;box-shadow:0 1px 3px rgba(0,0,0,0.08)}
295+
.card-title{font-weight:600;margin-bottom:0.35rem}
296+
.card-title a{color:#1a56db;text-decoration:none}
297+
.card-meta{font-size:0.82rem;color:#888;margin-bottom:0.75rem}
298+
.card p{font-size:0.92rem;color:#444;text-align:justify}
299+
.card-url{margin-top:0.5rem;font-size:0.82rem;word-break:break-all}
300+
.search-box{margin-bottom:1.5rem}
301+
.search-box input{width:100%;padding:0.65rem 1rem;border:1px solid #d0d0d0;border-radius:8px;font-size:1rem}
302+
.search-box input:focus{outline:none;border-color:#1a56db;box-shadow:0 0 0 3px rgba(26,86,219,0.15)}
303+
.footer{text-align:center;color:#999;font-size:0.8rem;margin-top:2rem;padding-top:1rem;border-top:1px solid #e0e0e0}
304+
</style></head><body><div class="container">
305+
<h1>Jurisprudencias &mdash; Documentacao Offline</h1>
306+
<p class="subtitle">Gerado em $(Get-Date -Format "dd/MM/yyyy HH:mm") &middot; Cache local</p>
307+
<div class="stats">
308+
<div class="stat"><div class="stat-num">$count</div><div class="stat-label">Decisoes</div></div>
309+
<div class="stat"><div class="stat-num">$($(($allDecisions | ForEach-Object { $_.court } | Where-Object { $_ } | Select-Object -Unique).Count))</div><div class="stat-label">Tribunais</div></div>
310+
</div>
311+
<div class="search-box"><input type="text" id="filter" placeholder="Filtrar..." oninput="filterCards()"></div>
312+
<div id="cards">$rows</div>
313+
<div class="footer">Cache TTL: $($Script:CacheTTLHours)h</div>
314+
</div>
315+
<script>function filterCards(){var q=document.getElementById('filter').value.toLowerCase();document.querySelectorAll('.card').forEach(function(c){c.style.display=q===''||c.textContent.toLowerCase().includes(q)?'':'none'})}</script>
316+
</body></html>
317+
"@
318+
319+
$html | Set-Content -LiteralPath $OutputPath -Encoding UTF8
320+
Write-Host "Documentacao gerada: $OutputPath ($count decisoes)" -ForegroundColor Green
321+
if ($Open) { Start-Process $OutputPath }
322+
}
323+
324+
function Invoke-JurPreFetch {
325+
param(
326+
[Parameter(Mandatory)] [string] $Court,
327+
[string[]] $Terms = @("desapropriacao","precatorio","imissao na posse","indenizacao","utilidade publica","justa indenizacao","tombamento","direito de propriedade","posse","reintegracao de posse","usucapiao","bem publico","servidao administrativa","limitação administrativa"),
328+
[int] $MaxPages = 0
329+
)
330+
Write-Host "=== PRE-FETCH: $Court ===" -ForegroundColor Yellow
331+
$total = 0
332+
foreach ($term in $Terms) {
333+
for ($p = 0; $p -le $MaxPages; $p++) {
334+
Write-Host "[$($Court)] '$term' pag.$p ... " -NoNewline
335+
try {
336+
$r = Search-JurDecisions -Court $Court -Query $term -Page $p -Force
337+
if ($r -and @($r).Count -gt 0) { Write-Host "$(@($r).Count) resultados" -ForegroundColor Green; $total += @($r).Count }
338+
else { Write-Host "0 resultados" -ForegroundColor DarkGray; break }
339+
} catch { Write-Host "ERRO: $_" -ForegroundColor Red; break }
340+
}
341+
}
342+
Write-Host "=== CONCLUIDO: $total decisoes cacheadas ===" -ForegroundColor Yellow
343+
}

0 commit comments

Comments
 (0)