feat: gate heavy solver JSON, field traceability API, GUI rod/export depth
- C: emit profiles/diagnostics/fourier only when enable flags are set; null otherwise - API: fieldTraceability on case parse/default and solve; fix GET /solve/default options - Tests: golden fingerprint, quality gates, C diagnostics invariants; cardQa mean empty guard - Makefile: test-solver-sanitize ASan/UBSan target; README and COMPUTE_PLAN updates - GUI: taper weight/MTS/guides/sinker round-trip, rod catalog, solver output toggles, results (profiles/diagnostics/Fourier/traceability), engineering checks and tabs - Restore canonical WellName in base-case for regression; trace TaperGuidesCountArray Made-with: Cursor
This commit is contained in:
@@ -17,6 +17,10 @@ describe("solver-api", () => {
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.runMetadata.source).toBe("base-case.xml");
|
||||
expect(response.body.solver.pointCount).toBe(200);
|
||||
expect(response.body.solver.profiles).toBeNull();
|
||||
expect(response.body.solver.diagnostics).toBeNull();
|
||||
expect(response.body.fieldTraceability?.schemaVersion).toBe(2);
|
||||
expect(Array.isArray(response.body.fieldTraceability?.fields)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns fea prototype result and comparison payload", async () => {
|
||||
@@ -70,7 +74,7 @@ describe("solver-api", () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.solver.pointCount).toBe(200);
|
||||
expect(response.body.parsed.model.wellName).toContain("191/01-27-007-09W2/00");
|
||||
expect(response.body.parsed.model.wellName).toBe("PLACEHOLDER-WELL");
|
||||
expect(Array.isArray(response.body.parsed.unsupportedFields)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -99,6 +103,7 @@ describe("solver-api", () => {
|
||||
expect(response.body.unsupportedFields.sort()).toEqual(
|
||||
defaultResp.body.unsupportedFields.sort()
|
||||
);
|
||||
expect(response.body.fieldTraceability?.fields?.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("rejects empty body on POST /case/parse", async () => {
|
||||
|
||||
44
solver-api/tests/quality.test.js
Normal file
44
solver-api/tests/quality.test.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import request from "supertest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildApp } from "../src/app.js";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../");
|
||||
const xml = fs.readFileSync(path.join(ROOT, "data/cases/base-case.xml"), "utf-8");
|
||||
|
||||
describe("quality gates", () => {
|
||||
it("cross-model comparison has finite residuals when both solvers run", async () => {
|
||||
const app = buildApp();
|
||||
const response = await request(app).post("/solve").send({ xml, solverModel: "both" });
|
||||
expect(response.status).toBe(200);
|
||||
const rms = response.body.comparison.residualSummary.rms;
|
||||
expect(Number.isFinite(rms)).toBe(true);
|
||||
expect(rms).toBeGreaterThan(0);
|
||||
expect(response.body.comparison.pointwiseResiduals.series.length).toBe(
|
||||
response.body.comparison.pointwiseResiduals.points
|
||||
);
|
||||
});
|
||||
|
||||
it("perturbing rod friction changes peak polished load (field sensitivity)", async () => {
|
||||
const app = buildApp();
|
||||
const xmlHi = xml.replace(
|
||||
"<RodFrictionCoefficient>0.2</RodFrictionCoefficient>",
|
||||
"<RodFrictionCoefficient>0.28</RodFrictionCoefficient>"
|
||||
);
|
||||
const base = await request(app).post("/solve").send({ xml, solverModel: "fdm" });
|
||||
const perturbed = await request(app).post("/solve").send({ xml: xmlHi, solverModel: "fdm" });
|
||||
expect(base.status).toBe(200);
|
||||
expect(perturbed.status).toBe(200);
|
||||
expect(perturbed.body.solver.maxPolishedLoad).not.toBe(base.body.solver.maxPolishedLoad);
|
||||
});
|
||||
|
||||
it("includes fieldTraceability on POST /solve", async () => {
|
||||
const app = buildApp();
|
||||
const response = await request(app).post("/solve").send({ xml });
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.fieldTraceability?.schemaVersion).toBe(2);
|
||||
const pumpDepth = response.body.fieldTraceability.fields.find((f) => f.xmlKey === "PumpDepth");
|
||||
expect(pumpDepth?.category).toBe("physics");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user