lovarch-cli 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/CHANGELOG.md +10 -0
  2. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/PKG-INFO +1 -1
  3. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/do_cmd.py +34 -0
  4. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/verifica_cmd.py +82 -0
  5. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/verify/__init__.py +6 -1
  6. lovarch_cli-0.3.1/lovarch_cli/verify/computo.py +149 -0
  7. lovarch_cli-0.3.1/lovarch_cli/verify/pratica.py +122 -0
  8. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/version.py +1 -1
  9. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/workflows/platform.py +31 -0
  10. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/.gitignore +0 -0
  11. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/LICENSE +0 -0
  12. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/README.md +0 -0
  13. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/__init__.py +0 -0
  14. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/__main__.py +0 -0
  15. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/agents/__init__.py +0 -0
  16. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/agents/prompts.py +0 -0
  17. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/agents/runner.py +0 -0
  18. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/ai/__init__.py +0 -0
  19. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/ai/gateway.py +0 -0
  20. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/api.py +0 -0
  21. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/auth/__init__.py +0 -0
  22. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/auth/keyring_store.py +0 -0
  23. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/auth/local_server.py +0 -0
  24. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/auth/pkce.py +0 -0
  25. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/auth/session.py +0 -0
  26. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/cli.py +0 -0
  27. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/clients/__init__.py +0 -0
  28. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/clients/factory.py +0 -0
  29. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/clients/local_client.py +0 -0
  30. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/clients/lovarch_storage.py +0 -0
  31. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/clients/lovarch_supabase.py +0 -0
  32. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/clients/persistence.py +0 -0
  33. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/clients/storage.py +0 -0
  34. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/__init__.py +0 -0
  35. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/account.py +0 -0
  36. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/agent_cmd.py +0 -0
  37. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/audit.py +0 -0
  38. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/config_cmd.py +0 -0
  39. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/consolidate.py +0 -0
  40. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/context_cmd.py +0 -0
  41. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/dev.py +0 -0
  42. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/init.py +0 -0
  43. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/jobs_cmd.py +0 -0
  44. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/login.py +0 -0
  45. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/mcp_cmd.py +0 -0
  46. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/run.py +0 -0
  47. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/signup.py +0 -0
  48. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/skills_cmd.py +0 -0
  49. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/status.py +0 -0
  50. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/commands/upgrade.py +0 -0
  51. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/config.py +0 -0
  52. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/config_store.py +0 -0
  53. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/credits/__init__.py +0 -0
  54. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/credits/base.py +0 -0
  55. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/credits/factory.py +0 -0
  56. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/credits/local.py +0 -0
  57. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/credits/lovarch.py +0 -0
  58. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/i18n/__init__.py +0 -0
  59. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/i18n/loader.py +0 -0
  60. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/i18n/translations/en.json +0 -0
  61. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/i18n/translations/es.json +0 -0
  62. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/i18n/translations/it.json +0 -0
  63. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/i18n/translations/pt.json +0 -0
  64. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/mcp/__init__.py +0 -0
  65. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/mcp/server.py +0 -0
  66. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/mcp/tools.py +0 -0
  67. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/sample_downloader.py +0 -0
  68. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/skills/lovarch-capitolato/SKILL.md +0 -0
  69. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/skills/lovarch-direzione-lavori/SKILL.md +0 -0
  70. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/skills/lovarch-interior-designer/SKILL.md +0 -0
  71. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/skills/lovarch-preventivi/SKILL.md +0 -0
  72. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/skills/lovarch-render/SKILL.md +0 -0
  73. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/skills/lovarch-verifica-normativa/SKILL.md +0 -0
  74. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/README.md +0 -0
  75. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/auditor-input.md +0 -0
  76. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/bim-engineer.md +0 -0
  77. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/briefing-architect.md +0 -0
  78. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/cad-engineer.md +0 -0
  79. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/capitolato-writer.md +0 -0
  80. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/computo-engineer.md +0 -0
  81. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/concept-designer.md +0 -0
  82. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/contratto-architect.md +0 -0
  83. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/deliverable-builder.md +0 -0
  84. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/energy-prelim.md +0 -0
  85. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/pratiche-it.md +0 -0
  86. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/progetto-chief.md +0 -0
  87. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/quality-dati.md +0 -0
  88. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/quality-misure.md +0 -0
  89. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/quality-normativa.md +0 -0
  90. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/quality-output.md +0 -0
  91. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/agents/regolatorio-it.md +0 -0
  92. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/checklists/handoff-quality-gate.md +0 -0
  93. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/checklists/quality-dati-checklist.md +0 -0
  94. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/checklists/quality-misure-checklist.md +0 -0
  95. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/checklists/quality-normativa-checklist.md +0 -0
  96. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/checklists/quality-output-checklist.md +0 -0
  97. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/config.yaml +0 -0
  98. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/data/CHANGELOG.md +0 -0
  99. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/data/agents-prd.md +0 -0
  100. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/data/architettura-progetto-rules.md +0 -0
  101. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/data/handoff-card-template.md +0 -0
  102. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/data/mocks/catasto-visura.json +0 -0
  103. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/data/mocks/firma-envelope.json +0 -0
  104. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/data/prezzario-lombardia-sample.json +0 -0
  105. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/api_clients.py +0 -0
  106. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/architect_profile.py +0 -0
  107. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/deliverable_generators.py +0 -0
  108. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/generate_attico_brera_dwg.py +0 -0
  109. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/generate_chianti_dxf.py +0 -0
  110. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/generate_chianti_images.py +0 -0
  111. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/generate_real_sample_images.py +0 -0
  112. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/generate_sample_assets.py +0 -0
  113. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/input_parser.py +0 -0
  114. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/lovarch_client.py +0 -0
  115. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/pipeline_runner.py +0 -0
  116. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/render_dxf_to_png.py +0 -0
  117. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/run_palestra_demo.sh +0 -0
  118. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/simulate_squad_execution.py +0 -0
  119. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/scripts/validate-squad.py +0 -0
  120. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/tasks/audit-input.md +0 -0
  121. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/tasks/compute-metric.md +0 -0
  122. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/tasks/consolidate-dossier.md +0 -0
  123. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/tasks/generate-cad-plan.md +0 -0
  124. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/tasks/generate-ifc-model.md +0 -0
  125. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/tasks/write-capitolato.md +0 -0
  126. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/templates/asseverazione-tecnica.md +0 -0
  127. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/templates/capitolato-uni-11337.md +0 -0
  128. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/templates/cila-comune-milano.md +0 -0
  129. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/templates/contratto-cnappc.md +0 -0
  130. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad/workflows/dal-brief-al-cantiere.yaml +0 -0
  131. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/squad_loader.py +0 -0
  132. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/verify/contratto.py +0 -0
  133. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/verify/dossier.py +0 -0
  134. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/verify/misure.py +0 -0
  135. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/verify/normativa.py +0 -0
  136. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/lovarch_cli/workflows/__init__.py +0 -0
  137. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/pyproject.toml +0 -0
  138. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/scripts/refresh_squad_vendor.py +0 -0
  139. {lovarch_cli-0.3.0 → lovarch_cli-0.3.1}/scripts/sync_squad.py +0 -0
