Faits :
A.
A._ SA et B._ AG, sont l'une et l'autre actives dans la conception et la réalisation d'installations de chauffage; celle-ci se consacre spécialement aux installations de chauffage au bois.
En 2006, la commune de X._ a adjugé à A._ SA l'installation d'un système complet de chauffage avec conduites à distance pour des bâtiments communaux. Le 17 novembre 2006, A._ SA a commandé à B._ AG la livraison et l'installation d'une chaudière à bois destinée aux locaux de l'adjudicatrice; le prix était fixé à 266'716 fr.25, taxes comprises.
La chaudière a été mise en service au mois d'octobre 2007. Le 18 de ce mois, B._ AG a adressé à A._ SA une facture de 26'669 fr.30 pour solde du prix de livraison et d'installation de la chaudière. Le 15 mai 2008, elle a réclamé le paiement d'une somme supplémentaire de 860 fr.80 relative à divers travaux complémentaires. Ces factures n'ont pas été acquittées.
B.
Le 1er décembre 2008, A._ SA a conclu avec la commune de X._ un contrat de maintenance, de conduite et d'entretien des installations de chauffage et de production d'eau chaude de la commune, pour une durée initiale de deux ans. Ce contrat portait notamment sur l'entretien de la chaudière à bois fournie par B._ AG. La rémunération convenue en faveur de A._ SA s'élevait à 18'960 fr. hors taxes, soit 20'400 fr.95 taxes comprises; cette rémunération était indexée.
Le 7 juillet 2009, la commune de X._ a averti A._ SA qu'il était nécessaire de procéder à des travaux de réfection du foyer de la chaudière et de mettre cet appareil en conformité avec les directives de la Caisse nationale suisse d'assurance en cas d'accidents (CNA). A._ SA a transmis cette demande à B._ AG le 21 juillet 2009; elle lui a imparti un délai échéant le 1er septembre 2009 pour l'exécution des travaux.
Le 12 octobre 2009, la commune de X._ a mis A._ SA en demeure d'exécuter les travaux nécessaires avant le 30 octobre 2009; elle a requis une prolongation de la garantie jusqu'au 30 octobre 2010.
Le 29 octobre 2009, B._ AG a accepté d'exécuter les travaux exigés par la Caisse d'assurance mais elle a refusé de prolonger sa garantie parce que la chaudière était déjà en service depuis deux ans. Au sujet de la dégradation du béton réfractaire, elle a indiqué qu'elle allait prendre rendez-vous avec la commune de X._ afin qu'une solution fût trouvée à la satisfaction de toutes les parties.
Les représentants de A._ SA, de B._ AG et de la commune de X._ se sont rencontrés le 9 août 2010. Selon le procès-verbal de cette séance, le responsable du service après-vente de B._ AG a déclaré que la durée de vie habituelle d'une installation de chauffage du type de celle installée à X._ atteignait quinze à vingt ans ; il a également admis qu'après trois ans d'utilisation, une usure d'environ 50 mm de la paroi en béton réfractaire présentait un caractère anormal. Les représentants de la commune ont rappelé à A._ SA que la commune n'avait pas de relations contractuelles avec B._ AG et que celle-ci était un sous-traitant. Ils ont invité A._ SA à prendre en charge le coût des travaux de réfection, à exécuter par B._ AG ou par un autre sous-traitant.
Le 13 octobre 2010, par l'entremise de son conseil, la commune de X._ a informé A._ SA qu'elle avait instruit directement B._ AG de procéder aux travaux nécessaires et que ceux-ci seraient exécutés aux frais exclusifs de A._ SA.
Les travaux de réparation du béton réfractaire ont été exécutés les 25 et 29 octobre 2010. Le 2 décembre suivant, B._ AG a adressé à la commune de Chêne Bougeries une facture de 14'265 fr.55 pour la révision de la chaudière et la remise en état du béton réfractaire. La commune a transmis cette facture à A._ SA avec sommation de l'acquitter avant le 20 mars 2011. A._ SA n'ayant pas donné suite, la commune a finalement versé elle-même le montant réclamé.
Le 29 avril 2011, la commune a résilié le contrat de maintenance, de conduite et d'entretien des installations de chauffage conclu avec A._ SA, au motif que celle-ci n'avait pas exécuté ses obligations.
C.
Le 16 septembre 2011, B._ AG a ouvert action contre A._ SA devant le Tribunal de première instance du canton de Genève. La défenderesse devait être condamnée à payer 34'284 fr.65 et 860 fr.80, avec intérêts au taux de 5% par an, respectivement dès le 18 novembre 2007 et le 15 juin 2008.
La défenderesse a conclu au rejet de l'action. Elle a introduit une action reconventionnelle: la demanderesse devait être condamnée à payer 22'848 fr.95 avec intérêts dès le 14 octobre 2009. Cette prétention comprenait une perte de gain chiffrée à 19'293 fr., consécutive à la résiliation du contrat de maintenance et d'entretien par la commune de X._.
Le tribunal a interrogé des témoins. Il a notamment interrogé le directeur du marketing de la demanderesse. Celui-ci a déclaré avoir été informé des problèmes de la chaudière de X._. Selon son souvenir, la demanderesse avait un contrat d'entretien; elle avait procédé régulièrement à des révisions et à des mesures d'émissions, ce qui signifiait que l'installation fonctionnait.
Le tribunal s'est prononcé le 9 janvier 2014. Accueillant l'action principale, il a condamné la défenderesse selon les conclusions de la demande en justice; il a rejeté l'action reconventionnelle.
D.
La défenderesse a appelé du jugement pour persister dans l'action reconventionnelle, à concurrence de 19'293 fr. avec intérêts au taux de 5% par an dès le 29 avril 2011. Ces prestations devaient être imputées sur celles obtenues par la demanderesse, lesquelles n'étaient plus contestées.
La Chambre civile de la Cour de justice a statué le 26 septembre 2014; elle a rejeté l'appel et confirmé le jugement.
E.
Agissant par la voie du recours constitutionnel, la défenderesse requiert le Tribunal fédéral de condamner la demanderesse à payer 19'293 fr. avec intérêts au taux de 5% par an dès le 29 avril 2011. Des conclusions subsidiaires tendent à l'annulation de l'arrêt de la Cour de justice et au renvoi de la cause à cette autorité pour nouvelle décision.
La demanderesse conclut au rejet du recours.
La défenderesse a spontanément déposé une réplique.

