spacr.qt.ai.manuscript

Generate the Methods and Results sections through the AI console’s providers.

spacr.methods_export builds the run digest, writes the prompt, and checks a draft’s numbers against the digest. This module is the half that talks to a model, and it deliberately does NOT open a second client: it uses the same spacr.qt.ai.providers.ChatProvider objects the AI console already streams through, so a user who authenticated once has authenticated for this too, and a provider added there is available here the same day.

Two behaviours are the whole point of the module:

A draft that invents a number is not returned as a draft. The model’s output goes straight into spacr.methods_export.check_draft(). If it carries a figure that is not in the digest — or if the Methods section drops one of the caveats the run recorded — the draft is marked ok=False, the offending numbers are named in ManuscriptDraft.problems, the model’s text is preserved in ManuscriptDraft.rejected so a human can look at it, and the sections handed back are the deterministic ones from the digest. That is what makes “every number in the output comes from the digest” a property of the system rather than a hope about the model.

No key configured is an answer, not a traceback. availability() says exactly what is missing and what to type to fix it, and generate_sections() returns a complete draft anyway — the deterministic renderers need no model at all. A user with no CLI installed still gets their methods section; it is just written by spaCR instead of by a model.

Classes

Availability

Whether a model can be reached, and what to do when it cannot.

ManuscriptDraft

The two sections, plus everything about how they were arrived at.

Functions

availability(→ Availability)

Report which AI providers are usable, and how to fix it if none are.

generate_sections(→ ManuscriptDraft)

Ask a model for the two sections; refuse a draft that invents a number.

split_sections(→ Tuple[str, str])

Split a model's reply into (methods, results).

Module Contents

class spacr.qt.ai.manuscript.Availability[source]

Whether a model can be reached, and what to do when it cannot.

Parameters:
  • ok – at least one provider is installed and logged in.

  • providers – the names of the ones that are.

  • message – one paragraph for the user. When ok is False it names every provider, whether its CLI is installed, and the command to install or log in — because “no AI configured” with no next step is the same as a traceback for anyone who wanted to use it.

__bool__() → bool[source]

True when a model can be reached.

class spacr.qt.ai.manuscript.ManuscriptDraft[source]

The two sections, plus everything about how they were arrived at.

Parameters:
  • methods – the Methods section to use.

  • results – the Results section to use.

  • ok – the returned sections came from a model AND passed the number check. False means the deterministic renderer wrote them.

  • source – "model" or "digest".

  • provider – which provider was used, when one was.

  • methods_check – the verification of the model’s Methods section.

  • results_check – the same for Results.

  • problems – sentences for the user: what was missing, what was invented, what was rejected.

  • rejected – the model’s text, kept when it was refused so a human can see what it said.

text() → str[source]

Both sections, ready to paste. No trailing newline.

to_dict() → Dict[str, Any][source]

A JSON-serializable copy.

spacr.qt.ai.manuscript.availability() → Availability[source]

Report which AI providers are usable, and how to fix it if none are.

Never raises and never touches the network: ChatProvider.is_installed() is a PATH lookup and is_logged_in() is best-effort.

spacr.qt.ai.manuscript.generate_sections(digest: Mapping[str, Any], *, provider: spacr.qt.ai.providers.ChatProvider | None = None, model: str | None = None, stream=None) → ManuscriptDraft[source]

Ask a model for the two sections; refuse a draft that invents a number.

Parameters:
  • digest – the run digest — see spacr.methods_export.build_digest(). It is the model’s ONLY input; raw data never reaches it.

  • provider – the provider to use. Defaults to the first configured one; with none configured the deterministic renderer answers and the draft says so.

  • model – optional model override passed to the provider.

  • stream – optional callable invoked with each chunk as it arrives, for a live view. Exceptions from it are ignored — a UI that fails to paint must not lose the generation.

Returns:

a ManuscriptDraft. Always carries usable sections: the model’s when they passed, spaCR’s own when they did not.

spacr.qt.ai.manuscript.split_sections(text: str) → Tuple[str, str][source]

Split a model’s reply into (methods, results).

Tolerant of the usual drift — a preamble before the first heading, a different heading level, a stray sign-off — because a reply that is otherwise correct must not be discarded over a hash mark. Anything before the Methods heading is dropped; anything after the Results heading is kept as part of Results.

Parameters:

text – the model’s whole reply.

Returns:

the two sections, each without its heading. Either may be "" when the model did not produce it.