@enerlence/suntropy-cli 0.4.0 → 0.5.1

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.
package/README.md CHANGED
@@ -209,6 +209,51 @@ suntropy solarform calculate --data '{"center":{"lat":37.39,"lng":-5.99},...}'
209
209
  suntropy solarform config
210
210
  ```
211
211
 
212
+ ### `suntropy ppa` - PPA Analysis
213
+
214
+ Read PPA (Power Purchase Agreement) templates and run PPA simulations on a solar study.
215
+
216
+ ```bash
217
+ # Read-only templates
218
+ suntropy ppa templates list --fields _id,name,mode,ppaPrice,useInSolarForm
219
+ suntropy ppa templates get <ppaTemplateId>
220
+
221
+ # Run a PPA simulation on a study (read-only, does not persist)
222
+ suntropy ppa calculate --study <studyId> # uses client useInSolarForm templates
223
+ suntropy ppa calculate --study <studyId> --template <ppaTemplateId> # uses a single template
224
+ cat study.json | suntropy ppa calculate --data - # full SolarStudy via stdin
225
+ suntropy ppa calculate --study <studyId> --raw --save-file ppa.json
226
+ ```
227
+
228
+ The response is the study with `economicResults.ppaAnalysis` populated
229
+ (`simulationResults[].profitabilityResults` → `irr`, `paybackYears`). Heavy
230
+ PowerCurve objects are summarized unless `--raw` is passed.
231
+
232
+ ### `suntropy shareables` - Shareable Links
233
+
234
+ Create shareable links for studies (sharing service, exposed under `/templates`).
235
+ The backend fills `uid`, `url`, `clientUID` and `idShareable` from the token.
236
+
237
+ ```bash
238
+ # Public shareable for a solar study
239
+ suntropy shareables create --element-id <studyId>
240
+
241
+ # With template, name and expiration
242
+ suntropy shareables create --element-id <studyId> \
243
+ --template-id <templateId> --name "Estudio Juan" --expiration-date 2026-12-31
244
+
245
+ # Private (password) + send to recipients
246
+ suntropy shareables create --element-id <studyId> \
247
+ --privacy PRIVATE --password secret --email-list "a@x.com;b@y.com"
248
+
249
+ # Extra link query params / extra DTO fields
250
+ suntropy shareables create --element-id <studyId> --link-params "utm_source=cli"
251
+ suntropy shareables create --element-id <studyId> --data '{"customLayout":true}'
252
+ ```
253
+
254
+ `--element-type` (default `solarStudy`): `solarStudy | colectiveSolarStudy | veChargerStudy | heatpumpStudy | billing`.
255
+ `--shareable-type` (default `TEMPLATE`): `TEMPLATE | CONTRACT`. The response includes the public `url` and `uid`.
256
+
212
257
  ### `suntropy config` - Configuration
213
258
 
214
259
  ```bash
@@ -4044,10 +4044,173 @@ Examples:
4044
4044
  });
4045
4045
  }
4046
4046
 
