lovarch-cli 0.2.1__py3-none-any.whl

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 (122) hide show
  1. lovarch_cli/__init__.py +16 -0
  2. lovarch_cli/__main__.py +10 -0
  3. lovarch_cli/ai/__init__.py +21 -0
  4. lovarch_cli/ai/gateway.py +240 -0
  5. lovarch_cli/api.py +111 -0
  6. lovarch_cli/auth/__init__.py +32 -0
  7. lovarch_cli/auth/keyring_store.py +214 -0
  8. lovarch_cli/auth/local_server.py +165 -0
  9. lovarch_cli/auth/pkce.py +57 -0
  10. lovarch_cli/auth/session.py +189 -0
  11. lovarch_cli/cli.py +262 -0
  12. lovarch_cli/clients/__init__.py +33 -0
  13. lovarch_cli/clients/factory.py +54 -0
  14. lovarch_cli/clients/local_client.py +432 -0
  15. lovarch_cli/clients/lovarch_storage.py +174 -0
  16. lovarch_cli/clients/lovarch_supabase.py +295 -0
  17. lovarch_cli/clients/persistence.py +166 -0
  18. lovarch_cli/clients/storage.py +66 -0
  19. lovarch_cli/commands/__init__.py +10 -0
  20. lovarch_cli/commands/account.py +172 -0
  21. lovarch_cli/commands/audit.py +394 -0
  22. lovarch_cli/commands/config_cmd.py +80 -0
  23. lovarch_cli/commands/consolidate.py +217 -0
  24. lovarch_cli/commands/context_cmd.py +73 -0
  25. lovarch_cli/commands/dev.py +287 -0
  26. lovarch_cli/commands/do_cmd.py +120 -0
  27. lovarch_cli/commands/init.py +218 -0
  28. lovarch_cli/commands/jobs_cmd.py +95 -0
  29. lovarch_cli/commands/login.py +202 -0
  30. lovarch_cli/commands/mcp_cmd.py +26 -0
  31. lovarch_cli/commands/run.py +375 -0
  32. lovarch_cli/commands/signup.py +185 -0
  33. lovarch_cli/commands/status.py +243 -0
  34. lovarch_cli/commands/upgrade.py +108 -0
  35. lovarch_cli/commands/verifica_cmd.py +174 -0
  36. lovarch_cli/config.py +101 -0
  37. lovarch_cli/config_store.py +111 -0
  38. lovarch_cli/credits/__init__.py +35 -0
  39. lovarch_cli/credits/base.py +84 -0
  40. lovarch_cli/credits/factory.py +36 -0
  41. lovarch_cli/credits/local.py +34 -0
  42. lovarch_cli/credits/lovarch.py +56 -0
  43. lovarch_cli/i18n/__init__.py +27 -0
  44. lovarch_cli/i18n/loader.py +121 -0
  45. lovarch_cli/i18n/translations/en.json +168 -0
  46. lovarch_cli/i18n/translations/es.json +168 -0
  47. lovarch_cli/i18n/translations/it.json +168 -0
  48. lovarch_cli/i18n/translations/pt.json +168 -0
  49. lovarch_cli/mcp/__init__.py +9 -0
  50. lovarch_cli/mcp/server.py +199 -0
  51. lovarch_cli/mcp/tools.py +372 -0
  52. lovarch_cli/sample_downloader.py +255 -0
  53. lovarch_cli/squad/README.md +206 -0
  54. lovarch_cli/squad/agents/auditor-input.md +353 -0
  55. lovarch_cli/squad/agents/bim-engineer.md +404 -0
  56. lovarch_cli/squad/agents/briefing-architect.md +249 -0
  57. lovarch_cli/squad/agents/cad-engineer.md +278 -0
  58. lovarch_cli/squad/agents/capitolato-writer.md +256 -0
  59. lovarch_cli/squad/agents/computo-engineer.md +258 -0
  60. lovarch_cli/squad/agents/concept-designer.md +399 -0
  61. lovarch_cli/squad/agents/contratto-architect.md +243 -0
  62. lovarch_cli/squad/agents/deliverable-builder.md +253 -0
  63. lovarch_cli/squad/agents/energy-prelim.md +388 -0
  64. lovarch_cli/squad/agents/pratiche-it.md +251 -0
  65. lovarch_cli/squad/agents/progetto-chief.md +768 -0
  66. lovarch_cli/squad/agents/quality-dati.md +409 -0
  67. lovarch_cli/squad/agents/quality-misure.md +418 -0
  68. lovarch_cli/squad/agents/quality-normativa.md +417 -0
  69. lovarch_cli/squad/agents/quality-output.md +436 -0
  70. lovarch_cli/squad/agents/regolatorio-it.md +278 -0
  71. lovarch_cli/squad/checklists/handoff-quality-gate.md +232 -0
  72. lovarch_cli/squad/checklists/quality-dati-checklist.md +134 -0
  73. lovarch_cli/squad/checklists/quality-misure-checklist.md +139 -0
  74. lovarch_cli/squad/checklists/quality-normativa-checklist.md +121 -0
  75. lovarch_cli/squad/checklists/quality-output-checklist.md +116 -0
  76. lovarch_cli/squad/config.yaml +408 -0
  77. lovarch_cli/squad/data/CHANGELOG.md +272 -0
  78. lovarch_cli/squad/data/agents-prd.md +428 -0
  79. lovarch_cli/squad/data/architettura-progetto-rules.md +328 -0
  80. lovarch_cli/squad/data/handoff-card-template.md +231 -0
  81. lovarch_cli/squad/data/mocks/catasto-visura.json +72 -0
  82. lovarch_cli/squad/data/mocks/firma-envelope.json +43 -0
  83. lovarch_cli/squad/data/prezzario-lombardia-sample.json +312 -0
  84. lovarch_cli/squad/scripts/api_clients.py +206 -0
  85. lovarch_cli/squad/scripts/architect_profile.py +276 -0
  86. lovarch_cli/squad/scripts/deliverable_generators.py +844 -0
  87. lovarch_cli/squad/scripts/generate_attico_brera_dwg.py +369 -0
  88. lovarch_cli/squad/scripts/generate_chianti_dxf.py +368 -0
  89. lovarch_cli/squad/scripts/generate_chianti_images.py +223 -0
  90. lovarch_cli/squad/scripts/generate_real_sample_images.py +189 -0
  91. lovarch_cli/squad/scripts/generate_sample_assets.py +382 -0
  92. lovarch_cli/squad/scripts/lovarch_client.py +1046 -0
  93. lovarch_cli/squad/scripts/pipeline_runner.py +2095 -0
  94. lovarch_cli/squad/scripts/render_dxf_to_png.py +57 -0
  95. lovarch_cli/squad/scripts/run_palestra_demo.sh +277 -0
  96. lovarch_cli/squad/scripts/simulate_squad_execution.py +515 -0
  97. lovarch_cli/squad/scripts/validate-squad.py +383 -0
  98. lovarch_cli/squad/tasks/audit-input.md +146 -0
  99. lovarch_cli/squad/tasks/compute-metric.md +105 -0
  100. lovarch_cli/squad/tasks/consolidate-dossier.md +187 -0
  101. lovarch_cli/squad/tasks/generate-cad-plan.md +120 -0
  102. lovarch_cli/squad/tasks/generate-ifc-model.md +108 -0
  103. lovarch_cli/squad/tasks/write-capitolato.md +100 -0
  104. lovarch_cli/squad/templates/asseverazione-tecnica.md +126 -0
  105. lovarch_cli/squad/templates/capitolato-uni-11337.md +235 -0
  106. lovarch_cli/squad/templates/cila-comune-milano.md +177 -0
  107. lovarch_cli/squad/templates/contratto-cnappc.md +220 -0
  108. lovarch_cli/squad/workflows/dal-brief-al-cantiere.yaml +218 -0
  109. lovarch_cli/squad_loader.py +114 -0
  110. lovarch_cli/verify/__init__.py +15 -0
  111. lovarch_cli/verify/contratto.py +110 -0
  112. lovarch_cli/verify/dossier.py +97 -0
  113. lovarch_cli/verify/misure.py +83 -0
  114. lovarch_cli/verify/normativa.py +178 -0
  115. lovarch_cli/version.py +13 -0
  116. lovarch_cli/workflows/__init__.py +9 -0
  117. lovarch_cli/workflows/platform.py +212 -0
  118. lovarch_cli-0.2.1.dist-info/METADATA +232 -0
  119. lovarch_cli-0.2.1.dist-info/RECORD +122 -0
  120. lovarch_cli-0.2.1.dist-info/WHEEL +4 -0
  121. lovarch_cli-0.2.1.dist-info/entry_points.txt +3 -0
  122. lovarch_cli-0.2.1.dist-info/licenses/LICENSE +38 -0
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env python3
2
+ """Architect Profile Loader.
3
+
4
+ Consolidates all platform data about the architect/professional into a single dict.
5
+ Pulls from: profiles, user_settings, brand_profiles, style_profiles, avatars, user_tax_settings.
6
+
7
+ Italian-specific professional credentials (OAPPC, matricola, PEC) are loaded from:
8
+ 1. Environment variables (ARCH_OAPPC_ORDINE, ARCH_MATRICOLA, ARCH_PEC, ARCH_ALBO)
9
+ 2. data/sample-input/architetto-info.json (fallback)
10
+ 3. Hardcoded placeholders (last resort, with WARN)
11
+
12
+ Usage:
13
+ from architect_profile import load_architect_profile
14
+ profile = load_architect_profile(client, user_id)
15
+ print(profile["full_name"], profile["company_name"], profile["fiscal_address"])
16
+ """
17
+ from __future__ import annotations
18
+ import os
19
+ import json
20
+ import sys
21
+ from pathlib import Path
22
+ from typing import Dict, Any, Optional
23
+
24
+ ROOT = Path(__file__).resolve().parents[1]
25
+
26
+
27
+ def _safe_get(client, table: str, user_id: str, select: str = "*") -> Optional[Dict[str, Any]]:
28
+ """GET single row · returns None if not found."""
29
+ try:
30
+ result = client._rest("GET", f"/rest/v1/{table}?user_id=eq.{user_id}&select={select}&limit=1")
31
+ if isinstance(result, list) and result:
32
+ return result[0]
33
+ return None
34
+ except Exception as e:
35
+ print(f" ⚠ failed to load {table}: {e}", file=sys.stderr)
36
+ return None
37
+
38
+
39
+ def _safe_get_by_id(client, table: str, row_id: str, select: str = "*") -> Optional[Dict[str, Any]]:
40
+ """GET by id · returns None if not found."""
41
+ try:
42
+ result = client._rest("GET", f"/rest/v1/{table}?id=eq.{row_id}&select={select}&limit=1")
43
+ if isinstance(result, list) and result:
44
+ return result[0]
45
+ return None
46
+ except Exception:
47
+ return None
48
+
49
+
50
+ def _load_italian_credentials() -> Dict[str, str]:
51
+ """Load Italian-specific credentials from env vars or sample-input JSON.
52
+
53
+ Required fields for Italian arch deliverables:
54
+ - ordine_professionale (e.g., "OAPPC Milano")
55
+ - matricola (license number)
56
+ - pec (certified email)
57
+ - albo (e.g., "Arch. Sez. A")
58
+ """
59
+ # Try env vars first
60
+ creds = {
61
+ "ordine_professionale": os.environ.get("ARCH_OAPPC_ORDINE", ""),
62
+ "matricola": os.environ.get("ARCH_MATRICOLA", ""),
63
+ "pec": os.environ.get("ARCH_PEC", ""),
64
+ "albo": os.environ.get("ARCH_ALBO", ""),
65
+ }
66
+ if all(creds.values()):
67
+ return creds
68
+
69
+ # Try sample-input JSON
70
+ sample_path = ROOT / "data" / "sample-input" / "architetto-info.json"
71
+ if sample_path.exists():
72
+ try:
73
+ with open(sample_path) as f:
74
+ data = json.load(f)
75
+ for k in creds:
76
+ if not creds[k] and data.get(k):
77
+ creds[k] = data[k]
78
+ except Exception as e:
79
+ print(f" ⚠ failed to read {sample_path}: {e}", file=sys.stderr)
80
+
81
+ # Fallback placeholders with WARN
82
+ if not creds["ordine_professionale"]:
83
+ print(f" ⚠ ARCH_OAPPC_ORDINE not configured · using placeholder", file=sys.stderr)
84
+ creds["ordine_professionale"] = "OAPPC Milano"
85
+ if not creds["matricola"]:
86
+ print(f" ⚠ ARCH_MATRICOLA not configured · using placeholder", file=sys.stderr)
87
+ creds["matricola"] = "XXXX"
88
+ if not creds["pec"]:
89
+ creds["pec"] = "[da configurare]@archiworldpec.it"
90
+ if not creds["albo"]:
91
+ creds["albo"] = "Arch. Sez. A"
92
+
93
+ return creds
94
+
95
+
96
+ def load_architect_profile(client, user_id: str) -> Dict[str, Any]:
97
+ """Load complete architect profile from all platform tables.
98
+
99
+ Returns dict with structure:
100
+ {
101
+ user_id, email, full_name, first_name, last_name,
102
+ phone, instagram, position, subscription_status,
103
+ company_name, company_size,
104
+ fiscal: {country, city, region, postal_code, street, province, tax_id, tax_id_type},
105
+ currency, preferred_language,
106
+ brand: {name, mission, vision, values, promise, palette, fonts, logo_url, favicon_url, tone_of_voice},
107
+ style: {style_name, keywords, palette, materials, strengths, weaknesses},
108
+ avatar: {name, summary, demographic_profile, lifestyle_preferences, motivations_goals},
109
+ tax_settings: {tax_rate, tax_regime, tax_rate_imposta, tax_rate_inps, tax_rate_irap, profitability_coefficient},
110
+ italian_credentials: {ordine_professionale, matricola, pec, albo},
111
+ }
112
+ """
113
+ profile = {"user_id": user_id}
114
+
115
+ # === profiles ===
116
+ p = _safe_get(client, "profiles", user_id) or {}
117
+ # profiles uses id not user_id · try by id
118
+ if not p:
119
+ p = _safe_get_by_id(client, "profiles", user_id) or {}
120
+ profile["email"] = p.get("email", "")
121
+ profile["full_name"] = p.get("full_name", "")
122
+ profile["phone"] = p.get("phone", "")
123
+ profile["instagram"] = p.get("instagram_handle", "")
124
+ profile["position"] = p.get("position", "")
125
+ profile["subscription_status"] = p.get("subscription_status", "")
126
+ profile["is_admin"] = p.get("is_admin", False)
127
+
128
+ # === user_settings (fiscal info) ===
129
+ s = _safe_get(client, "user_settings", user_id) or {}
130
+ profile["first_name"] = s.get("first_name", "") or profile.get("full_name", "")
131
+ profile["last_name"] = s.get("last_name", "")
132
+ full_name_combined = f"{profile['first_name']} {profile['last_name']}".strip()
133
+ if full_name_combined:
134
+ profile["full_name"] = full_name_combined
135
+ profile["company_name"] = s.get("company_name", "") or p.get("company", "")
136
+ profile["company_size"] = s.get("company_size", "")
137
+ profile["professional_role"] = s.get("professional_role", "")
138
+ profile["preferred_language"] = s.get("preferred_language", "it")
139
+ profile["currency"] = s.get("currency", "EUR")
140
+ profile["fiscal"] = {
141
+ "country": s.get("fiscal_address_country") or s.get("country", ""),
142
+ "city": s.get("fiscal_address_city") or s.get("city", ""),
143
+ "region": s.get("region", ""),
144
+ "postal_code": s.get("fiscal_address_postal_code", ""),
145
+ "street": s.get("fiscal_address_street", ""),
146
+ "province": s.get("fiscal_address_province", ""),
147
+ "tax_id": s.get("tax_id", ""),
148
+ "tax_id_type": s.get("tax_id_type", ""),
149
+ "fiscal_country_code": s.get("fiscal_country", ""),
150
+ "fiscal_regime": s.get("fiscal_regime", ""),
151
+ }
152
+
153
+ # === brand_profiles ===
154
+ b = _safe_get(client, "brand_profiles", user_id) or {}
155
+ profile["brand"] = {
156
+ "name": b.get("name", "") or profile.get("company_name", ""),
157
+ "mission": b.get("mission", ""),
158
+ "vision": b.get("vision", ""),
159
+ "values": b.get("values", ""),
160
+ "promise": b.get("promise", ""),
161
+ "tone_of_voice": b.get("tone_of_voice", ""),
162
+ "palette": b.get("palette", []),
163
+ "fonts": b.get("fonts", {}),
164
+ "logo_url": b.get("logo_url", ""),
165
+ "favicon_url": b.get("favicon_url", ""),
166
+ "keywords": b.get("keywords", []),
167
+ }
168
+
169
+ # === style_profiles ===
170
+ st = _safe_get(client, "style_profiles", user_id) or {}
171
+ profile["style"] = {
172
+ "style_name": st.get("style_name", ""),
173
+ "keywords": st.get("keywords", []),
174
+ "palette": st.get("palette", []),
175
+ "materials": st.get("materials", []),
176
+ "strengths": st.get("strengths", []),
177
+ "weaknesses": st.get("weaknesses", []),
178
+ "summary": st.get("summary", ""),
179
+ }
180
+
181
+ # === avatars (target cliente persona) ===
182
+ av = _safe_get(client, "avatars", user_id) or {}
183
+ profile["avatar"] = {
184
+ "name": av.get("name", ""),
185
+ "summary": av.get("summary", ""),
186
+ "demographic_profile": av.get("demographic_profile", {}),
187
+ "lifestyle_preferences": av.get("lifestyle_preferences", {}),
188
+ "motivations_goals": av.get("motivations_goals", {}),
189
+ "barriers_objections": av.get("barriers_objections", {}),
190
+ }
191
+
192
+ # === user_tax_settings ===
193
+ tx = _safe_get(client, "user_tax_settings", user_id) or {}
194
+ profile["tax_settings"] = {
195
+ "tax_rate": tx.get("tax_rate", 22),
196
+ "tax_regime": tx.get("tax_regime", "manual"),
197
+ "tax_rate_imposta": tx.get("tax_rate_imposta", 25),
198
+ "tax_rate_inps": tx.get("tax_rate_inps", 4),
199
+ "tax_rate_irap": tx.get("tax_rate_irap", 0),
200
+ "profitability_coefficient": tx.get("profitability_coefficient", 78),
201
+ }
202
+
203
+ # === Italian professional credentials (env or JSON) ===
204
+ profile["italian_credentials"] = _load_italian_credentials()
205
+
206
+ return profile
207
+
208
+
209
+ def format_address(fiscal: Dict[str, str]) -> str:
210
+ """Format fiscal address as readable string."""
211
+ parts = []
212
+ if fiscal.get("street"):
213
+ parts.append(fiscal["street"])
214
+ city_zip = ""
215
+ if fiscal.get("postal_code"):
216
+ city_zip = fiscal["postal_code"]
217
+ if fiscal.get("city"):
218
+ city_zip = f"{city_zip} {fiscal['city']}".strip()
219
+ if fiscal.get("province"):
220
+ city_zip = f"{city_zip} ({fiscal['province']})".strip()
221
+ if city_zip:
222
+ parts.append(city_zip)
223
+ if fiscal.get("country"):
224
+ parts.append(fiscal["country"])
225
+ return ", ".join(parts) if parts else "[indirizzo da configurare]"
226
+
227
+
228
+ def format_tax_id(fiscal: Dict[str, str]) -> str:
229
+ """Format tax_id with country prefix."""
230
+ tax_id = fiscal.get("tax_id", "")
231
+ tid_type = fiscal.get("tax_id_type", "")
232
+ if not tax_id:
233
+ return "[P.IVA da configurare]"
234
+ if tid_type.startswith("vat_"):
235
+ country = tid_type.replace("vat_", "").upper()
236
+ return f"P.IVA {country} {tax_id}"
237
+ return f"P.IVA {tax_id}"
238
+
239
+
240
+ def format_architect_signature(profile: Dict[str, Any]) -> str:
241
+ """Format full architect signature line for documents.
242
+
243
+ Example: "Pablo Ruan · architetto · OAPPC Milano n. matr. 12345 · ArchPrime · Lambeth (GB)"
244
+ """
245
+ parts = []
246
+ if profile.get("full_name"):
247
+ parts.append(profile["full_name"])
248
+ parts.append("architetto")
249
+ creds = profile.get("italian_credentials", {})
250
+ if creds.get("ordine_professionale") and creds.get("matricola"):
251
+ parts.append(f"{creds['ordine_professionale']} n. matr. {creds['matricola']}")
252
+ if profile.get("company_name"):
253
+ parts.append(profile["company_name"])
254
+ fiscal = profile.get("fiscal", {})
255
+ if fiscal.get("city"):
256
+ loc = fiscal["city"]
257
+ if fiscal.get("country"):
258
+ loc = f"{loc} ({fiscal['country']})"
259
+ parts.append(loc)
260
+ return " · ".join(parts)
261
+
262
+
263
+ # ----------------------------------------------------------------------------
264
+ # CLI test
265
+ # ----------------------------------------------------------------------------
266
+ if __name__ == "__main__":
267
+ sys.path.insert(0, str(Path(__file__).parent))
268
+ from lovarch_client import LovarchClient
269
+ client = LovarchClient()
270
+ user_id = sys.argv[1] if len(sys.argv) > 1 else "a5f3ee03-a7df-4571-a68d-baf168bd4ba8"
271
+ profile = load_architect_profile(client, user_id)
272
+ print(json.dumps(profile, indent=2, ensure_ascii=False))
273
+ print("\n=== Formatted ===")
274
+ print(f"Signature: {format_architect_signature(profile)}")
275
+ print(f"Address: {format_address(profile['fiscal'])}")
276
+ print(f"Tax ID: {format_tax_id(profile['fiscal'])}")