Considérant en droit :
1.
Les conditions de recevabilité du recours constitutionnel subsidiaire sont satisfaites; en particulier, faute d'une valeur litigieuse suffisamment élevée, le recours ordinaire en matière civile n'est pas disponible. Les exigences concernant la motivation du recours lorsque la décision attaquée repose sur plusieurs motivations indépendantes, alternatives ou subsidiaires, sont également observées (cf. <ref-ruling> consid. 4.1.4 p. 100; <ref-ruling>).
2.
La défenderesse invoque la protection contre l'arbitraire conférée par l'art. 9 Cst. Une décision est contraire à cette disposition constitutionnelle lorsqu'elle viole gravement une norme ou un principe juridique clair et indiscuté, ou contredit d'une manière choquante le sentiment de la justice et de l'équité. Le Tribunal fédéral ne s'écarte de la solution retenue par l'autorité cantonale de dernière instance que si sa décision apparaît insoutenable, en contradiction manifeste avec la situation effective, adoptée sans motifs objectifs ou en violation d'un droit certain. En outre, il ne suffit pas que les motifs de la décision soient insoutenables; il faut encore que celle-ci soit arbitraire dans son résultat. Il ne suffit d'ailleurs pas non plus qu'une solution différente de celle retenue par l'autorité cantonale puisse être tenue pour également concevable ou apparaisse même préférable (<ref-ruling> consid. 2.1 p. 168; <ref-ruling> consid. 3.2.5 p. 339; <ref-ruling> consid. 4.3 p. 319).
En matière d'appréciation des preuves et de constatation des faits, l'autorité tombe dans l'arbitraire lorsqu'elle ne prend pas en considération, sans aucune raison sérieuse, un élément de preuve propre à modifier la décision, lorsqu'elle se trompe manifestement sur son sens et sa portée, ou encore lorsque, sur la base des éléments recueillis, elle parvient à des constatations insoutenables (<ref-ruling> consid. 2.3 p. 266; <ref-ruling> consid. 4.1.2 p. 62; <ref-ruling> consid. 4.2 p. 560).
3.
Il est constant que les parties ont conclu un contrat d'entreprise par lequel la défenderesse, dans le cadre de la mission qu'elle assumait envers la commune de X._, sous-traitait à la demanderesse la livraison et l'installation de la chaudière à bois.
Devant la Cour de justice, à l'appui de ses prétentions en réparation d'une perte de gain, la défenderesse a soutenu que la demanderesse s'était en outre obligée envers elle à entretenir la chaudière, qu'elle n'avait pas exécuté cette obligation et que pour ce motif, la commune de X._ avait résilié le contrat principal d'entretien conclu le 1 er décembre 2008. Dans son arrêt, la Cour a retenu que la défenderesse n'avait pas prouvé la conclusion d'un contrat de sous-traitance entre elle-même et la demanderesse, ayant pour objet l'entretien de la chaudière. Devant le Tribunal fédéral, la défenderesse se plaint d'une appréciation arbitraire des preuves.
Par le contrat du 17 novembre 2006, la demanderesse s'est obligée envers la défenderesse à livrer et à installer la chaudière; elle n'a alors explicitement contracté aucune obligation subséquente d'entretien de l'installation. A l'appui de sa thèse, la défenderesse fait seulement valoir le témoignage du directeur du marketing de la demanderesse, lequel a déclaré qu'à son souvenir, cette partie était liée par un contrat d'entretien et avait procédé régulièrement à des révisions et à des mesures d'émissions. Le témoin ne s'est pas exprimé de manière catégorique et, surtout, il n'a pas précisé si ce contrat d'entretien liait la demanderesse à la défenderesse ou à la commune de X._. Il est possible que le témoin ait fait allusion au contrat par lequel la commune a finalement confié directement à la demanderesse les travaux d'entretien nécessaires, notamment les travaux de réfection du foyer, au mois d'octobre 2010, sans traiter avec la défenderesse. Dans ces conditions, la défenderesse ne parvient pas à mettre en évidence une erreur certaine dans l'appréciation du témoignage.
4.
La Cour de justice a également retenu, à titre additionnel, que le lien de causalité entre un défaut d'entretien de la chaudière, à supposer que la demanderesse se fût obligée à entretenir cet appareil, d'une part, et le dommage allégué par la défenderesse, d'autre part, était interrompu par les manquements de cette partie-ci concernant ses propres obligations de diligence et de fidélité envers la commune de X._. La Cour a encore retenu que le dommage ainsi allégué n'avait pas été établi. La défenderesse se plaint d'arbitraire sur chacun de ces deux points. Il n'est cependant pas nécessaire de s'attarder à ces discussions car le fondement contractuel de la prétention en réparation d'une perte de gain n'est de toute manière pas établi, ce qui suffit à sceller le sort de la cause.
5.
Le recours se révèle privé de fondement, ce qui conduit à son rejet. A titre de partie qui succombe, son auteur doit acquitter l'émolument à percevoir par le Tribunal fédéral et les dépens auxquels l'autre partie peut prétendre.

Par ces motifs, le Tribunal fédéral prononce :
1.
Le recours est rejeté.
2.
La défenderesse acquittera un émolument judiciaire de 2'000 francs.
3.
La défenderesse versera une indemnité de 2'500 fr. à la demanderesse, à titre de dépens.
4.
Le présent arrêt est communiqué aux parties et à la Cour de justice du canton de Genève.
Lausanne, le 15 avril 2015
Au nom de la Ire Cour de droit civil
du Tribunal fédéral suisse
La présidente : Kiss
Le greffier : Thélin