4047
+ // src/commands/ppa/index.ts
4048
+ function getGlobalOpts12(cmd) {
4049
+ let root = cmd;
4050
+ while (root.parent) root = root.parent;
4051
+ return root.opts();
4052
+ }
4053
+ function readStdin3() {
4054
+ return new Promise((resolve, reject) => {
4055
+ let data = "";
4056
+ process.stdin.setEncoding("utf-8");
4057
+ process.stdin.on("data", (chunk) => {
4058
+ data += chunk;
4059
+ });
4060
+ process.stdin.on("end", () => resolve(data));
4061
+ process.stdin.on("error", reject);
4062
+ });
4063
+ }
4064
+ async function parseData5(data) {
4065
+ if (!data) return void 0;
4066
+ if (data === "-") {
4067
+ const input = await readStdin3();
4068
+ return JSON.parse(input);
4069
+ }
4070
+ return JSON.parse(data);
4071
+ }
4072
+ function compactStudyOutput2(study) {
4073
+ if (study === null || study === void 0 || typeof study !== "object") return study;
4074
+ if (Array.isArray(study)) return study.map(compactStudyOutput2);
4075
+ const record = study;
4076
+ if (Array.isArray(record.days) && record.days.length > 0 && record.identifier !== void 0) {
4077
+ return { _type: "PowerCurve", days: record.days.length, identifier: record.identifier };
4078
+ }
4079
+ const result = {};
4080
+ for (const [key, val] of Object.entries(record)) {
4081
+ if (val && typeof val === "object" && !Array.isArray(val)) {
4082
+ const v = val;
4083
+ if (Array.isArray(v.days) && v.days.length > 0 && v.identifier !== void 0) {
4084
+ result[key] = { _type: "PowerCurve", days: v.days.length, identifier: v.identifier };
4085
+ continue;
4086
+ }
4087
+ }
4088
+ result[key] = compactStudyOutput2(val);
4089
+ }
4090
+ return result;
4091
+ }
4092
+ function registerPPACommands(program2) {
4093
+ const ppa = program2.command("ppa").description(
4094
+ "PPA (Power Purchase Agreement) analysis.\nRead PPA templates and run PPA simulations on a solar study."
4095
+ );
4096
+ const templates = ppa.command("templates").description("Read-only access to the client PPA templates.");
4097
+ templates.command("list").description(
4098
+ "List all PPA templates for the authenticated client.\nExample:\n suntropy ppa templates list --fields _id,name,mode,ppaPrice,useInSolarForm"
4099
+ ).action(async () => {
4100
+ try {
4101
+ const global = getGlobalOpts12(ppa);
4102
+ const client = createServiceClient("solar", global);
4103
+ const res = await client.get("/solar-study/ppa-templates");
4104
+ output(res.data, global);
4105
+ } catch (err) {
4106
+ outputError(handleApiError(err));
4107
+ }
4108
+ });
4109
+ templates.command("get <id>").description(
4110
+ "Get a single PPA template by its Mongo _id.\nExample:\n suntropy ppa templates get 665f0a1b2c3d4e5f60718293"
4111
+ ).action(async (id) => {
4112
+ try {
4113
+ const global = getGlobalOpts12(ppa);
4114
+ const client = createServiceClient("solar", global);
4115
+ const res = await client.get(`/solar-study/ppa-templates/${id}`);
4116
+ output(res.data, global);
4117
+ } catch (err) {
4118
+ outputError(handleApiError(err));
4119
+ }
4120
+ });
4121
+ ppa.command("calculate").description(
4122
+ "Run PPA simulations on a solar study (read-only, does not persist).\n\nProvide the study via --study <id> (downloaded from the API) or --data <json>|- (full SolarStudy).\nWith --template <ppaTemplateId> the simulation uses that single PPA template;\nwithout it, the backend uses the client templates flagged useInSolarForm.\n\nNote: the study must include consumption and economicResults (a study fetched\nby --study already does). The response is the study with economicResults.ppaAnalysis populated.\n\nExamples:\n suntropy ppa calculate --study 665f0a1b2c3d4e5f60718293\n suntropy ppa calculate --study 665f... --template 6700aa11bb22cc33dd44ee55\n cat study.json | suntropy ppa calculate --data -"
4123
+ ).option("--study <id>", "Solar study Mongo id to download and analyze").option("--data <json>", "Full SolarStudy JSON body (or - for stdin)").option("--template <ppaTemplateId>", "PPA template id to apply (defaults to client useInSolarForm templates)").option("--raw", "Return full study with PowerCurve data (no compaction)").option("--save-file <file>", "Save result to local file").action(async (opts) => {
4124
+ try {
4125
+ const global = getGlobalOpts12(ppa);
4126
+ const client = createServiceClient("solar", global);
4127
+ let study;
4128
+ if (opts.study) {
4129
+ const res2 = await client.get(`/solar-study/findById/${opts.study}`);
4130
+ study = res2.data;
4131
+ } else if (opts.data) {
4132
+ study = await parseData5(opts.data);
4133
+ } else {
4134
+ outputError(new Error("Provide --study <id> or --data <json>|- (full SolarStudy)."));
4135
+ return;
4136
+ }
4137
+ const body = { data: study };
4138
+ if (opts.template) body.ppaTemplateId = opts.template;
4139
+ const res = await client.post("/solar-study/calculate-ppa-on-study", body);
4140
+ const result = opts.raw ? res.data : compactStudyOutput2(res.data);
4141
+ output(result, { ...global, save: opts.saveFile || global.save });
4142
+ } catch (err) {
4143
+ outputError(handleApiError(err));
4144
+ }
4145
+ });
4146
+ }
4147
+
4148
+ // src/commands/shareables/index.ts
4149
+ function getGlobalOpts13(cmd) {
4150
+ let root = cmd;
4151
+ while (root.parent) root = root.parent;
4152
+ return root.opts();
4153
+ }
4154
+ function readStdin4() {
4155
+ return new Promise((resolve, reject) => {
4156
+ let data = "";
4157
+ process.stdin.setEncoding("utf-8");
4158
+ process.stdin.on("data", (chunk) => {
4159
+ data += chunk;
4160
+ });
4161
+ process.stdin.on("end", () => resolve(data));
4162
+ process.stdin.on("error", reject);
4163
+ });
4164
+ }
4165
+ async function parseData6(data) {
4166
+ if (!data) return void 0;
4167
+ if (data === "-") {
4168
+ const input = await readStdin4();
4169
+ return JSON.parse(input);
4170
+ }
4171
+ return JSON.parse(data);
4172
+ }
4173
+ function registerShareableCommands(program2) {
4174
+ const shareables = program2.command("shareables").description(
4175
+ "Create shareable links for studies (sharing service, exposed under /templates)."
4176
+ );
4177
+ shareables.command("create").description(
4178
+ 'Create a shareable link for a study (POST /shareable).\nThe backend fills uid, url, clientUID and idShareable from the token.\n\nelementType: solarStudy (default), colectiveSolarStudy, veChargerStudy, heatpumpStudy, billing\nshareableType: TEMPLATE (default), CONTRACT\nprivacy: PUBLIC (default), PRIVATE (use --password)\n\nExamples:\n suntropy shareables create --element-id 665f0a1b2c3d4e5f60718293\n suntropy shareables create --element-id 665f... --template-id 6700aa... --name "Estudio Juan"\n suntropy shareables create --element-id 665f... --privacy PRIVATE --password secret --email-list "a@x.com;b@y.com"\n suntropy shareables create --element-id 665f... --expiration-date 2026-12-31 --link-params "utm_source=cli"'
4179
+ ).requiredOption("--element-id <id>", "Id of the element (study) to share").option("--element-type <type>", "solarStudy | colectiveSolarStudy | veChargerStudy | heatpumpStudy | billing", "solarStudy").option("--shareable-type <type>", "TEMPLATE | CONTRACT", "TEMPLATE").option("--privacy <privacy>", "PUBLIC | PRIVATE", "PUBLIC").option("--template-id <id>", "Template id to apply to the shareable").option("--name <name>", "Descriptive name for the shareable").option("--password <pwd>", "Protect the link with a password (implies PRIVATE)").option("--expiration-date <date>", "Expiration date (e.g. 2026-12-31)").option("--activation-date <date>", "Activation date (e.g. 2026-01-01)").option("--email-list <emails>", "Semicolon-separated list of recipient emails").option("--reply-to <email>", "Reply-to email for notifications").option("--custom-layout", "Enable custom layout").option("--read-only", "Mark the shareable as read-only").option("--link-params <qs>", "Extra query params appended to the shareable link (k=v&k2=v2)").option("--data <json>", "Extra ShareableDto fields as JSON (or - for stdin), merged last").action(async (opts) => {
4180
+ try {
4181
+ const global = getGlobalOpts13(shareables);
4182
+ const client = createServiceClient("templates", global);
4183
+ const extra = await parseData6(opts.data) || {};
4184
+ const body = {
4185
+ elementId: opts.elementId,
4186
+ elementType: opts.elementType,
4187
+ shareableType: opts.shareableType,
4188
+ privacy: opts.password ? "PRIVATE" : opts.privacy,
4189
+ ...opts.templateId ? { templateId: opts.templateId } : {},
4190
+ ...opts.name ? { name: opts.name } : {},
4191
+ ...opts.password ? { password: opts.password, passwordConfirm: opts.password } : {},
4192
+ ...opts.expirationDate ? { expirationDate: opts.expirationDate } : {},
4193
+ ...opts.activationDate ? { activationDate: opts.activationDate } : {},
4194
+ ...opts.emailList ? { emailList: opts.emailList } : {},
4195
+ ...opts.replyTo ? { replyTo: opts.replyTo } : {},
4196
+ ...opts.customLayout ? { customLayout: true } : {},
4197
+ ...opts.readOnly ? { readOnly: true } : {},
4198
+ ...extra
4199
+ };
4200
+ const url = opts.linkParams ? `/shareable?${opts.linkParams}` : "/shareable";
4201
+ const res = await client.post(url, body);
4202
+ output(res.data, { ...global, save: global.save });
4203
+ } catch (err) {
4204
+ outputError(handleApiError(err));
4205
+ }
4206
+ });
4207
+ }
4208
+
4047
4209
  // src/index.ts