@@ -5,6 +5,16 @@ All notable changes to `lovarch-cli` are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.1] — 2026-07-04
9
+
10
+ ### Added
11
+ - `lovarch do script <topic>` — script di contenuto strutturato via piattaforma (schema drift della EF scripts-generate corretto lato monorepo; refund resiliente).
12
+ - `lovarch verifica computo <file> [--region --version]` — confronto deterministico (gratis) delle voci di un computo col prezzario regionale (tabella `prezzari`, seed Lombardia): codici inesistenti, prezzi fuori tolleranza (±20%), unità incoerenti, totale.
13
+ - `lovarch verifica pratica <file> [--tipo CILA|SCIA]` — verifica adversarial (2 modelli) di una pratica edilizia: completezza (catasto, titolo, asseverazione) e coerenza titolo↔intervento.
14
+
15
+ ### Notes
16
+ - Persistenza premium: il runner ora crea il progetto CRM completo nell'account dell'utente tramite la EF `cli-persist` (scrittura controllata lato server, nessuna scrittura cross-tenant).
17
+
8
18
  ## [Unreleased]
9
19
 
10
20
  (No unreleased changes yet — last release was v0.3.0.)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lovarch-cli
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: AI-powered architectural project execution CLI by Lovarch
5
5
  Project-URL: Homepage, https://archprime.io