4210
+ var CLI_VERSION = true ? "0.5.1" : "0.0.0-dev";
4048
4211
  function createProgram() {
4049
4212
  const program2 = new Command2();
4050
- program2.name("suntropy").description("Agent-first CLI for Suntropy solar platform. Optimized for programmatic data manipulation and progressive exploration.").version("0.1.0").option("--format <format>", "Output format: json (default), human, csv", "json").option("--fields <fields>", "Comma-separated fields to include in output").option("--server <url>", "Override API server URL").option("--token <jwt>", "Override authentication token").option("--profile <name>", "Use a specific config profile").option("--verbose", "Show HTTP request/response details on stderr").option("--quiet", "Suppress non-data output").option("--save <file>", "Save output to file (also writes to stdout)");
4213
+ program2.name("suntropy").description("Agent-first CLI for Suntropy solar platform. Optimized for programmatic data manipulation and progressive exploration.").version(CLI_VERSION).option("--format <format>", "Output format: json (default), human, csv", "json").option("--fields <fields>", "Comma-separated fields to include in output").option("--server <url>", "Override API server URL").option("--token <jwt>", "Override authentication token").option("--profile <name>", "Use a specific config profile").option("--verbose", "Show HTTP request/response details on stderr").option("--quiet", "Suppress non-data output").option("--save <file>", "Save output to file (also writes to stdout)");
4051
4214
  registerAuthCommands(program2);
4052
4215
  registerConfigCommands(program2);
4053
4216
  registerInventoryCommands(program2);
@@ -4055,6 +4218,8 @@ function createProgram() {
4055
4218
  registerCurvesCommands(program2);
4056
4219
  registerConsumptionCommands(program2);
4057
4220
  registerSolarformCommands(program2);
4221
+ registerPPACommands(program2);
4222
+ registerShareableCommands(program2);
4058
4223
  return program2;
4059
4224
  }
4060
4225