6
6
  Project-URL: Documentation, https://lovarch.com/cli-docs
@@ -154,3 +154,37 @@ def site_command(
154
154
  err_console.print(f"[red]✗ {out.get('error')}[/red]"); raise typer.Exit(1)
155
155
  console.print(f"[green]✓[/green] Sito: [bold]{out['saved_to']}[/bold] ({out.get('bytes', 0)} bytes)")
156
156
 
157
+
158
+ @do_app.command("script")
159
+ def script_command(
160
+ topic: str = typer.Argument(..., help="Argomento dello script."),
161
+ type: str = typer.Option("reel", "--type", help="reel | post | carousel | video…"),
162
+ goal: str = typer.Option("educare", "--goal", help="Obiettivo del contenuto."),
163
+ tone: str = typer.Option("professionale", "--tone", help="Tono di voce."),
164
+ cta: str = typer.Option("", "--cta", help="Call-to-action."),
165
+ output: Path = typer.Option(None, "--output", "-o", help="Salva lo script su file."),
166
+ language: str = typer.Option(None, "--language"),
167
+ ) -> None:
168
+ """Script di contenuto strutturato via piattaforma (addebita crediti)."""
169
+ wf = _workflows()
170
+ from lovarch_cli.workflows import WorkflowError
171
+
172
+ try:
173
+ s = asyncio.run(wf.script(topic, type=type, goal=goal, tone=tone, cta=cta,
174
+ language=_lang(language)))
175
+ except WorkflowError as exc:
176
+ err_console.print(f"[red]✗ {exc}[/red]"); raise typer.Exit(1)
177
+
178
+ title = s.get("title") or topic
179
+ content = s.get("content") or ""
180
+ console.print(f"\n[bold gold1]{title}[/bold gold1]\n")
181
+ if content:
182
+ console.print(content)
183
+ if s.get("keywords"):
184
+ console.print("\n[dim]" + " ".join(s["keywords"]) + "[/dim]")
185
+ if not s.get("persisted", True):
186
+ console.print("\n[yellow]⚠ Non salvato nel tuo account (crediti rimborsati).[/yellow]")
187
+ if output:
188
+ Path(output).expanduser().write_text(content or title, encoding="utf-8")
189
+ console.print(f"\n[green]✓[/green] salvato: {output}")
190
+
@@ -49,6 +49,88 @@ def misure_command(
49
49
  raise typer.Exit(0 if report.verdict == "PASS" else (2 if report.verdict == "CONCERNS" else 1))
50
50
 
51
51
 
52
+ @verifica_app.command("computo")
53
+ def computo_command(
54
+ computo: Path = typer.Argument(..., help="Computo metrico (.csv o .json: codice,quantita,prezzo_unitario)."),
55
+ region: str = typer.Option("Lombardia", "--region", help="Regione del prezzario di riferimento."),
56
+ version: str = typer.Option(None, "--version", help="Versione del prezzario (opzionale)."),
57
+ ) -> None:
58
+ """Confronta le voci del computo col prezzario regionale (gratis, deterministico)."""
59
+ from lovarch_cli.auth.session import LovarchSession
60
+ from lovarch_cli.verify import verify_computo
61
+ from lovarch_cli.verify.computo import ComputoError
62
+
63
+ session = LovarchSession.load()
64
+ if session is None:
65
+ err_console.print("[red]✗ Non autenticato. Esegui `lovarch login --premium`.[/red]")
66
+ raise typer.Exit(1)
67
+
68
+ try:
69
+ report = asyncio.run(verify_computo(session, computo, region=region, version=version))
70
+ except ComputoError as exc:
71
+ err_console.print(f"[red]✗ {exc}[/red]")
72
+ raise typer.Exit(1)
73
+
74
+ table = Table(title=f"verifica computo — {computo.name}", header_style="bold gold1")
75
+ table.add_column("Metrica", style="cyan")
76
+ table.add_column("Valore", justify="right")
77
+ table.add_row("Prezzario", str(report.stats.get("prezzario", "—")))
78
+ table.add_row("Voci totali", str(report.stats.get("voci_totali", "—")))
79
+ table.add_row("Voci verificate", str(report.stats.get("voci_verificate", "—")))
80
+ table.add_row("Codici sconosciuti", str(report.stats.get("codici_sconosciuti", 0)))
81
+ table.add_row("Prezzi fuori tolleranza", str(report.stats.get("prezzi_fuori_tolleranza", 0)))
82
+ table.add_row("Unità incoerenti", str(report.stats.get("unita_incoerenti", 0)))
83
+ table.add_row("Totale computo (€)", f"{report.stats.get('totale_computo_eur', 0):,.2f}")
84
+ console.print(table)
85
+ for f in report.findings:
86
+ console.print(f" [yellow]·[/yellow] {f}")
87
+ _print_verdict(report.verdict)
88
+ raise typer.Exit(0 if report.verdict == "PASS" else (2 if report.verdict == "CONCERNS" else 1))
89
+
90
+
91
+ @verifica_app.command("pratica")
92
+ def pratica_command(
93
+ documento: Path = typer.Argument(..., help="Pratica edilizia (.pdf, .md, .txt)."),
94
+ tipo: str = typer.Option(None, "--tipo", help="CILA | SCIA (se noto)."),
95
+ language: str = typer.Option(None, "--language", help="Lingua del report."),
96
+ ) -> None:
97
+ """Verifica adversarial di una pratica CILA/SCIA (2 modelli · debita crediti)."""
98
+ from lovarch_cli.ai import LovarchAiGateway
99
+ from lovarch_cli.auth.session import LovarchSession
100
+ from lovarch_cli.i18n import current_lang
101
+ from lovarch_cli.verify import verify_pratica
102
+ from lovarch_cli.verify.normativa import NormativaError
103
+
104
+ session = LovarchSession.load()
105
+ if session is None:
106
+ err_console.print("[red]✗ Non autenticato. Esegui `lovarch login --premium`.[/red]")
107
+ raise typer.Exit(1)
108
+
109
+ try:
110
+ report = asyncio.run(verify_pratica(
111
+ LovarchAiGateway(session), documento, tipo=tipo,
112
+ language=language or current_lang(),
113
+ ))
114
+ except NormativaError as exc:
115
+ err_console.print(f"[red]✗ {exc}[/red]")
116
+ raise typer.Exit(1)
117
+
118
+ console.print(f"[dim]Tipo pratica: {report.tipo}[/dim]")
119
+ table = Table(title=f"verifica pratica — {documento.name}", header_style="bold gold1")
120
+ table.add_column("Area", style="cyan")
121
+ table.add_column("Severità", justify="center")
122
+ table.add_column("Motivo")
123
+ for f in report.findings:
124
+ sev = str(f.get("severity", "")).lower()
125
+ icon = {"critical": "[red]✗[/red]", "concern": "[yellow]?[/yellow]", "info": "[dim]·[/dim]"}.get(sev, "?")
126
+ table.add_row(str(f.get("area", "—")), icon, str(f.get("reason", ""))[:100])
127
+ console.print(table)
128
+ console.print("[dim]Firma e responsabilità restano del tecnico abilitato (BOZZA).[/dim]")
129
+ console.print(f"[dim]Crediti addebitati: {report.credits_charged}[/dim]")
130
+ _print_verdict(report.verdict)
131
+ raise typer.Exit(0 if report.verdict == "PASS" else (2 if report.verdict == "CONCERNS" else 1))
132
+
133
+
52
134
  @verifica_app.command("normativa")
53
135
  def normativa_command(
54
136
  documento: Path = typer.Argument(..., help="Documento da verificare (.pdf, .md, .txt)."),
@@ -7,9 +7,14 @@ Sonnet 5 (executor) extracts/structures → Opus 4.8 (verifier) tries to refute
7
7
  each claim independently. Credits are debited per real tokens via cli-ai-text;
8
8
  deterministic checks are free.
9
9
  """
10
+ from lovarch_cli.verify.computo import verify_computo
10
11
  from lovarch_cli.verify.contratto import verify_contratto
11
12
  from lovarch_cli.verify.dossier import verify_dossier
12
13
  from lovarch_cli.verify.misure import verify_misure
13
14
  from lovarch_cli.verify.normativa import verify_normativa
15
+ from lovarch_cli.verify.pratica import verify_pratica
14
16
 
15
- __all__ = ["verify_contratto", "verify_dossier", "verify_misure", "verify_normativa"]
17
+ __all__ = [
18
+ "verify_computo", "verify_contratto", "verify_dossier",
19
+ "verify_misure", "verify_normativa", "verify_pratica",
20
+ ]
@@ -0,0 +1,149 @@
1
+ """verifica computo — deterministic check of a computo metrico against the
2
+ regional prezzario (table `prezzari`). No LLM, no credits: it compares each
3
+ voce's unit price and unit of measure to the official reference and flags
4
+ codes that don't exist, prices out of tolerance, and unit mismatches.
5
+
6
+ Input file: CSV (header: codice,descrizione,quantita,prezzo_unitario[,unita])
7
+ or JSON (list of {codice, quantita, prezzo_unitario, unita?}).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import csv
12
+ import io
13
+ import json
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ # A unit price this far from the prezzario reference is flagged (±%).
19
+ PRICE_TOLERANCE = 0.20
20
+
21
+
22
+ class ComputoError(Exception):
23
+ pass
24
+
25
+
26
+ @dataclass
27
+ class ComputoReport:
28
+ verdict: str # PASS | CONCERNS | REJECT
29
+ findings: list[str] = field(default_factory=list)
30
+ stats: dict = field(default_factory=dict)
31
+
32
+
33
+ def _parse_computo(path: Path) -> list[dict]:
34
+ text = path.read_text(encoding="utf-8")
35
+ if path.suffix.lower() == ".json":
36
+ data = json.loads(text)
37
+ if not isinstance(data, list):
38
+ raise ComputoError("il JSON del computo deve essere una lista di voci")
39
+ return data
40
+ # CSV
41
+ reader = csv.DictReader(io.StringIO(text))
42
+ return [dict(row) for row in reader]
43
+
44
+
45
+ def _num(v: Any) -> float | None:
46
+ if v is None or v == "":
47
+ return None
48
+ try:
49
+ return float(str(v).replace(",", ".").replace("€", "").strip())
50
+ except ValueError:
51
+ return None
52
+
53
+
54
+ async def verify_computo(
55
+ session: Any,
56
+ computo_path: str | Path,
57
+ *,
58
+ region: str = "Lombardia",
59
+ version: str | None = None,
60
+ ) -> ComputoReport:
61
+ """Compare a computo file's voci against the prezzari reference for a region."""
62
+ path = Path(computo_path).expanduser()
63
+ if not path.is_file():
64
+ return ComputoReport(verdict="REJECT", findings=[f"file non trovato: {path}"])
65
+
66
+ try:
67
+ voci = _parse_computo(path)
68
+ except Exception as exc: # noqa: BLE001 — parse errors are the finding
69
+ return ComputoReport(verdict="REJECT", findings=[f"computo non leggibile: {str(exc)[:150]}"])
70
+
71
+ if not voci:
72
+ return ComputoReport(verdict="REJECT", findings=["nessuna voce nel computo"])
73
+
74
+ # Load the prezzario for the region (RLS: authenticated read).
75
+ params = {"region": f"eq.{region}", "select": "codice,prezzo,unita,descrizione"}
76
+ if version:
77
+ params["version"] = f"eq.{version}"
78
+ resp = await session.request("GET", "/rest/v1/prezzari", params=params)
79
+ if resp.status_code != 200:
80
+ raise ComputoError(f"prezzari non disponibili: HTTP {resp.status_code}")
81
+ ref_rows = resp.json()
82
+ ref = {r["codice"]: r for r in ref_rows if isinstance(r, dict) and r.get("codice")}
83
+ if not ref:
84
+ raise ComputoError(f"nessun prezzario per regione '{region}'"
85
+ + (f" versione '{version}'" if version else ""))
86
+
87
+ findings: list[str] = []
88
+ unknown = 0
89
+ out_of_range = 0
90
+ unit_mismatch = 0
91
+ total_computo = 0.0
92
+ checked = 0
93
+
94
+ for i, voce in enumerate(voci, 1):
95
+ codice = str(voce.get("codice") or "").strip()
96
+ if not codice:
97
+ findings.append(f"voce {i}: codice mancante")
98
+ continue
99
+ qta = _num(voce.get("quantita") or voce.get("quantità"))
100
+ prezzo = _num(voce.get("prezzo_unitario") or voce.get("prezzo"))
101
+ unita = str(voce.get("unita") or voce.get("unità") or "").strip()
102
+
103
+ if qta is not None and prezzo is not None:
104
+ total_computo += qta * prezzo
105
+
106
+ r = ref.get(codice)
107
+ if not r:
108
+ unknown += 1
109
+ findings.append(f"voce {i} [{codice}]: codice non presente nel prezzario {region}")
110
+ continue
111
+ checked += 1
112
+
113
+ ref_price = float(r["prezzo"])
114
+ if prezzo is not None and ref_price > 0:
115
+ delta = (prezzo - ref_price) / ref_price
116
+ if abs(delta) > PRICE_TOLERANCE:
117
+ out_of_range += 1
118
+ findings.append(
119
+ f"voce {i} [{codice}]: prezzo {prezzo:.2f} vs riferimento "
120
+ f"{ref_price:.2f} ({delta:+.0%}) — fuori tolleranza ±{PRICE_TOLERANCE:.0%}"
121
+ )
122
+ if unita and r.get("unita") and unita.lower() != str(r["unita"]).lower():
123
+ unit_mismatch += 1
124
+ findings.append(
125
+ f"voce {i} [{codice}]: unità '{unita}' ≠ prezzario '{r['unita']}'"
126
+ )
127
+
128
+ stats = {
129
+ "voci_totali": len(voci),
130
+ "voci_verificate": checked,
131
+ "codici_sconosciuti": unknown,
132
+ "prezzi_fuori_tolleranza": out_of_range,
133
+ "unita_incoerenti": unit_mismatch,
134
+ "totale_computo_eur": round(total_computo, 2),
135
+ "prezzario": f"{region}"
136
+ + (f" {version}" if version else "")
137
+ + f" ({len(ref)} voci)",
138
+ }
139
+
140
+ if unknown or out_of_range:
141
+ verdict = "CONCERNS"
142
+ else:
143
+ verdict = "PASS"
144
+ # A computo where nothing could be matched is not trustworthy.
145
+ if checked == 0:
146
+ verdict = "REJECT"
147
+ findings.insert(0, "nessuna voce del computo trovata nel prezzario")
148
+
149
+ return ComputoReport(verdict=verdict, findings=findings, stats=stats)
@@ -0,0 +1,122 @@
1
+ """verifica pratica — adversarial check of a building permit filing (CILA/SCIA).
2
+
3
+ Same two-model pattern as contratto/normativa: Sonnet 5 (executor) extracts the
4
+ filing's structure and declared data, Opus 4.8 (verifier) adversarially checks
5
+ completeness and coherence against the required elements of a CILA (Comunicazione
6
+ Inizio Lavori Asseverata) or SCIA (Segnalazione Certificata di Inizio Attività):
7
+ identificativi catastali, titolo di legittimità, asseverazione del tecnico,
8
+ elaborati allegati, e coerenza dell'intervento col titolo scelto (una CILA non
9
+ copre interventi che richiedono SCIA/permesso di costruire).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from dataclasses import dataclass, field
15
+ from typing import Any
16
+
17
+ from lovarch_cli.verify.normativa import NormativaError, _parse_json, extract_text
18
+
19
+ # Required elements per filing type (used to prompt the verifier, not as a
20
+ # rigid checklist — the LLM judges presence/coherence adversarially).
21
+ CILA_REQUIRED = [
22
+ "dati anagrafici del committente", "identificativi catastali (foglio/mappale/sub)",
23
+ "titolo di legittimità dell'immobile", "descrizione dell'intervento",
24
+ "asseverazione del tecnico abilitato", "elaborati grafici allegati",
25
+ "dichiarazione di conformità urbanistica",
26
+ ]
27
+ SCIA_REQUIRED = CILA_REQUIRED + [
28
+ "relazione tecnica di asseverazione", "eventuali pareri/nulla osta (paesaggistico, ecc.)",
29
+ ]
30
+
31
+ _EXTRACT_SYSTEM = (
32
+ "Sei un assistente per pratiche edilizie italiane (CILA/SCIA). Estrai dalla "
33
+ "pratica la struttura e i dati dichiarati. Rispondi SOLO con JSON valido: "
34
+ '{"tipo": "CILA|SCIA|sconosciuto", "sezioni_presenti": ["..."], '
35
+ '"sezioni_mancanti": ["..."], "identificativi_catastali": "... o null", '
36
+ '"titolo_legittimita": "... o null", "asseverazione_presente": true|false, '
37
+ '"intervento": "descrizione sintetica", "note": ["..."]}'
38
+ )
39
+
40
+ _REFUTE_SYSTEM = (
41
+ "Sei un verificatore ADVERSARIALE di pratiche edilizie (CILA/SCIA). "
42
+ "Controlla: (1) completezza degli elementi obbligatori per il tipo dichiarato; "
43
+ "(2) presenza di identificativi catastali e titolo di legittimità; "
44
+ "(3) presenza dell'asseverazione del tecnico abilitato; (4) COERENZA tra "
45
+ "l'intervento descritto e il titolo scelto — una CILA NON copre interventi "
46
+ "strutturali o che incidono su prospetti/volumi (quelli richiedono SCIA o "
47
+ "permesso di costruire); segnala il possibile sottodimensionamento del titolo. "
48
+ "Nel dubbio sii scettico. La firma e la responsabilità restano del tecnico "
49
+ "abilitato. Rispondi SOLO con JSON valido: "
50
+ '{"findings": [{"area": "...", "severity": "critical" | "concern" | "info", '
51
+ '"reason": "..."}], "overall": "PASS" | "CONCERNS" | "REJECT"}'
52
+ )
53
+
54
+
55
+ @dataclass
56
+ class PraticaReport:
57
+ verdict: str
58
+ tipo: str = "sconosciuto"
59
+ structure: dict = field(default_factory=dict)
60
+ findings: list[dict] = field(default_factory=list)
61
+ credits_charged: int = 0
62
+
63
+
64
+ async def verify_pratica(
65
+ gateway: Any,
66
+ document_path: str,
67
+ *,
68
+ tipo: str | None = None,
69
+ language: str = "it",
70
+ max_chars: int = 60_000,
71
+ ) -> PraticaReport:
72
+ """Adversarial CILA/SCIA filing check. Debits credits (2 calls)."""
73
+ text = extract_text(document_path)
74
+ if not text.strip():
75
+ raise NormativaError("documento vuoto o testo non estraibile.")
76
+ if len(text) > max_chars:
77
+ text = text[:max_chars]
78
+
79
+ credits = 0
80
+ hint = f" (tipo dichiarato dall'utente: {tipo.upper()})" if tipo else ""
81
+ extraction = await gateway.generate_text(
82
+ f"PRATICA EDILIZIA{hint}:\n\n{text}",
83
+ role="executor",
84
+ system=_EXTRACT_SYSTEM,
85
+ max_tokens=1500,
86
+ language=language,
87
+ operation_type="verify:pratica:extract",
88
+ )
89
+ credits += extraction.credits_charged
90
+ structure = _parse_json(extraction.text)
91
+
92
+ resolved_tipo = (tipo or str(structure.get("tipo") or "sconosciuto")).upper()
93
+ required = SCIA_REQUIRED if resolved_tipo == "SCIA" else CILA_REQUIRED
94
+
95
+ refutation = await gateway.generate_text(
96
+ "STRUTTURA ESTRATTA DALLA PRATICA:\n\n"
97
+ + json.dumps(structure, ensure_ascii=False)
98
+ + f"\n\nTipo: {resolved_tipo}. Elementi obbligatori attesi: "
99
+ + ", ".join(required),
100
+ role="verifier",
101
+ system=_REFUTE_SYSTEM,
102
+ max_tokens=2000,
103
+ language=language,
104
+ operation_type="verify:pratica:refute",
105
+ )
106
+ credits += refutation.credits_charged
107
+ judged = _parse_json(refutation.text)
108
+ findings = judged.get("findings") or []
109
+
110
+ overall = str(judged.get("overall", "")).upper()
111
+ if overall not in ("PASS", "CONCERNS", "REJECT"):
112
+ severities = [str(f.get("severity", "")).lower() for f in findings]
113
+ overall = ("REJECT" if "critical" in severities
114
+ else ("CONCERNS" if "concern" in severities else "PASS"))
115
+
116
+ return PraticaReport(
117
+ verdict=overall,
118
+ tipo=resolved_tipo,
119
+ structure=structure,
120
+ findings=findings,
121
+ credits_charged=credits,
122
+ )
@@ -10,4 +10,4 @@ Bump policy (semver):
10
10
  - MINOR: new subcommands, new agents, new languages
11
11
  - PATCH: bug fixes, dependency bumps
12
12
  """
13
- __version__ = "0.3.0"
13
+ __version__ = "0.3.1"
@@ -253,7 +253,38 @@ class MoreWorkflowsMixin:
253
253
  raise WorkflowError("ai-site-generate non ha restituito HTML.")
254
254
  return str(html)
255
255
 
256
+ async def script(
257
+ self,
258
+ topic: str,
259
+ *,
260
+ type: str = "reel",
261
+ goal: str = "educare",
262
+ tone: str = "professionale",
263
+ cta: str = "",
264
+ language: str = "it",
265
+ ) -> dict:
266
+ """Structured content script via scripts-generate. Returns the script
267
+ dict (title, content, outline, hashtags). `persisted` is True when it was
268
+ saved to the account; the platform refunds credits if it couldn't save."""
269
+ data = await self._invoke("scripts-generate", {
270
+ "type": type,
271
+ "topic": topic,
272
+ "goal": goal,
273
+ "tone": tone,
274
+ "cta": cta,
275
+ "editorial_line_id": "none",
276
+ "brainstorm_idea_id": "none",
277
+ "language": language,
278
+ }, timeout=_RENDER_TIMEOUT)
279
+ script = data.get("script") or {}
280
+ if not script:
281
+ raise WorkflowError(f"scripts-generate: {data.get('error', 'nessuno script')}")
282
+ # Normalize content field (real schema uses `content`; resilient path `full_script`).
283
+ script.setdefault("content", script.get("full_script"))
284
+ script["persisted"] = bool(data.get("persisted", True))
285
+ return script
256
286
 
257
287
 
258
288
  PlatformWorkflows.logo = MoreWorkflowsMixin.logo # type: ignore[attr-defined]
259
289
  PlatformWorkflows.site = MoreWorkflowsMixin.site # type: ignore[attr-defined]
290
+ PlatformWorkflows.script = MoreWorkflowsMixin.script # type: ignore[attr-defined]
File without changes
File without changes
File without changes
File without changes